Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions imednet/core/endpoint/mixins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
StrictListGetEndpoint,
)
from .caching import CacheMixin
from .create import CreateEndpointMixin
from .get import FilterGetEndpointMixin, PathGetEndpointMixin
from .list import ListEndpointMixin
from .params import ParamMixin
Expand All @@ -27,7 +26,6 @@
__all__ = [
"AsyncPaginator",
"CacheMixin",
"CreateEndpointMixin",
"EdcListEndpoint",
"EdcListGetEndpoint",
"EdcListPathGetEndpoint",
Expand Down
87 changes: 0 additions & 87 deletions imednet/core/endpoint/mixins/create.py

This file was deleted.

38 changes: 0 additions & 38 deletions imednet/endpoints/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,3 @@ class JobsEndpoint(EdcListPathGetEndpoint[JobStatus]):

def _raise_not_found(self, study_key: Optional[str], item_id: Any) -> None:
raise ValueError(f"Job {item_id} not found in study {study_key}")

def get(self, study_key: Optional[str], batch_id: str) -> JobStatus:
"""
Get a specific job by batch ID.

This method performs a direct API request using the provided
``batch_id``; it does not use caching or the ``refresh`` flag.

Args:
study_key: Study identifier
batch_id: Batch ID of the job

Returns:
JobStatus object with current state and timestamps

Raises:
ValueError: If the job is not found
"""
return super().get(study_key, batch_id)

async def async_get(self, study_key: Optional[str], batch_id: str) -> JobStatus:
"""
Asynchronously get a specific job by batch ID.

This is the async variant of :meth:`get`. Like the sync version,
it issues a direct request by ``batch_id`` without any caching.

Args:
study_key: Study identifier
batch_id: Batch ID of the job

Returns:
JobStatus object with current state and timestamps

Raises:
ValueError: If the job is not found
"""
return await super().async_get(study_key, batch_id)
4 changes: 2 additions & 2 deletions imednet/endpoints/subjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from imednet.core.endpoint.mixins import EdcListGetEndpoint
from imednet.models.subjects import Subject
from imednet.utils.filters import filter_by_attribute


class SubjectsEndpoint(EdcListGetEndpoint[Subject]):
Expand All @@ -19,8 +20,7 @@ class SubjectsEndpoint(EdcListGetEndpoint[Subject]):

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]
return filter_by_attribute(subjects, "site_id", site_id)

def list_by_site(self, study_key: str, site_id: str | int) -> List[Subject]:
"""
Expand Down
29 changes: 28 additions & 1 deletion imednet/utils/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

import functools
import re
from typing import Any, Dict, List, Tuple, Union
from typing import Any, Dict, List, Tuple, TypeVar, Union

# Pre-compiled regex for performance to avoid re-compilation in loops
_UNSAFE_CHARS_REGEX = re.compile(r"[^A-Za-z0-9_.-]")

T = TypeVar("T")


@functools.lru_cache(maxsize=128)
def _snake_to_camel(text: str) -> str:
Expand Down Expand Up @@ -67,3 +69,28 @@ def build_filter_string(
else:
parts.append(f"{camel_key}=={_format_filter_value(value)}")
return and_connector.join(parts)


def filter_by_attribute(items: List[T], attr_name: str, target_value: Any) -> List[T]:
"""
Filter a list of objects by a specific attribute value using strict string comparison.

This function handles the common case where API IDs might be returned as integers
or strings, ensuring consistent comparison by converting both to strings.

Args:
items: List of objects to filter.
attr_name: The name of the attribute to check on each object.
target_value: The value to filter for.

Returns:
A new list containing only the items where the attribute matches the target value.
"""
target_str = str(target_value)
filtered_items = []
for item in items:
# Use getattr to safely access the attribute
attr_val = getattr(item, attr_name, None)
if attr_val is not None and str(attr_val) == target_str:
filtered_items.append(item)
return filtered_items