Skip to content

Commit 520aecf

Browse files
committed
Test service interface/impl with/without name
1 parent d43f29a commit 520aecf

File tree

2 files changed

+139
-8
lines changed

2 files changed

+139
-8
lines changed

temporalio/workflow.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5159,19 +5159,21 @@ def __init__(
51595159
endpoint: str,
51605160
schedule_to_close_timeout: Optional[timedelta] = None,
51615161
) -> None:
5162-
# TODO(dan): getattr here is a temporary hack. We need to be able to attach
5163-
# metadata (such as the service name) to service interfaces (perhaps setting a
5164-
# __metadata__ attribute?), as well as service implementations (they have a
5165-
# decorator that sets NexusServiceDefinition), and ensure all semantics are
5166-
# completely clear and consistent when using e.g. a service impl without an
5167-
# interface, or impl and interface with clashing names, or one without an
5168-
# explicitly-set name and the other with, etc.
5162+
# If service is not a str, then it must be a service interface or implementation
5163+
# class.
51695164
if isinstance(service, str):
51705165
self._service_name = service
51715166
elif hasattr(service, "__nexus_service__"):
51725167
self._service_name = service.__nexus_service__.name
5168+
elif hasattr(service, "__nexus_service_metadata__"):
5169+
self._service_name = service.__nexus_service_metadata__.name
51735170
else:
5174-
self._service_name = service.__name__
5171+
raise ValueError(
5172+
f"`service` may be a name (str), or a class decorated with either "
5173+
f"@nexusrpc.handler.service or @nexusrpc.interface.service. "
5174+
f"Invalid service type: {type(service)}"
5175+
)
5176+
print(f"🌈 NexusClient using service name: {self._service_name}")
51755177
self._endpoint = endpoint
51765178
self._schedule_to_close_timeout = schedule_to_close_timeout
51775179

tests/worker/test_nexus.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,135 @@ async def _start_wf_and_nexus_op(
459459
return caller_wf_handle, handler_wf_handle
460460

461461

462+
@nexusrpc.interface.service
463+
class MyServiceInterfaceWithoutNameOverride:
464+
my_op: nexusrpc.interface.Operation[None, str]
465+
466+
467+
@nexusrpc.interface.service(name="my-service-interface-🌈")
468+
class MyServiceInterfaceWithNameOverride:
469+
my_op: nexusrpc.interface.Operation[None, str]
470+
471+
472+
@nexusrpc.handler.service(interface=MyServiceInterfaceWithoutNameOverride)
473+
class MyServiceImplWithoutNameOverride:
474+
@nexusrpc.handler.sync_operation
475+
async def my_op(
476+
self, input: None, options: nexusrpc.handler.StartOperationOptions
477+
) -> str:
478+
return "from service impl with interface"
479+
480+
481+
@nexusrpc.handler.service(name="my-service-impl-🌈")
482+
class MyServiceImplWithNameOverride:
483+
@nexusrpc.handler.sync_operation
484+
async def my_op(
485+
self, input: None, options: nexusrpc.handler.StartOperationOptions
486+
) -> str:
487+
return "from service impl with name override"
488+
489+
490+
class NameOverride(StrEnum):
491+
YES = "yes"
492+
NO = "no"
493+
494+
495+
@workflow.defn
496+
class MyServiceInterfaceAndImplCallerWorkflow:
497+
@workflow.run
498+
async def run(
499+
self,
500+
caller_reference: CallerReference,
501+
name_override: NameOverride,
502+
task_queue: str,
503+
) -> str:
504+
service_cls = {
505+
(
506+
CallerReference.INTERFACE,
507+
NameOverride.YES,
508+
): MyServiceInterfaceWithNameOverride,
509+
(
510+
CallerReference.INTERFACE,
511+
NameOverride.NO,
512+
): MyServiceInterfaceWithoutNameOverride,
513+
(
514+
CallerReference.IMPLEMENTATION,
515+
NameOverride.YES,
516+
): MyServiceImplWithNameOverride,
517+
(
518+
CallerReference.IMPLEMENTATION,
519+
NameOverride.NO,
520+
): MyServiceImplWithoutNameOverride,
521+
}[caller_reference, name_override]
522+
nexus_client = workflow.NexusClient(
523+
service=service_cls,
524+
endpoint=make_nexus_endpoint_name(task_queue),
525+
)
526+
return await nexus_client.execute_operation(service_cls.my_op, None)
527+
528+
529+
# TODO(dan): make it possible to refer to an impl that doesn't itself refer to an interface in its decorator
530+
# TODO(dan): allow decorator to be used without calling it
531+
# TODO(dan): check missing decorator behavior
532+
533+
534+
async def test_service_interface_and_implementation_names(client: Client):
535+
task_queue = str(uuid.uuid4())
536+
async with Worker(
537+
client,
538+
nexus_services=[
539+
MyServiceImplWithoutNameOverride(),
540+
MyServiceImplWithNameOverride(),
541+
],
542+
workflows=[MyServiceInterfaceAndImplCallerWorkflow],
543+
task_queue=task_queue,
544+
workflow_runner=UnsandboxedWorkflowRunner(),
545+
):
546+
await create_nexus_endpoint(task_queue, client)
547+
assert (
548+
await client.execute_workflow(
549+
MyServiceInterfaceAndImplCallerWorkflow.run,
550+
args=(CallerReference.INTERFACE, NameOverride.YES, task_queue),
551+
id=str(uuid.uuid4()),
552+
task_queue=task_queue,
553+
)
554+
== "from service interface with name override"
555+
)
556+
assert (
557+
await client.execute_workflow(
558+
MyServiceInterfaceAndImplCallerWorkflow.run,
559+
args=(CallerReference.INTERFACE, NameOverride.NO, task_queue),
560+
id=str(uuid.uuid4()),
561+
task_queue=task_queue,
562+
)
563+
== "from service interface without name override"
564+
)
565+
assert (
566+
await client.execute_workflow(
567+
MyServiceInterfaceAndImplCallerWorkflow.run,
568+
args=(CallerReference.IMPLEMENTATION, NameOverride.YES, task_queue),
569+
id=str(uuid.uuid4()),
570+
task_queue=task_queue,
571+
)
572+
== "from service impl with interface and name override"
573+
)
574+
assert (
575+
await client.execute_workflow(
576+
MyServiceInterfaceAndImplCallerWorkflow.run,
577+
args=(CallerReference.IMPLEMENTATION, NameOverride.NO, task_queue),
578+
id=str(uuid.uuid4()),
579+
task_queue=task_queue,
580+
)
581+
== "from service impl with interface but without name override"
582+
)
583+
584+
585+
# TODO(dan): test invalid service interface implementations
586+
# TODO(dan): test service impls and interfaces with and without names, conflicting names, etc.
587+
# TODO(dan): test impl used without interface
588+
# TODO(dan): test empty service impl/interface names
589+
590+
462591
def make_nexus_endpoint_name(task_queue: str) -> str:
463592
# Create endpoints for different task queues without name collisions.
464593
return f"nexus-endpoint-{task_queue}"

0 commit comments

Comments
 (0)