Skip to content

Commit d651ff8

Browse files
committed
Tweaks from PR comments
1 parent a79da94 commit d651ff8

1 file changed

Lines changed: 49 additions & 48 deletions

File tree

‎temporalio/nexus/_link_conversion.py‎

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import urllib.parse
66
from dataclasses import dataclass
77
from enum import Enum
8+
from functools import cached_property
89
from typing import (
910
TYPE_CHECKING,
1011
Any,
@@ -35,39 +36,34 @@ class _LinkPath:
3536
"""The URL path shape for one link type.
3637
3738
Every link path is /namespaces/{namespace}/{keyword}/{id}/{run_id} with an optional trailing
38-
segment, so a link type is fully described by which keyword names it, what follows the run ID,
39-
and which proto field holds the ID.
39+
segment, so a link type is fully described by which keyword names it and what follows the
40+
run ID.
4041
"""
4142

4243
keyword: str
4344
tail: str | None
44-
id_field: str
4545
# A standalone Nexus operation need not have a run ID, so its path may carry an empty segment
4646
# there. The other link types always address a specific run.
4747
run_id_required: bool = True
4848

49+
@cached_property
50+
def regex(self) -> re.Pattern[str]:
51+
"""The pattern matching a URL path of this shape."""
52+
run_id = "[^/]+" if self.run_id_required else "[^/]*"
53+
tail = f"/{self.tail}" if self.tail else ""
54+
return re.compile(
55+
rf"^/namespaces/(?P<namespace>[^/]+)/{self.keyword}"
56+
rf"/(?P<id>[^/]+)/(?P<run_id>{run_id}){tail}$"
57+
)
58+
4959

5060
_LINK_PATHS: dict[_LinkType, _LinkPath] = {
51-
_LinkType.WORKFLOW_EVENT: _LinkPath("workflows", "history", "workflow_id"),
52-
_LinkType.WORKFLOW: _LinkPath("workflows", None, "workflow_id"),
61+
_LinkType.WORKFLOW_EVENT: _LinkPath("workflows", "history"),
62+
_LinkType.WORKFLOW: _LinkPath("workflows", None),
5363
_LinkType.NEXUS_OPERATION: _LinkPath(
54-
"nexus-operations", "details", "operation_id", run_id_required=False
64+
"nexus-operations", "details", run_id_required=False
5565
),
56-
_LinkType.ACTIVITY: _LinkPath("activities", "details", "activity_id"),
57-
}
58-
59-
60-
def _link_path_regex(path: _LinkPath) -> re.Pattern[str]:
61-
run_id = "[^/]+" if path.run_id_required else "[^/]*"
62-
tail = f"/{path.tail}" if path.tail else ""
63-
return re.compile(
64-
rf"^/namespaces/(?P<namespace>[^/]+)/{path.keyword}"
65-
rf"/(?P<id>[^/]+)/(?P<run_id>{run_id}){tail}$"
66-
)
67-
68-
69-
_LINK_PATH_REGEXES: dict[_LinkType, re.Pattern[str]] = {
70-
link_type: _link_path_regex(path) for link_type, path in _LINK_PATHS.items()
66+
_LinkType.ACTIVITY: _LinkPath("activities", "details"),
7167
}
7268

7369

@@ -277,10 +273,16 @@ def _temporal_nexus_url(path: str, *, query_params: str | None = "") -> str:
277273
return f"temporal://{urllib.parse.urlunparse(('', '', path, '', query_params or '', ''))}"
278274

279275

280-
def _parse_link_url(
281-
link: nexusrpc.Link, link_type: _LinkType
282-
) -> tuple[str, str, str, dict[str, list[str]]] | None:
283-
"""Return (namespace, id, run_id, query params), or None if the URL is not a link of this type.
276+
@dataclass(frozen=True)
277+
class _ParsedLinkUrl:
278+
namespace: str
279+
id: str
280+
run_id: str
281+
query_params: dict[str, list[str]]
282+
283+
284+
def _parse_link_url(link: nexusrpc.Link, link_type: _LinkType) -> _ParsedLinkUrl | None:
285+
"""Return the parts of a link URL, or None if the URL is not a link of this type.
284286
285287
The path is matched exactly, so the workflow and workflow-event shapes -- which differ only by
286288
the trailing /history -- cannot be mistaken for one another.
@@ -291,18 +293,18 @@ def _parse_link_url(
291293
f"Invalid Nexus link: {link}. Expected scheme {_URL_SCHEME!r}, got {url.scheme!r}"
292294
)
293295
return None
294-
regex = _LINK_PATH_REGEXES[link_type]
296+
regex = _LINK_PATHS[link_type].regex
295297
match = regex.match(url.path)
296298
if not match:
297299
logger.warning(
298300
f"Invalid Nexus link: {link}. Expected path to match {regex.pattern}"
299301
)
300302
return None
301-
return (
302-
urllib.parse.unquote(match.group("namespace")),
303-
urllib.parse.unquote(match.group("id")),
304-
urllib.parse.unquote(match.group("run_id")),
305-
urllib.parse.parse_qs(url.query),
303+
return _ParsedLinkUrl(
304+
namespace=urllib.parse.unquote(match.group("namespace")),
305+
id=urllib.parse.unquote(match.group("id")),
306+
run_id=urllib.parse.unquote(match.group("run_id")),
307+
query_params=urllib.parse.parse_qs(url.query),
306308
)
307309

308310

@@ -329,7 +331,7 @@ def nexus_link_to_workflow_event_link(
329331
parsed = _parse_link_url(link, _LinkType.WORKFLOW_EVENT)
330332
if parsed is None:
331333
return None
332-
namespace, workflow_id, run_id, query_params = parsed
334+
query_params = parsed.query_params
333335
try:
334336
request_id_ref = None
335337
event_ref = None
@@ -350,9 +352,9 @@ def nexus_link_to_workflow_event_link(
350352
return None
351353

352354
workflow_event_link = temporalio.api.common.v1.Link.WorkflowEvent(
353-
namespace=namespace,
354-
workflow_id=workflow_id,
355-
run_id=run_id,
355+
namespace=parsed.namespace,
356+
workflow_id=parsed.id,
357+
run_id=parsed.run_id,
356358
event_ref=event_ref,
357359
request_id_ref=request_id_ref,
358360
)
@@ -366,17 +368,18 @@ def nexus_link_to_workflow_link(
366368
parsed = _parse_link_url(link, _LinkType.WORKFLOW)
367369
if parsed is None:
368370
return None
369-
namespace, workflow_id, run_id, query_params = parsed
370371
try:
371-
reason = _optional_single_query_param(query_params, LINK_REASON_PARAM_NAME)
372+
reason = _optional_single_query_param(
373+
parsed.query_params, LINK_REASON_PARAM_NAME
374+
)
372375
except ValueError as err:
373376
logger.warning(f"Invalid Nexus link: {link}. {err}")
374377
return None
375378

376379
workflow_link = temporalio.api.common.v1.Link.Workflow(
377-
namespace=namespace,
378-
workflow_id=workflow_id,
379-
run_id=run_id,
380+
namespace=parsed.namespace,
381+
workflow_id=parsed.id,
382+
run_id=parsed.run_id,
380383
reason=reason,
381384
)
382385
return temporalio.api.common.v1.Link(workflow=workflow_link)
@@ -393,12 +396,11 @@ def nexus_link_to_nexus_operation_link(
393396
parsed = _parse_link_url(nexus_link, _LinkType.NEXUS_OPERATION)
394397
if parsed is None:
395398
return None
396-
namespace, operation_id, run_id, _ = parsed
397399
return temporalio.api.common.v1.Link(
398400
nexus_operation=temporalio.api.common.v1.Link.NexusOperation(
399-
namespace=namespace,
400-
operation_id=operation_id,
401-
run_id=run_id,
401+
namespace=parsed.namespace,
402+
operation_id=parsed.id,
403+
run_id=parsed.run_id,
402404
)
403405
)
404406

@@ -410,12 +412,11 @@ def nexus_link_to_activity_link(
410412
parsed = _parse_link_url(nexus_link, _LinkType.ACTIVITY)
411413
if parsed is None:
412414
return None
413-
namespace, activity_id, run_id, _ = parsed
414415
return temporalio.api.common.v1.Link(
415416
activity=temporalio.api.common.v1.Link.Activity(
416-
namespace=namespace,
417-
activity_id=activity_id,
418-
run_id=run_id,
417+
namespace=parsed.namespace,
418+
activity_id=parsed.id,
419+
run_id=parsed.run_id,
419420
)
420421
)
421422

0 commit comments

Comments
 (0)