diff --git a/pyoverkiz/auth/strategies.py b/pyoverkiz/auth/strategies.py index 00ab3973..b5a0e3c7 100644 --- a/pyoverkiz/auth/strategies.py +++ b/pyoverkiz/auth/strategies.py @@ -8,6 +8,7 @@ import json import ssl from collections.abc import Mapping +from http import HTTPStatus from typing import TYPE_CHECKING, Any, cast if TYPE_CHECKING: @@ -49,6 +50,8 @@ ) from pyoverkiz.models import ServerConfig +MIN_JWT_SEGMENTS = 2 + class BaseAuthStrategy(AuthStrategy): """Base class for authentication strategies.""" @@ -110,13 +113,13 @@ async def _post_login(self, data: Mapping[str, Any]) -> None: data=data, ssl=self._ssl, ) as response: - if response.status not in (200, 204): + if response.status not in (HTTPStatus.OK, HTTPStatus.NO_CONTENT): raise BadCredentialsError( f"Login failed for {self.server.name}: {response.status}" ) # A 204 No Content response cannot have a body, so skip JSON parsing. - if response.status == 204: + if response.status == HTTPStatus.NO_CONTENT: return result = await response.json() @@ -419,7 +422,7 @@ def auth_headers(self, path: str | None = None) -> Mapping[str, str]: def _decode_jwt_payload(token: str) -> dict[str, Any]: """Decode the payload of a JWT token.""" parts = token.split(".") - if len(parts) < 2: + if len(parts) < MIN_JWT_SEGMENTS: raise InvalidTokenError("Malformed JWT received.") payload_segment = parts[1] diff --git a/pyoverkiz/client.py b/pyoverkiz/client.py index bb905884..47ea116e 100644 --- a/pyoverkiz/client.py +++ b/pyoverkiz/client.py @@ -5,6 +5,7 @@ import logging import ssl import urllib.parse +from http import HTTPStatus from pathlib import Path from types import TracebackType from typing import Any, Self, cast @@ -728,7 +729,7 @@ async def _delete(self, path: str) -> None: async def _parse_response(response: ClientResponse) -> Any: """Check response status and parse JSON body (returns None for 204).""" await check_response(response) - if response.status == 204: + if response.status == HTTPStatus.NO_CONTENT: return None return await response.json() diff --git a/pyoverkiz/response_handler.py b/pyoverkiz/response_handler.py index 6669aa55..97e2582b 100644 --- a/pyoverkiz/response_handler.py +++ b/pyoverkiz/response_handler.py @@ -2,6 +2,7 @@ from __future__ import annotations +from http import HTTPStatus from json import JSONDecodeError from aiohttp import ClientResponse @@ -113,7 +114,7 @@ async def check_response(response: ClientResponse) -> None: """Check the response returned by the OverKiz API.""" - if response.status in [200, 204]: + if response.status in (HTTPStatus.OK, HTTPStatus.NO_CONTENT): return try: @@ -124,7 +125,7 @@ async def check_response(response: ClientResponse) -> None: if "is down for maintenance" in result: raise MaintenanceError("Server is down for maintenance") from error - if response.status == 503: + if response.status == HTTPStatus.SERVICE_UNAVAILABLE: raise ServiceUnavailableError(result) from error raise OverkizError( diff --git a/pyproject.toml b/pyproject.toml index a8e28ebe..f97104c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,16 +122,21 @@ select = [ "PTH", # flake8-builtins "A", + # pylint + "PL", ] ignore = [ "E501", # Line too long "TRY003", # Long exception messages are acceptable for domain-specific errors "A002", # Shadowing builtins (id, type) is intentional for API field names + "PLR0913", # Too many arguments — expected for API model constructors + "PLC0415", # Import not at top level — intentional lazy imports + "PLR0911", # Too many return statements — acceptable in factory functions ] [tool.ruff.lint.per-file-ignores] -"tests/**" = ["S101", "SLF001"] # Allow assert and private member access in tests -"utils/**" = ["INP001", "BLE001"] # Utils are scripts, not packages +"tests/**" = ["S101", "SLF001", "PLR2004"] # Allow assert, private member access, and magic values in tests +"utils/**" = ["INP001", "BLE001", "PLR0912", "PLR0915"] # Utils are scripts, not packages; complex generators acceptable [tool.ruff.lint.pydocstyle] convention = "google" # Accepts: "google", "numpy", or "pep257".