Module adcp.substitution
Producer-side translation of AdCP universal macros in pixel URLs.
native mappings are a deliberate raw-token escape hatch for downstream ad
servers. They bypass percent-encoding, so this module validates every native
entry before translating the URL, including entries the URL does not use.
Functions
def encode_unreserved(raw: str) ‑> str-
Expand source code
def encode_unreserved(raw: str) -> str: """UTF-8 encode ``raw``, escaping every byte outside RFC 3986 unreserved. Percent escapes use uppercase hexadecimal. Unlike :func:`urllib.parse.quote`, this helper does not preserve ``/``. """ return "".join( chr(byte) if byte in _UNRESERVED_BYTES else f"%{byte:02X}" for byte in raw.encode("utf-8") )UTF-8 encode
raw, escaping every byte outside RFC 3986 unreserved.Percent escapes use uppercase hexadecimal. Unlike :func:
urllib.parse.quote, this helper does not preserve/. def translate_universal_macros(pixel_url: str, mapping: MacroMapping) ‑> TranslateUniversalMacrosResult-
Expand source code
def translate_universal_macros( pixel_url: str, mapping: MacroMapping, ) -> TranslateUniversalMacrosResult: """Translate universal macros in query-parameter values. ``ValueMacroMapping`` values are UTF-8 percent-encoded with :func:`encode_unreserved`; ``NativeMacroMapping`` values are inserted verbatim after the full mapping passes the control-character guard. If a parameter value contains any unmapped universal macro, that whole parameter is dropped. Query keys, the path, the fragment, and parameters without universal macros pass through byte-for-byte. Replacement is single-pass. Consent macros supplied through ``ValueMacroMapping`` are translated but reported in ``frozen_consent_macros`` because freezing impression-time consent at producer time can create a privacy defect. Callers should also inspect ``dropped_consent_macros`` and ``suspect_native_values`` before publishing a tracker. Raises: UniversalMacroTranslationError: A native mapping, used or unused, contains U+0000-U+001F or U+007F. No URL is emitted. TypeError: A mapping entry is not a supported typed mapping model. """ frozen_consent_macros: list[str] = [] frozen_seen: set[str] = set() suspect_native_values: list[str] = [] suspect_seen: set[str] = set() validated_mapping: dict[str, MacroMappingEntry] = {} # Validate the entire raw-token trust boundary before doing any URL work. # An unused unsafe entry must reject just like an entry present in the URL. for macro, entry in mapping.items(): if isinstance(entry, NativeMacroMapping): native = entry.native if _has_unsafe_native_character(native): raise UniversalMacroTranslationError(macro) validated_mapping[macro] = NativeMacroMapping(native=native) elif isinstance(entry, ValueMacroMapping): value = entry.value if macro in _CONSENT_MACROS: _append_once(frozen_consent_macros, frozen_seen, macro) if _NATIVE_TOKEN_SHAPE.fullmatch(value): _append_once(suspect_native_values, suspect_seen, macro) validated_mapping[macro] = ValueMacroMapping(value=value) else: raise TypeError( f"mapping entry for {macro!r} must be NativeMacroMapping " "or ValueMacroMapping" ) fragment_index = pixel_url.find("#") if fragment_index == -1: without_fragment = pixel_url fragment = "" else: without_fragment = pixel_url[:fragment_index] fragment = pixel_url[fragment_index:] query_index = without_fragment.find("?") if query_index == -1: return TranslateUniversalMacrosResult( url=pixel_url, dropped_params=[], unmapped_macros=[], dropped_consent_macros=[], frozen_consent_macros=frozen_consent_macros, suspect_native_values=suspect_native_values, ) base = without_fragment[:query_index] raw_query = without_fragment[query_index + 1 :] dropped_params: list[str] = [] unmapped_macros: list[str] = [] unmapped_seen: set[str] = set() dropped_consent_macros: list[str] = [] dropped_consent_seen: set[str] = set() output_parts: list[str] = [] for raw_param in raw_query.split("&"): key, separator, value = raw_param.partition("=") tokens = _UNIVERSAL_MACRO.findall(value) if not tokens: output_parts.append(raw_param) continue missing = [token for token in tokens if token not in validated_mapping] if missing: dropped_params.append(key) for macro in missing: _append_once(unmapped_macros, unmapped_seen, macro) if macro in _CONSENT_MACROS: _append_once( dropped_consent_macros, dropped_consent_seen, macro, ) continue def replace(match: re.Match[str]) -> str: macro = match.group(0) entry = validated_mapping[macro] if isinstance(entry, NativeMacroMapping): return entry.native if isinstance(entry, ValueMacroMapping): return encode_unreserved(entry.value) # The mapping-wide validation above makes this unreachable even # for mutable custom Mapping implementations under normal use. raise TypeError(f"unsupported mapping entry for {macro!r}") translated = _UNIVERSAL_MACRO.sub(replace, value) output_parts.append(f"{key}{separator}{translated}") new_query = "&".join(output_parts) url = f"{base}?{new_query}{fragment}" if new_query else f"{base}{fragment}" return TranslateUniversalMacrosResult( url=url, dropped_params=dropped_params, unmapped_macros=unmapped_macros, dropped_consent_macros=dropped_consent_macros, frozen_consent_macros=frozen_consent_macros, suspect_native_values=suspect_native_values, )Translate universal macros in query-parameter values.
ValueMacroMappingvalues are UTF-8 percent-encoded with :func:encode_unreserved();NativeMacroMappingvalues are inserted verbatim after the full mapping passes the control-character guard. If a parameter value contains any unmapped universal macro, that whole parameter is dropped. Query keys, the path, the fragment, and parameters without universal macros pass through byte-for-byte. Replacement is single-pass.Consent macros supplied through
ValueMacroMappingare translated but reported infrozen_consent_macrosbecause freezing impression-time consent at producer time can create a privacy defect. Callers should also inspectdropped_consent_macrosandsuspect_native_valuesbefore publishing a tracker.- Raises
- -----=
UniversalMacroTranslationError- A native mapping, used or unused, contains U+0000-U+001F or U+007F. No URL is emitted.
TypeError- A mapping entry is not a supported typed mapping model.
Classes
class NativeMacroMapping (native: str)-
Expand source code
@dataclass(frozen=True, slots=True) class NativeMacroMapping: """A downstream ad-server token inserted without percent-encoding.""" native: strA downstream ad-server token inserted without percent-encoding.
Instance variables
var native : str-
Expand source code
@dataclass(frozen=True, slots=True) class NativeMacroMapping: """A downstream ad-server token inserted without percent-encoding.""" native: str
class TranslateUniversalMacrosResult (url: str,
dropped_params: list[str],
unmapped_macros: list[str],
dropped_consent_macros: list[str],
frozen_consent_macros: list[str],
suspect_native_values: list[str])-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str]Translated URL and deterministic diagnostics.
dropped_paramspreserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order.Instance variables
var dropped_consent_macros : list[str]-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str] var dropped_params : list[str]-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str] var frozen_consent_macros : list[str]-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str] var suspect_native_values : list[str]-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str] var unmapped_macros : list[str]-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str] var url : str-
Expand source code
@dataclass(slots=True) class TranslateUniversalMacrosResult: """Translated URL and deterministic diagnostics. ``dropped_params`` preserves query-parameter occurrence order and may contain duplicate keys. All macro diagnostic lists are deduplicated. URL-scoped diagnostics use first query occurrence order; mapping-scoped diagnostics preserve mapping iteration order. """ url: str dropped_params: list[str] unmapped_macros: list[str] dropped_consent_macros: list[str] frozen_consent_macros: list[str] suspect_native_values: list[str]
class UniversalMacroTranslationError (macro: str)-
Expand source code
class UniversalMacroTranslationError(ValueError): """Typed rejection raised before an unsafe native token can be emitted.""" code: UniversalMacroTranslationErrorCode macro: str def __init__(self, macro: str) -> None: self.code = "unsafe_native_mapping" self.macro = macro super().__init__(f"native mapping for {macro!r} contains an unsafe control character")Typed rejection raised before an unsafe native token can be emitted.
Ancestors
- builtins.ValueError
- builtins.Exception
- builtins.BaseException
Class variables
var code : Literal['unsafe_native_mapping']var macro : str
class ValueMacroMapping (value: str)-
Expand source code
@dataclass(frozen=True, slots=True) class ValueMacroMapping: """A literal value encoded with the RFC 3986 unreserved whitelist.""" value: strA literal value encoded with the RFC 3986 unreserved whitelist.
Instance variables
var value : str-
Expand source code
@dataclass(frozen=True, slots=True) class ValueMacroMapping: """A literal value encoded with the RFC 3986 unreserved whitelist.""" value: str