diff --git a/src/imednet/core/endpoint/mixins/get.py b/src/imednet/core/endpoint/mixins/get.py index 616f9c20..cd295ec6 100644 --- a/src/imednet/core/endpoint/mixins/get.py +++ b/src/imednet/core/endpoint/mixins/get.py @@ -3,6 +3,7 @@ from typing import Any, Dict, Iterable, List, Optional from imednet.core.endpoint.abc import EndpointABC +from imednet.core.endpoint.operations.get import PathGetOperation from imednet.core.paginator import AsyncPaginator, Paginator from imednet.core.protocols import AsyncRequestorProtocol, RequestorProtocol @@ -139,13 +140,6 @@ def _get_path_for_id(self, study_key: Optional[str], item_id: Any) -> str: def _raise_not_found(self, study_key: Optional[str], item_id: Any) -> None: raise ValueError(f"{self.MODEL.__name__} not found") - def _process_response(self, response: Any, study_key: Optional[str], item_id: Any) -> T: - data = response.json() - if not data: - # Enforce strict validation for empty body - self._raise_not_found(study_key, item_id) - return self._parse_item(data) - def _get_path_sync( self, client: RequestorProtocol, @@ -154,8 +148,12 @@ def _get_path_sync( item_id: Any, ) -> T: path = self._get_path_for_id(study_key, item_id) - response = client.get(path) - return self._process_response(response, study_key, item_id) + operation = PathGetOperation[T]( + path=path, + parse_func=self._parse_item, + not_found_func=lambda: self._raise_not_found(study_key, item_id), + ) + return operation.execute_sync(client) async def _get_path_async( self, @@ -165,8 +163,12 @@ async def _get_path_async( item_id: Any, ) -> T: path = self._get_path_for_id(study_key, item_id) - response = await client.get(path) - return self._process_response(response, study_key, item_id) + operation = PathGetOperation[T]( + path=path, + parse_func=self._parse_item, + not_found_func=lambda: self._raise_not_found(study_key, item_id), + ) + return await operation.execute_async(client) def get(self, study_key: Optional[str], item_id: Any) -> T: """Get an item by ID using direct path.""" diff --git a/src/imednet/core/endpoint/operations/__init__.py b/src/imednet/core/endpoint/operations/__init__.py index 6dd15cc7..b98dd128 100644 --- a/src/imednet/core/endpoint/operations/__init__.py +++ b/src/imednet/core/endpoint/operations/__init__.py @@ -1,4 +1,5 @@ +from .get import PathGetOperation from .list import ListOperation from .record_create import RecordCreateOperation -__all__ = ["ListOperation", "RecordCreateOperation"] +__all__ = ["ListOperation", "PathGetOperation", "RecordCreateOperation"] diff --git a/src/imednet/core/endpoint/operations/get.py b/src/imednet/core/endpoint/operations/get.py new file mode 100644 index 00000000..595f7d69 --- /dev/null +++ b/src/imednet/core/endpoint/operations/get.py @@ -0,0 +1,74 @@ +""" +Operation for executing get requests via direct path. + +This module encapsulates the logic for fetching and parsing a single resource +from the API using its ID. +""" + +from __future__ import annotations + +from typing import Any, Callable, Generic, TypeVar + +from imednet.core.protocols import AsyncRequestorProtocol, RequestorProtocol + +T = TypeVar("T") + + +class PathGetOperation(Generic[T]): + """ + Operation for executing get requests via direct path. + + Encapsulates the logic for making the HTTP request, handling empty + responses (not found), and parsing the result. + """ + + def __init__( + self, + path: str, + parse_func: Callable[[Any], T], + not_found_func: Callable[[], None], + ) -> None: + """ + Initialize the path get operation. + + Args: + path: The API endpoint path. + parse_func: A function to parse a raw JSON item into the model T. + not_found_func: A callback to raise the appropriate not found error. + """ + self.path = path + self.parse_func = parse_func + self.not_found_func = not_found_func + + def _process_response(self, response: Any) -> T: + """Process the raw HTTP response.""" + data = response.json() + if not data: + self.not_found_func() + return self.parse_func(data) + + def execute_sync(self, client: RequestorProtocol) -> T: + """ + Execute synchronous get request. + + Args: + client: The synchronous HTTP client. + + Returns: + The parsed item. + """ + response = client.get(self.path) + return self._process_response(response) + + async def execute_async(self, client: AsyncRequestorProtocol) -> T: + """ + Execute asynchronous get request. + + Args: + client: The asynchronous HTTP client. + + Returns: + The parsed item. + """ + response = await client.get(self.path) + return self._process_response(response)