From 508ccf1c6ab89c4132708cc9bde796b442e3cd7c Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Sat, 27 Jul 2024 21:14:59 +0200 Subject: [PATCH] more strict ruff rules --- .ruff.toml | 42 +++++++++++++++++++++ src/dataclass_rest/__init__.py | 2 +- src/dataclass_rest/base_client.py | 3 +- src/dataclass_rest/boundmethod.py | 18 ++++----- src/dataclass_rest/client_protocol.py | 12 +++++- src/dataclass_rest/http/aiohttp.py | 19 +++++++--- src/dataclass_rest/http/requests.py | 15 +++++--- src/dataclass_rest/http_request.py | 2 +- src/dataclass_rest/method.py | 2 +- src/dataclass_rest/methodspec.py | 3 +- src/dataclass_rest/parse_func.py | 5 ++- tests/requests/conftest.py | 5 ++- tests/requests/test_factory.py | 11 ++++-- tests/requests/test_params.py | 53 +++++++++++++++++---------- tests/test_init.py | 7 +++- 15 files changed, 142 insertions(+), 57 deletions(-) create mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..7fe0663 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,42 @@ +line-length = 79 +target-version="py38" +src = ["src"] + +include = ["src/**.py", "tests/**.py"] +exclude = ["src/dishka/_adaptix/**"] + +lint.select = [ +"ALL" +] +lint.ignore = [ + "ARG", + "ANN", + "D", + "EM101", + "EM102", + "PT001", + "PT023", + "SIM108", + "SIM114", + "TRY003", + "PLW2901", + "RET505", + "RET506", + "PLR0913", + "UP038", + "TCH001", + "FA100", + # tempraty disabled + "PGH005", + "PLR2004", + "N818", # compatibility issue +] + +[lint.per-file-ignores] +"tests/**" = ["TID252", "PLR2004", "S101", "A002"] + +[lint.isort] +no-lines-before = ["local-folder"] + +[lint.flake8-tidy-imports] +ban-relative-imports = "parents" diff --git a/src/dataclass_rest/__init__.py b/src/dataclass_rest/__init__.py index 6f444a2..064320e 100644 --- a/src/dataclass_rest/__init__.py +++ b/src/dataclass_rest/__init__.py @@ -5,4 +5,4 @@ ] from .http_request import File -from .rest import rest, get, put, post, patch, delete +from .rest import delete, get, patch, post, put, rest diff --git a/src/dataclass_rest/base_client.py b/src/dataclass_rest/base_client.py index 0de8b4f..8272430 100644 --- a/src/dataclass_rest/base_client.py +++ b/src/dataclass_rest/base_client.py @@ -1,7 +1,8 @@ from adaptix import Retort from .client_protocol import ( - ClientProtocol, FactoryProtocol, + ClientProtocol, + FactoryProtocol, ) diff --git a/src/dataclass_rest/boundmethod.py b/src/dataclass_rest/boundmethod.py index f69021a..866f670 100644 --- a/src/dataclass_rest/boundmethod.py +++ b/src/dataclass_rest/boundmethod.py @@ -1,11 +1,11 @@ from abc import ABC, abstractmethod from inspect import getcallargs from logging import getLogger -from typing import Dict, Any, Callable, Optional, NoReturn, Type +from typing import Any, Callable, Dict, NoReturn, Optional, Type -from .client_protocol import ClientProtocol, ClientMethodProtocol -from .exceptions import MalformedResponse -from .http_request import HttpRequest, File +from .client_protocol import ClientMethodProtocol, ClientProtocol +from .exceptions import ClientLibraryError, MalformedResponse +from .http_request import File, HttpRequest from .methodspec import MethodSpec logger = getLogger(__name__) @@ -74,7 +74,7 @@ def __call__(self, *args, **kwargs): raise NotImplementedError def _on_error_default(self, response: Any) -> Any: - raise RuntimeError # TODO exceptions + raise ClientLibraryError class SyncMethod(BoundMethod): @@ -90,8 +90,7 @@ def __call__(self, *args, **kwargs): request = self._pre_process_request(request) raw_response = self.client.do_request(request) response = self._pre_process_response(raw_response) - response = self._post_process_response(response) - return response + return self._post_process_response(response) def _pre_process_request(self, request: HttpRequest) -> HttpRequest: return request @@ -135,8 +134,7 @@ async def __call__(self, *args, **kwargs): raw_response = await self.client.do_request(request) response = await self._pre_process_response(raw_response) await self._release_raw_response(raw_response) - response = await self._post_process_response(response) - return response + return await self._post_process_response(response) async def _pre_process_request(self, request: HttpRequest) -> HttpRequest: return request @@ -162,7 +160,7 @@ async def _pre_process_response(self, response: Any) -> Any: raise MalformedResponse from e async def _on_error_default(self, response: Any) -> NoReturn: - raise RuntimeError # TODO exceptions + raise ClientLibraryError @abstractmethod async def _response_body(self, response: Any) -> Any: diff --git a/src/dataclass_rest/client_protocol.py b/src/dataclass_rest/client_protocol.py index eb8e11f..1ed30d5 100644 --- a/src/dataclass_rest/client_protocol.py +++ b/src/dataclass_rest/client_protocol.py @@ -1,5 +1,11 @@ from typing import ( - Protocol, Any, Optional, Callable, Type, runtime_checkable, TypeVar, + Any, + Callable, + Optional, + Protocol, + Type, + TypeVar, + runtime_checkable, ) from .http_request import HttpRequest @@ -18,7 +24,9 @@ class FactoryProtocol(Protocol): def load(self, data: Any, class_: Type[TypeT]) -> TypeT: raise NotImplementedError - def dump(self, data: TypeT, class_: Type[TypeT] = None) -> Any: + def dump( + self, data: TypeT, class_: Optional[Type[TypeT]] = None, + ) -> Any: raise NotImplementedError diff --git a/src/dataclass_rest/http/aiohttp.py b/src/dataclass_rest/http/aiohttp.py index 6d64e38..e8e400f 100644 --- a/src/dataclass_rest/http/aiohttp.py +++ b/src/dataclass_rest/http/aiohttp.py @@ -4,15 +4,22 @@ from aiohttp import FormData from aiohttp.client import ( - ClientResponse, ClientSession, ClientError as AioHttpClientError, + ClientError as AioHttpClientError, +) +from aiohttp.client import ( + ClientResponse, + ClientSession, ) -from ..base_client import BaseClient -from ..boundmethod import AsyncMethod -from ..exceptions import ( - ClientError, ClientLibraryError, ServerError, MalformedResponse, +from dataclass_rest.base_client import BaseClient +from dataclass_rest.boundmethod import AsyncMethod +from dataclass_rest.exceptions import ( + ClientError, + ClientLibraryError, + MalformedResponse, + ServerError, ) -from ..http_request import HttpRequest +from dataclass_rest.http_request import HttpRequest class AiohttpMethod(AsyncMethod): diff --git a/src/dataclass_rest/http/requests.py b/src/dataclass_rest/http/requests.py index 2082d51..6b17484 100644 --- a/src/dataclass_rest/http/requests.py +++ b/src/dataclass_rest/http/requests.py @@ -2,14 +2,17 @@ from json import JSONDecodeError from typing import Any, Optional, Tuple -from requests import Session, Response, RequestException +from requests import RequestException, Response, Session -from ..base_client import BaseClient -from ..boundmethod import SyncMethod -from ..exceptions import ( - ClientLibraryError, ClientError, ServerError, MalformedResponse, +from dataclass_rest.base_client import BaseClient +from dataclass_rest.boundmethod import SyncMethod +from dataclass_rest.exceptions import ( + ClientError, + ClientLibraryError, + MalformedResponse, + ServerError, ) -from ..http_request import HttpRequest, File +from dataclass_rest.http_request import File, HttpRequest class RequestsMethod(SyncMethod): diff --git a/src/dataclass_rest/http_request.py b/src/dataclass_rest/http_request.py index c5bc073..b7ba4e0 100644 --- a/src/dataclass_rest/http_request.py +++ b/src/dataclass_rest/http_request.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Dict, Union, IO, Optional +from typing import IO, Any, Dict, Optional, Union @dataclass diff --git a/src/dataclass_rest/method.py b/src/dataclass_rest/method.py index 66612e7..23c11fa 100644 --- a/src/dataclass_rest/method.py +++ b/src/dataclass_rest/method.py @@ -25,7 +25,7 @@ def __set_name__(self, owner, name): f"No type for bound method is specified. " f"Provide either `{owner.__name__}.method_class` attribute or " f"`method_class=` argument for decorator " - f"on your `{name}` method" + f"on your `{name}` method", ) def __get__( diff --git a/src/dataclass_rest/methodspec.py b/src/dataclass_rest/methodspec.py index bf42c61..0cfbd04 100644 --- a/src/dataclass_rest/methodspec.py +++ b/src/dataclass_rest/methodspec.py @@ -1,10 +1,11 @@ -from typing import Any, Dict, Type, Callable, List +from typing import Any, Callable, Dict, List, Type class MethodSpec: def __init__( self, func: Callable, + *, url_template: str, http_method: str, response_type: Type, diff --git a/src/dataclass_rest/parse_func.py b/src/dataclass_rest/parse_func.py index 3c02e8f..0bd54c6 100644 --- a/src/dataclass_rest/parse_func.py +++ b/src/dataclass_rest/parse_func.py @@ -1,6 +1,6 @@ import string -from inspect import getfullargspec, FullArgSpec, isclass -from typing import Callable, List, Sequence, Any, Type, TypedDict, Dict +from inspect import FullArgSpec, getfullargspec, isclass +from typing import Any, Callable, Dict, List, Sequence, Type, TypedDict from .http_request import File from .methodspec import MethodSpec @@ -53,6 +53,7 @@ def get_file_params(spec): def parse_func( func: Callable, + *, method: str, url_template: str, additional_params: Dict[str, Any], diff --git a/tests/requests/conftest.py b/tests/requests/conftest.py index 9ee4254..849f945 100644 --- a/tests/requests/conftest.py +++ b/tests/requests/conftest.py @@ -3,6 +3,7 @@ from dataclass_rest.http import requests + @pytest.fixture def session(): return requests.Session() @@ -10,5 +11,7 @@ def session(): @pytest.fixture def mocker(session): - with requests_mock.Mocker(session=session, case_sensitive=True) as session_mock: + with requests_mock.Mocker( + session=session, case_sensitive=True, + ) as session_mock: yield session_mock diff --git a/tests/requests/test_factory.py b/tests/requests/test_factory.py index 4a5d762..d6a5ebc 100644 --- a/tests/requests/test_factory.py +++ b/tests/requests/test_factory.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from enum import Enum -from adaptix import Retort, NameStyle, name_mapping +from adaptix import NameStyle, Retort, name_mapping from dataclass_rest import patch from dataclass_rest.http.requests import RequestsClient @@ -43,7 +43,7 @@ def _init_response_body_factory(self) -> Retort: @patch("/post/") def post_x(self, long_param: str, body: RequestBody) -> ResponseBody: - raise NotImplementedError() + raise NotImplementedError mocker.patch( url="http://example.com/post/?LONG.PARAM=hello", @@ -52,8 +52,11 @@ def post_x(self, long_param: str, body: RequestBody) -> ResponseBody: ) client = Api(base_url="http://example.com", session=session) result = client.post_x( - long_param="hello", body=RequestBody(int_param=42, selection=Selection.ONE), + long_param="hello", + body=RequestBody(int_param=42, selection=Selection.ONE), ) assert result == ResponseBody(int_param=1, selection=Selection.TWO) assert mocker.called_once - assert mocker.request_history[0].json() == {"intParam": 42, "selection": "ONE"} + + resp = mocker.request_history[0].json() + assert resp == {"intParam": 42, "selection": "ONE"} diff --git a/tests/requests/test_params.py b/tests/requests/test_params.py index 50a8573..bd8405b 100644 --- a/tests/requests/test_params.py +++ b/tests/requests/test_params.py @@ -1,19 +1,22 @@ from dataclasses import dataclass -from typing import Optional +from typing import List, Optional + +import requests +import requests_mock from dataclass_rest import get, post from dataclass_rest.http.requests import RequestsClient -def test_methods(session, mocker): +def test_methods(session: requests.Session, mocker: requests_mock.Mocker): class Api(RequestsClient): @get("/get") - def get_x(self) -> list[int]: - raise NotImplementedError() + def get_x(self) -> List[int]: + raise NotImplementedError @post("/post") - def post_x(self) -> list[int]: - raise NotImplementedError() + def post_x(self) -> List[int]: + raise NotImplementedError mocker.get("http://example.com/get", text="[1,2]", complete_qs=True) mocker.post("http://example.com/post", text="[1,2,3]", complete_qs=True) @@ -22,11 +25,11 @@ def post_x(self) -> list[int]: assert client.post_x() == [1, 2, 3] -def test_path_params(session, mocker): +def test_path_params(session: requests.Session, mocker: requests_mock.Mocker): class Api(RequestsClient): @post("/post/{id}") - def post_x(self, id) -> list[int]: - raise NotImplementedError() + def post_x(self, id) -> List[int]: + raise NotImplementedError mocker.post("http://example.com/post/1", text="[1]", complete_qs=True) mocker.post("http://example.com/post/2", text="[1,2]", complete_qs=True) @@ -35,15 +38,24 @@ def post_x(self, id) -> list[int]: assert client.post_x(2) == [1, 2] -def test_query_params(session, mocker): +def test_query_params(session: requests.Session, mocker: requests_mock.Mocker): class Api(RequestsClient): @post("/post/{id}") - def post_x(self, id: str, param: Optional[int]) -> list[int]: - raise NotImplementedError() - - mocker.post("http://example.com/post/x?", text="[0]", complete_qs=True) - mocker.post("http://example.com/post/x?param=1", text="[1]", complete_qs=True) - mocker.post("http://example.com/post/x?param=2", text="[1,2]", complete_qs=True) + def post_x(self, id: str, param: Optional[int]) -> List[int]: + raise NotImplementedError + + mocker.post( + url="http://example.com/post/x?", + text="[0]", complete_qs=True, + ) + mocker.post( + url="http://example.com/post/x?param=1", + text="[1]", complete_qs=True, + ) + mocker.post( + url="http://example.com/post/x?param=2", + text="[1,2]", complete_qs=True, + ) client = Api(base_url="http://example.com", session=session) assert client.post_x("x", None) == [0] assert client.post_x("x", 1) == [1] @@ -56,13 +68,16 @@ class RequestBody: y: str -def test_body(session, mocker): +def test_body(session: requests.Session, mocker: requests_mock.Mocker): class Api(RequestsClient): @post("/post/") def post_x(self, body: RequestBody) -> None: - raise NotImplementedError() + raise NotImplementedError - mocker.post("http://example.com/post/", text="null", complete_qs=True) + mocker.post( + url="http://example.com/post/", + text="null", complete_qs=True, + ) client = Api(base_url="http://example.com", session=session) assert client.post_x(RequestBody(x=1, y="test")) is None assert mocker.called_once diff --git a/tests/test_init.py b/tests/test_init.py index 42eedeb..e2226e8 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,7 +1,7 @@ from dataclasses import dataclass import pytest -from adaptix import Retort, NameStyle, name_mapping +from adaptix import NameStyle, Retort, name_mapping from requests import Session from dataclass_rest import get @@ -17,7 +17,10 @@ class Todo: def test_sync(): class RealClient(RequestsClient): def __init__(self): - super().__init__("https://jsonplaceholder.typicode.com/", Session()) + super().__init__( + "https://jsonplaceholder.typicode.com/", + Session(), + ) def _init_request_body_factory(self) -> Retort: return Retort(recipe=[