From 7dd38331a139481d8762eabb6207de707a792b6d Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:28:15 +0000 Subject: [PATCH 1/5] Refactor endpoints to separate Generic and EDC logic - Extract `EdcEndpointMixin` to `imednet/core/endpoint/edc_mixin.py` to decouple EDC-specific logic from the base endpoint. - Introduce `GenericEndpoint` in `imednet/core/endpoint/base.py` for pure HTTP/Context handling without EDC assumptions. - Maintain `BaseEndpoint` as a legacy composite of `GenericEndpoint` and `EdcEndpointMixin` for backward compatibility. - Create explicit `GenericListEndpoint`, `EdcListEndpoint`, and corresponding `Get/PathGet` variants in `imednet/core/endpoint/mixins/bases.py`. - Update all resource endpoints (e.g., `UsersEndpoint`, `RecordsEndpoint`) to explicitly inherit from `Edc*` base classes, clarifying their dependency on the EDC domain. - Fix circular import issues by placing `EdcEndpointMixin` outside the `mixins` package. - Update test fixtures in `tests/conftest.py` to correctly patch `Paginator` on the new `GenericListEndpoint`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- imednet/core/endpoint/base.py | 37 +++++++++--- imednet/core/endpoint/edc_mixin.py | 55 ++++++++++++++++++ imednet/core/endpoint/mixins/__init__.py | 16 ++++++ imednet/core/endpoint/mixins/bases.py | 71 ++++++++++++++++++++---- imednet/endpoints/codings.py | 4 +- imednet/endpoints/forms.py | 4 +- imednet/endpoints/intervals.py | 4 +- imednet/endpoints/jobs.py | 4 +- imednet/endpoints/queries.py | 4 +- imednet/endpoints/record_revisions.py | 4 +- imednet/endpoints/records.py | 4 +- imednet/endpoints/sites.py | 4 +- imednet/endpoints/studies.py | 4 +- imednet/endpoints/subjects.py | 4 +- imednet/endpoints/users.py | 4 +- imednet/endpoints/variables.py | 4 +- imednet/endpoints/visits.py | 4 +- tests/conftest.py | 12 +++- 18 files changed, 196 insertions(+), 47 deletions(-) create mode 100644 imednet/core/endpoint/edc_mixin.py diff --git a/imednet/core/endpoint/base.py b/imednet/core/endpoint/base.py index ac8b45b2..88d2b267 100644 --- a/imednet/core/endpoint/base.py +++ b/imednet/core/endpoint/base.py @@ -9,20 +9,22 @@ from imednet.core.context import Context from imednet.core.endpoint.abc import EndpointABC +from imednet.core.endpoint.edc_mixin import EdcEndpointMixin from imednet.core.protocols import AsyncRequestorProtocol, RequestorProtocol from imednet.models.json_base import JsonModel T = TypeVar("T", bound=JsonModel) -class BaseEndpoint(EndpointABC[T]): +class GenericEndpoint(EndpointABC[T]): """ - Shared base for endpoint wrappers. + Generic base for endpoint wrappers. - Handles context injection and filtering. + Handles context injection and basic path building. + Does NOT include EDC-specific logic. """ - BASE_PATH = "/api/v1/edc/studies" + BASE_PATH = "" def __init__( self, @@ -43,15 +45,21 @@ def __init__( setattr(self, cache_name, None) def _auto_filter(self, filters: Dict[str, Any]) -> Dict[str, Any]: - # inject default studyKey if missing - if "studyKey" not in filters and self._ctx.default_study_key: - filters["studyKey"] = self._ctx.default_study_key + """Pass-through for filters in generic endpoints.""" return filters def _build_path(self, *segments: Any) -> str: - """Return an API path joined with :data:`BASE_PATH`.""" + """ + Return an API path joined with :data:`BASE_PATH`. - parts = [self.BASE_PATH.strip("/")] + Args: + *segments: URL path segments to append. + + Returns: + The full API path string. + """ + base = self.BASE_PATH.strip("/") + parts = [base] if base else [] for seg in segments: text = str(seg).strip("/") if text: @@ -64,3 +72,14 @@ def _require_async_client(self) -> AsyncRequestorProtocol: if self._async_client is None: raise RuntimeError("Async client not configured") return self._async_client + + +class BaseEndpoint(EdcEndpointMixin, GenericEndpoint[T]): + """ + Shared base for endpoint wrappers (Legacy). + + Includes EDC-specific logic for backward compatibility. + New endpoints should use GenericEndpoint or explicit Edc* mixins. + """ + + pass diff --git a/imednet/core/endpoint/edc_mixin.py b/imednet/core/endpoint/edc_mixin.py new file mode 100644 index 00000000..b3865d2e --- /dev/null +++ b/imednet/core/endpoint/edc_mixin.py @@ -0,0 +1,55 @@ +"""Mixin for EDC-specific endpoint logic.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict +from urllib.parse import quote + +if TYPE_CHECKING: + from imednet.core.context import Context + + +class EdcEndpointMixin: + """ + Mixin providing EDC-specific logic for endpoints. + + This includes the base path for EDC resources and automatic injection + of the default study key into filters. + """ + + BASE_PATH = "/api/v1/edc/studies" + + if TYPE_CHECKING: + _ctx: Context + + def _auto_filter(self, filters: Dict[str, Any]) -> Dict[str, Any]: + """ + Inject default studyKey if missing. + + Args: + filters: The current dictionary of filters. + + Returns: + The filters dictionary with studyKey injected if applicable. + """ + if "studyKey" not in filters and self._ctx.default_study_key: + filters["studyKey"] = self._ctx.default_study_key + return filters + + def _build_path(self, *segments: Any) -> str: + """ + Return an API path joined with :data:`BASE_PATH`. + + Args: + *segments: URL path segments to append. + + Returns: + The full API path string. + """ + parts = [self.BASE_PATH.strip("/")] + for seg in segments: + text = str(seg).strip("/") + if text: + # Encode path segments to prevent traversal and injection + parts.append(quote(text, safe="")) + return "/" + "/".join(parts) diff --git a/imednet/core/endpoint/mixins/__init__.py b/imednet/core/endpoint/mixins/__init__.py index bab78bbc..363de74d 100644 --- a/imednet/core/endpoint/mixins/__init__.py +++ b/imednet/core/endpoint/mixins/__init__.py @@ -2,6 +2,14 @@ from imednet.utils.filters import build_filter_string from .bases import ( + EdcListEndpoint, + EdcListGetEndpoint, + EdcListPathGetEndpoint, + EdcMetadataListGetEndpoint, + EdcStrictListGetEndpoint, + GenericListEndpoint, + GenericListGetEndpoint, + GenericListPathGetEndpoint, ListEndpoint, ListGetEndpoint, ListGetEndpointMixin, @@ -20,7 +28,15 @@ "AsyncPaginator", "CacheMixin", "CreateEndpointMixin", + "EdcListEndpoint", + "EdcListGetEndpoint", + "EdcListPathGetEndpoint", + "EdcMetadataListGetEndpoint", + "EdcStrictListGetEndpoint", "FilterGetEndpointMixin", + "GenericListEndpoint", + "GenericListGetEndpoint", + "GenericListPathGetEndpoint", "ListEndpoint", "ListEndpointMixin", "ListGetEndpoint", diff --git a/imednet/core/endpoint/mixins/bases.py b/imednet/core/endpoint/mixins/bases.py index 29d05857..62a188ea 100644 --- a/imednet/core/endpoint/mixins/bases.py +++ b/imednet/core/endpoint/mixins/bases.py @@ -2,10 +2,11 @@ from typing import Any, Awaitable, List, Optional, cast +from imednet.core.endpoint.base import BaseEndpoint, GenericEndpoint +from imednet.core.endpoint.edc_mixin import EdcEndpointMixin from imednet.core.paginator import AsyncPaginator, Paginator from imednet.core.protocols import AsyncRequestorProtocol, RequestorProtocol -from ..base import BaseEndpoint from .get import FilterGetEndpointMixin, PathGetEndpointMixin from .list import ListEndpointMixin from .parsing import T @@ -17,8 +18,8 @@ class ListGetEndpointMixin(ListEndpointMixin[T], FilterGetEndpointMixin[T]): pass -class ListEndpoint(BaseEndpoint, ListEndpointMixin[T]): - """Endpoint base class implementing ``list`` helpers.""" +class GenericListEndpoint(GenericEndpoint[T], ListEndpointMixin[T]): + """Generic endpoint implementing ``list`` helpers.""" PAGINATOR_CLS: type[Paginator] = Paginator ASYNC_PAGINATOR_CLS: type[AsyncPaginator] = AsyncPaginator @@ -43,8 +44,20 @@ async def async_list(self, study_key: Optional[str] = None, **filters: Any) -> L ) -class ListGetEndpoint(ListEndpoint[T], FilterGetEndpointMixin[T]): - """Endpoint base class implementing ``list`` and ``get`` helpers.""" +class EdcListEndpoint(EdcEndpointMixin, GenericListEndpoint[T]): + """EDC-specific list endpoint.""" + + pass + + +class ListEndpoint(EdcListEndpoint[T]): + """Endpoint base class implementing ``list`` helpers (Legacy).""" + + pass + + +class GenericListGetEndpoint(GenericListEndpoint[T], FilterGetEndpointMixin[T]): + """Generic endpoint implementing ``list`` and ``get`` helpers.""" def _get_common( self, @@ -65,8 +78,20 @@ async def async_get(self, study_key: Optional[str], item_id: Any) -> T: ) -class ListPathGetEndpoint(ListEndpoint[T], PathGetEndpointMixin[T]): - """Endpoint base class implementing ``list`` and ``get`` (via path) helpers.""" +class EdcListGetEndpoint(EdcEndpointMixin, GenericListGetEndpoint[T]): + """EDC-specific list/get endpoint.""" + + pass + + +class ListGetEndpoint(EdcListGetEndpoint[T]): + """Endpoint base class implementing ``list`` and ``get`` helpers (Legacy).""" + + pass + + +class GenericListPathGetEndpoint(GenericListEndpoint[T], PathGetEndpointMixin[T]): + """Generic endpoint implementing ``list`` and ``get`` (via path) helpers.""" def get(self, study_key: Optional[str], item_id: Any) -> T: return cast(T, self._get_impl_path(self._client, study_key=study_key, item_id=item_id)) @@ -79,9 +104,21 @@ async def async_get(self, study_key: Optional[str], item_id: Any) -> T: ) -class StrictListGetEndpoint(ListGetEndpoint[T]): +class EdcListPathGetEndpoint(EdcEndpointMixin, GenericListPathGetEndpoint[T]): + """EDC-specific list/path-get endpoint.""" + + pass + + +class ListPathGetEndpoint(EdcListPathGetEndpoint[T]): + """Endpoint base class implementing ``list`` and ``get`` (via path) helpers (Legacy).""" + + pass + + +class EdcStrictListGetEndpoint(EdcListGetEndpoint[T]): """ - Endpoint base class enforcing strict study key requirements. + Endpoint base class enforcing strict study key requirements (EDC). Populates study key from filters and raises KeyError if missing. """ @@ -90,11 +127,23 @@ class StrictListGetEndpoint(ListGetEndpoint[T]): _missing_study_exception = KeyError -class MetadataListGetEndpoint(StrictListGetEndpoint[T]): +class StrictListGetEndpoint(EdcStrictListGetEndpoint[T]): + """Endpoint base class enforcing strict study key requirements (Legacy).""" + + pass + + +class EdcMetadataListGetEndpoint(EdcStrictListGetEndpoint[T]): """ - Endpoint base class for metadata resources. + Endpoint base class for metadata resources (EDC). Inherits strict study key requirements and sets a larger default page size. """ PAGE_SIZE = 500 + + +class MetadataListGetEndpoint(EdcMetadataListGetEndpoint[T]): + """Endpoint base class for metadata resources (Legacy).""" + + pass diff --git a/imednet/endpoints/codings.py b/imednet/endpoints/codings.py index 9d3ef0dd..52859bfb 100644 --- a/imednet/endpoints/codings.py +++ b/imednet/endpoints/codings.py @@ -1,10 +1,10 @@ """Endpoint for managing codings (medical coding) in a study.""" -from imednet.core.endpoint.mixins import StrictListGetEndpoint +from imednet.core.endpoint.mixins import EdcStrictListGetEndpoint from imednet.models.codings import Coding -class CodingsEndpoint(StrictListGetEndpoint[Coding]): +class CodingsEndpoint(EdcStrictListGetEndpoint[Coding]): """ API endpoint for interacting with codings (medical coding) in an iMedNet study. diff --git a/imednet/endpoints/forms.py b/imednet/endpoints/forms.py index 9fc16e22..1007ebde 100644 --- a/imednet/endpoints/forms.py +++ b/imednet/endpoints/forms.py @@ -1,10 +1,10 @@ """Endpoint for managing forms (eCRFs) in a study.""" -from imednet.core.endpoint.mixins import MetadataListGetEndpoint +from imednet.core.endpoint.mixins import EdcMetadataListGetEndpoint from imednet.models.forms import Form -class FormsEndpoint(MetadataListGetEndpoint[Form]): +class FormsEndpoint(EdcMetadataListGetEndpoint[Form]): """ API endpoint for interacting with forms (eCRFs) in an iMedNet study. diff --git a/imednet/endpoints/intervals.py b/imednet/endpoints/intervals.py index 08c10f1d..62dcf364 100644 --- a/imednet/endpoints/intervals.py +++ b/imednet/endpoints/intervals.py @@ -1,10 +1,10 @@ """Endpoint for managing intervals (visit definitions) in a study.""" -from imednet.core.endpoint.mixins import MetadataListGetEndpoint +from imednet.core.endpoint.mixins import EdcMetadataListGetEndpoint from imednet.models.intervals import Interval -class IntervalsEndpoint(MetadataListGetEndpoint[Interval]): +class IntervalsEndpoint(EdcMetadataListGetEndpoint[Interval]): """ API endpoint for interacting with intervals (visit definitions) in an iMedNet study. diff --git a/imednet/endpoints/jobs.py b/imednet/endpoints/jobs.py index d695b6c6..b3fc269d 100644 --- a/imednet/endpoints/jobs.py +++ b/imednet/endpoints/jobs.py @@ -2,12 +2,12 @@ from typing import Any, Optional -from imednet.core.endpoint.mixins import ListPathGetEndpoint +from imednet.core.endpoint.mixins import EdcListPathGetEndpoint from imednet.core.paginator import AsyncJsonListPaginator, JsonListPaginator from imednet.models.jobs import JobStatus -class JobsEndpoint(ListPathGetEndpoint[JobStatus]): +class JobsEndpoint(EdcListPathGetEndpoint[JobStatus]): """ API endpoint for retrieving status and details of jobs in an iMedNet study. diff --git a/imednet/endpoints/queries.py b/imednet/endpoints/queries.py index c723bcc2..7695cd46 100644 --- a/imednet/endpoints/queries.py +++ b/imednet/endpoints/queries.py @@ -1,10 +1,10 @@ """Endpoint for managing queries (dialogue/questions) in a study.""" -from imednet.core.endpoint.mixins import ListGetEndpoint +from imednet.core.endpoint.mixins import EdcListGetEndpoint from imednet.models.queries import Query -class QueriesEndpoint(ListGetEndpoint[Query]): +class QueriesEndpoint(EdcListGetEndpoint[Query]): """ API endpoint for interacting with queries (dialogue/questions) in an iMedNet study. diff --git a/imednet/endpoints/record_revisions.py b/imednet/endpoints/record_revisions.py index e484e4a7..9d55b4cf 100644 --- a/imednet/endpoints/record_revisions.py +++ b/imednet/endpoints/record_revisions.py @@ -1,10 +1,10 @@ """Endpoint for retrieving record revision history in a study.""" -from imednet.core.endpoint.mixins import ListGetEndpoint +from imednet.core.endpoint.mixins import EdcListGetEndpoint from imednet.models.record_revisions import RecordRevision -class RecordRevisionsEndpoint(ListGetEndpoint[RecordRevision]): +class RecordRevisionsEndpoint(EdcListGetEndpoint[RecordRevision]): """ API endpoint for accessing record revision history in an iMedNet study. diff --git a/imednet/endpoints/records.py b/imednet/endpoints/records.py index 2e698b4e..d4924067 100644 --- a/imednet/endpoints/records.py +++ b/imednet/endpoints/records.py @@ -3,13 +3,13 @@ from typing import Any, Dict, List, Optional, Union from imednet.constants import HEADER_EMAIL_NOTIFY -from imednet.core.endpoint.mixins import CreateEndpointMixin, ListGetEndpoint +from imednet.core.endpoint.mixins import CreateEndpointMixin, EdcListGetEndpoint from imednet.models.jobs import Job from imednet.models.records import Record from imednet.validation.cache import SchemaCache, validate_record_data -class RecordsEndpoint(ListGetEndpoint[Record], CreateEndpointMixin[Job]): +class RecordsEndpoint(EdcListGetEndpoint[Record], CreateEndpointMixin[Job]): """ API endpoint for interacting with records (eCRF instances) in an iMedNet study. diff --git a/imednet/endpoints/sites.py b/imednet/endpoints/sites.py index 2da54f73..d177aab8 100644 --- a/imednet/endpoints/sites.py +++ b/imednet/endpoints/sites.py @@ -1,10 +1,10 @@ """Endpoint for managing sites (study locations) in a study.""" -from imednet.core.endpoint.mixins import StrictListGetEndpoint +from imednet.core.endpoint.mixins import EdcStrictListGetEndpoint from imednet.models.sites import Site -class SitesEndpoint(StrictListGetEndpoint[Site]): +class SitesEndpoint(EdcStrictListGetEndpoint[Site]): """ API endpoint for interacting with sites (study locations) in an iMedNet study. diff --git a/imednet/endpoints/studies.py b/imednet/endpoints/studies.py index 8657a918..39f5d88d 100644 --- a/imednet/endpoints/studies.py +++ b/imednet/endpoints/studies.py @@ -1,10 +1,10 @@ """Endpoint for managing studies in the iMedNet system.""" -from imednet.core.endpoint.mixins import ListGetEndpoint +from imednet.core.endpoint.mixins import EdcListGetEndpoint from imednet.models.studies import Study -class StudiesEndpoint(ListGetEndpoint[Study]): +class StudiesEndpoint(EdcListGetEndpoint[Study]): """ API endpoint for interacting with studies in the iMedNet system. diff --git a/imednet/endpoints/subjects.py b/imednet/endpoints/subjects.py index 73ab5d94..6d8ca587 100644 --- a/imednet/endpoints/subjects.py +++ b/imednet/endpoints/subjects.py @@ -2,11 +2,11 @@ from typing import List -from imednet.core.endpoint.mixins import ListGetEndpoint +from imednet.core.endpoint.mixins import EdcListGetEndpoint from imednet.models.subjects import Subject -class SubjectsEndpoint(ListGetEndpoint[Subject]): +class SubjectsEndpoint(EdcListGetEndpoint[Subject]): """ API endpoint for interacting with subjects in an iMedNet study. diff --git a/imednet/endpoints/users.py b/imednet/endpoints/users.py index d52e62b4..9e207218 100644 --- a/imednet/endpoints/users.py +++ b/imednet/endpoints/users.py @@ -2,11 +2,11 @@ from typing import Any, Dict -from imednet.core.endpoint.mixins import ListGetEndpoint +from imednet.core.endpoint.mixins import EdcListGetEndpoint from imednet.models.users import User -class UsersEndpoint(ListGetEndpoint[User]): +class UsersEndpoint(EdcListGetEndpoint[User]): """ API endpoint for interacting with users in an iMedNet study. diff --git a/imednet/endpoints/variables.py b/imednet/endpoints/variables.py index 85307d6f..727ce34a 100644 --- a/imednet/endpoints/variables.py +++ b/imednet/endpoints/variables.py @@ -1,10 +1,10 @@ """Endpoint for managing variables (data points on eCRFs) in a study.""" -from imednet.core.endpoint.mixins import MetadataListGetEndpoint +from imednet.core.endpoint.mixins import EdcMetadataListGetEndpoint from imednet.models.variables import Variable -class VariablesEndpoint(MetadataListGetEndpoint[Variable]): +class VariablesEndpoint(EdcMetadataListGetEndpoint[Variable]): """ API endpoint for interacting with variables (data points on eCRFs) in an iMedNet study. diff --git a/imednet/endpoints/visits.py b/imednet/endpoints/visits.py index be4db0ef..41bf702c 100644 --- a/imednet/endpoints/visits.py +++ b/imednet/endpoints/visits.py @@ -1,10 +1,10 @@ """Endpoint for managing visits (interval instances) in a study.""" -from imednet.core.endpoint.mixins import ListGetEndpoint +from imednet.core.endpoint.mixins import EdcListGetEndpoint from imednet.models.visits import Visit -class VisitsEndpoint(ListGetEndpoint[Visit]): +class VisitsEndpoint(EdcListGetEndpoint[Visit]): """ API endpoint for interacting with visits (interval instances) in an iMedNet study. diff --git a/tests/conftest.py b/tests/conftest.py index b0a4097f..dd881725 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -59,6 +59,10 @@ def __iter__(self): # Also patch the class attribute since it's now used if hasattr(mixins_module, "ListEndpoint"): monkeypatch.setattr(mixins_module.ListEndpoint, "PAGINATOR_CLS", DummyPaginator) + if hasattr(mixins_module, "GenericListEndpoint"): + monkeypatch.setattr( + mixins_module.GenericListEndpoint, "PAGINATOR_CLS", DummyPaginator + ) return captured return factory @@ -87,7 +91,13 @@ async def __aiter__(self): monkeypatch.setattr(mixins_module, "AsyncPaginator", DummyPaginator) # Also patch the class attribute since it's now used if hasattr(mixins_module, "ListEndpoint"): - monkeypatch.setattr(mixins_module.ListEndpoint, "ASYNC_PAGINATOR_CLS", DummyPaginator) + monkeypatch.setattr( + mixins_module.ListEndpoint, "ASYNC_PAGINATOR_CLS", DummyPaginator + ) + if hasattr(mixins_module, "GenericListEndpoint"): + monkeypatch.setattr( + mixins_module.GenericListEndpoint, "ASYNC_PAGINATOR_CLS", DummyPaginator + ) return captured return factory From 7388f998c91f4f6cb574ed76c104e8963bf0d8d1 Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:33:54 +0000 Subject: [PATCH 2/5] Fix formatting in tests/conftest.py Apply black formatting to resolve CI check failure. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- tests/conftest.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index dd881725..409120b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,9 +60,7 @@ def __iter__(self): if hasattr(mixins_module, "ListEndpoint"): monkeypatch.setattr(mixins_module.ListEndpoint, "PAGINATOR_CLS", DummyPaginator) if hasattr(mixins_module, "GenericListEndpoint"): - monkeypatch.setattr( - mixins_module.GenericListEndpoint, "PAGINATOR_CLS", DummyPaginator - ) + monkeypatch.setattr(mixins_module.GenericListEndpoint, "PAGINATOR_CLS", DummyPaginator) return captured return factory @@ -91,9 +89,7 @@ async def __aiter__(self): monkeypatch.setattr(mixins_module, "AsyncPaginator", DummyPaginator) # Also patch the class attribute since it's now used if hasattr(mixins_module, "ListEndpoint"): - monkeypatch.setattr( - mixins_module.ListEndpoint, "ASYNC_PAGINATOR_CLS", DummyPaginator - ) + monkeypatch.setattr(mixins_module.ListEndpoint, "ASYNC_PAGINATOR_CLS", DummyPaginator) if hasattr(mixins_module, "GenericListEndpoint"): monkeypatch.setattr( mixins_module.GenericListEndpoint, "ASYNC_PAGINATOR_CLS", DummyPaginator From 864d7cd6a90dfa58a580d97593817817dd647fdb Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:39:49 +0000 Subject: [PATCH 3/5] Fix unused imports and linting issues - Remove unused `BaseEndpoint` import from `imednet/core/endpoint/mixins/bases.py`. - Fix import sorting in `tests/integration/test_airflow_dag.py`. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- imednet/core/endpoint/mixins/bases.py | 2 +- tests/integration/test_airflow_dag.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/imednet/core/endpoint/mixins/bases.py b/imednet/core/endpoint/mixins/bases.py index 62a188ea..f321b0c2 100644 --- a/imednet/core/endpoint/mixins/bases.py +++ b/imednet/core/endpoint/mixins/bases.py @@ -2,7 +2,7 @@ from typing import Any, Awaitable, List, Optional, cast -from imednet.core.endpoint.base import BaseEndpoint, GenericEndpoint +from imednet.core.endpoint.base import GenericEndpoint from imednet.core.endpoint.edc_mixin import EdcEndpointMixin from imednet.core.paginator import AsyncPaginator, Paginator from imednet.core.protocols import AsyncRequestorProtocol, RequestorProtocol diff --git a/tests/integration/test_airflow_dag.py b/tests/integration/test_airflow_dag.py index fbce2db7..0d8daea3 100644 --- a/tests/integration/test_airflow_dag.py +++ b/tests/integration/test_airflow_dag.py @@ -12,6 +12,7 @@ def test_dag_runs(monkeypatch): pytest.importorskip("airflow") from airflow.models import DAG, TaskInstance from airflow.utils.state import State + from imednet.integrations.airflow import ImednetJobSensor, ImednetToS3Operator s3 = boto3.client("s3", region_name="us-east-1") From 36c1b6c79f372c6fd5750edd45bd187052bc1441 Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:46:16 +0000 Subject: [PATCH 4/5] Fix isort import sorting in tests Run isort manually to fix import ordering in tests/integration/test_airflow_dag.py. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- tests/integration/test_airflow_dag.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/test_airflow_dag.py b/tests/integration/test_airflow_dag.py index 0d8daea3..fbce2db7 100644 --- a/tests/integration/test_airflow_dag.py +++ b/tests/integration/test_airflow_dag.py @@ -12,7 +12,6 @@ def test_dag_runs(monkeypatch): pytest.importorskip("airflow") from airflow.models import DAG, TaskInstance from airflow.utils.state import State - from imednet.integrations.airflow import ImednetJobSensor, ImednetToS3Operator s3 = boto3.client("s3", region_name="us-east-1") From da8c5e3bd5b819c3c92562b63918726f20583b88 Mon Sep 17 00:00:00 2001 From: fderuiter <127706008+fderuiter@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:12:34 +0000 Subject: [PATCH 5/5] Fix mypy type errors in registry Update ENDPOINT_REGISTRY type hint to use GenericEndpoint instead of BaseEndpoint. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- imednet/endpoints/registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imednet/endpoints/registry.py b/imednet/endpoints/registry.py index 0be07885..5f8e5beb 100644 --- a/imednet/endpoints/registry.py +++ b/imednet/endpoints/registry.py @@ -9,7 +9,7 @@ from typing import Dict, Type -from imednet.core.endpoint.base import BaseEndpoint +from imednet.core.endpoint.base import GenericEndpoint from imednet.endpoints.codings import CodingsEndpoint from imednet.endpoints.forms import FormsEndpoint from imednet.endpoints.intervals import IntervalsEndpoint @@ -24,7 +24,7 @@ from imednet.endpoints.variables import VariablesEndpoint from imednet.endpoints.visits import VisitsEndpoint -ENDPOINT_REGISTRY: Dict[str, Type[BaseEndpoint]] = { +ENDPOINT_REGISTRY: Dict[str, Type[GenericEndpoint]] = { "codings": CodingsEndpoint, "forms": FormsEndpoint, "intervals": IntervalsEndpoint,