diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a64429e..cc3750b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,104 +1,94 @@ --- repos: - - repo: builtin + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 hooks: - id: check-case-conflict name: 🔠 Check for case conflicts - id: check-executables-have-shebangs name: 🧐 Check that executables have shebangs - language: system types: [text, executable] stages: [commit, push, manual] - id: check-json name: īŊ› Check JSON files - language: system types: [json] - id: check-merge-conflict name: đŸ’Ĩ Check for merge conflicts - language: system types: [text] - id: check-symlinks name: 🔗 Check for broken symlinks - language: system types: [symlink] - id: check-toml name: ✅ Check TOML files - language: system types: [toml] - id: check-xml name: ✅ Check XML files - language: system types: [xml] - id: check-yaml name: ✅ Check YAML files - language: system types: [yaml] - id: detect-private-key name: đŸ•ĩī¸ Detect Private Keys - language: system types: [text] - id: end-of-file-fixer name: ⎐ Fix End of Files - language: system types: [text] stages: [commit, push, manual] - - id: no-commit-to-branch - name: 🛑 Don't commit to main branch - language: system - pass_filenames: false - always_run: true - args: - - --branch=main - id: trailing-whitespace name: ✄ Trim Trailing Whitespace - language: system types: [text] stages: [commit, push, manual] - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - id: check-ast name: 🐍 Check Python AST types: [python] - id: check-docstring-first name: â„šī¸ Check docstring is first types: [python] + - id: no-commit-to-branch + name: 🛑 Don't commit to main branch + args: [--branch=main] - - repo: local + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.8 hooks: - - id: ruff-check + - id: ruff name: đŸļ Ruff Linter - language: system - types: [python] - entry: uv run ruff check --fix - require_serial: true stages: [commit, push, manual] + args: [--fix] - id: ruff-format name: đŸļ Ruff Formatter - language: system - types: [python] - entry: uv run ruff format - require_serial: true stages: [commit, push, manual] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.14.1 + hooks: - id: mypy name: 🆎 Static type checking using mypy - language: system types: [python] - entry: uv run mypy - require_serial: true + additional_dependencies: [] + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0 + hooks: + - id: prettier + name: 💄 Ensuring files are prettier + types: [yaml, json, markdown] + + - repo: https://github.com/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + name: 🎗 Check YAML files with yamllint + types: [yaml] + + - repo: local + hooks: - id: uv name: 📜 Check pyproject with uv language: system entry: uv lock --check pass_filenames: false always_run: true - - id: prettier - name: 💄 Ensuring files are prettier - language: system - types: [yaml, json, markdown] - entry: npm run prettier - pass_filenames: false - id: pylint name: 🌟 Starring code with pylint language: system @@ -110,8 +100,3 @@ repos: types: [python] entry: uv run pytest --cov=tadoasync --cov-report=term-missing --cov-report=xml --cov-fail-under=95 pass_filenames: false - - id: yamllint - name: 🎗 Check YAML files with yamllint - language: system - types: [yaml] - entry: uv run yamllint diff --git a/docs/source/conf.py b/docs/source/conf.py index 27f5320..9403495 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,5 @@ """Config file for Sphinx and documentation.""" + import os import sys diff --git a/pyproject.toml b/pyproject.toml index 0278842..6a59c58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,34 @@ warn_return_any = true warn_unused_configs = true warn_unused_ignores = true +# Per-module overrides for libraries without type stubs +[[tool.mypy.overrides]] +module = "mashumaro.*" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "syrupy" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "aresponses" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "tadoasync.models" +disallow_any_generics = false +disallow_subclassing_any = false + +[[tool.mypy.overrides]] +module = "tadoasync.tadoasync" +disallow_any_generics = false +warn_return_any = false + +[[tool.mypy.overrides]] +module = "tests.*" +disallow_untyped_decorators = false +ignore_errors = true + [tool.pylint.MASTER] ignore = ["tests"] @@ -120,7 +148,7 @@ good-names = ["_", "ex", "fp", "i", "id", "j", "k", "on", "Run", "T"] max-attributes = 8 [tool.pylint."MESSAGES CONTROL"] -disable = ["duplicate-code", "format", "unsubscriptable-object", "no-member", "too-many-arguments", "protected-access", "invalid-name", "wrong-import-order", "too-many-statements", "too-many-public-methods", "too-many-instance-attributes", "redefined-builtin"] +disable = ["duplicate-code", "format", "unsubscriptable-object", "no-member", "too-many-arguments", "protected-access", "invalid-name", "wrong-import-order", "too-many-statements", "too-many-public-methods", "too-many-instance-attributes", "redefined-builtin", "import-error"] [tool.pylint.SIMILARITIES] ignore-imports = true @@ -133,6 +161,7 @@ addopts = "--cov" asyncio_mode = "auto" [tool.ruff] +[tool.ruff.lint] ignore = [ "ANN101", # Self... explanatory "ANN102", # cls... just as useless @@ -158,11 +187,11 @@ ignore = [ ] select = ["ALL"] -[tool.ruff.flake8-pytest-style] +[tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false mark-parentheses = false -[tool.ruff.isort] +[tool.ruff.lint.isort] known-first-party = ["tado"] [tool.ruff.lint.flake8-type-checking] @@ -170,5 +199,5 @@ runtime-evaluated-base-classes = [ "mashumaro.mixins.orjson.DataClassORJSONMixin", ] -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] max-complexity = 25 diff --git a/src/tadoasync/tadoasync.py b/src/tadoasync/tadoasync.py index 864857c..c1f1ad2 100644 --- a/src/tadoasync/tadoasync.py +++ b/src/tadoasync/tadoasync.py @@ -156,7 +156,7 @@ async def login_device_flow(self) -> DeviceActivationStatus: session = self._ensure_session() request = await session.post(url=DEVICE_AUTH_URL, data=data) request.raise_for_status() - except asyncio.TimeoutError as err: + except TimeoutError as err: raise TadoConnectionError( "Timeout occurred while connecting to Tado." ) from err @@ -219,7 +219,7 @@ async def _check_device_activation(self) -> bool: _LOGGER.info("Authorization pending. Continuing polling...") return False request.raise_for_status() - except asyncio.TimeoutError as err: + except TimeoutError as err: raise TadoConnectionError( "Timeout occurred while connecting to Tado." ) from err @@ -281,7 +281,7 @@ async def login(self) -> None: async with asyncio.timeout(self._request_timeout): request = await self._session.post(url=TOKEN_URL, data=data) request.raise_for_status() - except asyncio.TimeoutError as err: + except TimeoutError as err: raise TadoConnectionError( "Timeout occurred while connecting to Tado." ) from err @@ -355,7 +355,7 @@ async def _refresh_auth(self) -> None: session = self._ensure_session() request = await session.post(url=TOKEN_URL, data=data) request.raise_for_status() - except asyncio.TimeoutError as err: + except TimeoutError as err: raise TadoConnectionError( "Timeout occurred while connecting to Tado." ) from err @@ -587,7 +587,7 @@ async def _request( method=method.value, url=str(url), headers=headers, json=data ) request.raise_for_status() - except asyncio.TimeoutError as err: + except TimeoutError as err: raise TadoConnectionError( "Timeout occurred while connecting to Tado." ) from err diff --git a/tests/__init__.py b/tests/__init__.py index b0241e4..d12a68e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,5 @@ """Tests for Python Tado client.""" + from pathlib import Path diff --git a/tests/conftest.py b/tests/conftest.py index 602b2c8..5ce5a65 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,10 +23,13 @@ def snapshot_assertion(snapshot: SnapshotAssertion) -> SnapshotAssertion: @pytest.fixture(name="python_tado") async def client() -> AsyncGenerator[Tado, None]: """Return a Tado client.""" - async with aiohttp.ClientSession() as session, Tado( - session=session, - request_timeout=10, - ) as tado: + async with ( + aiohttp.ClientSession() as session, + Tado( + session=session, + request_timeout=10, + ) as tado, + ): await tado.device_activation() yield tado diff --git a/tests/ruff.toml b/tests/ruff.toml index 1257561..e881fe5 100644 --- a/tests/ruff.toml +++ b/tests/ruff.toml @@ -1,6 +1,7 @@ # This extend our general Ruff rules specifically for tests extend = "../pyproject.toml" +[lint] extend-select = [ "PT", # Use @pytest.fixture without parentheses ] diff --git a/tests/syrupy.py b/tests/syrupy.py index ef0e9bb..c4359b0 100644 --- a/tests/syrupy.py +++ b/tests/syrupy.py @@ -1,4 +1,5 @@ """Asynchronous Python client for Tado.""" + from __future__ import annotations from dataclasses import asdict, is_dataclass diff --git a/tests/test_tado.py b/tests/test_tado.py index 9b29ea7..5046664 100644 --- a/tests/test_tado.py +++ b/tests/test_tado.py @@ -121,7 +121,7 @@ async def test_login_device_flow_timeout(responses: aioresponses) -> None: responses._matches.clear() responses.post( DEVICE_AUTH_URL, - exception=asyncio.TimeoutError(), + exception=TimeoutError(), repeat=True, ) @@ -161,8 +161,9 @@ async def mock_post(*args: Any, **kwargs: Any) -> ClientResponse: # noqa: ARG00 async with aiohttp.ClientSession() as session: tado = Tado(session=session) - with patch("aiohttp.ClientSession.post", new=mock_post), pytest.raises( - TadoError + with ( + patch("aiohttp.ClientSession.post", new=mock_post), + pytest.raises(TadoError), ): await tado.async_init() @@ -182,8 +183,9 @@ async def mock_post(*args: Any, **kwargs: Any) -> ClientResponse: # noqa: ARG00 async with aiohttp.ClientSession() as session: tado = Tado(session=session) - with patch("aiohttp.ClientSession.post", new=mock_post), pytest.raises( - TadoAuthenticationError + with ( + patch("aiohttp.ClientSession.post", new=mock_post), + pytest.raises(TadoAuthenticationError), ): await tado.async_init() @@ -203,8 +205,9 @@ async def mock_post(*args: Any, **kwargs: Any) -> ClientResponse: # noqa: ARG00 async with aiohttp.ClientSession() as session: tado = Tado(session=session) - with patch("aiohttp.ClientSession.post", new=mock_post), pytest.raises( - TadoAuthenticationError + with ( + patch("aiohttp.ClientSession.post", new=mock_post), + pytest.raises(TadoAuthenticationError), ): await tado.async_init() @@ -236,7 +239,7 @@ async def test_refresh_auth_timeout(python_tado: Tado, responses: aioresponses) """Test timeout during refresh of auth token.""" responses.post( TADO_TOKEN_URL, - exception=asyncio.TimeoutError(), + exception=TimeoutError(), ) async with aiohttp.ClientSession(): python_tado._access_token = "old_test_access_token" @@ -635,8 +638,9 @@ async def test_request_client_response_error(python_tado: Tado) -> None: async def mock_get(*args: Any, **kwargs: Any) -> ClientResponse: # noqa: ARG001 # pylint: disable=unused-argument return mock_response - with patch("aiohttp.ClientSession.request", new=mock_get), pytest.raises( - TadoBadRequestError + with ( + patch("aiohttp.ClientSession.request", new=mock_get), + pytest.raises(TadoBadRequestError), ): await python_tado._request("me")