From 46d91f5787e91fd2f78835ca9ba8dbb38180f684 Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:24:35 +0000 Subject: [PATCH 1/5] Refactor: Collapse list logic and remove duplication in Endpoints Collapsed redundant list implementation logic in `UsersEndpoint` and `RecordsEndpoint` by introducing `_extract_special_params` hook in `ListGetEndpointMixin`. Extracted duplicate site filtering logic in `SubjectsEndpoint` to a shared helper method. This enforces DRY and Open/Closed principles without altering behavior. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- imednet/endpoints/_mixins.py | 22 ++++++++++++++++++++++ imednet/endpoints/records.py | 24 +++++++----------------- imednet/endpoints/subjects.py | 12 +++++++----- imednet/endpoints/users.py | 32 +++++++------------------------- 4 files changed, 43 insertions(+), 47 deletions(-) diff --git a/imednet/endpoints/_mixins.py b/imednet/endpoints/_mixins.py index b52bc25e..0baa925b 100644 --- a/imednet/endpoints/_mixins.py +++ b/imednet/endpoints/_mixins.py @@ -56,6 +56,23 @@ class ListGetEndpointMixin(Generic[T]): _pop_study_filter: bool = False _missing_study_exception: type[Exception] = ValueError + def _extract_special_params(self, filters: Dict[str, Any]) -> Dict[str, Any]: + """ + Extract special parameters from filters. + + Subclasses can override this to extract specific parameters from the + filters dictionary (e.g., 'includeInactive') and return them as a + dictionary of query parameters to be added to the request. + The extracted keys should be removed from the filters dictionary. + + Args: + filters: The dictionary of filters passed to the list method. + + Returns: + A dictionary of parameters to add to the request query string. + """ + return {} + def _parse_item(self, item: Any) -> T: """ Parse a single item into the model type. @@ -96,6 +113,9 @@ def _prepare_list_params( ) -> tuple[Optional[str], Any, Dict[str, Any], Dict[str, Any]]: # This method handles filter normalization and cache retrieval preparation filters = self._auto_filter(filters) # type: ignore[attr-defined] + + extracted_params = self._extract_special_params(filters) + if study_key: filters["studyKey"] = study_key @@ -123,6 +143,8 @@ def _prepare_list_params( params["filter"] = build_filter_string(filters) if extra_params: params.update(extra_params) + if extracted_params: + params.update(extracted_params) return study, cache, params, other_filters diff --git a/imednet/endpoints/records.py b/imednet/endpoints/records.py index 8c445023..2162ea03 100644 --- a/imednet/endpoints/records.py +++ b/imednet/endpoints/records.py @@ -131,20 +131,10 @@ async def async_create( response = await client.post(path, json=records_data, headers=headers) return Job.from_json(response.json()) - def _list_impl( - self, - client: Any, - paginator_cls: type[Any], - *, - study_key: Optional[str] = None, - record_data_filter: Optional[str] = None, - **filters: Any, - ) -> Any: - extra = {"recordDataFilter": record_data_filter} if record_data_filter else None - return super()._list_impl( - client, - paginator_cls, - study_key=study_key, - extra_params=extra, - **filters, - ) + def _extract_special_params(self, filters: Dict[str, Any]) -> Dict[str, Any]: + params = {} + if "record_data_filter" in filters: + val = filters.pop("record_data_filter") + if val: + params["recordDataFilter"] = val + return params diff --git a/imednet/endpoints/subjects.py b/imednet/endpoints/subjects.py index 09975f91..0dd35cec 100644 --- a/imednet/endpoints/subjects.py +++ b/imednet/endpoints/subjects.py @@ -17,6 +17,11 @@ class SubjectsEndpoint(ListGetEndpoint[Subject]): MODEL = Subject _id_param = "subjectKey" + def _filter_by_site(self, subjects: List[Subject], site_id: str | int) -> List[Subject]: + # TUI Logic: Strict string comparison to handle int/str mismatch + target_site = str(site_id) + return [s for s in subjects if str(s.site_id) == target_site] + def list_by_site(self, study_key: str, site_id: str | int) -> List[Subject]: """ List subjects filtered by a specific site ID. @@ -24,12 +29,9 @@ def list_by_site(self, study_key: str, site_id: str | int) -> List[Subject]: Migrated from TUI logic to core SDK to support filtering. """ all_subjects = self.list(study_key) - # TUI Logic: Strict string comparison to handle int/str mismatch - target_site = str(site_id) - return [s for s in all_subjects if str(s.site_id) == target_site] + return self._filter_by_site(all_subjects, site_id) async def async_list_by_site(self, study_key: str, site_id: str | int) -> List[Subject]: """Asynchronously list subjects filtered by a specific site ID.""" all_subjects = await self.async_list(study_key) - target_site = str(site_id) - return [s for s in all_subjects if str(s.site_id) == target_site] + return self._filter_by_site(all_subjects, site_id) diff --git a/imednet/endpoints/users.py b/imednet/endpoints/users.py index 711148c5..62c39ed8 100644 --- a/imednet/endpoints/users.py +++ b/imednet/endpoints/users.py @@ -1,9 +1,7 @@ """Endpoint for managing users in a study.""" -from typing import Any, Awaitable, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional -from imednet.core.paginator import AsyncPaginator, Paginator -from imednet.core.protocols import AsyncRequestorProtocol, RequestorProtocol from imednet.endpoints._mixins import ListGetEndpoint from imednet.models.users import User @@ -20,25 +18,9 @@ class UsersEndpoint(ListGetEndpoint[User]): _id_param = "userId" _pop_study_filter = True - def _list_impl( - self, - client: RequestorProtocol | AsyncRequestorProtocol, - paginator_cls: Union[type[Paginator], type[AsyncPaginator]], - *, - study_key: Optional[str] = None, - refresh: bool = False, - extra_params: Optional[Dict[str, Any]] = None, - include_inactive: bool = False, - **filters: Any, - ) -> List[User] | Awaitable[List[User]]: - params = extra_params or {} - params["includeInactive"] = str(include_inactive).lower() - - return super()._list_impl( - client, - paginator_cls, - study_key=study_key, - refresh=refresh, - extra_params=params, - **filters, - ) + def _extract_special_params(self, filters: Dict[str, Any]) -> Dict[str, Any]: + params = {} + if "include_inactive" in filters: + val = filters.pop("include_inactive") + params["includeInactive"] = str(val).lower() + return params From 4949026b37a115a7962b3239f690d2a0ea05c0ed Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:47:41 +0000 Subject: [PATCH 2/5] Refactor: Collapse list logic and remove duplication in Endpoints Collapsed redundant list implementation logic in `UsersEndpoint` and `RecordsEndpoint` by introducing `_extract_special_params` hook in `ListGetEndpointMixin`. Extracted duplicate site filtering logic in `SubjectsEndpoint` to a shared helper method. This enforces DRY and Open/Closed principles without altering behavior. Fixed unused imports in `UsersEndpoint`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- imednet/endpoints/users.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imednet/endpoints/users.py b/imednet/endpoints/users.py index 62c39ed8..8fb16fcd 100644 --- a/imednet/endpoints/users.py +++ b/imednet/endpoints/users.py @@ -1,6 +1,6 @@ """Endpoint for managing users in a study.""" -from typing import Any, Dict, List, Optional +from typing import Any, Dict from imednet.endpoints._mixins import ListGetEndpoint from imednet.models.users import User From 45290bebfe5e7f717d3990afcddad7b0695fba34 Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:08:50 +0000 Subject: [PATCH 3/5] Refactor: Collapse list logic and remove duplication in Endpoints Collapsed redundant list implementation logic in `UsersEndpoint` and `RecordsEndpoint` by introducing `_extract_special_params` hook in `ListGetEndpointMixin`. Extracted duplicate site filtering logic in `SubjectsEndpoint` to a shared helper method. This enforces DRY and Open/Closed principles without altering behavior. Fixed unused imports in `UsersEndpoint`. Fixed mypy errors in `tests/unit/test_core_paginator.py`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- tests/unit/test_core_paginator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_core_paginator.py b/tests/unit/test_core_paginator.py index 8ecaa623..7e086fcc 100644 --- a/tests/unit/test_core_paginator.py +++ b/tests/unit/test_core_paginator.py @@ -1,6 +1,7 @@ -from typing import Any, Dict, List +from typing import Any, Dict, List, cast from imednet.core.paginator import Paginator +from imednet.core.protocols import RequestorProtocol class DummyClient: @@ -16,7 +17,7 @@ def get(self, path: str, params: Dict[str, Any] | None = None): def test_single_page_iteration() -> None: client = DummyClient([{"data": [1, 2]}]) - paginator = Paginator(client, "/p") + paginator = Paginator(cast(RequestorProtocol, client), "/p") assert list(paginator) == [1, 2] assert client.calls[0]["params"]["page"] == 0 @@ -28,7 +29,7 @@ def test_multiple_page_iteration() -> None: {"data": [2], "pagination": {"totalPages": 2}}, ] ) - paginator = Paginator(client, "/p", params={"a": 1}, page_size=10) + paginator = Paginator(cast(RequestorProtocol, client), "/p", params={"a": 1}, page_size=10) items = list(paginator) assert items == [1, 2] assert client.calls[0]["params"] == {"a": 1, "page": 0, "size": 10} From 046788774b08d441127e780e87d7c5f9311a7509 Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:26:29 +0000 Subject: [PATCH 4/5] Refactor: Collapse list logic and remove duplication in Endpoints Collapsed redundant list implementation logic in `UsersEndpoint` and `RecordsEndpoint` by introducing `_extract_special_params` hook in `ListGetEndpointMixin`. Extracted duplicate site filtering logic in `SubjectsEndpoint` to a shared helper method. This enforces DRY and Open/Closed principles without altering behavior. Fixed unused imports in `UsersEndpoint`. Fixed mypy errors in `tests/unit/test_core_paginator.py`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> From c07397304c75ec7aa1db7c7c94abe0efc0c4026a Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:30:57 +0000 Subject: [PATCH 5/5] Refactor: Collapse list logic and remove duplication in Endpoints Collapsed redundant list implementation logic in `UsersEndpoint` and `RecordsEndpoint` by introducing `_extract_special_params` hook in `ListGetEndpointMixin`. Extracted duplicate site filtering logic in `SubjectsEndpoint` to a shared helper method. This enforces DRY and Open/Closed principles without altering behavior. Fixed unused imports in `UsersEndpoint`. Fixed mypy errors in `tests/unit/test_core_paginator.py`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>