-
Notifications
You must be signed in to change notification settings - Fork 1
Initial implementation #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fb231a0
6e6a28a
c4c9740
89fd030
4c6bdc8
6367411
a94f181
29a5504
a5cc3b4
26f5522
e67dbef
696da1b
a75c8ee
a029d06
090edff
ec039c1
201ab95
1d65a51
c71f0f0
48ad60e
87c4adb
79eb6a1
91855da
dbcc009
79d73e0
ce33dd4
a63024a
c92d4c2
abb05cc
23b3fa6
45c076a
810d0cb
4371d45
3548e04
d604a66
8fd0f06
d86d67e
38e5a91
e48eb12
d443b29
8c06955
e93c4c0
1b6fd90
7280381
a063676
ef55dad
b632074
1df5c7e
cefb8fd
311eda4
0bd864b
ca7af9a
e4652de
5b659d9
449f28a
bbd6bff
1e4d8fe
996da25
7255c49
05fc6fe
ba86bdd
96e1618
af99478
3aaa53a
f5a1cae
043f4af
95f7874
8c3be18
721f3d1
770ffbc
5a88b85
a60cb3a
964a99a
69c501d
b6e792c
a938cfd
ef3aeb9
56f86fe
a6a2e97
e4a8762
cf79d71
0e2a28b
32f9e55
f87ee04
0508c56
ed75834
4494e2a
5b2fc91
280b8ab
59f3f1b
7eb03d5
937511f
42a1e34
11d7d4e
56f8c23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: [ main, v0 ] | ||
pull_request: | ||
branches: [ main ] | ||
|
||
jobs: | ||
test: | ||
# TODO(preview): other platforms | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you plan on addressing this or tracking in an issue? |
||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: ['3.9', '3.13', '3.14'] | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Install uv | ||
uses: astral-sh/setup-uv@v6 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install dependencies | ||
run: | | ||
uv sync | ||
|
||
- name: Lint | ||
run: | | ||
uv run ruff format --check | ||
uv run ruff check | ||
|
||
- name: Type check | ||
# TODO(preview): Get both passing | ||
run: | | ||
uv run pyright . || true | ||
uv run mypy --check-untyped-defs . || true | ||
|
||
- name: Run tests | ||
run: | | ||
uv run pytest --cov=src --cov-report=html:coverage_html_report | ||
|
||
- name: Upload coverage report | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: coverage-html-report-${{ matrix.python-version }} | ||
path: coverage_html_report/ | ||
|
||
- name: Build API docs | ||
run: uv run pydoctor src/nexusrpc | ||
|
||
- name: Deploy prod API docs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this set up? What domain are you planning to push to? I was planning on using GH docs for TS for now. |
||
if: ${{ github.ref == 'refs/heads/main' }} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we should publish docs on a stable release not on push to main. We made this mistake with Temporal SDKs, let's not repeat. |
||
env: | ||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | ||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | ||
run: npx vercel deploy build/apidocs -t ${{ secrets.VERCEL_TOKEN }} --prod --yes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,5 @@ | ||
# Python-generated files | ||
__pycache__/ | ||
*.py[oc] | ||
build/ | ||
dist/ | ||
wheels/ | ||
*.egg-info | ||
|
||
# Virtual environments | ||
__pycache__ | ||
.venv | ||
apidocs | ||
dist | ||
docs |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
### Type-checking, Linting, and Formatting | ||
|
||
```sh | ||
uv run pyright | ||
uv run mypy --check-untyped-defs . | ||
uv run ruff check --select I | ||
uv run ruff format --check | ||
Comment on lines
+4
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can all of this be packaged in one command? |
||
``` | ||
|
||
### Formatting | ||
``` | ||
uv run ruff check --select I --fix | ||
uv run ruff format | ||
``` | ||
|
||
### API docs | ||
``` | ||
uv run pydoctor src/nexusrpc | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2025 Temporal Technologies Inc. All Rights Reserved | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Nexus Python SDK | ||
|
||
## What is Nexus? | ||
|
||
[Nexus](https://github.com/nexus-rpc/) is a synchronous RPC protocol. Arbitrary duration operations are modeled on top of | ||
a set of pre-defined synchronous RPCs. | ||
|
||
A Nexus caller calls a handler. The handler may respond inline (synchronous response) or | ||
return a token referencing the ongoing operation (asynchronous response). The caller can | ||
cancel an asynchronous operation, check for its outcome, or fetch its current state. The | ||
caller can also specify a callback URL, which the handler uses to deliver the result of | ||
an asynchronous operation when it is ready. | ||
|
||
## Installation | ||
|
||
``` | ||
uv add nexus-rpc | ||
``` | ||
or | ||
``` | ||
pip install nexus-rpc | ||
``` | ||
|
||
## Usage | ||
|
||
The SDK currently supports two use cases: | ||
|
||
1. As an end user, defining Nexus services and operations. | ||
|
||
2. Implementing a Nexus handler that can accept and respond to incoming Nexus requests, dispatching to the corresponding user-defined Nexus operation. | ||
|
||
The handler in (2) would form part of a server or worker that processes Nexus requests; the SDK does not yet provide reference implementations of these, or of a Nexus client. | ||
|
||
### Defining Nexus services and operations | ||
|
||
```python | ||
from dataclasses import dataclass | ||
|
||
import nexusrpc | ||
from nexusrpc.handler import StartOperationContext, service_handler, sync_operation | ||
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you only sometimes import |
||
|
||
|
||
@dataclass | ||
class MyInput: | ||
name: str | ||
|
||
|
||
@dataclass | ||
class MyOutput: | ||
message: str | ||
|
||
|
||
@nexusrpc.service | ||
class MyNexusService: | ||
my_sync_operation: nexusrpc.Operation[MyInput, 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 will typically instantiate your service handler class | ||
# when starting your Nexus server/worker. | ||
|
||
# This is a Nexus operation that responds synchronously to all requests. That means | ||
# that the `start` method returns the final operation result. | ||
# | ||
# Sync operations are free to make arbitrary network calls. | ||
@sync_operation | ||
async def my_sync_operation( | ||
self, ctx: StartOperationContext, input: MyInput | ||
) -> MyOutput: | ||
return MyOutput(message=f"Hello {input.name}!") | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,35 @@ readme = "README.md" | |
authors = [ | ||
{ name = "Dan Davison", email = "[email protected]" } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authors should be the Temporal SDK team. |
||
] | ||
requires-python = ">=3.13" | ||
dependencies = [] | ||
requires-python = ">=3.9" | ||
dependencies = [ | ||
"typing-extensions>=4.12.2", | ||
] | ||
|
||
[dependency-groups] | ||
dev = [ | ||
"mypy>=1.15.0", | ||
"pydoctor>=25.4.0", | ||
"pyright>=1.1.400", | ||
"pytest>=8.3.5", | ||
"pytest-asyncio>=0.26.0", | ||
"pytest-cov>=6.1.1", | ||
"pytest-pretty>=1.3.0", | ||
"ruff>=0.12.0", | ||
] | ||
|
||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
|
||
[tool.hatch.build.targets.wheel] | ||
packages = ["src/nexusrpc"] | ||
|
||
[tool.pyright] | ||
include = ["src", "tests"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Python packages don't typically put code in |
||
|
||
[tool.ruff] | ||
target-version = "py39" | ||
|
||
[tool.ruff.lint.isort] | ||
combine-as-imports = true |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from dataclasses import dataclass | ||
from enum import Enum | ||
|
||
from ._serializer import Content as Content, LazyValue as LazyValue | ||
from ._service import ( | ||
Operation as Operation, | ||
ServiceDefinition as ServiceDefinition, | ||
service as service, | ||
) | ||
from ._types import InputT as InputT, OutputT as OutputT | ||
|
||
|
||
@dataclass(frozen=True) | ||
class Link: | ||
""" | ||
Link contains a URL and a Type that can be used to decode the URL. | ||
Links can contain any arbitrary information as a percent-encoded URL. | ||
It can be used to pass information about the caller to the handler, or vice versa. | ||
""" | ||
|
||
# The URL must be percent-encoded. | ||
url: str | ||
# Can describe an actual data type for decoding the URL. Valid chars: alphanumeric, '_', '.', | ||
# '/' | ||
type: str | ||
Comment on lines
+21
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These comments don't count as docstrings AFAIU, They should (not just here, throughout the entire codebase). |
||
|
||
|
||
class OperationState(Enum): | ||
""" | ||
Describes the current state of an operation. | ||
""" | ||
|
||
SUCCEEDED = "succeeded" | ||
FAILED = "failed" | ||
CANCELED = "canceled" | ||
RUNNING = "running" | ||
|
||
|
||
@dataclass(frozen=True) | ||
class OperationInfo: | ||
""" | ||
Information about an operation. | ||
""" | ||
|
||
# Token identifying the operation (returned on operation start). | ||
token: str | ||
|
||
# The operation's state | ||
state: OperationState | ||
|
||
|
||
class OperationErrorState(Enum): | ||
""" | ||
The state of an operation as described by an OperationError. | ||
""" | ||
|
||
FAILED = "failed" | ||
CANCELED = "canceled" | ||
|
||
|
||
class OperationError(Exception): | ||
""" | ||
An error that represents "failed" and "canceled" operation results. | ||
""" | ||
|
||
def __init__(self, message: str, *, state: OperationErrorState): | ||
super().__init__(message) | ||
self.state = state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why
v0
? Is it to run CI in this PR? Will you remove it eventually?