Skip to content

Commit dedb389

Browse files
committed
Support pascal case in event type name received from server
1 parent 3a27fe8 commit dedb389

File tree

2 files changed

+63
-13
lines changed

2 files changed

+63
-13
lines changed

temporalio/nexus/_operation_context.py

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ def _workflow_event_to_nexus_link(
524524
_LINK_URL_PATH_REGEX = re.compile(
525525
r"^/namespaces/(?P<namespace>[^/]+)/workflows/(?P<workflow_id>[^/]+)/(?P<run_id>[^/]+)/history$"
526526
)
527+
LINK_EVENT_ID_PARAM_NAME = "eventID"
528+
LINK_EVENT_TYPE_PARAM_NAME = "eventType"
527529

528530

529531
def _nexus_link_to_workflow_event(
@@ -537,21 +539,10 @@ def _nexus_link_to_workflow_event(
537539
)
538540
return None
539541
try:
540-
query_params = urllib.parse.parse_qs(url.query)
541-
[reference_type] = query_params.get("referenceType", [])
542-
if reference_type != "EventReference":
543-
raise ValueError(
544-
f"Expected Nexus link URL query parameter referenceType to be EventReference but got: {reference_type}"
545-
)
546-
[event_type_name] = query_params.get("eventType", [])
547-
event_ref = temporalio.api.common.v1.Link.WorkflowEvent.EventReference(
548-
# TODO(nexus-prerelease): confirm that it is correct not to use event_id.
549-
# Should the proto say explicitly that it's optional or how it behaves when it's missing?
550-
event_type=temporalio.api.enums.v1.EventType.Value(event_type_name)
551-
)
542+
event_ref = _event_reference_from_query_params(urllib.parse.parse_qs(url.query))
552543
except ValueError as err:
553544
logger.warning(
554-
f"Failed to parse event type from Nexus link URL query parameters: {link} ({err})"
545+
f"Failed to parse event reference from Nexus link URL query parameters: {link} ({err})"
555546
)
556547
event_ref = None
557548

@@ -564,6 +555,55 @@ def _nexus_link_to_workflow_event(
564555
)
565556

566557

558+
def _event_reference_from_query_params(
559+
query_params: Mapping[str, list[str]],
560+
) -> temporalio.api.common.v1.Link.WorkflowEvent.EventReference:
561+
"""
562+
Return an EventReference from the query params or raise ValueError.
563+
"""
564+
[reference_type] = query_params.get("referenceType", [])
565+
if reference_type != "EventReference":
566+
raise ValueError(
567+
f"Expected Nexus link URL query parameter referenceType to be EventReference but got: {reference_type}"
568+
)
569+
# event type
570+
[raw_event_type_name] = query_params.get(LINK_EVENT_TYPE_PARAM_NAME, [None])
571+
if not raw_event_type_name:
572+
raise ValueError(f"query params do not contain event type: {query_params}")
573+
if raw_event_type_name.startswith("EVENT_TYPE_"):
574+
event_type_name = raw_event_type_name
575+
elif re.match("[A-Z][a-z]", raw_event_type_name):
576+
event_type_name = "EVENT_TYPE_" + _pascal_case_to_constant_case(
577+
raw_event_type_name
578+
)
579+
else:
580+
raise ValueError(f"Invalid event type name: {raw_event_type_name}")
581+
582+
# event id
583+
event_id = 0
584+
[raw_event_id] = query_params.get(LINK_EVENT_ID_PARAM_NAME, [None])
585+
if raw_event_id:
586+
try:
587+
event_id = int(raw_event_id)
588+
except ValueError:
589+
raise ValueError(f"Query params contain invalid event id: {raw_event_id}")
590+
591+
return temporalio.api.common.v1.Link.WorkflowEvent.EventReference(
592+
event_type=temporalio.api.enums.v1.EventType.Value(event_type_name),
593+
event_id=event_id,
594+
)
595+
596+
597+
def _pascal_case_to_constant_case(s: str) -> str:
598+
"""
599+
Convert a PascalCase string to CONSTANT_CASE.
600+
601+
>>> _pascal_case_to_constant_case("NexusOperationScheduled")
602+
"NEXUS_OPERATION_SCHEDULED"
603+
"""
604+
return re.sub(r"([^\b])([A-Z])", lambda m: "_".join(m.groups()), s).upper()
605+
606+
567607
class LoggerAdapter(logging.LoggerAdapter):
568608
"""Logger adapter that adds Nexus operation context information."""
569609

tests/nexus/test_workflow_caller.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import temporalio.api.operatorservice
3232
import temporalio.api.operatorservice.v1
3333
import temporalio.exceptions
34+
import temporalio.nexus._operation_context
3435
import temporalio.nexus._operation_handlers
3536
from temporalio import nexus, workflow
3637
from temporalio.client import (
@@ -1273,3 +1274,12 @@ async def test_workflow_run_operation_overloads(
12731274
if op != "no_param"
12741275
else OverloadTestValue(value=0)
12751276
)
1277+
1278+
1279+
def test_link_conversion_utilities():
1280+
pc2cc = temporalio.nexus._operation_context._pascal_case_to_constant_case
1281+
assert pc2cc("") == ""
1282+
assert pc2cc("A") == "A"
1283+
assert pc2cc("a") == "A"
1284+
assert pc2cc("AbCd") == "AB_CD"
1285+
assert pc2cc("NexusOperationScheduled") == "NEXUS_OPERATION_SCHEDULED"

0 commit comments

Comments
 (0)