Skip to content

feat(sdk): Add a generic NemoClient#370

Draft
matthewgrossman wants to merge 19 commits into
mainfrom
mgrossman/files-with-nemo-plugin-client
Draft

feat(sdk): Add a generic NemoClient#370
matthewgrossman wants to merge 19 commits into
mainfrom
mgrossman/files-with-nemo-plugin-client

Conversation

@matthewgrossman

@matthewgrossman matthewgrossman commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a typed HTTP client infrastructure to nemo_platform_plugin that 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

# 1. Define your request and response as plain Pydantic models
from pydantic import BaseModel

class CreateItemRequest(BaseModel):
    name: str
    title: str

class ItemResponse(BaseModel):
    id: int
    name: str
    title: str

# 2. Define path parameters and endpoints
from typing import NotRequired
from nemo_platform_plugin.client.types import BasePath
from nemo_platform_plugin.client.endpoint import get, post, delete

class WorkspacePath(BasePath):
    workspace: NotRequired[str]

class WorkspaceItemPath(BasePath):
    workspace: NotRequired[str]
    name: str

CreateItemEndpoint = post(
    "/apis/my-plugin/v2/workspaces/{workspace}/items",
    path_type=WorkspacePath,
    request_type=CreateItemRequest,
    response_type=ItemResponse,
)
GetItemEndpoint = get(
    "/apis/my-plugin/v2/workspaces/{workspace}/items/{name}",
    path_type=WorkspaceItemPath,
    response_type=ItemResponse,
)
DeleteItemEndpoint = delete(
    "/apis/my-plugin/v2/workspaces/{workspace}/items/{name}",
    path_type=WorkspaceItemPath,
)

# 3. Create sync and async clients — endpoints become methods automatically
from nemo_platform_plugin.client.client import NemoClient, AsyncNemoClient

class _Endpoints:
    create_item = CreateItemEndpoint
    get_item = GetItemEndpoint
    delete_item = DeleteItemEndpoint

class MyPluginClient(_Endpoints, NemoClient):
    pass

class AsyncMyPluginClient(_Endpoints, AsyncNemoClient):
    pass

# 4. Use it
client = MyPluginClient(base_url="http://localhost:8080", workspace="default")

# Typed end-to-end: ty/pyright knows the return type is NemoResponse[ItemResponse]
resp = client.create_item(CreateItemRequest(name="widget", title="My Widget"), workspace="default")
item = resp.data()  # ItemResponse

resp = client.get_item(workspace="default", name="widget")
item = resp.data()  # ItemResponse

client.delete_item(workspace="default", name="widget")

Key design decisions

  • Endpoints are descriptors: when assigned as class attributes on a NemoClient subclass, they return typed bound callables via __get__. No wrapper functions needed.
  • Sync/async from one definition: define endpoints once in a mixin, inherit into both NemoClient and AsyncNemoClient subclasses. The descriptor dispatches the right bound callable.
  • Four response kinds: BaseModel (JSON), None (DELETE), BinaryContent (file download/upload), Stream[T] (SSE/NDJSON). Overloaded send() returns the right type.
  • Path params are typed: TypedDict + Unpack enforces correct kwargs. workspace uses NotRequired so the client default can fill it in.
  • Factory functions not classmethods: ty can't infer class-level TypeVars from classmethods on generic classes (astral-sh/ty#541). Standalone get(), post(), etc. infer correctly.
  • Backward compatible: NemoPluginSDKResources registration still works via from_platform() adapter.

Test plan

  • All existing example-plugin tests pass (service, SDK)
  • New client infrastructure tests pass (endpoint, client)
  • ty check passes on all source files with zero diagnostics

🤖 Generated with Claude Code

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>
@github-actions github-actions Bot added the feat label Jun 17, 2026
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 19767/26264 75.3% 60.5%
Integration Tests 11620/25036 46.4% 20.3%

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>
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant