From f7c4f06560cd9dc885ed9395c17f4e4bc76c4a01 Mon Sep 17 00:00:00 2001 From: Simon Hansen <67142049+DurgNomis-drol@users.noreply.github.com> Date: Tue, 31 Aug 2021 21:33:58 +0200 Subject: [PATCH] Use objects instead of dicts (#38) * Return objects instead of dicts * Bump version * Better handling of http errors and error's occurring at toyota's end * Update README.md * bump version --- .pre-commit-config.yaml | 13 +++ README.md | 62 +++++++++++--- mytoyota/api.py | 78 ++++++++++------- mytoyota/client.py | 76 +++++++++-------- mytoyota/const.py | 19 +++++ mytoyota/exceptions.py | 8 +- mytoyota/statistics.py | 135 +++++++++++++++++------------- mytoyota/utils.py | 2 +- mytoyota/vehicle.py | 180 +++++++++++++++++++++++++++++----------- poetry.lock | 179 +++++++++++++++++++++++++++------------ pyproject.toml | 5 +- 11 files changed, 517 insertions(+), 240 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9203552b..f5b90439 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,12 @@ repos: language: system types: [python] require_serial: true + - id: isort + name: isort + entry: poetry run isort + language: system + types: [python] + require_serial: true - id: flake8 name: flake8 entry: poetry run flake8 @@ -22,8 +28,15 @@ repos: - id: pylint name: pylint entry: poetry run pylint + exclude: ^glocaltokens/google/ language: system types: [python] + - id: codespell + name: codespell + entry: poetry run codespell --skip="./*" + language: system + pass_filenames: false + always_run: true - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.2.1 hooks: diff --git a/README.md b/README.md index b0dd1437..fd953bb8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ pip install mytoyota ## Usage ```python -import arrow +import json import asyncio from mytoyota.client import MyT @@ -41,29 +41,67 @@ async def get_information(): cars = await client.get_vehicles() for car in cars: + + # Returns live data from car/last time you used it as an object. + vehicle = await client.get_vehicle_status(car) + + + # You can either get them all async (Recommended) or sync (Look further down). + data = await asyncio.gather( + *[ + client.get_driving_statistics(vehicle.vin, interval="day"), + client.get_driving_statistics(vehicle.vin, interval="isoweek"), + client.get_driving_statistics(vehicle.vin), + client.get_driving_statistics(vehicle.vin, interval="year"), + ] + ) + + # You can deposit each result into premade object holder for statistics. This will make it easier to use in your code. + + vehicle.statistics.daily = data[0] + vehicle.statistics.weekly = data[1] + vehicle.statistics.monthly = data[2] + vehicle.statistics.yearly = data[3] + + + # You can access odometer data like this: + fuel = vehicle.odometer.fuel + # Or Parking information: + latitude = vehicle.parking.latitude + + + # Pretty print the object. This will provide you with all available information. + print(json.dumps(vehicle.as_dict(), indent=3)) + + + # ------------------------------- + # All data is return in an object. + # ------------------------------- + # Returns live data from car/last time you used it. vehicle = await client.get_vehicle_status(car) - print(vehicle) + print(vehicle.as_dict()) # Stats returned in a dict - daily_stats = await client.get_driving_statistics(cars[0]['vin'], interval="day") - print(daily_stats) + daily_stats = await client.get_driving_statistics(vehicle.vin, interval="day") + print(daily_stats.as_list()) # Stats returned in json. - weekly_stats = await client.get_driving_statistics_json(cars[0]['vin'], interval="week") - print(weekly_stats) + weekly_stats = await client.get_driving_statistics(vehicle.vin, interval="isoweek") + print(weekly_stats.as_list()) # ISO 8601 week stats - iso_weekly_stats = await client.get_driving_statistics(cars[0]['vin'], interval="isoweek") - print(iso_weekly_stats) + iso_weekly_stats = await client.get_driving_statistics(vehicle.vin, interval="isoweek") + print(iso_weekly_stats.as_list) # Monthly stats is returned by default - monthly_stats = await client.get_driving_statistics(cars[0]['vin']) - print(monthly_stats) + monthly_stats = await client.get_driving_statistics(vehicle.vin) + print(monthly_stats.as_list()) #Get year to date stats. - yearly_stats = await client.get_driving_statistics(car['vin'], interval="year") - print(yearly_stats) + yearly_stats = await client.get_driving_statistics(vehicle.vin, interval="year") + print(yearly_stats.as_list()) + loop = asyncio.get_event_loop() loop.run_until_complete(get_information()) diff --git a/mytoyota/api.py b/mytoyota/api.py index beb9b966..bcbd0d5b 100644 --- a/mytoyota/api.py +++ b/mytoyota/api.py @@ -1,7 +1,8 @@ """Toyota Connected Services Controller""" +from datetime import datetime import logging from typing import Optional, Union -from datetime import datetime + import httpx from .const import ( @@ -9,22 +10,20 @@ BASE_URL_CARS, CUSTOMERPROFILE, ENDPOINT_AUTH, + HTTP_INTERNAL, + HTTP_NO_CONTENT, + HTTP_OK, + HTTP_SERVICE_UNAVAILABLE, PASSWORD, + SUPPORTED_REGIONS, TIMEOUT, TOKEN, + TOKEN_DURATION, + TOKEN_VALID_URL, USERNAME, UUID, - HTTP_OK, - HTTP_NO_CONTENT, - SUPPORTED_REGIONS, - TOKEN_VALID_URL, - TOKEN_DURATION, - HTTP_INTERNAL, -) -from .exceptions import ( - ToyotaLoginError, - ToyotaInternalServerError, ) +from .exceptions import ToyotaApiError, ToyotaInternalError, ToyotaLoginError from .utils import is_valid_token _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -77,7 +76,7 @@ async def get_uuid(self) -> str: return self.uuid @staticmethod - def _has_expired(creation_dt, duration) -> bool: + def _has_expired(creation_dt: datetime, duration: int) -> bool: """Checks if an specified token/object has expired""" return datetime.now().timestamp() - creation_dt.timestamp() > duration @@ -124,7 +123,7 @@ async def get_new_token(self) -> str: result = response.json() if TOKEN not in result or UUID not in result[CUSTOMERPROFILE]: - _LOGGER.error("[!] Could not get token or UUID.") + raise ToyotaLoginError("Could not get token or UUID from result") token = result.get(TOKEN) uuid = result[CUSTOMERPROFILE][UUID] @@ -141,7 +140,10 @@ async def get_new_token(self) -> str: return self.token async def get( - self, endpoint: str, headers=None, params=None + self, + endpoint: str, + headers: Optional[dict] = None, + params: Optional[dict] = None, ) -> Union[dict, list, None]: """Make the request.""" @@ -172,26 +174,32 @@ async def get( if resp.status_code == HTTP_OK: result = resp.json() - elif resp.status_code == HTTP_NO_CONTENT: + elif resp.status_code is HTTP_NO_CONTENT: # This prevents raising or logging an error # if the user have not setup Connected Services result = None - elif resp.status_code == HTTP_INTERNAL: + _LOGGER.debug("Connected services is disabled") + elif resp.status_code is HTTP_INTERNAL: response = resp.json() - raise ToyotaInternalServerError( + raise ToyotaInternalError( "Internal server error occurred! Code: " + response["code"] + " - " + response["message"], ) + elif resp.status_code is HTTP_SERVICE_UNAVAILABLE: + raise ToyotaApiError( + "Toyota Connected Services are temporarily unavailable" + ) else: - _LOGGER.error("HTTP: %i - %s", resp.status_code, resp.text) - result = None + raise ToyotaInternalError( + "HTTP: " + resp.status_code + " - " + resp.text + ) return result async def put( - self, endpoint: str, body: dict, headers=None + self, endpoint: str, body: dict, headers: Optional[dict] = None ) -> Union[dict, list, None]: """Make the request.""" @@ -224,21 +232,29 @@ async def put( # This prevents raising or logging an error # if the user have not setup Connected Services result = None + _LOGGER.debug("Connected services is disabled") elif resp.status_code == HTTP_INTERNAL: response = resp.json() - raise ToyotaInternalServerError( + raise ToyotaInternalError( "Internal server error occurred! Code: " + response["code"] + " - " + response["message"], ) + elif resp.status_code is HTTP_SERVICE_UNAVAILABLE: + raise ToyotaApiError( + "Toyota Connected Services are temporarily unavailable" + ) else: - _LOGGER.error("HTTP: %i - %s", resp.status_code, resp.text) - result = None + raise ToyotaInternalError( + "HTTP: " + resp.status_code + " - " + resp.text + ) return result - async def set_vehicle_alias_endpoint(self, new_alias: str, vehicle_id: int) -> dict: + async def set_vehicle_alias_endpoint( + self, new_alias: str, vehicle_id: int + ) -> Optional[dict]: """Set vehicle alias.""" return await self.put( @@ -246,7 +262,7 @@ async def set_vehicle_alias_endpoint(self, new_alias: str, vehicle_id: int) -> d {"id": vehicle_id, "alias": new_alias}, ) - async def get_vehicles_endpoint(self) -> list: + async def get_vehicles_endpoint(self) -> Optional[list]: """Retrieves list of cars you have registered with MyT""" arguments = "?services=uio&legacy=true" @@ -255,7 +271,7 @@ async def get_vehicles_endpoint(self) -> list: f"{self.get_base_url_cars()}/vehicle/user/{self.uuid}/vehicles{arguments}" ) - async def get_connected_services_endpoint(self, vin: str) -> dict: + async def get_connected_services_endpoint(self, vin: str) -> Optional[dict]: """Get information about connected services for the given car.""" arguments = "?legacy=true&services=fud,connected" @@ -264,19 +280,19 @@ async def get_connected_services_endpoint(self, vin: str) -> dict: f"{self.get_base_url_cars()}/vehicle/user/{self.uuid}/vehicle/{vin}{arguments}" ) - async def get_odometer_endpoint(self, vin: str) -> list: + async def get_odometer_endpoint(self, vin: str) -> Optional[list]: """Get information from odometer.""" return await self.get(f"{self.get_base_url()}/vehicle/{vin}/addtionalInfo") - async def get_parking_endpoint(self, vin: str) -> dict: + async def get_parking_endpoint(self, vin: str) -> Optional[dict]: """Get where you have parked your car.""" return await self.get( f"{self.get_base_url()}/users/{self.uuid}/vehicle/location", {"VIN": vin} ) - async def get_vehicle_status_endpoint(self, vin: str) -> dict: + async def get_vehicle_status_endpoint(self, vin: str) -> Optional[dict]: """Get information about the vehicle.""" return await self.get( @@ -284,8 +300,8 @@ async def get_vehicle_status_endpoint(self, vin: str) -> dict: ) async def get_driving_statistics_endpoint( - self, vin: str, from_date: str, interval: str = None - ) -> dict: + self, vin: str, from_date: str, interval: Optional[str] = None + ) -> Optional[dict]: """Get driving statistic""" params = {"from": from_date, "calendarInterval": interval} diff --git a/mytoyota/client.py b/mytoyota/client.py index a7fc655c..3f6cda97 100644 --- a/mytoyota/client.py +++ b/mytoyota/client.py @@ -7,12 +7,18 @@ from .api import Controller from .const import ( - SUPPORTED_REGIONS, + DATE_FORMAT, + DAY, INTERVAL_SUPPORTED, + ISOWEEK, + MONTH, + SUPPORTED_REGIONS, + WEEK, + YEAR, ) from .exceptions import ( - ToyotaLocaleNotValid, ToyotaInvalidUsername, + ToyotaLocaleNotValid, ToyotaRegionNotSupported, ) from .statistics import Statistics @@ -55,11 +61,11 @@ def __init__( # pylint: disable=too-many-arguments ) @staticmethod - def get_supported_regions(): + def get_supported_regions() -> list: """Return supported regions""" regions = [] - for key, value in SUPPORTED_REGIONS.items(): # pylint: disable=unused-variable + for key, _ in SUPPORTED_REGIONS.items(): # pylint: disable=unused-variable regions.append(key) return regions @@ -93,15 +99,17 @@ async def get_vehicles_json(self) -> str: json_string = json.dumps(vehicles, indent=3) return json_string - async def get_vehicle_status(self, vehicle: dict) -> dict: + async def get_vehicle_status(self, vehicle: dict) -> Vehicle: """Return information for given vehicle""" vin = vehicle["vin"] info = await asyncio.gather( - self.api.get_connected_services_endpoint(vin), - self.api.get_odometer_endpoint(vin), - self.api.get_parking_endpoint(vin), - self.api.get_vehicle_status_endpoint(vin), + *[ + self.api.get_connected_services_endpoint(vin), + self.api.get_odometer_endpoint(vin), + self.api.get_parking_endpoint(vin), + self.api.get_vehicle_status_endpoint(vin), + ] ) car = Vehicle( @@ -112,17 +120,17 @@ async def get_vehicle_status(self, vehicle: dict) -> dict: status=info[3], ) - return car.as_dict() + return car async def get_vehicle_status_json(self, vehicle: dict) -> str: """Return vehicle information as json""" vehicle = await self.get_vehicle_status(vehicle) - json_string = json.dumps(vehicle, indent=3) + json_string = json.dumps(vehicle.as_dict(), indent=3) return json_string async def get_driving_statistics( # pylint: disable=too-many-branches - self, vin: str, interval: str = "month", from_date=None + self, vin: str, interval: str = MONTH, from_date: str = None ) -> list: """ params: vin: Vin number of your car. @@ -152,27 +160,25 @@ async def get_driving_statistics( # pylint: disable=too-many-branches return [{"error_mesg": "This is not a timemachine!", "error_code": 5}] if from_date is None: - if interval == "day": - from_date = arrow.now().shift(days=-1).format("YYYY-MM-DD") + if interval is DAY: + from_date = arrow.now().shift(days=-1).format(DATE_FORMAT) - if interval == "week": - from_date = ( - arrow.now().span("week", week_start=7)[0].format("YYYY-MM-DD") - ) + if interval is WEEK: + from_date = arrow.now().span(WEEK, week_start=7)[0].format(DATE_FORMAT) - if interval == "isoweek": - stats_interval = "day" - from_date = arrow.now().floor("week").format("YYYY-MM-DD") + if interval is ISOWEEK: + stats_interval = DAY + from_date = arrow.now().floor(WEEK).format(DATE_FORMAT) - if interval == "month": - from_date = arrow.now().floor("month").format("YYYY-MM-DD") + if interval is MONTH: + from_date = arrow.now().floor(MONTH).format(DATE_FORMAT) - if interval == "year": - stats_interval = "month" - from_date = arrow.now().floor("year").format("YYYY-MM-DD") + if interval is YEAR: + stats_interval = MONTH + from_date = arrow.now().floor(YEAR).format(DATE_FORMAT) - if interval == "isoweek": - stats_interval = "day" + if interval is ISOWEEK: + stats_interval = DAY time_between = arrow.now() - arrow.get(from_date) if time_between.days > 7: @@ -184,12 +190,12 @@ async def get_driving_statistics( # pylint: disable=too-many-branches } ] - arrow.get(from_date).floor("week").format("YYYY-MM-DD") + arrow.get(from_date).floor(WEEK).format(DATE_FORMAT) - if interval == "year": - stats_interval = "month" + if interval is YEAR: + stats_interval = MONTH - if arrow.get(from_date) < arrow.now().floor("year"): + if arrow.get(from_date) < arrow.now().floor(YEAR): return [ { "error_mesg": "Invalid date provided. from_date can" @@ -198,9 +204,9 @@ async def get_driving_statistics( # pylint: disable=too-many-branches } ] - from_date = arrow.get(from_date).floor("year").format("YYYY-MM-DD") + from_date = arrow.get(from_date).floor(YEAR).format(DATE_FORMAT) - today = arrow.now().format("YYYY-MM-DD") + today = arrow.now().format(DATE_FORMAT) if from_date is today: raw_statistics = None @@ -226,7 +232,7 @@ async def get_driving_statistics( # pylint: disable=too-many-branches return statistics.as_list() async def get_driving_statistics_json( - self, vin: str, interval: str = "month", from_date=None + self, vin: str, interval: str = MONTH, from_date: str = None ) -> str: """Return driving statistics in json""" return json.dumps( diff --git a/mytoyota/const.py b/mytoyota/const.py index 0d3b121f..4dcb8fc7 100644 --- a/mytoyota/const.py +++ b/mytoyota/const.py @@ -38,6 +38,24 @@ CHARGE_INFO = "ChargeInfo" HVAC = "RemoteHvacInfo" +BUCKET = "bucket" +DAYOFYEAR = "dayOfYear" +PERIODE_START = "periode_start" +DATE = "date" +DATA = "data" +SUMMARY = "summary" +HISTOGRAM = "histogram" + +DAY = "day" +WEEK = "week" +ISOWEEK = "isoweek" +MONTH = "month" +YEAR = "year" + +# DATE FORMATS +DATE_FORMAT_YEAR = "YYYY" +DATE_FORMAT = "YYYY-MM-DD" + # HTTP TIMEOUT = 15 @@ -45,6 +63,7 @@ HTTP_NO_CONTENT = 204 HTTP_UNAUTHORIZED = 401 HTTP_INTERNAL = 500 +HTTP_SERVICE_UNAVAILABLE = 503 RETURNED_BAD_REQUEST = "bad_request" diff --git a/mytoyota/exceptions.py b/mytoyota/exceptions.py index 0fd2df65..9dacd645 100644 --- a/mytoyota/exceptions.py +++ b/mytoyota/exceptions.py @@ -21,5 +21,9 @@ class ToyotaRegionNotSupported(Exception): """Raise if region is not supported""" -class ToyotaInternalServerError(Exception): - """Raise if an internal server error occurred from toyota.""" +class ToyotaApiError(Exception): + """Raise if a API error occurres.""" + + +class ToyotaInternalError(Exception): + """Raise if an internal server error occurres from Toyota.""" diff --git a/mytoyota/statistics.py b/mytoyota/statistics.py index 0f53f9d1..e3a37b61 100644 --- a/mytoyota/statistics.py +++ b/mytoyota/statistics.py @@ -1,76 +1,95 @@ """Statistics class""" import logging + import arrow +from arrow import Arrow + +from mytoyota.const import ( + BUCKET, + DATA, + DATE, + DATE_FORMAT, + DATE_FORMAT_YEAR, + DAY, + DAYOFYEAR, + HISTOGRAM, + ISOWEEK, + MONTH, + PERIODE_START, + SUMMARY, + WEEK, + YEAR, +) _LOGGER: logging.Logger = logging.getLogger(__package__) -class Statistics: +class Statistics: # pylint: disable=too-few-public-methods) """Class to hold statistical information.""" def __init__(self, raw_statistics: dict, interval: str) -> None: - self.formated: list = [] + self._now: Arrow = arrow.now() if not raw_statistics: _LOGGER.error("No statistical information provided!") return - if interval == "day": - self.formated = self.add_date_to_bucket_day(raw_statistics["histogram"]) - - if interval in ("week", "month"): - self.formated = raw_statistics["histogram"] - - if interval == "isoweek": - self.formated.append(self.add_bucket_to_isoweek(raw_statistics)) - - if interval == "year": - self.formated.append(self.add_year_to_bucket_year(raw_statistics)) + self._statistic = self._make_bucket(raw_statistics, interval) def as_list(self) -> list: - """Return formated data.""" - return self.formated - - @staticmethod - def add_date_to_bucket_day(days): - """Adds date to bucket.""" - for day in days: - year = day["bucket"]["year"] - dayofyear = day["bucket"]["dayOfYear"] - - day["bucket"].update( - { - "date": arrow.now() - .strptime("{} {}".format(dayofyear, year), "%j %Y") - .format("YYYY-MM-DD") - } - ) - - return days - - @staticmethod - def add_bucket_to_isoweek(isoweek): - """Adds bucket to isoweek and formats it.""" - data: dict = { - "bucket": { - "year": arrow.now().format("YYYY"), - "week": arrow.now().strftime("%V"), - "week_start": isoweek["from"], - }, - "data": isoweek["summary"], - } - - return data - - @staticmethod - def add_year_to_bucket_year(year): - """Adds year to bucket.""" - data: dict = { - "bucket": { - "year": arrow.now().format("YYYY"), - }, - "data": year["summary"], - } - - return data + """Return formatted data.""" + return self._statistic + + def _make_bucket(self, data: dict, interval: str) -> list: + """Make bucket.""" + + if interval is DAY: + for day in data[HISTOGRAM]: + year = day[BUCKET][YEAR] + dayofyear = day[BUCKET][DAYOFYEAR] + + day[BUCKET].update( + { + DATE: self._now.strptime( + "{} {}".format(dayofyear, year), "%j %Y" + ).format(DATE_FORMAT), + } + ) + return data[HISTOGRAM] + + if interval is ISOWEEK: + data_with_bucket: dict = { + BUCKET: { + YEAR: self._now.format(DATE_FORMAT_YEAR), + WEEK: self._now.strftime("%V"), + PERIODE_START: data["from"], + }, + DATA: data[SUMMARY], + } + return [data_with_bucket] + + if interval is MONTH: + for month in data[HISTOGRAM]: + month[BUCKET].update( + { + PERIODE_START: self._now.replace( + year=month[BUCKET][YEAR], month=month[BUCKET][MONTH] + ) + .floor(MONTH) + .format(DATE_FORMAT), + } + ) + return data[HISTOGRAM] + + if interval is YEAR: + data_with_bucket: dict = { + BUCKET: { + YEAR: self._now.format(DATE_FORMAT_YEAR), + PERIODE_START: data["from"], + }, + DATA: data[SUMMARY], + } + return [data_with_bucket] + + return data[HISTOGRAM] diff --git a/mytoyota/utils.py b/mytoyota/utils.py index 22ff4079..756dee6d 100644 --- a/mytoyota/utils.py +++ b/mytoyota/utils.py @@ -10,7 +10,7 @@ def is_valid_locale(locale: str) -> bool: return Language.make(locale).is_valid() -def is_valid_token(token): +def is_valid_token(token: str) -> bool: """Checks if token is the correct length""" if len(token) == TOKEN_LENGTH and token.endswith("..*"): return True diff --git a/mytoyota/vehicle.py b/mytoyota/vehicle.py index 768f6014..972c1018 100644 --- a/mytoyota/vehicle.py +++ b/mytoyota/vehicle.py @@ -5,12 +5,107 @@ _LOGGER: logging.Logger = logging.getLogger(__package__) +class VehicleStatistics: + """Vehicle statistics representation""" + + daily: list = None + weekly: list = None + monthly: list = None + yearly: list = None + + def __str__(self) -> str: + return str(self.as_dict()) + + def as_dict(self) -> dict: + """Return vehicle statistics as dict.""" + return vars(self) + + +class Odometer: + """Odometer representation""" + + mileage: int = None + unit: str = None + fuel: int = None + + def __init__(self, odometer: list) -> None: + + _LOGGER.debug("Raw odometer data: %s", str(odometer)) + + odometer_dict = self._format_odometer(odometer) + + _LOGGER.debug("Formatted odometer data: %s", str(odometer_dict)) + + if "mileage" in odometer_dict: + self.mileage = odometer_dict["mileage"] + if "mileage_unit" in odometer_dict: + self.unit = odometer_dict["mileage_unit"] + if "Fuel" in odometer_dict: + self.fuel = odometer_dict["Fuel"] + + def __str__(self) -> str: + return str(self.as_dict()) + + def as_dict(self) -> dict: + """Return odometer as dict.""" + return vars(self) + + @staticmethod + def _format_odometer(raw: list) -> dict: + """Formats odometer information from a list to a dict.""" + instruments: dict = {} + for instrument in raw: + instruments[instrument["type"]] = instrument["value"] + if "unit" in instrument: + instruments[instrument["type"] + "_unit"] = instrument["unit"] + + return instruments + + +class ParkingLocation: + """ParkingLocation representation""" + + latitude: float = None + longitude: float = None + timestamp: int = None + trip_status: str = None + + def __init__(self, parking: dict) -> None: + + _LOGGER.debug("Raw parking location data: %s", str(parking)) + + self.latitude = float(parking["event"]["lat"]) + self.longitude = float(parking["event"]["lon"]) + self.timestamp = int(parking["event"]["timestamp"]) + self.trip_status = parking["tripStatus"] + + def __str__(self) -> str: + return str(self.as_dict()) + + def as_dict(self) -> dict: + """Return parking location as dict.""" + return vars(self) + + class Vehicle: # pylint: disable=too-many-instance-attributes - """Class to hold car information for each car""" + """Vehicle representation""" + + id: int = 0 + vin: str = None + alias: str = None + is_connected: bool = False + details: Optional[dict] = None + odometer: Optional[Odometer] = None + parking: Optional[ParkingLocation] = None + statistics: VehicleStatistics = VehicleStatistics() + + # Not known yet. + battery = None + hvac = None def __init__( # pylint: disable=too-many-arguments self, - vehicle_info: Optional[dict], + vehicle_info: dict, connected_services: Optional[dict], odometer: Optional[list], parking: Optional[dict], @@ -22,62 +117,60 @@ def __init__( # pylint: disable=too-many-arguments _LOGGER.error("No vehicle information provided!") return - self.odometer = None - self.parking = None - self.battery = None - self.hvac = None + _LOGGER.debug("Raw connected services data: %s", str(connected_services)) + + if connected_services is not None: + self.is_connected = self._has_connected_services_enabled(connected_services) - # Holds status for each service. - self.services = {} + _LOGGER.debug("Raw vehicle info: %s", str(vehicle_info)) # Vehicle information - self.alias = vehicle_info["alias"] if "alias" in vehicle_info else None - self.vin = vehicle_info["vin"] if "vin" in vehicle_info else None + if "id" in vehicle_info: + self.id = vehicle_info["id"] # pylint: disable=invalid-name + if "vin" in vehicle_info: + self.vin = vehicle_info["vin"] + if "alias" in vehicle_info: + self.alias = vehicle_info["alias"] # Format vehicle information. - self.details = self.format_details(vehicle_info) + self.details = self._format_details(vehicle_info) - if connected_services is not None: - self.services["connectedServices"] = self.has_connected_services_enabled( - connected_services - ) - else: - self.services["connectedServices"] = False - - # Checks if connected services has been enabled. - if self.services["connectedServices"]: + # Extract odometer information. + self.odometer = Odometer(odometer) if self.is_connected and odometer else None - # Extract odometer information. - if odometer: - self.odometer = self.format_odometer(odometer) - - # Extract parking information. - if parking: - self.parking = parking + # Extract parking information. + self.parking = ( + ParkingLocation(parking) if self.is_connected and parking else None + ) - # Extracts information from status. - if status: - self.extract_status(status) + # Extracts information from status. + if self.is_connected and status: + _LOGGER.debug("Raw status data: %s", str(status)) + self._extract_status(status) def __str__(self) -> str: return str(self.as_dict()) def as_dict(self) -> dict: - """Return car information in dict""" + """Return vehicle as dict.""" return { + "id": self.id, "alias": self.alias, "vin": self.vin, "details": self.details, "status": { "battery": self.battery, "hvac": self.hvac, - "odometer": self.odometer, - "parking": self.parking, + "odometer": self.odometer.as_dict(), + "parking": self.parking.as_dict(), }, - "servicesEnabled": self.services, + "servicesEnabled": { + "connectedServices": self.is_connected, + }, + "statistics": self.statistics.as_dict(), } - def extract_status(self, status) -> None: + def _extract_status(self, status: dict) -> None: """Extract information like battery and hvac from status.""" if "VehicleInfo" in status: if "RemoteHvacInfo" in status["VehicleInfo"]: @@ -86,7 +179,7 @@ def extract_status(self, status) -> None: if "ChargeInfo" in status["VehicleInfo"]: self.battery = status["VehicleInfo"]["ChargeInfo"] - def has_connected_services_enabled(self, json_dict) -> bool: + def _has_connected_services_enabled(self, json_dict: dict) -> bool: """Checks if the user has enabled connected services.""" if ( @@ -109,22 +202,11 @@ def has_connected_services_enabled(self, json_dict) -> bool: return False @staticmethod - def format_odometer(raw) -> dict: - """Formats odometer information from a list to a dict.""" - instruments: dict = {} - for instrument in raw: - instruments[instrument["type"]] = instrument["value"] - if "unit" in instrument: - instruments[instrument["type"] + "_unit"] = instrument["unit"] - - return instruments - - @staticmethod - def format_details(raw) -> dict: + def _format_details(raw: dict) -> dict: """Formats vehicle info into a dict.""" details: dict = {} for item in sorted(raw): - if item in ("vin", "alias"): + if item in ("vin", "alias", "id"): continue details[item] = raw[item] return details diff --git a/poetry.lock b/poetry.lock index b0268f4d..2e98703b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -36,7 +36,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.6.6" +version = "2.7.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -46,6 +46,20 @@ python-versions = "~=3.6" lazy-object-proxy = ">=1.4.0" wrapt = ">=1.11,<1.13" +[[package]] +name = "attrs" +version = "21.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + [[package]] name = "backports.entry-points-selectable" version = "1.1.0" @@ -107,6 +121,18 @@ python-versions = ">=3.6" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "codespell" +version = "2.1.0" +description = "Codespell" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["check-manifest", "flake8", "pytest", "pytest-cov", "pytest-dependency"] +hard-encoding-detection = ["chardet"] + [[package]] name = "colorama" version = "0.4.4" @@ -144,6 +170,32 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.7.0,<2.8.0" pyflakes = ">=2.3.0,<2.4.0" +[[package]] +name = "flake8-bugbear" +version = "21.4.3" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=3.0.0" + +[package.extras] +dev = ["coverage", "black", "hypothesis", "hypothesmith"] + +[[package]] +name = "flake8-comprehensions" +version = "3.6.1" +description = "A flake8 plugin to help you write better list/set/dict comprehensions." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +flake8 = ">=3.0,<3.2.0 || >3.2.0,<4" + [[package]] name = "h11" version = "0.12.0" @@ -188,7 +240,7 @@ http2 = ["h2 (>=3.0.0,<4.0.0)"] [[package]] name = "identify" -version = "2.2.12" +version = "2.2.13" description = "File identification library for Python" category = "dev" optional = false @@ -284,7 +336,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock [[package]] name = "pre-commit" -version = "2.13.0" +version = "2.14.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -316,17 +368,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pylint" -version = "2.9.6" +version = "2.10.2" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = ">=2.6.5,<2.7" +astroid = ">=2.7.2,<2.8" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" +platformdirs = ">=2.2.0" toml = ">=0.7.1" [[package]] @@ -350,7 +403,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.8.3" +version = "2021.8.21" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -412,7 +465,7 @@ python-versions = "*" [[package]] name = "virtualenv" -version = "20.7.0" +version = "20.7.2" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -440,7 +493,7 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "736d1a5c44552e9fde7aaedf344a3e51c16be03b200e7e864421e54af7070686" +content-hash = "fe38c7c321d220af0eabb1300f0313e1050d83b9b5d7b3f110c11f7d8b7567da" [metadata.files] anyio = [ @@ -456,8 +509,12 @@ arrow = [ {file = "arrow-1.1.1.tar.gz", hash = "sha256:dee7602f6c60e3ec510095b5e301441bc56288cb8f51def14dcb3079f623823a"}, ] astroid = [ - {file = "astroid-2.6.6-py3-none-any.whl", hash = "sha256:ab7f36e8a78b8e54a62028ba6beef7561db4cdb6f2a5009ecc44a6f42b5697ef"}, - {file = "astroid-2.6.6.tar.gz", hash = "sha256:3975a0bd5373bdce166e60c851cfcbaf21ee96de80ec518c1f4cb3e94c3fb334"}, + {file = "astroid-2.7.2-py3-none-any.whl", hash = "sha256:ecc50f9b3803ebf8ea19aa2c6df5622d8a5c31456a53c741d3be044d96ff0948"}, + {file = "astroid-2.7.2.tar.gz", hash = "sha256:b6c2d75cd7c2982d09e7d41d70213e863b3ba34d3bd4014e08f167cee966e99e"}, +] +attrs = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] "backports.entry-points-selectable" = [ {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, @@ -478,6 +535,10 @@ click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, ] +codespell = [ + {file = "codespell-2.1.0-py3-none-any.whl", hash = "sha256:b864c7d917316316ac24272ee992d7937c3519be4569209c5b60035ac5d569b5"}, + {file = "codespell-2.1.0.tar.gz", hash = "sha256:19d3fe5644fef3425777e66f225a8c82d39059dcfe9edb3349a8a2cf48383ee5"}, +] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, @@ -494,6 +555,14 @@ flake8 = [ {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] +flake8-bugbear = [ + {file = "flake8-bugbear-21.4.3.tar.gz", hash = "sha256:2346c81f889955b39e4a368eb7d508de723d9de05716c287dc860a4073dc57e7"}, + {file = "flake8_bugbear-21.4.3-py36.py37.py38-none-any.whl", hash = "sha256:4f305dca96be62bf732a218fe6f1825472a621d3452c5b994d8f89dae21dbafa"}, +] +flake8-comprehensions = [ + {file = "flake8-comprehensions-3.6.1.tar.gz", hash = "sha256:4888de89248b7f7535159189ff693c77f8354f6d37a02619fa28c9921a913aa0"}, + {file = "flake8_comprehensions-3.6.1-py3-none-any.whl", hash = "sha256:e9a010b99aa90c05790d45281ad9953df44a4a08a1a8f6cd41f98b4fc6a268a0"}, +] h11 = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, @@ -507,8 +576,8 @@ httpx = [ {file = "httpx-0.18.2.tar.gz", hash = "sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"}, ] identify = [ - {file = "identify-2.2.12-py2.py3-none-any.whl", hash = "sha256:a510cbe155f39665625c8a4c4b4f9360cbce539f51f23f47836ab7dd852db541"}, - {file = "identify-2.2.12.tar.gz", hash = "sha256:242332b3bdd45a8af1752d5d5a3afb12bee26f8e67c4be06e394f82d05ef1a4d"}, + {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"}, + {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"}, ] idna = [ {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, @@ -566,8 +635,8 @@ platformdirs = [ {file = "platformdirs-2.2.0.tar.gz", hash = "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"}, ] pre-commit = [ - {file = "pre_commit-2.13.0-py2.py3-none-any.whl", hash = "sha256:b679d0fddd5b9d6d98783ae5f10fd0c4c59954f375b70a58cbe1ce9bcf9809a4"}, - {file = "pre_commit-2.13.0.tar.gz", hash = "sha256:764972c60693dc668ba8e86eb29654ec3144501310f7198742a767bec385a378"}, + {file = "pre_commit-2.14.0-py2.py3-none-any.whl", hash = "sha256:ec3045ae62e1aa2eecfb8e86fa3025c2e3698f77394ef8d2011ce0aedd85b2d4"}, + {file = "pre_commit-2.14.0.tar.gz", hash = "sha256:2386eeb4cf6633712c7cc9ede83684d53c8cafca6b59f79c738098b51c6d206c"}, ] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, @@ -578,8 +647,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pylint = [ - {file = "pylint-2.9.6-py3-none-any.whl", hash = "sha256:2e1a0eb2e8ab41d6b5dbada87f066492bb1557b12b76c47c2ee8aa8a11186594"}, - {file = "pylint-2.9.6.tar.gz", hash = "sha256:8b838c8983ee1904b2de66cce9d0b96649a91901350e956d78f289c3bc87b48e"}, + {file = "pylint-2.10.2-py3-none-any.whl", hash = "sha256:e178e96b6ba171f8ef51fbce9ca30931e6acbea4a155074d80cc081596c9e852"}, + {file = "pylint-2.10.2.tar.gz", hash = "sha256:6758cce3ddbab60c52b57dcc07f0c5d779e5daf0cf50f6faacbef1d3ea62d2a1"}, ] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, @@ -617,39 +686,47 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8764a78c5464ac6bde91a8c87dd718c27c1cabb7ed2b4beaf36d3e8e390567f9"}, - {file = "regex-2021.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4551728b767f35f86b8e5ec19a363df87450c7376d7419c3cac5b9ceb4bce576"}, - {file = "regex-2021.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:577737ec3d4c195c4aef01b757905779a9e9aee608fa1cf0aec16b5576c893d3"}, - {file = "regex-2021.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c856ec9b42e5af4fe2d8e75970fcc3a2c15925cbcc6e7a9bcb44583b10b95e80"}, - {file = "regex-2021.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3835de96524a7b6869a6c710b26c90e94558c31006e96ca3cf6af6751b27dca1"}, - {file = "regex-2021.8.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cea56288eeda8b7511d507bbe7790d89ae7049daa5f51ae31a35ae3c05408531"}, - {file = "regex-2021.8.3-cp36-cp36m-win32.whl", hash = "sha256:a4eddbe2a715b2dd3849afbdeacf1cc283160b24e09baf64fa5675f51940419d"}, - {file = "regex-2021.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:57fece29f7cc55d882fe282d9de52f2f522bb85290555b49394102f3621751ee"}, - {file = "regex-2021.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a5c6dbe09aff091adfa8c7cfc1a0e83fdb8021ddb2c183512775a14f1435fe16"}, - {file = "regex-2021.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff4a8ad9638b7ca52313d8732f37ecd5fd3c8e3aff10a8ccb93176fd5b3812f6"}, - {file = "regex-2021.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b63e3571b24a7959017573b6455e05b675050bbbea69408f35f3cb984ec54363"}, - {file = "regex-2021.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fbc20975eee093efa2071de80df7f972b7b35e560b213aafabcec7c0bd00bd8c"}, - {file = "regex-2021.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14caacd1853e40103f59571f169704367e79fb78fac3d6d09ac84d9197cadd16"}, - {file = "regex-2021.8.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb350eb1060591d8e89d6bac4713d41006cd4d479f5e11db334a48ff8999512f"}, - {file = "regex-2021.8.3-cp37-cp37m-win32.whl", hash = "sha256:18fdc51458abc0a974822333bd3a932d4e06ba2a3243e9a1da305668bd62ec6d"}, - {file = "regex-2021.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:026beb631097a4a3def7299aa5825e05e057de3c6d72b139c37813bfa351274b"}, - {file = "regex-2021.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16d9eaa8c7e91537516c20da37db975f09ac2e7772a0694b245076c6d68f85da"}, - {file = "regex-2021.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3905c86cc4ab6d71635d6419a6f8d972cab7c634539bba6053c47354fd04452c"}, - {file = "regex-2021.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937b20955806381e08e54bd9d71f83276d1f883264808521b70b33d98e4dec5d"}, - {file = "regex-2021.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:28e8af338240b6f39713a34e337c3813047896ace09d51593d6907c66c0708ba"}, - {file = "regex-2021.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c09d88a07483231119f5017904db8f60ad67906efac3f1baa31b9b7f7cca281"}, - {file = "regex-2021.8.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:85f568892422a0e96235eb8ea6c5a41c8ccbf55576a2260c0160800dbd7c4f20"}, - {file = "regex-2021.8.3-cp38-cp38-win32.whl", hash = "sha256:bf6d987edd4a44dd2fa2723fca2790f9442ae4de2c8438e53fcb1befdf5d823a"}, - {file = "regex-2021.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:8fe58d9f6e3d1abf690174fd75800fda9bdc23d2a287e77758dc0e8567e38ce6"}, - {file = "regex-2021.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7976d410e42be9ae7458c1816a416218364e06e162b82e42f7060737e711d9ce"}, - {file = "regex-2021.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9569da9e78f0947b249370cb8fadf1015a193c359e7e442ac9ecc585d937f08d"}, - {file = "regex-2021.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bbe342c5b2dec5c5223e7c363f291558bc27982ef39ffd6569e8c082bdc83"}, - {file = "regex-2021.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f421e3cdd3a273bace013751c345f4ebeef08f05e8c10757533ada360b51a39"}, - {file = "regex-2021.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea212df6e5d3f60341aef46401d32fcfded85593af1d82b8b4a7a68cd67fdd6b"}, - {file = "regex-2021.8.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a3b73390511edd2db2d34ff09aa0b2c08be974c71b4c0505b4a048d5dc128c2b"}, - {file = "regex-2021.8.3-cp39-cp39-win32.whl", hash = "sha256:f35567470ee6dbfb946f069ed5f5615b40edcbb5f1e6e1d3d2b114468d505fc6"}, - {file = "regex-2021.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:bfa6a679410b394600eafd16336b2ce8de43e9b13f7fb9247d84ef5ad2b45e91"}, - {file = "regex-2021.8.3.tar.gz", hash = "sha256:8935937dad2c9b369c3d932b0edbc52a62647c2afb2fafc0c280f14a8bf56a6a"}, + {file = "regex-2021.8.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b0c211c55d4aac4309c3209833c803fada3fc21cdf7b74abedda42a0c9dc3ce"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d5209c3ba25864b1a57461526ebde31483db295fc6195fdfc4f8355e10f7376"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c835c30f3af5c63a80917b72115e1defb83de99c73bc727bddd979a3b449e183"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:615fb5a524cffc91ab4490b69e10ae76c1ccbfa3383ea2fad72e54a85c7d47dd"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9966337353e436e6ba652814b0a957a517feb492a98b8f9d3b6ba76d22301dcc"}, + {file = "regex-2021.8.21-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a49f85f0a099a5755d0a2cc6fc337e3cb945ad6390ec892332c691ab0a045882"}, + {file = "regex-2021.8.21-cp310-cp310-win32.whl", hash = "sha256:f93a9d8804f4cec9da6c26c8cfae2c777028b4fdd9f49de0302e26e00bb86504"}, + {file = "regex-2021.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a795829dc522227265d72b25d6ee6f6d41eb2105c15912c230097c8f5bfdbcdc"}, + {file = "regex-2021.8.21-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bca14dfcfd9aae06d7d8d7e105539bd77d39d06caaae57a1ce945670bae744e0"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41acdd6d64cd56f857e271009966c2ffcbd07ec9149ca91f71088574eaa4278a"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96f0c79a70642dfdf7e6a018ebcbea7ea5205e27d8e019cad442d2acfc9af267"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45f97ade892ace20252e5ccecdd7515c7df5feeb42c3d2a8b8c55920c3551c30"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f9974826aeeda32a76648fc677e3125ade379869a84aa964b683984a2dea9f1"}, + {file = "regex-2021.8.21-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea9753d64cba6f226947c318a923dadaf1e21cd8db02f71652405263daa1f033"}, + {file = "regex-2021.8.21-cp36-cp36m-win32.whl", hash = "sha256:ef9326c64349e2d718373415814e754183057ebc092261387a2c2f732d9172b2"}, + {file = "regex-2021.8.21-cp36-cp36m-win_amd64.whl", hash = "sha256:6dbd51c3db300ce9d3171f4106da18fe49e7045232630fe3d4c6e37cb2b39ab9"}, + {file = "regex-2021.8.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a89ca4105f8099de349d139d1090bad387fe2b208b717b288699ca26f179acbe"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6c2b1d78ceceb6741d703508cd0e9197b34f6bf6864dab30f940f8886e04ade"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34ba9e39f8269fd66ab4f7a802794ffea6d6ac500568ec05b327a862c21ce23"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ecb6e7c45f9cd199c10ec35262b53b2247fb9a408803ed00ee5bb2b54aa626f5"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:330836ad89ff0be756b58758878409f591d4737b6a8cef26a162e2a4961c3321"}, + {file = "regex-2021.8.21-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a904da8c9c02aee581f4452a5a988c3003207cb8033db426f29e5b2c0b7aea"}, + {file = "regex-2021.8.21-cp37-cp37m-win32.whl", hash = "sha256:b511c6009d50d5c0dd0bab85ed25bc8ad6b6f5611de3a63a59786207e82824bb"}, + {file = "regex-2021.8.21-cp37-cp37m-win_amd64.whl", hash = "sha256:93f9f720081d97acee38a411e861d4ce84cbc8ea5319bc1f8e38c972c47af49f"}, + {file = "regex-2021.8.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a195e26df1fbb40ebee75865f9b64ba692a5824ecb91c078cc665b01f7a9a36"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ba444bbf7ede3890a912bd4904bb65bf0da8f0d8808b90545481362c978642"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8d551f1bd60b3e1c59ff55b9e8d74607a5308f66e2916948cafd13480b44a3"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ebbceefbffae118ab954d3cd6bf718f5790db66152f95202ebc231d58ad4e2c2"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccd721f1d4fc42b541b633d6e339018a08dd0290dc67269df79552843a06ca92"}, + {file = "regex-2021.8.21-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ae87ab669431f611c56e581679db33b9a467f87d7bf197ac384e71e4956b4456"}, + {file = "regex-2021.8.21-cp38-cp38-win32.whl", hash = "sha256:38600fd58c2996829480de7d034fb2d3a0307110e44dae80b6b4f9b3d2eea529"}, + {file = "regex-2021.8.21-cp38-cp38-win_amd64.whl", hash = "sha256:61e734c2bcb3742c3f454dfa930ea60ea08f56fd1a0eb52d8cb189a2f6be9586"}, + {file = "regex-2021.8.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b091dcfee169ad8de21b61eb2c3a75f9f0f859f851f64fdaf9320759a3244239"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:640ccca4d0a6fcc6590f005ecd7b16c3d8f5d52174e4854f96b16f34c39d6cb7"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac95101736239260189f426b1e361dc1b704513963357dc474beb0f39f5b7759"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b79dc2b2e313565416c1e62807c7c25c67a6ff0a0f8d83a318df464555b65948"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b623fc429a38a881ab2d9a56ef30e8ea20c72a891c193f5ebbddc016e083ee"}, + {file = "regex-2021.8.21-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8021dee64899f993f4b5cca323aae65aabc01a546ed44356a0965e29d7893c94"}, + {file = "regex-2021.8.21-cp39-cp39-win32.whl", hash = "sha256:d6ec4ae13760ceda023b2e5ef1f9bc0b21e4b0830458db143794a117fdbdc044"}, + {file = "regex-2021.8.21-cp39-cp39-win_amd64.whl", hash = "sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd"}, + {file = "regex-2021.8.21.tar.gz", hash = "sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"}, ] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, @@ -705,8 +782,8 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] virtualenv = [ - {file = "virtualenv-20.7.0-py2.py3-none-any.whl", hash = "sha256:fdfdaaf0979ac03ae7f76d5224a05b58165f3c804f8aa633f3dd6f22fbd435d5"}, - {file = "virtualenv-20.7.0.tar.gz", hash = "sha256:97066a978431ec096d163e72771df5357c5c898ffdd587048f45e0aecc228094"}, + {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"}, + {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"}, ] wrapt = [ {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, diff --git a/pyproject.toml b/pyproject.toml index 255283d9..d6fb9a53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ description = "Python client for Toyota Connected Services." authors = ["Simon Grud Hansen "] license = "MIT" readme = "README.md" -version = "0.4.1" +version = "0.5.0" classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", @@ -33,8 +33,11 @@ arrow = "^1.1.1" pre-commit = "^2.11.1" black = "^20.8b1" flake8 = "^3.8.4" +flake8-bugbear = "^21.4.3" +flake8-comprehensions = "^3.4.0" pylint = "^2.7.2" isort = "^5.7.0" +codespell = "^2.0.0" [tool.pylint.format] max-line-length = 99