Skip to content
Open
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
103 changes: 103 additions & 0 deletions demo/use_case/grpc_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""gRPC demo — UseCaseService exposed as gRPC server.

Run:
uv run --with grpcio python -m demo.use_case.grpc_demo
"""

import asyncio
import json

from pydantic import BaseModel

from nexusx import UseCaseAppConfig, UseCaseService, create_use_case_grpc_server, query

# ── DTOs ──────────────────────────────────────────


class ItemDTO(BaseModel):
id: int
name: str
price: float


# ── Service ───────────────────────────────────────


class CatalogService(UseCaseService):
"""Product catalog service."""

@query
async def list_items(cls) -> list[ItemDTO]:
return [
ItemDTO(id=1, name="Widget", price=9.99),
ItemDTO(id=2, name="Gadget", price=19.99),
ItemDTO(id=3, name="Doohickey", price=4.99),
]

@query
async def get_item(cls, item_id: int) -> ItemDTO | None:
items = {
1: ItemDTO(id=1, name="Widget", price=9.99),
2: ItemDTO(id=2, name="Gadget", price=19.99),
3: ItemDTO(id=3, name="Doohickey", price=4.99),
}
return items.get(item_id)


# ── Main ──────────────────────────────────────────

PORT = 50051


async def server():
config = UseCaseAppConfig(
name="catalog",
services=[CatalogService],
)
server = create_use_case_grpc_server(config, port=PORT)
await server.start()
print(f"gRPC server listening on port {PORT}")
await server.wait_for_termination()


async def client():
from grpc import aio as grpc_aio

await asyncio.sleep(0.5) # wait for server

async with grpc_aio.insecure_channel(f"localhost:{PORT}") as channel:
call = channel.unary_unary(
"/CatalogService/list_items",
request_serializer=lambda d: json.dumps(d).encode(),
response_deserializer=lambda b: b,
)
response = await call({})
data = json.loads(response)
print("\n── list_items ──")
for item in data["result"]:
print(f" {item['id']}: {item['name']} — ${item['price']}")

# Get single item
call = channel.unary_unary(
"/CatalogService/get_item",
request_serializer=lambda d: json.dumps(d).encode(),
response_deserializer=lambda b: b,
)
response = await call({"item_id": 2})
data = json.loads(response)
print("\n── get_item(2) ──")
print(f" {data['result']}")


async def main():
server_task = asyncio.create_task(server())
await client()
server_task.cancel()
try:
await server_task
except asyncio.CancelledError:
pass


if __name__ == "__main__":
asyncio.run(main())
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ fastmcp = [
cli = [
"typer>=0.12.0",
]
grpc = [
"grpcio>=1.60.0",
]

[project.urls]
Homepage = "https://github.com/allmonday/nexusx"
Expand Down
2 changes: 2 additions & 0 deletions src/nexusx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def resolve_author(self, loader=Loader('author')):
create_jsonrpc_router,
create_use_case_cli,
create_use_case_flat_server,
create_use_case_grpc_server,
create_use_case_mcp_server,
)
from nexusx.use_case import (
Expand Down Expand Up @@ -109,6 +110,7 @@ def resolve_author(self, loader=Loader('author')):
"SelectionError",
"create_use_case_mcp_server",
"create_use_case_flat_server",
"create_use_case_grpc_server",
"create_use_case_cli",
"create_jsonrpc_router",
"create_use_case_router",
Expand Down
2 changes: 2 additions & 0 deletions src/nexusx/use_case/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from nexusx.use_case.cli import create_use_case_cli
from nexusx.use_case.context import FromContext
from nexusx.use_case.flat_server import create_use_case_flat_server
from nexusx.use_case.grpc_server import create_use_case_grpc_server
from nexusx.use_case.jsonrpc import create_jsonrpc_router
from nexusx.use_case.router import create_router
from nexusx.use_case.selection import SelectionError
Expand All @@ -17,6 +18,7 @@
__all__ = [
"create_use_case_cli",
"create_use_case_flat_server",
"create_use_case_grpc_server",
"create_jsonrpc_router",
"create_router",
"create_use_case_mcp_server",
Expand Down
Loading
Loading