Skip to content

Commit

Permalink
feat(vehicle): add request method to Vehicle (#106)
Browse files Browse the repository at this point in the history
Add a new general purpose and BSE related method request(self, method: str, path: str, body: dict = {}, headers: dict = {}) -> types.Response to Vehicle that allows for making requests to the Smartcar API. Also adds new type Response to types.py.

Test Plan: create a simple app with my Smartcar Developer account and make requests with this method once it's in prod.

Asana: https://app.asana.com/0/1201332815308984/1201617268953139/f

* feat(vehicle): add general purpose and BSE request method

* fix: make Response type "meta" attribute namedtuple instead of dict

* extra checks for batch request response
  • Loading branch information
naomiperez authored Jan 25, 2022
1 parent 3bf067d commit abec89d
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 0 deletions.
5 changes: 5 additions & 0 deletions smartcar/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ def make_access_object(access: dict) -> Access:
[("webhook_id", str), ("vehicle_id", str), ("meta", namedtuple)],
)

Response = NamedTuple("Response", [("body", dict), ("meta", namedtuple)])

# ===========================================
# Named Tuple Selector Function
# ===========================================
Expand Down Expand Up @@ -330,6 +332,9 @@ def select_named_tuple(path: str, response_or_dict) -> NamedTuple:
headers,
)

elif path == "request":
return Response(data, headers)

elif type(data) == dict:
return generate_named_tuple(data, "Data")

Expand Down
37 changes: 37 additions & 0 deletions smartcar/vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,43 @@ def unsubscribe(self, amt: str, webhook_id: str) -> types.Status:
response = helpers.requester("DELETE", url, headers=headers)
return types.select_named_tuple("unsubscribe", response)

# ===========================================
# General Purpose Request Method
# ===========================================

def request(
self, method: str, path: str, body: dict = {}, headers: dict = {}
) -> types.Response:
"""
Utility method to make a request to a Smartcar endpoint - can be used
to make requests to brand specific endpoints.
Args:
method (str): The HTTP request method to use.
path (str): The path to make the request to.
body (dict): The request body.
headers (dict): The headers to include in the request.
Returns:
Response = NamedTuple("Response", [("body", dict), ("meta", namedtuple)])
Raises:
SmartcarException
"""
url = self._format_url(path)

# Authorization header not provided
if not "Authorization" in headers:
has_units_header = "sc-unit-system" in headers
generated_headers = self._get_headers(
need_unit_system=(not has_units_header)
)
headers.update(generated_headers)

response = helpers.requester(method, url, headers=headers, json=body)

return types.select_named_tuple("request", response)

# ===========================================
# Utility
# ===========================================
Expand Down
49 changes: 49 additions & 0 deletions tests/e2e/test_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,55 @@ def test_webhooks(chevy_volt):
assert unsubscribe._fields == ("status", "meta")


def test_request(chevy_volt):
odometer = chevy_volt.request(
"GET", "odometer", None, {"sc-unit-system": "imperial"}
)
assert type(odometer) == types.Response
assert odometer.body is not None
assert isinstance(odometer.meta, tuple)
assert odometer._fields == ("body", "meta")
assert odometer.meta.unit_system == "imperial"


def test_request_override_header(chevy_volt):
try:
chevy_volt.request(
"GET",
"odometer",
None,
{
"sc-unit-system": "imperial",
"Authorization": "Bearer abc",
},
)
except SmartcarException as sc_e:
assert (
sc_e.message
== "AUTHENTICATION - The authorization header is missing or malformed, or it contains invalid or expired authentication credentials. Please check for missing parameters, spelling and casing mistakes, and other syntax issues."
)


def test_request_with_body(chevy_volt):
batch = chevy_volt.request(
"post",
"batch",
{"requests": [{"path": "/odometer"}, {"path": "/tires/pressure"}]},
)
assert type(batch) is types.Response
assert batch.body is not None
assert isinstance(batch.meta, tuple)
assert batch.body["responses"][0]["path"] == "/odometer"
assert batch.body["responses"][0]["path"] == "/odometer"
assert batch.body["responses"][0]["code"] == 200
assert isinstance(batch.body["responses"][0]["body"]["distance"], float)
assert batch.body["responses"][1]["path"] == "/tires/pressure"
assert isinstance(batch.body["responses"][1]["body"]["frontLeft"], float)
assert isinstance(batch.body["responses"][1]["body"]["frontRight"], float)
assert isinstance(batch.body["responses"][1]["body"]["backLeft"], float)
assert isinstance(batch.body["responses"][1]["body"]["backRight"], float)


def test_chevy_imperial(chevy_volt_imperial):
response = chevy_volt_imperial.odometer()
assert response.meta.unit_system == "imperial"
Expand Down

0 comments on commit abec89d

Please sign in to comment.