|
2 | 2 |
|
3 | 3 | import dataclasses
|
4 | 4 | import logging
|
5 |
| -import re |
6 |
| -import urllib.parse |
7 | 5 | from contextvars import ContextVar
|
8 | 6 | from dataclasses import dataclass
|
9 | 7 | from datetime import timedelta
|
|
20 | 18 | overload,
|
21 | 19 | )
|
22 | 20 |
|
23 |
| -import nexusrpc.handler |
24 | 21 | from nexusrpc.handler import CancelOperationContext, StartOperationContext
|
25 | 22 | from typing_extensions import Concatenate
|
26 | 23 |
|
27 | 24 | import temporalio.api.common.v1
|
28 |
| -import temporalio.api.enums.v1 |
29 | 25 | import temporalio.client
|
30 | 26 | import temporalio.common
|
| 27 | +from temporalio.nexus import _link_conversion |
31 | 28 | from temporalio.nexus._token import WorkflowHandle
|
32 | 29 | from temporalio.types import (
|
33 | 30 | MethodAsyncNoParam,
|
@@ -147,16 +144,16 @@ def _get_workflow_event_links(
|
147 | 144 | ) -> list[temporalio.api.common.v1.Link.WorkflowEvent]:
|
148 | 145 | event_links = []
|
149 | 146 | for inbound_link in self.nexus_context.inbound_links:
|
150 |
| - if link := _nexus_link_to_workflow_event(inbound_link): |
| 147 | + if link := _link_conversion.nexus_link_to_workflow_event(inbound_link): |
151 | 148 | event_links.append(link)
|
152 | 149 | return event_links
|
153 | 150 |
|
154 | 151 | def _add_outbound_links(
|
155 | 152 | self, workflow_handle: temporalio.client.WorkflowHandle[Any, Any]
|
156 | 153 | ):
|
157 | 154 | try:
|
158 |
| - link = _workflow_event_to_nexus_link( |
159 |
| - _workflow_handle_to_workflow_execution_started_event_link( |
| 155 | + link = _link_conversion.workflow_event_to_nexus_link( |
| 156 | + _link_conversion.workflow_handle_to_workflow_execution_started_event_link( |
160 | 157 | workflow_handle
|
161 | 158 | )
|
162 | 159 | )
|
@@ -479,145 +476,6 @@ def set(self) -> None:
|
479 | 476 | _temporal_cancel_operation_context.set(self)
|
480 | 477 |
|
481 | 478 |
|
482 |
| -def _workflow_handle_to_workflow_execution_started_event_link( |
483 |
| - handle: temporalio.client.WorkflowHandle[Any, Any], |
484 |
| -) -> temporalio.api.common.v1.Link.WorkflowEvent: |
485 |
| - if handle.first_execution_run_id is None: |
486 |
| - raise ValueError( |
487 |
| - f"Workflow handle {handle} has no first execution run ID. " |
488 |
| - "Cannot create WorkflowExecutionStarted event link." |
489 |
| - ) |
490 |
| - return temporalio.api.common.v1.Link.WorkflowEvent( |
491 |
| - namespace=handle._client.namespace, |
492 |
| - workflow_id=handle.id, |
493 |
| - run_id=handle.first_execution_run_id, |
494 |
| - event_ref=temporalio.api.common.v1.Link.WorkflowEvent.EventReference( |
495 |
| - event_id=1, |
496 |
| - event_type=temporalio.api.enums.v1.EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, |
497 |
| - ), |
498 |
| - # TODO(nexus-prerelease): RequestIdReference? |
499 |
| - ) |
500 |
| - |
501 |
| - |
502 |
| -_LINK_URL_PATH_REGEX = re.compile( |
503 |
| - r"^/namespaces/(?P<namespace>[^/]+)/workflows/(?P<workflow_id>[^/]+)/(?P<run_id>[^/]+)/history$" |
504 |
| -) |
505 |
| -LINK_EVENT_ID_PARAM_NAME = "eventID" |
506 |
| -LINK_EVENT_TYPE_PARAM_NAME = "eventType" |
507 |
| - |
508 |
| - |
509 |
| -def _workflow_event_to_nexus_link( |
510 |
| - workflow_event: temporalio.api.common.v1.Link.WorkflowEvent, |
511 |
| -) -> nexusrpc.Link: |
512 |
| - scheme = "temporal" |
513 |
| - namespace = urllib.parse.quote(workflow_event.namespace) |
514 |
| - workflow_id = urllib.parse.quote(workflow_event.workflow_id) |
515 |
| - run_id = urllib.parse.quote(workflow_event.run_id) |
516 |
| - path = f"/namespaces/{namespace}/workflows/{workflow_id}/{run_id}/history" |
517 |
| - query_params = _query_params_from_event_reference(workflow_event.event_ref) |
518 |
| - return nexusrpc.Link( |
519 |
| - url=urllib.parse.urlunparse((scheme, "", path, "", query_params, "")), |
520 |
| - type=workflow_event.DESCRIPTOR.full_name, |
521 |
| - ) |
522 |
| - |
523 |
| - |
524 |
| -def _nexus_link_to_workflow_event( |
525 |
| - link: nexusrpc.Link, |
526 |
| -) -> Optional[temporalio.api.common.v1.Link.WorkflowEvent]: |
527 |
| - url = urllib.parse.urlparse(link.url) |
528 |
| - match = _LINK_URL_PATH_REGEX.match(url.path) |
529 |
| - if not match: |
530 |
| - logger.warning( |
531 |
| - f"Invalid Nexus link: {link}. Expected path to match {_LINK_URL_PATH_REGEX.pattern}" |
532 |
| - ) |
533 |
| - return None |
534 |
| - try: |
535 |
| - event_ref = _event_reference_from_query_params(url.query) |
536 |
| - except ValueError as err: |
537 |
| - logger.warning( |
538 |
| - f"Failed to parse event reference from Nexus link URL query parameters: {link} ({err})" |
539 |
| - ) |
540 |
| - return None |
541 |
| - |
542 |
| - groups = match.groupdict() |
543 |
| - return temporalio.api.common.v1.Link.WorkflowEvent( |
544 |
| - namespace=urllib.parse.unquote(groups["namespace"]), |
545 |
| - workflow_id=urllib.parse.unquote(groups["workflow_id"]), |
546 |
| - run_id=urllib.parse.unquote(groups["run_id"]), |
547 |
| - event_ref=event_ref, |
548 |
| - ) |
549 |
| - |
550 |
| - |
551 |
| -def _query_params_from_event_reference( |
552 |
| - event_ref: temporalio.api.common.v1.Link.WorkflowEvent.EventReference, |
553 |
| -) -> str: |
554 |
| - event_type_name = temporalio.api.enums.v1.EventType.Name(event_ref.event_type) |
555 |
| - if event_type_name.startswith("EVENT_TYPE_"): |
556 |
| - event_type_name = _constant_case_to_pascal_case( |
557 |
| - event_type_name.removeprefix("EVENT_TYPE_") |
558 |
| - ) |
559 |
| - return urllib.parse.urlencode( |
560 |
| - {"eventType": event_type_name, "referenceType": "EventReference"} |
561 |
| - ) |
562 |
| - |
563 |
| - |
564 |
| -def _event_reference_from_query_params( |
565 |
| - raw_query_params: str, |
566 |
| -) -> temporalio.api.common.v1.Link.WorkflowEvent.EventReference: |
567 |
| - """Return an EventReference from the query params or raise ValueError.""" |
568 |
| - query_params = urllib.parse.parse_qs(raw_query_params) |
569 |
| - |
570 |
| - [reference_type] = query_params.get("referenceType") or [""] |
571 |
| - if reference_type != "EventReference": |
572 |
| - raise ValueError( |
573 |
| - f"Expected Nexus link URL query parameter referenceType to be EventReference but got: {reference_type}" |
574 |
| - ) |
575 |
| - # event type |
576 |
| - [raw_event_type_name] = query_params.get(LINK_EVENT_TYPE_PARAM_NAME) or [""] |
577 |
| - if not raw_event_type_name: |
578 |
| - raise ValueError(f"query params do not contain event type: {query_params}") |
579 |
| - if raw_event_type_name.startswith("EVENT_TYPE_"): |
580 |
| - event_type_name = raw_event_type_name |
581 |
| - elif re.match("[A-Z][a-z]", raw_event_type_name): |
582 |
| - event_type_name = "EVENT_TYPE_" + _pascal_case_to_constant_case( |
583 |
| - raw_event_type_name |
584 |
| - ) |
585 |
| - else: |
586 |
| - raise ValueError(f"Invalid event type name: {raw_event_type_name}") |
587 |
| - |
588 |
| - # event id |
589 |
| - event_id = 0 |
590 |
| - [raw_event_id] = query_params.get(LINK_EVENT_ID_PARAM_NAME) or [""] |
591 |
| - if raw_event_id: |
592 |
| - try: |
593 |
| - event_id = int(raw_event_id) |
594 |
| - except ValueError: |
595 |
| - raise ValueError(f"Query params contain invalid event id: {raw_event_id}") |
596 |
| - |
597 |
| - return temporalio.api.common.v1.Link.WorkflowEvent.EventReference( |
598 |
| - event_type=temporalio.api.enums.v1.EventType.Value(event_type_name), |
599 |
| - event_id=event_id, |
600 |
| - ) |
601 |
| - |
602 |
| - |
603 |
| -def _constant_case_to_pascal_case(s: str) -> str: |
604 |
| - """Convert a CONSTANT_CASE string to PascalCase. |
605 |
| -
|
606 |
| - >>> _constant_case_to_pascal_case("NEXUS_OPERATION_SCHEDULED") |
607 |
| - "NexusOperationScheduled" |
608 |
| - """ |
609 |
| - return re.sub(r"(\b|_)([a-z])", lambda m: m.groups()[1].upper(), s.lower()) |
610 |
| - |
611 |
| - |
612 |
| -def _pascal_case_to_constant_case(s: str) -> str: |
613 |
| - """Convert a PascalCase string to CONSTANT_CASE. |
614 |
| -
|
615 |
| - >>> _pascal_case_to_constant_case("NexusOperationScheduled") |
616 |
| - "NEXUS_OPERATION_SCHEDULED" |
617 |
| - """ |
618 |
| - return re.sub(r"([^\b])([A-Z])", lambda m: "_".join(m.groups()), s).upper() |
619 |
| - |
620 |
| - |
621 | 479 | class LoggerAdapter(logging.LoggerAdapter):
|
622 | 480 | """Logger adapter that adds Nexus operation context information."""
|
623 | 481 |
|
|
0 commit comments