Module adcp.decisioning.media_buy_store
create_media_buy_store() — opt-in framework wiring that gates
targeting_overlay echo on the seller's declared specialisms.
The seller's spec contract on
schemas/cache/<v>/media-buy/get-media-buys-response.json requires
that sellers claiming the property-lists or collection-lists
specialism echo persisted property_list / collection_list
references inside the packages[].targeting_overlay they return on
get_media_buys. This factory lets adopters wire that contract once,
at the framework boundary, instead of every adapter persisting +
echoing by hand.
Mirrors the JS-side createMediaBuyStore from @adcp/sdk@6.7
(commit dda2a77e) with one shape change: the JS factory builds the
persistence itself on top of an AdcpStateStore. The Python SDK
doesn't ship an AdcpStateStore Protocol, so adopters supply their
own :class:MediaBuyStore implementation; this factory wraps it with
specialism-aware gating. Adopters who want a reference impl can crib
the in-memory pattern from the test suite.
Usage::
from adcp.decisioning import create_media_buy_store
platform.media_buy_store = create_media_buy_store(
adopter_store, capabilities=platform.capabilities,
)
Behavior:
- Seller claims
property-listsorcollection-lists→ every method delegates to the adopter store. - Seller claims neither →
persist_from_createandmerge_from_updateare no-ops;backfillreturns the response unchanged. The adopter store is never invoked.
The wrapper is always a fresh object so adopters can reason about identity at the assignment site without aliasing surprises.
Functions
def create_media_buy_store(adopter_store: MediaBuyStore,
*,
capabilities: DecisioningCapabilities) ‑> MediaBuyStore-
Expand source code
def create_media_buy_store( adopter_store: MediaBuyStore, *, capabilities: DecisioningCapabilities, ) -> MediaBuyStore: """Wrap an adopter :class:`MediaBuyStore` with specialism-aware ``targeting_overlay`` echo gating. :param adopter_store: The persistence + echo implementation. Must satisfy the :class:`MediaBuyStore` Protocol — three methods (``persist_from_create``, ``merge_from_update``, ``backfill``) sync or async. :param capabilities: The seller's :class:`DecisioningCapabilities`. Read once at construction to decide whether the wrapper delegates or no-ops; not re-read per request, so adopters who mutate ``capabilities.specialisms`` after building the store won't see the change reflected. Build-time decision matches the boot-time validation pattern used elsewhere (``validate_platform``). :returns: A :class:`MediaBuyStore` wrapper. When ``capabilities.specialisms`` intersects ``{property-lists, collection-lists}``, every method delegates to ``adopter_store``. Otherwise every method is a no-op pass-through and the adopter store is never invoked. The returned object is always distinct from ``adopter_store`` — even on the no-op path — so adopters can reason about identity at the assignment site (``platform.media_buy_store = ...``) without aliasing the underlying persistence layer. """ # ``capabilities.specialisms`` is ``list[Specialism | str]`` per #479 # — spec-known slugs are coerced to ``Specialism`` enum members at # construction; novel/typo slugs pass through as strings. Extract the # slug uniformly before set intersection. declared_slugs = { entry.value if hasattr(entry, "value") else entry for entry in capabilities.specialisms } claimed = declared_slugs & _OVERLAY_ECHO_SPECIALISMS if claimed: return _ActiveMediaBuyStore(adopter_store) return _NoopMediaBuyStore(adopter_store)Wrap an adopter :class:
MediaBuyStorewith specialism-awaretargeting_overlayecho gating.:param adopter_store: The persistence + echo implementation. Must satisfy the :class:
MediaBuyStoreProtocol — three methods (persist_from_create,merge_from_update,backfill) sync or async. :param capabilities: The seller's :class:DecisioningCapabilities. Read once at construction to decide whether the wrapper delegates or no-ops; not re-read per request, so adopters who mutatecapabilities.specialismsafter building the store won't see the change reflected. Build-time decision matches the boot-time validation pattern used elsewhere (validate_platform).:returns: A :class:
MediaBuyStorewrapper. Whencapabilities.specialismsintersects{property-lists, collection-lists}, every method delegates toadopter_store. Otherwise every method is a no-op pass-through and the adopter store is never invoked.The returned object is always distinct from
adopter_store— even on the no-op path — so adopters can reason about identity at the assignment site (platform.media_buy_store = ...) without aliasing the underlying persistence layer.
Classes
class MediaBuyStore (*args, **kwargs)-
Expand source code
@runtime_checkable class MediaBuyStore(Protocol): """Adopter-supplied persistence + echo for ``targeting_overlay``. Three methods cover the full lifecycle of a per-package overlay: * :meth:`persist_from_create` records overlays from a successful ``create_media_buy``, joining the request's per-package overlay with the response's seller-assigned ``package_id`` (or ``buyer_ref`` when present). * :meth:`merge_from_update` applies ``update_media_buy`` patches with deep-merge semantics: keys absent from the patch keep prior values, keys present with non-null values replace, keys present and ``None`` clear. * :meth:`backfill` fills in missing ``packages[].targeting_overlay`` on a ``get_media_buys`` response from the persisted store. Mutates the response in place; packages the seller already echoed are left untouched. Methods may be sync or async — the wrapper awaits at call time. """ def persist_from_create( self, account_id: str, request: Any, result: Any, ) -> Any: ... def merge_from_update( self, account_id: str, media_buy_id: str, patch: Any, ) -> Any: ... def backfill(self, account_id: str, result: Any) -> Any: ...Adopter-supplied persistence + echo for
targeting_overlay.Three methods cover the full lifecycle of a per-package overlay:
- :meth:
persist_from_createrecords overlays from a successfulcreate_media_buy, joining the request's per-package overlay with the response's seller-assignedpackage_id(orbuyer_refwhen present). - :meth:
merge_from_updateappliesupdate_media_buypatches with deep-merge semantics: keys absent from the patch keep prior values, keys present with non-null values replace, keys present andNoneclear. - :meth:
backfillfills in missingpackages[].targeting_overlayon aget_media_buysresponse from the persisted store. Mutates the response in place; packages the seller already echoed are left untouched.
Methods may be sync or async — the wrapper awaits at call time.
Ancestors
- typing.Protocol
- typing.Generic
Methods
def backfill(self, account_id: str, result: Any) ‑> Any-
Expand source code
def backfill(self, account_id: str, result: Any) -> Any: ... def merge_from_update(self, account_id: str, media_buy_id: str, patch: Any) ‑> Any-
Expand source code
def merge_from_update( self, account_id: str, media_buy_id: str, patch: Any, ) -> Any: ... def persist_from_create(self, account_id: str, request: Any, result: Any) ‑> Any-
Expand source code
def persist_from_create( self, account_id: str, request: Any, result: Any, ) -> Any: ...
- :meth: