-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathtranslation.py
More file actions
145 lines (118 loc) · 5.33 KB
/
Copy pathtranslation.py
File metadata and controls
145 lines (118 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Bidirectional, type-safe key translation between AdCP and upstream values.
Every translator-pattern adapter owes the framework two things: a thin
HTTP client (see :mod:`adcp.decisioning.upstream`) and a set of small
bidirectional key maps (AdCP ``"olv"`` ↔ upstream ``"video"``, AdCP
``"guaranteed"`` ↔ upstream ``"GUARANTEED_AT_PRICE"``, …).
:func:`create_translation_map` ships the map primitive so adapters
declare the mapping once and the framework projects it both ways.
Mirrors the JS ``createTranslationMap`` helper from
``@adcp/sdk@6.7`` (``src/lib/server/upstream-helpers.ts``).
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Generic, TypeVar
A = TypeVar("A")
U = TypeVar("U")
class TranslationMap(Generic[A, U]):
"""Reversible mapping between AdCP wire values and upstream platform values.
Construct via :func:`create_translation_map`. Both lookup methods
raise :class:`KeyError` for unknown keys unless a default is
configured. Use the ``has_*`` predicates to guard a lookup, or
pass ``default_adcp`` / ``default_upstream`` for graceful
fallback.
Example::
channel_map = create_translation_map({
"olv": "video",
"ctv": "ctv",
"display": "display",
"streaming_audio": "audio",
})
channel_map.to_upstream("olv") # "video"
channel_map.to_adcp("video") # "olv"
channel_map.has_adcp("olv") # True
channel_map.has_upstream("audio") # True
"""
__slots__ = ("_forward", "_reverse", "_default_adcp", "_default_upstream")
def __init__(
self,
adcp_to_upstream: Mapping[A, U],
*,
default_adcp: A | None = None,
default_upstream: U | None = None,
) -> None:
self._forward: dict[A, U] = dict(adcp_to_upstream)
self._default_adcp = default_adcp
self._default_upstream = default_upstream
# Build reverse map and detect collisions in the same pass. Two
# AdCP keys mapping to the same upstream value would silently
# overwrite without this check — fail loud at construction so
# the bug is caught at boot, not on a request.
reverse: dict[U, A] = {}
for adcp_key, upstream_key in self._forward.items():
if upstream_key in reverse:
raise ValueError(
f"translation collision: AdCP keys "
f"{reverse[upstream_key]!r} and {adcp_key!r} both map to "
f"upstream value {upstream_key!r}"
)
reverse[upstream_key] = adcp_key
self._reverse: dict[U, A] = reverse
def to_upstream(self, adcp_key: A) -> U:
"""Translate an AdCP wire value to the upstream platform value.
:raises KeyError: when ``adcp_key`` isn't in the map and no
``default_upstream`` was provided.
"""
if adcp_key in self._forward:
return self._forward[adcp_key]
if self._default_upstream is not None:
return self._default_upstream
raise KeyError(f"unknown AdCP key: {adcp_key!r}")
def to_adcp(self, upstream_key: U) -> A:
"""Translate an upstream platform value back to the AdCP wire value.
:raises KeyError: when ``upstream_key`` isn't in the map and
no ``default_adcp`` was provided.
"""
if upstream_key in self._reverse:
return self._reverse[upstream_key]
if self._default_adcp is not None:
return self._default_adcp
raise KeyError(f"unknown upstream key: {upstream_key!r}")
def has_adcp(self, value: object) -> bool:
"""True when ``value`` is a known AdCP-side key."""
return value in self._forward
def has_upstream(self, value: object) -> bool:
"""True when ``value`` is a known upstream-side key."""
return value in self._reverse
def create_translation_map(
adcp_to_upstream: Mapping[A, U],
*,
default_adcp: A | None = None,
default_upstream: U | None = None,
) -> TranslationMap[A, U]:
"""Build a bidirectional translation map from an AdCP→upstream record.
Keys on the left side are AdCP wire values; values on the right
are upstream platform values. Collisions in either direction (two
AdCP keys mapping to the same upstream value) are detected at
construction time and raise :class:`ValueError` — silent
overwrite would produce wrong-tenant routing in production.
:param adcp_to_upstream: ``{adcp_key: upstream_key}`` mapping.
:param default_adcp: Returned by :meth:`TranslationMap.to_adcp`
when the upstream key isn't in the map. ``None`` (default)
raises ``KeyError``.
:param default_upstream: Returned by
:meth:`TranslationMap.to_upstream` when the AdCP key isn't in
the map. ``None`` (default) raises ``KeyError``.
Example::
delivery_map = create_translation_map({
"guaranteed": "GUARANTEED_AT_PRICE",
"non_guaranteed": "STANDARD",
})
delivery_map.to_upstream("guaranteed") # "GUARANTEED_AT_PRICE"
delivery_map.to_adcp("STANDARD") # "non_guaranteed"
"""
return TranslationMap(
adcp_to_upstream,
default_adcp=default_adcp,
default_upstream=default_upstream,
)
__all__ = ["TranslationMap", "create_translation_map"]