Skip to content

Commit 4b49b45

Browse files
committed
Test operations without type annotations
1 parent 9824a23 commit 4b49b45

File tree

1 file changed

+57
-9
lines changed

1 file changed

+57
-9
lines changed

tests/nexus/test_handler.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -791,12 +791,12 @@ async def _test_start_operation_without_service_definition(
791791

792792

793793
@nexusrpc.service
794-
class MyServiceWithOperationsWithoutTypeAnnotations(MyService):
794+
class MyServiceWithOperationsWithoutTypeAnnotations:
795795
workflow_run_operation_without_type_annotations: nexusrpc.Operation[Input, Output]
796796
sync_operation_without_type_annotations: nexusrpc.Operation[Input, Output]
797797

798798

799-
class MyServiceHandlerWithOperationsWithoutTypeAnnotations(MyServiceHandler):
799+
class MyServiceHandlerWithOperationsWithoutTypeAnnotations:
800800
@sync_operation
801801
async def sync_operation_without_type_annotations(self, ctx, input):
802802
# Despite the lack of type annotations, the input type from the op definition in
@@ -820,13 +820,7 @@ class SyncHandlerHappyPathWithoutTypeAnnotations(_TestCase):
820820
expected = SuccessfulResponse(
821821
status_code=200,
822822
body_json={
823-
"value": "from start method on MyServiceHandler without type annotations: Input(value='hello')"
824-
},
825-
)
826-
expected_without_service_definition = SuccessfulResponse(
827-
status_code=200,
828-
body_json={
829-
"value": "from start method on MyServiceHandler without type annotations: {'value': 'hello'}"
823+
"value": "from start method on MyServiceHandlerWithOperationsWithoutTypeAnnotations without type annotations: Input(value='hello')"
830824
},
831825
)
832826

@@ -839,6 +833,60 @@ class AsyncHandlerHappyPathWithoutTypeAnnotations(_TestCase):
839833
)
840834

841835

836+
# Attempting to use the service_handler decorator on a class containing an operation
837+
# without type annotations is a validation error (test coverage in nexusrpc)
838+
@pytest.mark.parametrize(
839+
"test_case",
840+
[
841+
SyncHandlerHappyPathWithoutTypeAnnotations,
842+
AsyncHandlerHappyPathWithoutTypeAnnotations,
843+
],
844+
)
845+
async def test_start_operation_without_type_annotations(
846+
test_case: Type[_TestCase], env: WorkflowEnvironment
847+
):
848+
if test_case.skip:
849+
pytest.skip(test_case.skip)
850+
task_queue = str(uuid.uuid4())
851+
endpoint = (await create_nexus_endpoint(task_queue, env.client)).endpoint.id
852+
service_client = ServiceClient(
853+
server_address=server_address(env),
854+
endpoint=endpoint,
855+
service=MyServiceWithOperationsWithoutTypeAnnotations.__name__,
856+
)
857+
858+
with pytest.WarningsRecorder() as warnings:
859+
decorator = service_handler(
860+
service=MyServiceWithOperationsWithoutTypeAnnotations
861+
)
862+
user_service_handler = decorator(
863+
MyServiceHandlerWithOperationsWithoutTypeAnnotations
864+
)()
865+
866+
async with Worker(
867+
env.client,
868+
task_queue=task_queue,
869+
nexus_service_handlers=[user_service_handler],
870+
nexus_task_executor=concurrent.futures.ThreadPoolExecutor(),
871+
):
872+
response = await service_client.start_operation(
873+
test_case.operation,
874+
dataclass_as_dict(test_case.input),
875+
test_case.headers,
876+
)
877+
test_case.check_response(response, with_service_definition=True)
878+
879+
assert not any(warnings), [w.message for w in warnings]
880+
881+
882+
def test_operation_without_type_annotations_without_service_definition_raises_validation_error():
883+
with pytest.raises(
884+
ValueError,
885+
match=r"has no input type.+has no output type",
886+
):
887+
service_handler(MyServiceHandlerWithOperationsWithoutTypeAnnotations)
888+
889+
842890
async def test_logger_uses_operation_context(env: WorkflowEnvironment, caplog: Any):
843891
task_queue = str(uuid.uuid4())
844892
service_name = MyService.__name__

0 commit comments

Comments
 (0)