Skip to content

Conversation

@fderuiter
Copy link
Owner

This PR refactors the endpoint mixins in imednet/core/endpoint/mixins.py to adhere to SOLID principles and improve type safety.

Key changes:

  • Introduced EndpointProtocol in imednet/core/endpoint/protocols.py to define the interface expected by mixins.
  • Refactored ListEndpointMixin to separate concerns (request preparation vs execution) and use the protocol for typing.
  • Updated PathGetEndpointMixin to take an explicit is_async argument, removing fragile runtime inspection of client methods.
  • Updated JobsEndpoint to pass is_async=True when calling _get_impl_path asynchronously.
  • Added tests/unit/endpoints/test_jobs_async.py to verify the fix and prevent regression.

This refactor improves modularity, testability, and code clarity.


PR created automatically by Jules for task 9882042388692222746 started by @fderuiter

- Decoupled `ListEndpointMixin` and related mixins by introducing `EndpointProtocol`.
- Extracted request preparation logic in `ListEndpointMixin` to `_prepare_list_request`.
- Updated `PathGetEndpointMixin` to explicitly handle async requests via `is_async` parameter.
- Updated `JobsEndpoint` to use the new `is_async` parameter.
- Added regression tests for `JobsEndpoint` async get.
- Enforced strict typing in mixins using `cast` and protocols.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

fderuiter and others added 2 commits February 9, 2026 18:43
Formatted `imednet/endpoints/jobs.py`, `imednet/core/endpoint/mixins.py`, and `tests/unit/endpoints/test_jobs_async.py` using `black` and `isort` to resolve CI failures.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Fixed line length error in `imednet/core/endpoint/mixins.py` and removed unused import in `tests/unit/endpoints/test_jobs_async.py`.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@fderuiter fderuiter marked this pull request as ready for review February 10, 2026 15:57
Copilot AI review requested due to automatic review settings February 10, 2026 15:57
@fderuiter fderuiter merged commit 7531546 into main Feb 10, 2026
13 checks passed
@fderuiter fderuiter deleted the refactor/endpoint-mixins-protocols-9882042388692222746 branch February 10, 2026 15:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors core endpoint mixins to improve modularity and typing, and fixes async job retrieval by removing reliance on runtime coroutine-function inspection.

Changes:

  • Added EndpointProtocol to formalize the interface expected by endpoint mixins.
  • Refactored ListEndpointMixin to split request preparation from execution and tightened typing via the protocol.
  • Updated PathGetEndpointMixin/JobsEndpoint async-get flow to use an explicit async path, and added a regression test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
imednet/core/endpoint/protocols.py Introduces EndpointProtocol to define the endpoint surface area mixins rely on.
imednet/core/endpoint/mixins.py Refactors list logic and changes path-based get implementation to support explicit async execution.
imednet/endpoints/jobs.py Passes is_async=True for async_get to ensure correct awaiting behavior.
tests/unit/endpoints/test_jobs_async.py Adds coverage to ensure JobsEndpoint.async_get works with async clients and raises on empty responses.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +29 to +32
if TYPE_CHECKING: # pragma: no cover
# EndpointProtocol is imported from .protocols, but we keep this import for
# backward compatibility if needed
pass
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if TYPE_CHECKING block is now a no-op (pass) and the accompanying comment about “backward compatibility” is misleading since nothing is conditionally imported/defined anymore. Consider removing this block entirely, or restoring a concrete type-only import/alias if something actually needs to be guarded for type-checking.

Copilot uses AI. Check for mistakes.
Comment on lines +359 to +360
# self is mixed into BaseEndpoint which implements _build_path
# Use cast for type checking compliance without importing BaseEndpoint
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this cast is used “without importing BaseEndpoint”, but BaseEndpoint is already imported at the top of this module. Either update the comment to reflect the real intent (type narrowing) or remove it to avoid confusion.

Suggested change
# self is mixed into BaseEndpoint which implements _build_path
# Use cast for type checking compliance without importing BaseEndpoint
# self is expected to be mixed into BaseEndpoint, which implements _build_path;
# cast narrows self to BaseEndpoint so type checkers see _build_path as available

Copilot uses AI. Check for mistakes.
Comment on lines +366 to +397
def _get_impl_path(
self,
client: RequestorProtocol | AsyncRequestorProtocol,
*,
study_key: Optional[str],
item_id: Any,
is_async: bool = False,
) -> T | Awaitable[T]:
path = self._get_path_for_id(study_key, item_id)

# Helper to process response
def process_response(response: 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)

if inspect.iscoroutinefunction(client.get):
if is_async:

async def _await() -> T:
response = await client.get(path) # type: ignore
# We assume client is AsyncRequestorProtocol because is_async=True
# But we can't be sure type-wise unless we narrow it down.
# In practice, caller ensures this.
aclient = cast(AsyncRequestorProtocol, client)
response = await aclient.get(path)
return process_response(response)

return _await()

response = client.get(path) # type: ignore
sclient = cast(RequestorProtocol, client)
response = sclient.get(path)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_impl_path now relies on the caller to pass is_async=True when an async client is provided; if a caller passes an AsyncRequestorProtocol but forgets the flag, the sync branch will call .json() on an awaitable/coroutine and fail at runtime. To make this safer, consider branching based on whether the result of client.get(path) is awaitable (or split into separate sync/async helpers) so the correct behavior doesn’t depend on an extra boolean flag.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant