From 8345d8a211ed74e99efccd3376f8f930a3d28066 Mon Sep 17 00:00:00 2001 From: sh1man Date: Wed, 6 Aug 2025 02:16:53 +0300 Subject: [PATCH] allow specifying expected response type --- src/dataclass_rest/boundmethod.py | 3 +++ src/dataclass_rest/http/aiohttp.py | 12 +++++++++++- src/dataclass_rest/http/requests.py | 12 +++++++++++- src/dataclass_rest/method.py | 4 ++++ src/dataclass_rest/response_type.py | 10 ++++++++++ src/dataclass_rest/rest.py | 11 ++++++++++- 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/dataclass_rest/response_type.py diff --git a/src/dataclass_rest/boundmethod.py b/src/dataclass_rest/boundmethod.py index 8fb7b6a..6420230 100644 --- a/src/dataclass_rest/boundmethod.py +++ b/src/dataclass_rest/boundmethod.py @@ -7,6 +7,7 @@ from .exceptions import ClientLibraryError, MalformedResponse from .http_request import File, HttpRequest from .methodspec import MethodSpec +from .response_type import ResponseType logger = getLogger(__name__) @@ -18,11 +19,13 @@ def __init__( method_spec: MethodSpec, client: ClientProtocol, on_error: Optional[Callable[[Any], Any]], + response_type: ResponseType, ): self.name = name self.method_spec = method_spec self.client = client self.on_error = on_error or self._on_error_default + self.response_type = response_type def _apply_args(self, *args, **kwargs) -> Dict: return getcallargs( diff --git a/src/dataclass_rest/http/aiohttp.py b/src/dataclass_rest/http/aiohttp.py index 833ec80..38472bf 100644 --- a/src/dataclass_rest/http/aiohttp.py +++ b/src/dataclass_rest/http/aiohttp.py @@ -20,6 +20,7 @@ ServerError, ) from dataclass_rest.http_request import HttpRequest +from dataclass_rest.response_type import ResponseType class AiohttpMethod(AsyncMethod): @@ -34,7 +35,16 @@ async def _release_raw_response(self, response: ClientResponse) -> None: async def _response_body(self, response: ClientResponse) -> Any: try: - return await response.json() + if self.response_type == ResponseType.JSON: + return await response.json() + elif self.response_type == ResponseType.TEXT: + return await response.text() + elif self.response_type == ResponseType.BYTES: + return await response.read() + elif self.response_type == ResponseType.NO_CONTENT: + return None + else: + raise ValueError("Unknown expected response type") except AioHttpClientError as e: raise ClientLibraryError from e except JSONDecodeError as e: diff --git a/src/dataclass_rest/http/requests.py b/src/dataclass_rest/http/requests.py index 70ed560..40f8330 100644 --- a/src/dataclass_rest/http/requests.py +++ b/src/dataclass_rest/http/requests.py @@ -13,6 +13,7 @@ ServerError, ) from dataclass_rest.http_request import File, HttpRequest +from dataclass_rest.response_type import ResponseType class RequestsMethod(SyncMethod): @@ -27,7 +28,16 @@ def _response_ok(self, response: Response) -> bool: def _response_body(self, response: Response) -> Any: try: - return response.json() + if self.response_type == ResponseType.JSON: + return response.json() + elif self.response_type == ResponseType.TEXT: + return response.text + elif self.response_type == ResponseType.BYTES: + return response.content + elif self.response_type == ResponseType.NO_CONTENT: + return None + else: + raise ValueError("Unknown expected response type") except RequestException as e: raise ClientLibraryError from e except JSONDecodeError as e: diff --git a/src/dataclass_rest/method.py b/src/dataclass_rest/method.py index b8d0f0e..f6957ae 100644 --- a/src/dataclass_rest/method.py +++ b/src/dataclass_rest/method.py @@ -3,16 +3,19 @@ from .boundmethod import BoundMethod from .client_protocol import ClientProtocol from .methodspec import MethodSpec +from .response_type import ResponseType class Method: def __init__( self, method_spec: MethodSpec, + response_type: ResponseType, method_class: Optional[Callable[..., BoundMethod]] = None, ): self.name = method_spec.func.__name__ self.method_spec = method_spec + self.response_type = response_type self.method_class = method_class self._on_error = None @@ -42,6 +45,7 @@ def __get__( method_spec=self.method_spec, client=instance, on_error=self._on_error, + response_type=self.response_type, ) def on_error(self, func) -> "Method": diff --git a/src/dataclass_rest/response_type.py b/src/dataclass_rest/response_type.py new file mode 100644 index 0000000..4d087f8 --- /dev/null +++ b/src/dataclass_rest/response_type.py @@ -0,0 +1,10 @@ +from enum import Enum +from typing import Literal + +class ResponseType(Enum): + JSON = "json" + TEXT = "text" + BYTES = "bytes" + NO_CONTENT = "no_content" + +ResponseTypeLiteral = Literal["json", "text", "bytes", "no_content"] \ No newline at end of file diff --git a/src/dataclass_rest/rest.py b/src/dataclass_rest/rest.py index 61e29d1..d95c9a1 100644 --- a/src/dataclass_rest/rest.py +++ b/src/dataclass_rest/rest.py @@ -3,6 +3,7 @@ from .boundmethod import BoundMethod from .method import Method from .parse_func import DEFAULT_BODY_PARAM, UrlTemplate, parse_func +from .response_type import ResponseTypeLiteral, ResponseType _Func = TypeVar("_Func", bound=Callable[..., Any]) @@ -15,9 +16,17 @@ def rest( additional_params: Optional[Dict[str, Any]] = None, method_class: Optional[Callable[..., BoundMethod]] = None, send_json: bool = True, + response_type: ResponseTypeLiteral = "json", ) -> Callable[[Callable], Method]: if additional_params is None: additional_params = {} + try: + response_type_enum = ResponseType(response_type) + except ValueError: + raise TypeError( + f"'{response_type}' is not a valid response type. " + f"Use one of {list(ResponseTypeLiteral.__args__)}" + ) def dec(func: Callable) -> Method: method_spec = parse_func( @@ -28,7 +37,7 @@ def dec(func: Callable) -> Method: additional_params=additional_params, is_json_request=send_json, ) - return Method(method_spec, method_class=method_class) + return Method(method_spec, method_class=method_class, response_type=response_type_enum) return dec