Skip to content

Commit

Permalink
Reduce API calls (#486)
Browse files Browse the repository at this point in the history
* Call vehicles endpoint only on initialization
* Add option to force vehicle init
  • Loading branch information
rikroe authored Sep 21, 2022
1 parent 2edabf0 commit ed289df
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 13 deletions.
35 changes: 25 additions & 10 deletions bimmer_connected/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def __post_init__(self, password, log_responses, observer_position, use_metric_u
use_metric_units=use_metric_units,
)

async def get_vehicles(self) -> None:
"""Retrieve vehicle data from BMW servers."""
async def _init_vehicles(self) -> None:
"""Initialize vehicles from BMW servers."""
_LOGGER.debug("Getting vehicle list")

fetched_at = datetime.datetime.now(datetime.timezone.utc)
Expand All @@ -77,14 +77,29 @@ async def get_vehicles(self) -> None:

for response in vehicles_responses:
for vehicle_base in response.json():
# Get the detailed vehicle state
state_response = await client.get(
VEHICLE_STATE_URL.format(vin=vehicle_base["vin"]),
headers=response.request.headers, # Reuse the same headers as used to get vehicle list
)
vehicle_state = state_response.json()

self.add_vehicle(vehicle_base, vehicle_state, fetched_at)
self.add_vehicle(vehicle_base, {}, fetched_at)

async def get_vehicles(self, force_init: bool = False) -> None:
"""Retrieve vehicle data from BMW servers."""
_LOGGER.debug("Getting vehicle list")

fetched_at = datetime.datetime.now(datetime.timezone.utc)

if len(self.vehicles) == 0 or force_init:
await self._init_vehicles()

async with MyBMWClient(self.config) as client:
for vehicle in self.vehicles:
# Get the detailed vehicle state
state_response = await client.get(
VEHICLE_STATE_URL.format(vin=vehicle.vin),
headers={
**client.generate_default_header(vehicle.brand),
"bmw-current-date": fetched_at.isoformat(),
},
)
vehicle_state = state_response.json()
self.add_vehicle(vehicle.data, vehicle_state, fetched_at)

def add_vehicle(self, vehicle_base: dict, vehicle_state: dict, fetched_at: datetime.datetime = None) -> None:
"""Add or update a vehicle from the API responses."""
Expand Down
32 changes: 29 additions & 3 deletions test/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ async def test_login_refresh_token_row_na_401():
"bimmer_connected.api.authentication.MyBMWAuthentication._refresh_token_row_na",
wraps=account.config.authentication._refresh_token_row_na, # pylint: disable=protected-access
) as mock_listener:
mock_api.get("/eadrax-vcs/v2/vehicles").mock(
mock_api.get(path__regex=r"^/eadrax-vcs/v2/vehicles/(?P<vin>\w+)/state").mock(
side_effect=[httpx.Response(401), *([httpx.Response(200, json=[])] * 10)]
)
mock_listener.reset_mock()
Expand Down Expand Up @@ -234,7 +234,7 @@ async def test_login_refresh_token_china_401():
"bimmer_connected.api.authentication.MyBMWAuthentication._refresh_token_china",
wraps=account.config.authentication._refresh_token_china, # pylint: disable=protected-access
) as mock_listener:
mock_api.get("/eadrax-vcs/v2/vehicles").mock(
mock_api.get(path__regex=r"^/eadrax-vcs/v2/vehicles/(?P<vin>\w+)/state").mock(
side_effect=[httpx.Response(401), *([httpx.Response(200, json=[])] * 10)]
)
mock_listener.reset_mock()
Expand Down Expand Up @@ -283,6 +283,32 @@ async def test_vehicles():
assert account.get_vehicle("invalid_vin") is None


@account_mock()
@pytest.mark.asyncio
async def test_vehicle_init():
"""Test vehicle initialization."""
account = MyBMWAccount(TEST_USERNAME, TEST_PASSWORD, TEST_REGION)
with mock.patch(
"bimmer_connected.account.MyBMWAccount._init_vehicles",
wraps=account._init_vehicles, # pylint: disable=protected-access
) as mock_listener:
mock_listener.reset_mock()

# First call on init
await account.get_vehicles()
assert len(account.vehicles) == get_fingerprint_count()

# No call to _init_vehicles()
await account.get_vehicles()
assert len(account.vehicles) == get_fingerprint_count()

# Second, forced call _init_vehicles()
await account.get_vehicles(force_init=True)
assert len(account.vehicles) == get_fingerprint_count()

assert mock_listener.call_count == 2


@pytest.mark.asyncio
async def test_invalid_password():
"""Test parsing the results of an invalid password."""
Expand Down Expand Up @@ -337,7 +363,7 @@ async def test_storing_fingerprints(tmp_path):
account = MyBMWAccount(TEST_USERNAME, TEST_PASSWORD, TEST_REGION, log_responses=tmp_path)
await account.get_vehicles()

mock_api.get("/eadrax-vcs/v2/vehicles").respond(
mock_api.get(path__regex=r"^/eadrax-vcs/v2/vehicles/(?P<vin>\w+)/state").respond(
500, text=load_response(RESPONSE_DIR / "auth" / "auth_error_internal_error.txt")
)
with pytest.raises(httpx.HTTPStatusError):
Expand Down

0 comments on commit ed289df

Please sign in to comment.