feat(sdk): Add a generic NemoClient - #370
Conversation
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
|
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a typed HTTP client system to ChangesTyped client infrastructure (nemo_platform_plugin)
Example plugin migration to typed client
Suggested Reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
plugins/example-plugin/tests/test_sdk.py (1)
137-164: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueMissing async tests for
list_itemsandupdate_item.Sync tests cover all CRUD operations, but async is missing list and update. Consider adding for parity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/example-plugin/tests/test_sdk.py` around lines 137 - 164, Add two missing async test functions to achieve parity with the sync tests: test_async_list_items and test_async_update_item. For test_async_list_items, follow the pattern of the existing async tests by decorating with `@pytest.mark.asyncio`, creating a client with _async_client(), mocking the HTTP response with _resp(200, ...), calling client.list_items(...) with appropriate parameters, and asserting on the response data. For test_async_update_item, follow the same pattern but call client.update_item(...) with an UpdateExampleItemRequest object and verify the response. Ensure both tests include the mock_http.request.assert_awaited_once() assertion to verify the HTTP request was made.plugins/example-plugin/src/nemo_example_plugin/service.py (1)
163-168: 🧹 Nitpick | 🔵 TrivialAdd
BlobUploadResponseimport and use it as the typed return forupload_blob.
upload_blobcurrently returns untypeddict, which produces a vague OpenAPI schema. ImportBlobUploadResponsefromtypes.payloadsand use it as both the return type andresponse_modelparameter for better API documentation.Suggested change
from nemo_example_plugin.types.payloads import ( CreateExampleItemRequest, ExampleItemPage, + BlobUploadResponse, HelloResponse, UpdateExampleItemRequest, )- `@router.put`("/blob/{name}", status_code=200) - async def upload_blob(name: str, request: Request) -> dict: + `@router.put`("/blob/{name}", status_code=200, response_model=BlobUploadResponse) + async def upload_blob(name: str, request: Request) -> BlobUploadResponse: """Accept raw binary and store it. Returns byte count.""" data = await request.body() _store[name] = data - return {"name": name, "size": len(data)} + return BlobUploadResponse(name=name, size=len(data))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/example-plugin/src/nemo_example_plugin/service.py` around lines 163 - 168, The upload_blob function currently returns an untyped dict, resulting in poor OpenAPI schema documentation. Import BlobUploadResponse from types.payloads, then update the upload_blob function by adding response_model=BlobUploadResponse to the `@router.put`() decorator and changing the function's return type annotation from dict to BlobUploadResponse. This will ensure the API generates proper typed response documentation.packages/nemo_platform_plugin/tests/client/test_client.py (1)
1-216: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winConsider adding tests for binary and streaming responses.
The PR introduces
BinaryContentandStream[T]response types as key features, but these aren't covered in this test module. Adding tests for those code paths would increase confidence.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/nemo_platform_plugin/tests/client/test_client.py` around lines 1 - 216, Add test cases for binary and streaming response types by first creating endpoint definitions similar to CREATE_ITEM and GET_ITEM that use BinaryContent and Stream[T] as response_type parameters. Then write test functions following the same pattern as test_send_post and test_send_get_with_path_params that verify the StubClient and AsyncStubClient properly handle binary content responses and streaming responses respectively, including appropriate mock assertions for both sync and async variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/client/client.py`:
- Around line 146-149: The synchronous send method (around line 146-149) and the
async send method (around line 197-200) both call raise_for_status() immediately
after receiving the HTTP response, which prevents NemoResponse from being
constructed with error responses. Remove the raise_for_status() calls from both
methods so that all responses (including errors) are wrapped in NemoResponse
objects, allowing the deferred error checking in NemoResponse.data() to handle
error raising with full response context and enable callers to inspect error
details.
---
Nitpick comments:
In `@packages/nemo_platform_plugin/tests/client/test_client.py`:
- Around line 1-216: Add test cases for binary and streaming response types by
first creating endpoint definitions similar to CREATE_ITEM and GET_ITEM that use
BinaryContent and Stream[T] as response_type parameters. Then write test
functions following the same pattern as test_send_post and
test_send_get_with_path_params that verify the StubClient and AsyncStubClient
properly handle binary content responses and streaming responses respectively,
including appropriate mock assertions for both sync and async variants.
In `@plugins/example-plugin/src/nemo_example_plugin/service.py`:
- Around line 163-168: The upload_blob function currently returns an untyped
dict, resulting in poor OpenAPI schema documentation. Import BlobUploadResponse
from types.payloads, then update the upload_blob function by adding
response_model=BlobUploadResponse to the `@router.put`() decorator and changing
the function's return type annotation from dict to BlobUploadResponse. This will
ensure the API generates proper typed response documentation.
In `@plugins/example-plugin/tests/test_sdk.py`:
- Around line 137-164: Add two missing async test functions to achieve parity
with the sync tests: test_async_list_items and test_async_update_item. For
test_async_list_items, follow the pattern of the existing async tests by
decorating with `@pytest.mark.asyncio`, creating a client with _async_client(),
mocking the HTTP response with _resp(200, ...), calling client.list_items(...)
with appropriate parameters, and asserting on the response data. For
test_async_update_item, follow the same pattern but call client.update_item(...)
with an UpdateExampleItemRequest object and verify the response. Ensure both
tests include the mock_http.request.assert_awaited_once() assertion to verify
the HTTP request was made.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 3bb1e3a7-b507-4de0-89b9-20b34fad6771
📒 Files selected for processing (15)
NEMO_CLIENT_NOTES.local.mdpackages/nemo_platform_plugin/src/nemo_platform_plugin/client/adapter.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/client/bound.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/client/client.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/client/endpoint.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/client/response.pypackages/nemo_platform_plugin/src/nemo_platform_plugin/client/types.pypackages/nemo_platform_plugin/tests/client/test_client.pypackages/nemo_platform_plugin/tests/client/test_endpoint.pyplugins/example-plugin/src/nemo_example_plugin/schema.pyplugins/example-plugin/src/nemo_example_plugin/sdk.pyplugins/example-plugin/src/nemo_example_plugin/service.pyplugins/example-plugin/src/nemo_example_plugin/types/endpoints.pyplugins/example-plugin/src/nemo_example_plugin/types/payloads.pyplugins/example-plugin/tests/test_sdk.py
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/nemo_platform_plugin/src/nemo_platform_plugin/client/client.py (1)
107-110:⚠️ Potential issue | 🟠 MajorAdd explicit close/aclose for owned HTTP clients.
When
http_clientis omitted, these constructors create internalhttpxclients with connection pools that are never closed. Although all current code paths pass explicit clients, this is a public API risk.Proposed fix
class NemoClient(BaseNemoClient): @@ ) -> None: super().__init__(base_url=base_url, workspace=workspace) - self._http = http_client or httpx.Client( + self._owns_http = http_client is None + self._http = http_client or httpx.Client( headers=dict(default_headers) if default_headers else None, timeout=timeout, ) + + def close(self) -> None: + if self._owns_http: + self._http.close() @@ class AsyncNemoClient(BaseNemoClient): @@ ) -> None: super().__init__(base_url=base_url, workspace=workspace) - self._http = http_client or httpx.AsyncClient( + self._owns_http = http_client is None + self._http = http_client or httpx.AsyncClient( headers=dict(default_headers) if default_headers else None, timeout=timeout, ) + + async def aclose(self) -> None: + if self._owns_http: + await self._http.aclose()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/client/client.py` around lines 107 - 110, The constructor creates an internal httpx.Client when http_client parameter is None and stores it in self._http, but never closes it, causing a resource leak. Add a flag to track whether the HTTP client is owned internally (created by the constructor) or was provided externally. Then implement a __del__ method that calls self._http.close() only when the client is owned internally, ensuring proper cleanup of connection pools for internally created clients while leaving externally provided clients for the caller to manage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/nemo_platform_plugin/src/nemo_platform_plugin/client/client.py`:
- Around line 107-110: The constructor creates an internal httpx.Client when
http_client parameter is None and stores it in self._http, but never closes it,
causing a resource leak. Add a flag to track whether the HTTP client is owned
internally (created by the constructor) or was provided externally. Then
implement a __del__ method that calls self._http.close() only when the client is
owned internally, ensuring proper cleanup of connection pools for internally
created clients while leaving externally provided clients for the caller to
manage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 289ffe00-2127-4ae3-aacc-423aac107c17
📒 Files selected for processing (3)
packages/nemo_platform_plugin/src/nemo_platform_plugin/client/client.pyplugins/example-plugin/src/nemo_example_plugin/service.pyplugins/example-plugin/tests/test_sdk.py
🚧 Files skipped from review as they are similar to previous changes (2)
- plugins/example-plugin/tests/test_sdk.py
- plugins/example-plugin/src/nemo_example_plugin/service.py
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Summary
Adds a typed HTTP client infrastructure to
nemo_platform_pluginthat plugin authors can use to build fully type-safe SDK clients. Endpoints are the single source of truth for the HTTP contract — request type, response type, path parameters, and HTTP method are all declared once and flow through to the client with full type inference.Usage
Key design decisions
NemoClientsubclass, they return typed bound callables via__get__. No wrapper functions needed.NemoClientandAsyncNemoClientsubclasses. The descriptor dispatches the right bound callable.BaseModel(JSON),None(DELETE),BinaryContent(file download/upload),Stream[T](SSE/NDJSON). Overloadedsend()returns the right type.TypedDict+Unpackenforces correct kwargs.workspaceusesNotRequiredso the client default can fill it in.tycan't infer class-level TypeVars from classmethods on generic classes (astral-sh/ty#541). Standaloneget(),post(), etc. infer correctly.NemoPluginSDKResourcesregistration still works viafrom_platform()adapter.Test plan
ty checkpasses on all source files with zero diagnostics🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Refactor
Tests
Bug Fixes