diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bab750..c83a11e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: pip install poetry sudo apt-get install libblas3 liblapack3 liblapack-dev libblas-dev gfortran libatlas-base-dev poetry config --local virtualenvs.in-project true - poetry install --no-interaction --no-root + poetry install --no-interaction - name: check quality run: | source .venv/bin/activate diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62388b7..e912037 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,15 +1,13 @@ -default_language_version: - python: python3.7 repos: - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: 'v1.6.0' # Use the sha / tag you want to point at + rev: 'v2.0.4' # Use the sha / tag you want to point at + language: system hooks: - id: autopep8 language: system - args: - - "--exit-code 0" - repo: https://github.com/PyCQA/prospector - rev: 1.7.7 # The version of Prospector to use, if not 'master' for latest + rev: v1.10.3 # The version of Prospector to use, if not 'master' for latest hooks: - id: prospector - language: system \ No newline at end of file + language: system + diff --git a/.prospector.yaml b/.prospector.yaml index a644cdc..b23f579 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -1,5 +1,8 @@ doc-warnings: false max-line-length: 120 +autodetect: false +uses: + - flask ignore-paths: - tests - psa_car_controller/psa/connected_car_api diff --git a/config.ini b/config.ini index 1b16fcd..02a7599 100644 --- a/config.ini +++ b/config.ini @@ -1,9 +1,12 @@ [General] currency = € +# define format for data export, can be csv or xlsx +export_format = csv # minimum trip length in km so it's added to stats and map in website minimum trip length = 10 - +# for future use length unit = km +export format = csv [Electricity config] # price by kw/h day price = 0.15 diff --git a/psa_car_controller/__init__.py b/psa_car_controller/__init__.py index db8e305..db5f862 100644 --- a/psa_car_controller/__init__.py +++ b/psa_car_controller/__init__.py @@ -1,6 +1,5 @@ import sys - -if sys.version_info[:2] >= (3, 8): +if sys.version_info >= (3, 8): from importlib import metadata else: import importlib_metadata as metadata diff --git a/psa_car_controller/common/utils.py b/psa_car_controller/common/utils.py index 1ba973a..b949a5b 100644 --- a/psa_car_controller/common/utils.py +++ b/psa_car_controller/common/utils.py @@ -3,6 +3,7 @@ from threading import Semaphore, Timer from typing import List import requests +TIMEOUT_IN_S = 10 def rate_limit(limit, every): @@ -16,7 +17,7 @@ def rate_limit(limit, every): return func(*args, **kwargs) finally: # don't catch but ensure semaphore release timer = Timer(every, semaphore.release) - timer.setDaemon(True) + timer.daemon = True timer.start() else: raise RateLimitException @@ -56,5 +57,6 @@ def get_positions(locations): locations_str += str(line[latitude]) + "," + str(line[longitude]) + "|" locations_str = locations_str[:-1] res = requests.get("https://api.opentopodata.org/v1/srtm30m", - params={"locations": locations_str}) + params={"locations": locations_str}, + timeout=TIMEOUT_IN_S) return res.json()["results"] diff --git a/psa_car_controller/psa/RemoteClient.py b/psa_car_controller/psa/RemoteClient.py index 7e8d047..65f0d59 100644 --- a/psa_car_controller/psa/RemoteClient.py +++ b/psa_car_controller/psa/RemoteClient.py @@ -125,7 +125,7 @@ class RemoteClient: except RateLimitException: logger.exception("__keep_mqtt") t = threading.Timer(timeout, self.__keep_mqtt) - t.setDaemon(True) + t.daemon = True t.start() def veh_charge_request(self, vin, hour, minute, charge_type): diff --git a/psa_car_controller/psa/oauth.py b/psa_car_controller/psa/oauth.py index 7d7d03e..26a4590 100644 --- a/psa_car_controller/psa/oauth.py +++ b/psa_car_controller/psa/oauth.py @@ -19,10 +19,8 @@ class OpenIdCredentialManager(CredentialManager): self.refresh_callbacks = [] def _grant_password_request_realm(self, login: str, password: str, realm: str) -> dict: - return dict(grant_type='password', - username=login, - scope=' '.join(self.service_information.scopes), - password=password, realm=realm) + return {"grant_type": 'password', "username": login, "scope": ' '.join(self.service_information.scopes), + "password": password, "realm": realm} def init_with_user_credentials_realm(self, login: str, password: str, realm: str): self._token_request(self._grant_password_request_realm(login, password, realm), True) diff --git a/psa_car_controller/psa/otp/otp.py b/psa_car_controller/psa/otp/otp.py index 1803c9c..7dc0187 100644 --- a/psa_car_controller/psa/otp/otp.py +++ b/psa_car_controller/psa/otp/otp.py @@ -16,6 +16,8 @@ from .load import IWData # pylint: disable=invalid-name CONFIG_NAME = "otp.bin" +TIMEOUT_IN_S = 10 + logger = logging.getLogger(__name__) @@ -161,7 +163,8 @@ class Otp: }, params=param, proxies=self.proxies, - verify=self.proxies is None + verify=self.proxies is None, + timeout=TIMEOUT_IN_S ).text try: raw_xml = raw_xml[raw_xml.index("?>") + 2:] diff --git a/psa_car_controller/psa/setup/app_decoder.py b/psa_car_controller/psa/setup/app_decoder.py index d66be6f..c6f4128 100755 --- a/psa_car_controller/psa/setup/app_decoder.py +++ b/psa_car_controller/psa/setup/app_decoder.py @@ -16,6 +16,7 @@ logger = logging.getLogger(__name__) APP_VERSION = "1.33.0" GITHUB_USER = "flobz" GITHUB_REPO = "psa_apk" +TIMEOUT_IN_S = 10 def get_content_from_apk(filename: str, country_code: str) -> ApkParser: @@ -42,7 +43,8 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code, "fields": {"USR_EMAIL": {"value": client_email}, "USR_PASSWORD": {"value": client_password}} } - )} + )}, + timeout=TIMEOUT_IN_S ) token = res.json()["accessToken"] @@ -54,7 +56,7 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code, except BaseException: pass logger.error(msg) - raise Exception(msg) from ex + raise ConnectionError(msg) from ex try: res2 = requests.post( f"https://mw-{BRAND[package_name]['brand_code'].lower()}-m2c.mym.awsmpsa.com/api/v1/user", @@ -73,6 +75,7 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code, "Version": APP_VERSION }, cert=("certs/public.pem", "certs/private.pem"), + timeout=TIMEOUT_IN_S ) res_dict = res2.json()["success"] @@ -84,7 +87,7 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code, except BaseException: pass logger.error(msg) - raise Exception(msg) from ex + raise ConnectionError(msg) from ex # Psacc psacc = PSAClient(None, apk_parser.client_id, apk_parser.client_secret, None, customer_id, BRAND[package_name]["realm"], @@ -94,7 +97,7 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code, res = psacc.get_vehicles() if len(res) == 0: - Exception("No vehicle in your account is compatible with this API, you vehicle is probably too old...") + raise ValueError("No vehicle in your account is compatible with this API, you vehicle is probably too old...") for vehicle in res_dict["vehicles"]: car = psacc.vehicles_list.get_car_by_vin(vehicle["vin"]) diff --git a/psa_car_controller/psa/setup/github.py b/psa_car_controller/psa/setup/github.py index a4f9fce..86ad39f 100644 --- a/psa_car_controller/psa/setup/github.py +++ b/psa_car_controller/psa/setup/github.py @@ -4,10 +4,12 @@ from hashlib import sha1 import requests logger = logging.getLogger(__name__) +TIMEOUT_IN_S = 10 def get_github_sha_from_file(user, repo, directory, filename): - res = requests.get("https://api.github.com/repos/{}/{}/git/trees/main:{}".format(user, repo, directory)).json() + res = requests.get("https://api.github.com/repos/{}/{}/git/trees/main:{}".format(user, repo, directory), + timeout=TIMEOUT_IN_S).json() try: file_info = next((file for file in res["tree"] if file['path'] == filename)) except KeyError as e: @@ -36,12 +38,14 @@ def github_file_need_to_be_downloaded(user, repo, directory, filename): def urlretrieve_from_github(user, repo, directory, filename, branch="main"): if github_file_need_to_be_downloaded(user, repo, directory, filename): with open(filename, 'wb') as f: - r = requests.get("https://github.com/{}/{}/raw/{}/{}{}".format(user, repo, branch, directory, filename), + url = "https://github.com/{}/{}/raw/{}/{}{}".format(user, repo, branch, directory, filename) + r = requests.get(url, headers={ "Accept": "application/vnd.github.VERSION.raw" - }, - stream=True - ) + }, + stream=True, + timeout=TIMEOUT_IN_S + ) r.raise_for_status() for chunk in r.iter_content(1024): diff --git a/psa_car_controller/psacc/application/abrp.py b/psa_car_controller/psacc/application/abrp.py index f6ed7ad..3b5e7cd 100644 --- a/psa_car_controller/psacc/application/abrp.py +++ b/psa_car_controller/psacc/application/abrp.py @@ -8,6 +8,7 @@ import requests from psa_car_controller.psacc.model.car import Car logger = logging.getLogger(__name__) +TIMEOUT_IN_S = 10 class Abrp: @@ -47,7 +48,7 @@ class Abrp: tlm["ext_temp"] = ext_temp params = {"tlm": json.dumps(tlm), "token": self.token, "api_key": self.api_key} response = requests.request("POST", self.url, params=params, proxies=self.proxies, - verify=self.proxies is None) + verify=self.proxies is None, timeout=TIMEOUT_IN_S) logger.debug(response.text) try: return json.loads(response.text)["status"] == "ok" diff --git a/psa_car_controller/psacc/application/car_controller.py b/psa_car_controller/psacc/application/car_controller.py index c09df1c..25ada72 100644 --- a/psa_car_controller/psacc/application/car_controller.py +++ b/psa_car_controller/psacc/application/car_controller.py @@ -69,6 +69,7 @@ class PSACarController(metaclass=Singleton): logger.error("start_remote_control failed redo otp config") def load_app(self) -> bool: + # pylint: disable=too-many-branches my_logger(handler_level=int(self.args.debug)) if self.args.config: self.config_name = self.args.config @@ -99,7 +100,10 @@ class PSACarController(metaclass=Singleton): self.is_good = True else: self.is_good = False - logger.error("Please reconnect by going to config web page") + if self.args.web_conf: + logger.error("Please reconnect by going to config web page") + else: + logger.error("Connection need to be updated, Please redo authentication process.") if self.args.refresh: self.myp.info_refresh_rate = self.args.refresh * 60 if self.is_good: diff --git a/psa_car_controller/psacc/application/charge_control.py b/psa_car_controller/psacc/application/charge_control.py index 6083c41..d20a513 100644 --- a/psa_car_controller/psacc/application/charge_control.py +++ b/psa_car_controller/psacc/application/charge_control.py @@ -101,7 +101,7 @@ class ChargeControl: if next_in_second < self.psacc.info_refresh_rate: periodicity = next_in_second thread = threading.Timer(periodicity, self.process) - thread.setDaemon(True) + thread.daemon = True thread.start() elif status == STOPPED and has_threshold and hit_threshold and self.__is_approaching_scheduled_time(now): logger.info("Approaching scheduled charging time, but should not charge. Postponing charge hour!") diff --git a/psa_car_controller/psacc/application/ecomix.py b/psa_car_controller/psacc/application/ecomix.py index 6ed9283..15da2ea 100644 --- a/psa_car_controller/psacc/application/ecomix.py +++ b/psa_car_controller/psacc/application/ecomix.py @@ -12,7 +12,7 @@ from requests import RequestException CO2_SIGNAL_REQ_INTERVAL = 600 CO2_SIGNAL_URL = "https://api.co2signal.com" - +TIMEOUT_IN_S = 10 logger = logging.getLogger(__name__) @@ -31,7 +31,8 @@ class Ecomix: headers={ "Origin": "https://www.rte-france.com", "Referer": "https://www.rte-france.com/eco2mix/les-emissions-de-co2-par-kwh-produit-en-france", - } + }, + timeout=TIMEOUT_IN_S ) except RequestException: logger.exception("get_data_france: ") @@ -71,7 +72,8 @@ class Ecomix: return False res = requests.get(CO2_SIGNAL_URL + "/v1/latest", headers={"auth-token": Ecomix.co2_signal_key}, - params={"countryCode": country_code}) + params={"countryCode": country_code}, + timeout=TIMEOUT_IN_S) data = res.json() value = data["data"]["carbonIntensity"] assert isinstance(value, numbers.Number) diff --git a/psa_car_controller/psacc/application/psa_client.py b/psa_car_controller/psacc/application/psa_client.py index a3f1a6f..7356517 100644 --- a/psa_car_controller/psacc/application/psa_client.py +++ b/psa_car_controller/psacc/application/psa_client.py @@ -86,7 +86,7 @@ class PSAClient: def set_proxies(self, proxies): if proxies is None: - proxies = dict(http='', https='') + proxies = {"http": '', "https": ''} self.api_config.proxy = None else: self.api_config.proxy = proxies['http'] @@ -117,7 +117,7 @@ class PSAClient: if self.refresh_thread and self.refresh_thread.is_alive(): logger.debug("refresh_vehicle_info: precedent task still alive") self.refresh_thread = threading.Timer(self.info_refresh_rate, self.__refresh_vehicle_info) - self.refresh_thread.setDaemon(True) + self.refresh_thread.daemon = True self.refresh_thread.start() try: logger.debug("refresh_vehicle_info") @@ -162,7 +162,7 @@ class PSAClient: def load_config(name="config.json"): with open(name, "r", encoding="utf-8") as f: config_str = f.read() - config = dict(**json.loads(config_str)) + config = {**json.loads(config_str)} if "country_code" not in config: config["country_code"] = input("What is your country code ? (ex: FR, GB, DE, ES...)\n") for new_el in ["abrp", "co2_signal_api"]: diff --git a/psa_car_controller/psacc/utils/utils.py b/psa_car_controller/psacc/utils/utils.py index 3fdce38..2729511 100644 --- a/psa_car_controller/psacc/utils/utils.py +++ b/psa_car_controller/psacc/utils/utils.py @@ -4,6 +4,7 @@ import socket import requests logger = logging.getLogger(__name__) +TIMEOUT_IN_S = 10 def get_temp(latitude: str, longitude: str, api_key: str) -> float: @@ -13,7 +14,8 @@ def get_temp(latitude: str, longitude: str, api_key: str) -> float: params={"lat": latitude, "lon": longitude, "exclude": "minutely,hourly,daily,alerts", "appid": api_key, - "units": "metric"}) + "units": "metric"}, + timeout=TIMEOUT_IN_S) temp = weather_rep.json()["current"]["temp"] logger.debug("Temperature :%fc", temp) return temp diff --git a/psa_car_controller/web/app.py b/psa_car_controller/web/app.py index 9a84654..977933f 100644 --- a/psa_car_controller/web/app.py +++ b/psa_car_controller/web/app.py @@ -1,5 +1,6 @@ import locale import logging +import sys import dash_bootstrap_components as dbc from flask import Flask @@ -14,7 +15,10 @@ except ImportError: from werkzeug import DispatcherMiddleware from psa_car_controller.common.mylogger import file_handler -import importlib +if sys.version_info >= (3, 8): + import importlib +else: + import importlib_metadata as importlib # pylint: disable=invalid-name app = None diff --git a/pyproject.toml b/pyproject.toml index 9e55e31..e47f5e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,9 +36,16 @@ six = ">=1.10" python-dateutil = ">=2.5.3" urllib3 = ">=1.15.1 <2.0.0" importlib-metadata = {version = ">=1.7.0", python = "<3.8"} +pandas = "^1.1.5" +numpy = [{version = ">=1.24.0", python = ">=3.11"}, + {version = "<1.26.0", python = "<3.9"}, + {version = "<1.22.0", python = "<3.8"}] +scipy = [{version = ">=1.9.2", python = ">=3.11"}, + {version = "<1.11.0", python = "<3.8"}, + {version = "<1.8.0", python = "<3.8"}] [tool.poetry.dev-dependencies] -prospector = "1.10.2" +prospector = "1.10.3" pre-commit = "^2.17.0" coverage = "^6.3.2" deepdiff = "^5.7.0"