-
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?
Conversation
a0db8c4
to
2733044
Compare
4c284df
to
9bdf24d
Compare
fbb91b0
to
659557b
Compare
96f9be3
to
9711999
Compare
fa76eac
to
706b354
Compare
This reverts commit e83999b.
packages = ["src/nexusrpc"] | ||
|
||
[tool.pyright] | ||
include = ["src", "tests"] |
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.
Hmm... maybe this pattern started being adopted more, I have typically seen Python project not have an src
directory.
src/nexus_rpc/__init__.py
Outdated
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.
Clean this up?
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.
Looks like this comment was made on a deleted file.
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.
Wonder if we should be exporting handler
here too so users can use nexusrpc.handler.service_handler
directly without having to import handler
separately.
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.
I was thinking the same. I've done it.
src/nexusrpc/__init__.py
Outdated
from ._serializer import Content as Content, LazyValue as LazyValue | ||
from ._service import ( | ||
Operation as Operation, | ||
ServiceDefinition as ServiceDefinition, | ||
service as service, | ||
) |
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 do you need import as
if you're not renaming the items?
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.
OK so
(a) Type checkers complain if the imported name is unused. So this style is just how modern Python does it, and how the de-factor formatter ruff
formats it.
(b) But you're right, I recently added __all__
collections of exported names to all __init__.py
s, because the API docs needed it, and with that we no longer need the as
stuff.
|
||
Includes information from the request.""" | ||
|
||
request_id: str |
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.
FTR, an empty request ID is not rejected by the server. This string may be empty.
from nexusrpc.handler._core import BaseServiceCollectionHandler | ||
from nexusrpc.handler._util import get_start_method_input_and_output_type_annotations | ||
|
||
from ...handler._operation_handler import OperationHandler |
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.
Given that the OperationHandler works for both sync and async, this separate syncio module seems like overkill.
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.
What arrangement do you prefer? We have the following components that at the moment have sync and asynv versions:
Handler
LazyValue
sync_operation
In any case, I think that what we should do is delete all of the above sync support, until we're sure how we want to present it. I'll do that in a follow-up PR.
To be clear for anyone else reading: when we "delete sync support", the SDK will continue to support user operation handlers with def
methods (as long as an executor
has been supplied).
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.
Added a few comments but AFAIC, there's nothing major.
... | ||
|
||
|
||
class LazyValueT(Protocol): |
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.
But I thought we were removing sync form? At which point is there value here?
"""Request or response data.""" | ||
|
||
|
||
class Serializer(Protocol): |
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.
It's not incorrect, it's just too limiting and not common for these use cases. I think we should revisit this since it makes sense for serializer to be a base class one extends instead of duck-typed.
... | ||
|
||
|
||
class LazyValueT(Protocol): |
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.
Yeah, I think my main problem was that this didn't make sense as a protocol
) | ||
return defn | ||
|
||
def _validation_errors(self) -> list[str]: |
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.
Can we get a general decision on operation-less Nexus service contracts and whether we generally believe they are valid? A dataclass with no fields may be valid, but a service with no calls may not be.
from nexusrpc.handler._operation_handler import OperationHandler | ||
|
||
|
||
def get_service_definition( |
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.
Concern about the name is different than concern about the placement. I think there's not a need to clutter the top-level API with a top-level function here. Whether we call this from_class
or get_from_class
or whatever I think is a secondary question (I don't see a from_whatever
to be a constructor, but can be documented away).
src/nexusrpc/handler/_common.py
Outdated
|
||
|
||
@dataclass(frozen=True) | ||
class OperationContext: |
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.
I think it's also a signal of intent as well (i.e. "this is always used as a base class")
... | ||
|
||
|
||
class BaseServiceCollectionHandler(AbstractHandler): |
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.
👍 Would definitely like to reduce class/abstraction count if we can
) | ||
self.service_handlers[sh.service.name] = sh | ||
|
||
async def start_operation( |
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.
👍 I trust the removal of the unused syncio stuff is being tracked
def service_handler(cls: Type[ServiceHandlerT]) -> Type[ServiceHandlerT]: ... | ||
|
||
|
||
# TODO(preview): allow service to be provided as positional argument? |
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.
I guess what I mean is why is service
before *
in the impl but after the *
here?
self.headers = headers | ||
self.stream = stream | ||
|
||
def consume(self, as_type: Optional[Type[Any]] = None) -> Any: |
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.
A thought while we're here - arguably a sync implementation should allow async operations and provide an event loop to run them on via run_coroutine_threadsafe
similar to how an executor is provided to go from async to sync.
Nexus Python SDK initial content.
For an overview of the user experience of implementing and calling Nexus handlers, see the Temporal sample, and tests in this PR.