diff --git a/hello_nexus/README.md b/hello_nexus/README.md new file mode 100644 index 00000000..0177d868 --- /dev/null +++ b/hello_nexus/README.md @@ -0,0 +1,37 @@ +This sample shows how to define a Nexus service, implement the operation handlers, and +call the operations from a workflow. + +### Sample directory structure + +- [service.py](./service.py) - shared Nexus service definition +- [caller](./caller) - a caller workflow that executes Nexus operations, together with a worker and starter code +- [handler](./handler) - Nexus operation handlers, together with a workflow used by one of the Nexus operations, and a worker that polls for both workflow and Nexus tasks. + + +### Instructions + +Start a Temporal server. (See the main samples repo [README](../README.md)). + +Run the following: + +``` +temporal operator namespace create --namespace hello-nexus-basic-handler-namespace +temporal operator namespace create --namespace hello-nexus-basic-caller-namespace + +temporal operator nexus endpoint create \ + --name hello-nexus-basic-nexus-endpoint \ + --target-namespace hello-nexus-basic-handler-namespace \ + --target-task-queue my-handler-task-queue \ + --description-file endpoint_description.md +``` + +In one terminal, in this directory, run the Temporal worker in the handler namespace: +``` +uv run handler/worker.py +``` + +In another terminal, in this directory, run the Temporal worker in the caller namespace and start the caller +workflow: +``` +uv run caller/app.py +``` diff --git a/hello_nexus/__init__.py b/hello_nexus/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hello_nexus/caller/__init__.py b/hello_nexus/caller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hello_nexus/caller/app.py b/hello_nexus/caller/app.py new file mode 100644 index 00000000..40785b90 --- /dev/null +++ b/hello_nexus/caller/app.py @@ -0,0 +1,43 @@ +import asyncio +import uuid +from typing import Optional + +from temporalio.client import Client +from temporalio.worker import Worker + +from hello_nexus.caller.workflows import CallerWorkflow +from hello_nexus.service import MyOutput + +NAMESPACE = "hello-nexus-basic-caller-namespace" +TASK_QUEUE = "hello-nexus-basic-caller-task-queue" + + +async def execute_caller_workflow( + client: Optional[Client] = None, +) -> tuple[MyOutput, MyOutput]: + client = client or await Client.connect( + "localhost:7233", + namespace=NAMESPACE, + ) + + async with Worker( + client, + task_queue=TASK_QUEUE, + workflows=[CallerWorkflow], + ): + return await client.execute_workflow( + CallerWorkflow.run, + arg="world", + id=str(uuid.uuid4()), + task_queue=TASK_QUEUE, + ) + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + try: + results = loop.run_until_complete(execute_caller_workflow()) + for output in results: + print(output.message) + except KeyboardInterrupt: + loop.run_until_complete(loop.shutdown_asyncgens()) diff --git a/hello_nexus/caller/workflows.py b/hello_nexus/caller/workflows.py new file mode 100644 index 00000000..022eb8d9 --- /dev/null +++ b/hello_nexus/caller/workflows.py @@ -0,0 +1,36 @@ +from temporalio import workflow +from temporalio.workflow import NexusClient + +with workflow.unsafe.imports_passed_through(): + from hello_nexus.service import MyInput, MyNexusService, MyOutput + +NEXUS_ENDPOINT = "hello-nexus-basic-nexus-endpoint" + + +# This is a workflow that calls a nexus operation. +@workflow.defn +class CallerWorkflow: + # An __init__ method is always optional on a Workflow class. Here we use it to set the + # NexusClient, but that could alternatively be done in the run method. + def __init__(self): + self.nexus_client = NexusClient( + MyNexusService, + endpoint=NEXUS_ENDPOINT, + ) + + # The Wokflow run method invokes two Nexus operations. + @workflow.run + async def run(self, name: str) -> tuple[MyOutput, MyOutput]: + # Start the Nexus operation and wait for the result in one go, using execute_operation. + wf_result = await self.nexus_client.execute_operation( + MyNexusService.my_workflow_run_operation, + MyInput(name), + ) + # We could use execute_operation for this one also, but here we demonstrate + # obtaining the operation handle and then using it to get the result. + sync_operation_handle = await self.nexus_client.start_operation( + MyNexusService.my_sync_operation, + MyInput(name), + ) + sync_result = await sync_operation_handle + return sync_result, wf_result diff --git a/hello_nexus/endpoint_description.md b/hello_nexus/endpoint_description.md new file mode 100644 index 00000000..9a381cd0 --- /dev/null +++ b/hello_nexus/endpoint_description.md @@ -0,0 +1,3 @@ +## Service: [MyNexusService](https://github.com/temporalio/samples-python/blob/main/hello_nexus/basic/service.py) + - operation: `my_sync_operation` + - operation: `my_workflow_run_operation` diff --git a/hello_nexus/handler/__init__.py b/hello_nexus/handler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hello_nexus/handler/db_client.py b/hello_nexus/handler/db_client.py new file mode 100644 index 00000000..fac38a1d --- /dev/null +++ b/hello_nexus/handler/db_client.py @@ -0,0 +1,23 @@ +from __future__ import annotations + + +class MyDBClient: + """ + This class represents a resource that your Nexus operation handlers may need when they + are handling Nexus requests, but which is only available when the Nexus worker is + started. Notice that: + + (a) The user's service handler class __init__ constructor takes a MyDBClient instance + (see hello_nexus.handler.MyNexusService) + + (b) The user is responsible for instantiating the service handler class when they + start the worker (see hello_nexus.handler.worker), so they can pass any + necessary resources (such as this database client) to the service handler. + """ + + @classmethod + def connect(cls) -> MyDBClient: + return cls() + + def execute(self, query: str) -> str: + return "query-result" diff --git a/hello_nexus/handler/service_handler.py b/hello_nexus/handler/service_handler.py new file mode 100644 index 00000000..3a373691 --- /dev/null +++ b/hello_nexus/handler/service_handler.py @@ -0,0 +1,58 @@ +""" +This file demonstrates how to implement a Nexus service. +""" + +from __future__ import annotations + +import uuid + +from nexusrpc.handler import StartOperationContext, service_handler, sync_operation +from temporalio import nexus +from temporalio.nexus import WorkflowRunOperationContext, workflow_run_operation + +from hello_nexus.handler.db_client import MyDBClient +from hello_nexus.handler.workflows import WorkflowStartedByNexusOperation +from hello_nexus.service import MyInput, MyNexusService, MyOutput + + +@service_handler(service=MyNexusService) +class MyNexusServiceHandler: + # You can create an __init__ method accepting what is needed by your operation + # handlers to handle requests. You typically instantiate your service handler class + # when starting your worker. See hello_nexus/basic/handler/worker.py. + def __init__(self, connected_db_client: MyDBClient): + # `connected_db_client` is intended as an example of something that might be + # required by your operation handlers when handling requests, but is only + # available at worker-start time. + self.connected_db_client = connected_db_client + + # This is a nexus operation that is backed by a Temporal workflow. The start method + # starts a workflow, and returns a nexus operation token. Meanwhile, the workflow + # executes in the background; Temporal server takes care of delivering the eventual + # workflow result (success or failure) to the calling workflow. + # + # The token will be used by the caller if it subsequently wants to cancel the Nexus + # operation. + @workflow_run_operation + async def my_workflow_run_operation( + self, ctx: WorkflowRunOperationContext, input: MyInput + ) -> nexus.WorkflowHandle[MyOutput]: + # You could use self.connected_db_client here. + return await ctx.start_workflow( + WorkflowStartedByNexusOperation.run, + input, + id=str(uuid.uuid4()), + ) + + # This is a Nexus operation that responds synchronously to all requests. That means + # that unlike the workflow run operation above, in this case the `start` method + # returns the final operation result. + # + # Sync operations are free to make arbitrary network calls, or perform CPU-bound + # computations. Total execution duration must not exceed 10s. + @sync_operation + async def my_sync_operation( + self, ctx: StartOperationContext, input: MyInput + ) -> MyOutput: + # You could use self.connected_db_client here. + return MyOutput(message=f"Hello {input.name} from sync operation!") diff --git a/hello_nexus/handler/worker.py b/hello_nexus/handler/worker.py new file mode 100644 index 00000000..b982c542 --- /dev/null +++ b/hello_nexus/handler/worker.py @@ -0,0 +1,54 @@ +import asyncio +import logging +from typing import Optional + +from temporalio.client import Client +from temporalio.worker import Worker + +from hello_nexus.handler.db_client import MyDBClient +from hello_nexus.handler.service_handler import MyNexusServiceHandler +from hello_nexus.handler.workflows import WorkflowStartedByNexusOperation + +interrupt_event = asyncio.Event() + +NAMESPACE = "hello-nexus-basic-handler-namespace" +TASK_QUEUE = "my-handler-task-queue" + + +async def main(client: Optional[Client] = None): + logging.basicConfig(level=logging.INFO) + + client = client or await Client.connect( + "localhost:7233", + namespace=NAMESPACE, + ) + + # Create an instance of the service handler. Your service handler class __init__ can + # be written to accept any arguments that your operation handlers need when handling + # requests. In this example we provide a database client object to the service hander. + connected_db_client = MyDBClient.connect() + + # Start the worker, passing the Nexus service handler instance, in addition to the + # workflow classes that are started by your nexus operations, and any activities + # needed. This Worker will poll for both workflow tasks and Nexus tasks (this example + # doesn't use any activities). + async with Worker( + client, + task_queue=TASK_QUEUE, + workflows=[WorkflowStartedByNexusOperation], + nexus_service_handlers=[ + MyNexusServiceHandler(connected_db_client=connected_db_client) + ], + ): + logging.info("Worker started, ctrl+c to exit") + await interrupt_event.wait() + logging.info("Shutting down") + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + try: + loop.run_until_complete(main()) + except KeyboardInterrupt: + interrupt_event.set() + loop.run_until_complete(loop.shutdown_asyncgens()) diff --git a/hello_nexus/handler/workflows.py b/hello_nexus/handler/workflows.py new file mode 100644 index 00000000..a41b29ef --- /dev/null +++ b/hello_nexus/handler/workflows.py @@ -0,0 +1,12 @@ +from temporalio import workflow + +with workflow.unsafe.imports_passed_through(): + from hello_nexus.service import MyInput, MyOutput + + +# This is the workflow that is started by the `my_workflow_run_operation` nexus operation. +@workflow.defn +class WorkflowStartedByNexusOperation: + @workflow.run + async def run(self, input: MyInput) -> MyOutput: + return MyOutput(message=f"Hello {input.name} from workflow run operation!") diff --git a/hello_nexus/service.py b/hello_nexus/service.py new file mode 100644 index 00000000..6528775d --- /dev/null +++ b/hello_nexus/service.py @@ -0,0 +1,33 @@ +""" +This is a Nexus service definition. + +A service definition defines a Nexus service as a named collection of operations, each +with input and output types. It does not implement operation handling: see the service +handler and operation handlers in hello_nexus.handler.nexus_service for that. + +A Nexus service definition is used by Nexus callers (e.g. a Temporal workflow) to create +type-safe clients, and it is used by Nexus handlers to validate that they implement +correctly-named operation handlers with the correct input and output types. + +The service defined in this file features two operations: echo and hello. +""" + +from dataclasses import dataclass + +import nexusrpc + + +@dataclass +class MyInput: + name: str + + +@dataclass +class MyOutput: + message: str + + +@nexusrpc.service +class MyNexusService: + my_sync_operation: nexusrpc.Operation[MyInput, MyOutput] + my_workflow_run_operation: nexusrpc.Operation[MyInput, MyOutput] diff --git a/open_telemetry/worker.py b/open_telemetry/worker.py index 4b344123..04095ca7 100644 --- a/open_telemetry/worker.py +++ b/open_telemetry/worker.py @@ -3,7 +3,7 @@ from opentelemetry import trace from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter -from opentelemetry.sdk.resources import SERVICE_NAME, Resource +from opentelemetry.sdk.resources import SERVICE_NAME, Resource # type: ignore from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from temporalio import activity, workflow diff --git a/pyproject.toml b/pyproject.toml index b82bd912..7ce9c0ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,9 @@ dev = [ "pytest>=7.1.2,<8", "pytest-asyncio>=0.18.3,<0.19", "frozenlist>=1.4.0,<2", + "pyright>=1.1.394", "types-pyyaml>=6.0.12.20241230,<7", + "pytest-pretty>=1.3.0", ] bedrock = ["boto3>=1.34.92,<2"] dsl = [ @@ -44,9 +46,12 @@ langchain = [ "tqdm>=4.62.0,<5", "uvicorn[standard]>=0.24.0.post1,<0.25", ] +nexus = [ + "nexus-rpc", +] open-telemetry = [ "temporalio[opentelemetry]", - "opentelemetry-exporter-otlp-proto-grpc==1.18.0", + "opentelemetry-exporter-otlp-proto-grpc", ] openai-agents = [ "openai-agents >= 0.0.19", @@ -73,12 +78,17 @@ default-groups = [ "encryption", "gevent", "langchain", + "nexus", "open-telemetry", "pydantic-converter", "sentry", "trio-async", ] +[tool.uv.sources] +nexus-rpc = { path = "../nexus-sdk-python", editable = true } +temporalio = { path = "../sdk-python", editable = true } + [tool.hatch.build.targets.sdist] include = ["./**/*.py"] @@ -98,6 +108,7 @@ packages = [ "hello", "langchain", "message_passing", + "nexus", "open_telemetry", "patching", "polling", diff --git a/tests/hello_nexus/hello_nexus_test.py b/tests/hello_nexus/hello_nexus_test.py new file mode 100644 index 00000000..c8a5b673 --- /dev/null +++ b/tests/hello_nexus/hello_nexus_test.py @@ -0,0 +1,42 @@ +import asyncio + +from temporalio.client import Client + +import hello_nexus.caller.app +import hello_nexus.caller.workflows +import hello_nexus.handler.worker +from tests.hello_nexus.helpers import create_nexus_endpoint, delete_nexus_endpoint + + +async def test_nexus_service_basic(client: Client): + create_response = await create_nexus_endpoint( + name=hello_nexus.caller.workflows.NEXUS_ENDPOINT, + task_queue=hello_nexus.handler.worker.TASK_QUEUE, + client=client, + ) + try: + handler_worker_task = asyncio.create_task( + hello_nexus.handler.worker.main( + client, + ) + ) + await asyncio.sleep(1) + results = await hello_nexus.caller.app.execute_caller_workflow( + client, + ) + hello_nexus.handler.worker.interrupt_event.set() + await handler_worker_task + hello_nexus.handler.worker.interrupt_event.clear() + print("\n\n") + print([r.message for r in results]) + print("\n\n") + assert [r.message for r in results] == [ + "Hello world from sync operation!", + "Hello world from workflow run operation!", + ] + finally: + await delete_nexus_endpoint( + id=create_response.endpoint.id, + version=create_response.endpoint.version, + client=client, + ) diff --git a/tests/hello_nexus/helpers.py b/tests/hello_nexus/helpers.py new file mode 100644 index 00000000..dee8dc18 --- /dev/null +++ b/tests/hello_nexus/helpers.py @@ -0,0 +1,39 @@ +import temporalio.api +import temporalio.api.common +import temporalio.api.common.v1 +import temporalio.api.enums.v1 +import temporalio.api.nexus +import temporalio.api.nexus.v1 +import temporalio.api.operatorservice +import temporalio.api.operatorservice.v1 +from temporalio.client import Client + + +# TODO: copied from sdk-python tests/helpers/nexus +async def create_nexus_endpoint( + name: str, task_queue: str, client: Client +) -> temporalio.api.operatorservice.v1.CreateNexusEndpointResponse: + return await client.operator_service.create_nexus_endpoint( + temporalio.api.operatorservice.v1.CreateNexusEndpointRequest( + spec=temporalio.api.nexus.v1.EndpointSpec( + name=name, + target=temporalio.api.nexus.v1.EndpointTarget( + worker=temporalio.api.nexus.v1.EndpointTarget.Worker( + namespace=client.namespace, + task_queue=task_queue, + ) + ), + ) + ) + ) + + +async def delete_nexus_endpoint( + id: str, version: int, client: Client +) -> temporalio.api.operatorservice.v1.DeleteNexusEndpointResponse: + return await client.operator_service.delete_nexus_endpoint( + temporalio.api.operatorservice.v1.DeleteNexusEndpointRequest( + id=id, + version=version, + ) + ) diff --git a/uv.lock b/uv.lock index 21e6c17e..ce1e68cf 100644 --- a/uv.lock +++ b/uv.lock @@ -502,6 +502,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -1180,6 +1189,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/f0/63b06b99b730b9954f8709f6f7d9b8d076fa0a973e472efe278089bde42b/langsmith-0.1.147-py3-none-any.whl", hash = "sha256:7166fc23b965ccf839d64945a78e9f1157757add228b086141eb03a60d699a15", size = 311812, upload-time = "2024-11-27T17:32:39.569Z" }, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + [[package]] name = "marshmallow" version = "3.26.1" @@ -1212,6 +1233,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232, upload-time = "2025-06-12T08:20:28.551Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "multidict" version = "6.5.0" @@ -1386,6 +1416,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nexus-rpc" +version = "0.1.0" +source = { editable = "../nexus-sdk-python" } +dependencies = [ + { name = "typing-extensions" }, +] + +[package.metadata] +requires-dist = [{ name = "typing-extensions", specifier = ">=4.12.2" }] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.15.0" }, + { name = "poethepoet", specifier = ">=0.35.0" }, + { name = "pydoctor", specifier = ">=25.4.0" }, + { name = "pyright", specifier = ">=1.1.402" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-asyncio", specifier = ">=0.26.0" }, + { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "pytest-pretty", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.12.0" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + [[package]] name = "numpy" version = "1.26.4" @@ -2060,6 +2123,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.402" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/04/ce0c132d00e20f2d2fb3b3e7c125264ca8b909e693841210534b1ea1752f/pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683", size = 3888207, upload-time = "2025-06-11T08:48:35.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004, upload-time = "2025-06-11T08:48:33.998Z" }, +] + [[package]] name = "pytest" version = "7.4.4" @@ -2090,6 +2175,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/4b/7c400506ec484ec999b10133aa8e31af39dfc727042dc6944cd45fd927d0/pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84", size = 14597, upload-time = "2022-03-25T09:43:57.106Z" }, ] +[[package]] +name = "pytest-pretty" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/d7/c699e0be5401fe9ccad484562f0af9350b4e48c05acf39fb3dab1932128f/pytest_pretty-1.3.0.tar.gz", hash = "sha256:97e9921be40f003e40ae78db078d4a0c1ea42bf73418097b5077970c2cc43bf3", size = 219297, upload-time = "2025-06-04T12:54:37.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/85/2f97a1b65178b0f11c9c77c35417a4cc5b99a80db90dad4734a129844ea5/pytest_pretty-1.3.0-py3-none-any.whl", hash = "sha256:074b9d5783cef9571494543de07e768a4dda92a3e85118d6c7458c67297159b7", size = 5620, upload-time = "2025-06-04T12:54:36.229Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2295,6 +2393,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + [[package]] name = "s3transfer" version = "0.13.0" @@ -2438,24 +2550,18 @@ wheels = [ [[package]] name = "temporalio" version = "1.13.0" -source = { registry = "https://pypi.org/simple" } +source = { editable = "../sdk-python" } dependencies = [ + { name = "nexus-rpc" }, { name = "protobuf" }, { name = "python-dateutil", marker = "python_full_version < '3.11'" }, { name = "types-protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/a3/a76477b523937f47a21941188c16b3c6b1eef6baadc7c8efeea497d909de/temporalio-1.13.0.tar.gz", hash = "sha256:5a979eee5433da6ab5d8a2bcde25a1e7d454e91920acb0bf7ca93d415750828b", size = 1558745, upload-time = "2025-06-20T19:57:26.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/f4/a5a74284c671bd50ce7353ad1dad7dab1a795f891458454049e95bc5378f/temporalio-1.13.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7ee14cab581352e77171d1e4ce01a899231abfe75c5f7233e3e260f361a344cc", size = 12086961, upload-time = "2025-06-20T19:57:15.25Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b7/5dc6e34f4e9a3da8b75cb3fe0d32edca1d9201d598c38d022501d38650a9/temporalio-1.13.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:575a0c57dbb089298b4775f3aca86ebaf8d58d5ba155e7fc5509877c25e6bb44", size = 11745239, upload-time = "2025-06-20T19:57:17.934Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/4b9b15af87c181fd9364b61971faa0faa07d199320d7ff1712b5d51b5bbb/temporalio-1.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf099a27f22c0dbc22f3d86dba76d59be5da812ff044ba3fa183e3e14bd5e9a", size = 12119197, upload-time = "2025-06-20T19:57:20.509Z" }, - { url = "https://files.pythonhosted.org/packages/46/9f/a5b627d773974c654b6cd22ed3937e7e2471023af244ea417f0e917e617b/temporalio-1.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7e20c711f41c66877b9d54ab33c79a14ccaac9ed498a174274f6129110f4d84", size = 12413459, upload-time = "2025-06-20T19:57:22.816Z" }, - { url = "https://files.pythonhosted.org/packages/a3/73/efb6957212eb8c8dfff26c7c2c6ddf745aa5990a3b722cff17c8feaa66fc/temporalio-1.13.0-cp39-abi3-win_amd64.whl", hash = "sha256:9286cb84c1e078b2bcc6e8c6bd0be878d8ed395be991ac0d7cff555e3a82ac0b", size = 12440644, upload-time = "2025-06-20T19:57:25.175Z" }, -] [package.optional-dependencies] openai-agents = [ + { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, { name = "openai-agents" }, ] opentelemetry = [ @@ -2463,6 +2569,44 @@ opentelemetry = [ { name = "opentelemetry-sdk" }, ] +[package.metadata] +requires-dist = [ + { name = "eval-type-backport", marker = "python_full_version < '3.10' and extra == 'openai-agents'", specifier = ">=0.2.2" }, + { name = "grpcio", marker = "extra == 'grpc'", specifier = ">=1.48.2,<2" }, + { name = "nexus-rpc", editable = "../nexus-sdk-python" }, + { name = "openai-agents", marker = "extra == 'openai-agents'", specifier = ">=0.0.19,<0.1" }, + { name = "opentelemetry-api", marker = "extra == 'opentelemetry'", specifier = ">=1.11.1,<2" }, + { name = "opentelemetry-sdk", marker = "extra == 'opentelemetry'", specifier = ">=1.11.1,<2" }, + { name = "protobuf", specifier = ">=3.20,<6" }, + { name = "pydantic", marker = "extra == 'pydantic'", specifier = ">=2.0.0,<3" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'", specifier = ">=2.8.2,<3" }, + { name = "types-protobuf", specifier = ">=3.20" }, + { name = "typing-extensions", specifier = ">=4.2.0,<5" }, +] +provides-extras = ["grpc", "opentelemetry", "pydantic", "openai-agents"] + +[package.metadata.requires-dev] +dev = [ + { name = "cibuildwheel", specifier = ">=2.22.0,<3" }, + { name = "grpcio-tools", specifier = ">=1.48.2,<2" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "maturin", specifier = ">=1.8.2" }, + { name = "mypy", specifier = "==1.4.1" }, + { name = "mypy-protobuf", specifier = ">=3.3.0,<4" }, + { name = "psutil", specifier = ">=5.9.3,<6" }, + { name = "pydocstyle", specifier = ">=6.3.0,<7" }, + { name = "pydoctor", specifier = ">=24.11.1,<25" }, + { name = "pyright", specifier = "==1.1.400" }, + { name = "pytest", specifier = "~=7.4" }, + { name = "pytest-asyncio", specifier = ">=0.21,<0.22" }, + { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "pytest-pretty", specifier = ">=1.3.0" }, + { name = "pytest-timeout", specifier = "~=2.2" }, + { name = "ruff", specifier = ">=0.5.0,<0.6" }, + { name = "toml", specifier = ">=0.10.2,<0.11" }, + { name = "twine", specifier = ">=4.0.1,<5" }, +] + [[package]] name = "temporalio-samples" version = "0.1a1" @@ -2486,8 +2630,10 @@ dev = [ { name = "frozenlist" }, { name = "isort" }, { name = "mypy" }, + { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-pretty" }, { name = "types-pyyaml" }, ] dsl = [ @@ -2511,6 +2657,9 @@ langchain = [ { name = "tqdm" }, { name = "uvicorn", extra = ["standard"] }, ] +nexus = [ + { name = "nexus-rpc" }, +] open-telemetry = [ { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "temporalio", extra = ["opentelemetry"] }, @@ -2531,7 +2680,7 @@ trio-async = [ ] [package.metadata] -requires-dist = [{ name = "temporalio", specifier = ">=1.13.0,<2" }] +requires-dist = [{ name = "temporalio", editable = "../sdk-python" }] [package.metadata.requires-dev] bedrock = [{ name = "boto3", specifier = ">=1.34.92,<2" }] @@ -2546,8 +2695,10 @@ dev = [ { name = "frozenlist", specifier = ">=1.4.0,<2" }, { name = "isort", specifier = ">=5.10.1,<6" }, { name = "mypy", specifier = ">=1.4.1,<2" }, + { name = "pyright", specifier = ">=1.1.394" }, { name = "pytest", specifier = ">=7.1.2,<8" }, { name = "pytest-asyncio", specifier = ">=0.18.3,<0.19" }, + { name = "pytest-pretty", specifier = ">=1.3.0" }, { name = "types-pyyaml", specifier = ">=6.0.12.20241230,<7" }, ] dsl = [ @@ -2569,13 +2720,14 @@ langchain = [ { name = "tqdm", specifier = ">=4.62.0,<5" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.24.0.post1,<0.25" }, ] +nexus = [{ name = "nexus-rpc", editable = "../nexus-sdk-python" }] open-telemetry = [ - { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = "==1.18.0" }, - { name = "temporalio", extras = ["opentelemetry"] }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "temporalio", extras = ["opentelemetry"], editable = "../sdk-python" }, ] openai-agents = [ { name = "openai-agents", specifier = ">=0.0.19" }, - { name = "temporalio", extras = ["openai-agents"], specifier = ">=1.13.0" }, + { name = "temporalio", extras = ["openai-agents"], editable = "../sdk-python" }, ] pydantic-converter = [{ name = "pydantic", specifier = ">=2.10.6,<3" }] sentry = [{ name = "sentry-sdk", specifier = ">=1.11.0,<2" }]