From 3539db5d348a26e91c0992affb869521ef962a14 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Fri, 4 Mar 2022 04:06:43 +0100 Subject: [PATCH 1/4] Add mypy to this project --- poetry.lock | 39 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index edc36e4..9a3bbee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -489,6 +489,23 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "mypy" +version = "0.931" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] + [[package]] name = "mypy-extensions" version = "0.4.3" @@ -1371,6 +1388,28 @@ multidict = [ {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] +mypy = [ + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, +] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, diff --git a/pyproject.toml b/pyproject.toml index 972ce76..3fef42a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ coverage = {version = "^6.3", extras = ["toml"]} flake8 = "^4.0.1" flake8-docstrings = "^1.5.0" isort = "^5.10.1" +mypy = "^0.931" pre-commit = "^2.17.0" pre-commit-hooks = "^4.1.0" pylint = "^2.12.2" @@ -80,6 +81,43 @@ source = ["omnikinverter"] profile = "black" multi_line_output = 3 +[tool.mypy] +# Specify the target platform details in config, so your developers are +# free to run mypy on Windows, Linux, or macOS and get consistent +# results. +platform = "linux" +python_version = 3.9 + +# flake8-mypy expects the two following for sensible formatting +show_column_numbers = true + +# show error messages from unrelated files +follow_imports = "normal" + +# suppress errors about unsatisfied imports +ignore_missing_imports = true + +# be strict +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_defs = true +disallow_untyped_decorators = false # thanks backoff :( +no_implicit_optional = true +no_implicit_reexport = true +strict_optional = true +warn_incomplete_stub = true +warn_no_return = true +warn_redundant_casts = true +warn_return_any = true +warn_unused_configs = true +warn_unused_ignores = true + +# No incremental mode +cache_dir = "/dev/null" + [tool.pylint.BASIC] good-names = [ "_", @@ -113,6 +151,7 @@ max-attributes=20 [tool.pytest.ini_options] addopts = "--cov" +asyncio_mode = "auto" [tool.vulture] min_confidence = 80 @@ -120,5 +159,5 @@ paths = ["omnikinverter"] verbose = true [build-system] -requires = ["setuptools","poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From f04662de6719bc286df6e18f90d495ff1f348608 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Tue, 8 Mar 2022 11:15:25 +0100 Subject: [PATCH 2/4] Apply stricter typing to this project --- omnikinverter/models.py | 96 +++++++++++++++++++--------------- omnikinverter/omnikinverter.py | 13 ++--- tests/__init__.py | 2 +- tests/test_models.py | 23 ++++---- tests/test_omnik.py | 31 ++++++----- 5 files changed, 91 insertions(+), 74 deletions(-) diff --git a/omnikinverter/models.py b/omnikinverter/models.py index 5c9cfe0..b0060db 100644 --- a/omnikinverter/models.py +++ b/omnikinverter/models.py @@ -1,10 +1,9 @@ """Models for Omnik Inverter.""" from __future__ import annotations -import json import re from dataclasses import dataclass -from typing import Any +from typing import Any, cast from .exceptions import OmnikInverterWrongSourceError, OmnikInverterWrongValuesError @@ -36,14 +35,13 @@ def from_json(data: dict[str, Any]) -> Inverter: OmnikInverterWrongValuesError: Inverter pass on incorrect data (day and total are equal). """ - data = json.loads(data) - def get_value(search): + def get_value(search: str) -> Any: if data[search] != "": return data[search] return None - def validation(data_list): + def validation(data_list: list[Any]) -> bool: """Check if the values are not equal to each other. Args: @@ -72,7 +70,7 @@ def validation(data_list): ) @staticmethod - def from_html(data: dict[str, Any]) -> Inverter: + def from_html(data: str) -> Inverter: """Return Inverter object from the Omnik Inverter response. Args: @@ -82,16 +80,18 @@ def from_html(data: dict[str, Any]) -> Inverter: An Inverter object. """ - def get_value(search_key): - match = re.search(f'(?<={search_key}=").*?(?=";)', data.replace(" ", "")) + def get_value(search_key: str) -> Any: try: - value = match.group(0) - if value != "": + match = cast( + re.Match[str], + re.search(f'(?<={search_key}=").*?(?=";)', data.replace(" ", "")), + ).group(0) + if match != "": if search_key in ["webdata_now_p", "webdata_rate_p"]: - return int(value) + return int(match) if search_key in ["webdata_today_e", "webdata_total_e"]: - return float(value) - return value + return float(match) + return match return None except AttributeError as exception: raise OmnikInverterWrongSourceError( @@ -110,7 +110,7 @@ def get_value(search_key): ) @staticmethod - def from_js(data: dict[str, Any]) -> Inverter: + def from_js(data: str) -> Inverter: """Return Inverter object from the Omnik Inverter response. Args: @@ -120,25 +120,35 @@ def from_js(data: dict[str, Any]) -> Inverter: An Inverter object. """ - def get_value(position): - if data.find("webData") != -1: - matches = re.search(r'(?<=webData=").*?(?=";)', data) - else: - matches = re.search(r'(?<=myDeviceArray\[0\]=").*?(?=";)', data) - + def get_value(position: int) -> Any: try: - data_list = matches.group(0).split(",") - if data_list[position] != "": + if data.find("webData") != -1: + matches = ( + cast(re.Match[str], re.search(r'(?<=webData=").*?(?=";)', data)) + .group(0) + .split(",") + ) + else: + matches = ( + cast( + re.Match[str], + re.search(r'(?<=myDeviceArray\[0\]=").*?(?=";)', data), + ) + .group(0) + .split(",") + ) + + if matches[position] != "": if position in [4, 5, 6, 7]: if position in [4, 5]: - return int(data_list[position]) + return int(matches[position]) if position == 6: - energy_value = float(data_list[position]) / 100 + energy_value = float(matches[position]) / 100 if position == 7: - energy_value = float(data_list[position]) / 10 + energy_value = float(matches[position]) / 10 return energy_value - return data_list[position].replace(" ", "") + return matches[position].replace(" ", "") return None except AttributeError as exception: raise OmnikInverterWrongSourceError( @@ -175,8 +185,6 @@ def from_json(data: dict[str, Any]) -> Device: Returns: An Device object. """ - - data = json.loads(data) return Device( signal_quality=None, firmware=data["g_ver"].replace("VER:", ""), @@ -184,7 +192,7 @@ def from_json(data: dict[str, Any]) -> Device: ) @staticmethod - def from_html(data: dict[str, Any]) -> Device: + def from_html(data: str) -> Device: """Return Device object from the Omnik Inverter response. Args: @@ -197,13 +205,15 @@ def from_html(data: dict[str, Any]) -> Device: for correction in [" ", "%"]: data = data.replace(correction, "") - def get_value(search_key): - match = re.search(f'(?<={search_key}=").*?(?=";)', data) - value = match.group(0) - if value != "": + def get_value(search_key: str) -> Any: + match = cast( + re.Match[str], re.search(f'(?<={search_key}=").*?(?=";)', data) + ).group(0) + + if match != "": if search_key in ["cover_sta_rssi"]: - return int(value) - return value + return int(match) + return match return None return Device( @@ -213,7 +223,7 @@ def get_value(search_key): ) @staticmethod - def from_js(data: dict[str, Any]) -> Device: + def from_js(data: str) -> Device: """Return Device object from the Omnik Inverter response. Args: @@ -225,13 +235,15 @@ def from_js(data: dict[str, Any]) -> Device: for correction in [" ", "%"]: data = data.replace(correction, "") - def get_value(search_key): - match = re.search(f'(?<={search_key}=").*?(?=";)', data) - value = match.group(0) - if value != "": + def get_value(search_key: str) -> Any: + match = cast( + re.Match[str], re.search(f'(?<={search_key}=").*?(?=";)', data) + ).group(0) + + if match != "": if search_key == "m2mRssi": - return int(value) - return value + return int(match) + return match return None return Device( diff --git a/omnikinverter/omnikinverter.py b/omnikinverter/omnikinverter.py index f2ee703..88dd18a 100644 --- a/omnikinverter/omnikinverter.py +++ b/omnikinverter/omnikinverter.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +import json from collections.abc import Mapping from dataclasses import dataclass from typing import Any @@ -28,7 +29,7 @@ class OmnikInverter: username: str | None = None password: str | None = None source_type: str = "javascript" - request_timeout: int = 10 + request_timeout: float = 10.0 session: ClientSession | None = None _close_session: bool = False @@ -38,8 +39,8 @@ async def request( uri: str, *, method: str = METH_GET, - params: Mapping[str, str] | None = None, - ) -> dict[str, Any]: + params: Mapping[str, Any] | None = None, + ) -> str: """Handle a request to a Omnik Inverter device. Args: @@ -124,7 +125,7 @@ async def inverter(self) -> Inverter: """ if self.source_type == "json": data = await self.request("status.json", params={"CMD": "inv_query"}) - return Inverter.from_json(data) + return Inverter.from_json(json.loads(data)) if self.source_type == "html": data = await self.request("status.html") return Inverter.from_html(data) @@ -139,7 +140,7 @@ async def device(self) -> Device: """ if self.source_type == "json": data = await self.request("status.json", params={"CMD": "inv_query"}) - return Device.from_json(data) + return Device.from_json(json.loads(data)) if self.source_type == "html": data = await self.request("status.html") return Device.from_html(data) @@ -159,7 +160,7 @@ async def __aenter__(self) -> OmnikInverter: """ return self - async def __aexit__(self, *_exc_info) -> None: + async def __aexit__(self, *_exc_info: str) -> None: """Async exit. Args: diff --git a/tests/__init__.py b/tests/__init__.py index 5075ad4..878f370 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -2,7 +2,7 @@ import os -def load_fixtures(filename): +def load_fixtures(filename: str) -> str: """Load a fixture.""" path = os.path.join(os.path.dirname(__file__), "fixtures", filename) with open(path, encoding="utf-8") as fptr: diff --git a/tests/test_models.py b/tests/test_models.py index 7bbf468..107d136 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,7 @@ """Test the models.""" import aiohttp import pytest +from aresponses import ResponsesMockServer from omnikinverter import Device, Inverter, OmnikInverter from omnikinverter.exceptions import OmnikInverterWrongValuesError @@ -9,7 +10,7 @@ @pytest.mark.asyncio -async def test_inverter_js_webdata(aresponses): +async def test_inverter_js_webdata(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - JS Webdata source.""" aresponses.add( "example.com", @@ -37,7 +38,7 @@ async def test_inverter_js_webdata(aresponses): @pytest.mark.asyncio -async def test_device_js_webdata(aresponses): +async def test_device_js_webdata(aresponses: ResponsesMockServer) -> None: """Test request from a Device - JS Webdata source.""" aresponses.add( "example.com", @@ -60,7 +61,7 @@ async def test_device_js_webdata(aresponses): @pytest.mark.asyncio -async def test_inverter_html(aresponses): +async def test_inverter_html(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - HTML source.""" aresponses.add( "example.com", @@ -94,7 +95,7 @@ async def test_inverter_html(aresponses): @pytest.mark.asyncio -async def test_device_html(aresponses): +async def test_device_html(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - HTML source.""" aresponses.add( "example.com", @@ -123,7 +124,7 @@ async def test_device_html(aresponses): @pytest.mark.asyncio -async def test_inverter_without_session(aresponses): +async def test_inverter_without_session(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - HTML source and without session.""" aresponses.add( "example.com", @@ -155,7 +156,7 @@ async def test_inverter_without_session(aresponses): @pytest.mark.asyncio -async def test_device_without_session(aresponses): +async def test_device_without_session(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - HTML source and without session.""" aresponses.add( "example.com", @@ -182,7 +183,7 @@ async def test_device_without_session(aresponses): @pytest.mark.asyncio -async def test_inverter_js_devicearray(aresponses): +async def test_inverter_js_devicearray(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - JS DeviceArray source.""" aresponses.add( "example.com", @@ -210,7 +211,7 @@ async def test_inverter_js_devicearray(aresponses): @pytest.mark.asyncio -async def test_device_js_devicearray(aresponses): +async def test_device_js_devicearray(aresponses: ResponsesMockServer) -> None: """Test request from a Device - JS DeviceArray source.""" aresponses.add( "example.com", @@ -233,7 +234,7 @@ async def test_device_js_devicearray(aresponses): @pytest.mark.asyncio -async def test_inverter_json(aresponses): +async def test_inverter_json(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - JSON source.""" aresponses.add( "example.com", @@ -261,7 +262,7 @@ async def test_inverter_json(aresponses): @pytest.mark.asyncio -async def test_device_json(aresponses): +async def test_device_json(aresponses: ResponsesMockServer) -> None: """Test request from a Inverter - JSON source.""" aresponses.add( "example.com", @@ -284,7 +285,7 @@ async def test_device_json(aresponses): @pytest.mark.asyncio -async def test_wrong_values(aresponses): +async def test_wrong_values(aresponses: ResponsesMockServer) -> None: """Test on wrong inverter values.""" aresponses.add( "example.com", diff --git a/tests/test_omnik.py b/tests/test_omnik.py index aa1864b..37cc874 100644 --- a/tests/test_omnik.py +++ b/tests/test_omnik.py @@ -4,6 +4,7 @@ import aiohttp import pytest +from aresponses import Response, ResponsesMockServer from omnikinverter import ( OmnikInverter, @@ -16,7 +17,7 @@ @pytest.mark.asyncio -async def test_json_request(aresponses): +async def test_json_request(aresponses: ResponsesMockServer) -> None: """Test JSON response is handled correctly.""" aresponses.add( "example.com", @@ -35,7 +36,7 @@ async def test_json_request(aresponses): @pytest.mark.asyncio -async def test_internal_session(aresponses): +async def test_internal_session(aresponses: ResponsesMockServer) -> None: """Test JSON response is handled correctly.""" aresponses.add( "example.com", @@ -53,11 +54,13 @@ async def test_internal_session(aresponses): @pytest.mark.asyncio -async def test_internal_session_close_while_in_progress(aresponses): +async def test_internal_session_close_while_in_progress( + aresponses: ResponsesMockServer, +) -> None: """Test internal session is closed/cleaned up when closed during request.""" # Delay response so connection can be closed during request - async def response_handler(_): + async def response_handler(_: aiohttp.ClientResponse) -> Response: await asyncio.sleep(0.2) return aresponses.Response( status=200, @@ -75,7 +78,7 @@ async def response_handler(_): @pytest.mark.asyncio -async def test_internal_session_error(aresponses): +async def test_internal_session_error(aresponses: ResponsesMockServer) -> None: """Test JSON response is handled correctly.""" aresponses.add( "example.com", @@ -94,7 +97,7 @@ async def test_internal_session_error(aresponses): @pytest.mark.asyncio -async def test_wrong_js_source(aresponses): +async def test_wrong_js_source(aresponses: ResponsesMockServer) -> None: """Test on wrong data source error raise.""" aresponses.add( "example.com", @@ -114,7 +117,7 @@ async def test_wrong_js_source(aresponses): @pytest.mark.asyncio -async def test_wrong_html_source(aresponses): +async def test_wrong_html_source(aresponses: ResponsesMockServer) -> None: """Test on wrong data source error raise.""" aresponses.add( "example.com", @@ -140,7 +143,7 @@ async def test_wrong_html_source(aresponses): @pytest.mark.asyncio -async def test_html_no_auth(aresponses): +async def test_html_no_auth(aresponses: ResponsesMockServer) -> None: """Test on html request without auth.""" aresponses.add( "example.com", @@ -159,10 +162,10 @@ async def test_html_no_auth(aresponses): @pytest.mark.asyncio -async def test_timeout(aresponses): +async def test_timeout(aresponses: ResponsesMockServer) -> None: """Test request timeout from Omnik Inverter.""" # Faking a timeout by sleeping - async def response_handler(_): + async def response_handler(_: aiohttp.ClientResponse) -> Response: await asyncio.sleep(0.2) return aresponses.Response( body="Goodmorning!", text=load_fixtures("status_webdata.js") @@ -177,7 +180,7 @@ async def response_handler(_): @pytest.mark.asyncio -async def test_content_type(aresponses): +async def test_content_type(aresponses: ResponsesMockServer) -> None: """Test request content type error from Omnik Inverter.""" aresponses.add( "example.com", @@ -196,7 +199,7 @@ async def test_content_type(aresponses): @pytest.mark.asyncio -async def test_client_error(): +async def test_client_error() -> None: """Test request client error from Omnik Inverter.""" async with aiohttp.ClientSession() as session: client = OmnikInverter(host="example.com", session=session) @@ -207,7 +210,7 @@ async def test_client_error(): @pytest.mark.asyncio -async def test_http_error404(aresponses): +async def test_http_error404(aresponses: ResponsesMockServer) -> None: """Test HTTP 404 response handling.""" aresponses.add( "example.com", @@ -223,7 +226,7 @@ async def test_http_error404(aresponses): @pytest.mark.asyncio -async def test_unexpected_response(aresponses): +async def test_unexpected_response(aresponses: ResponsesMockServer) -> None: """Test unexpected response handling.""" aresponses.add( "example.com", From 55e9f48b7b32baa7ec1d043be7b00512697a5b1e Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Tue, 8 Mar 2022 11:15:51 +0100 Subject: [PATCH 3/4] Add typing GitHub workflow --- .github/workflows/typing.yaml | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/typing.yaml diff --git a/.github/workflows/typing.yaml b/.github/workflows/typing.yaml new file mode 100644 index 0000000..7639869 --- /dev/null +++ b/.github/workflows/typing.yaml @@ -0,0 +1,52 @@ +--- +name: Typing + +# yamllint disable-line rule:truthy +on: + push: + pull_request: + workflow_dispatch: + +jobs: + mypy: + name: mypy on Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: ["3.9", "3.10"] + steps: + - name: โคต๏ธ Check out code from GitHub + uses: actions/checkout@v3 + - name: ๐Ÿ— Set up Python ${{ matrix.python }} + id: python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + - name: ๐Ÿ— Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: โคต๏ธ Restore cached Python PIP packages + uses: actions/cache@v2.1.7 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ runner.os }}-v1-${{ steps.python.outputs.python-version }}-${{ hashFiles('.github/workflows/requirements.txt') }} + restore-keys: | + pip-${{ runner.os }}-v1-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install workflow dependencies + run: | + pip install -r .github/workflows/requirements.txt + poetry config virtualenvs.create true + poetry config virtualenvs.in-project true + - name: โคต๏ธ Restore cached Python virtual environment + id: cached-poetry-dependencies + uses: actions/cache@v2.1.7 + with: + path: .venv + key: venv-${{ runner.os }}-v1-${{ steps.python.outputs.python-version }}-${{ hashFiles('poetry.lock') }} + restore-keys: | + venv-${{ runner.os }}-v1-${{ steps.python.outputs.python-version }}- + - name: ๐Ÿ— Install dependencies + run: poetry install --no-interaction + - name: ๐Ÿš€ Run mypy + run: poetry run mypy examples omnikinverter tests From c1a27cd1005ee358245a66e079bd1f6b68a95a96 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Tue, 8 Mar 2022 11:33:19 +0100 Subject: [PATCH 4/4] Remove examples as folder from typing test --- .github/workflows/typing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typing.yaml b/.github/workflows/typing.yaml index 7639869..8111a02 100644 --- a/.github/workflows/typing.yaml +++ b/.github/workflows/typing.yaml @@ -49,4 +49,4 @@ jobs: - name: ๐Ÿ— Install dependencies run: poetry install --no-interaction - name: ๐Ÿš€ Run mypy - run: poetry run mypy examples omnikinverter tests + run: poetry run mypy omnikinverter tests