Skip to content

PricingOption RootModel doesn't proxy attribute access — breaks intuitive iteration #145

@bokelley

Description

@bokelley

Summary

PricingOption is implemented as a RootModel (discriminated union), which means the obvious pattern for iterating a product's pricing options fails:

from adcp.types import Product

product: Product = ...
for po in product.pricing_options:
    print(po.pricing_option_id)  # AttributeError!
    print(po.root.pricing_option_id)  # works, but non-obvious

This is the single biggest DX friction point when building a sales agent with the library.

Steps to Reproduce

from adcp.types import (
    Product, FlatRatePricingOption, FormatId,
    DeliveryMeasurement, DeliveryType, PublisherPropertiesAll,
)

product = Product(
    product_id="test",
    name="Test",
    description="Test product",
    publisher_properties=[
        PublisherPropertiesAll(publisher_domain="example.com", selection_type="all")
    ],
    format_ids=[FormatId(agent_url="http://localhost", id="display_300x250")],
    delivery_type=DeliveryType.guaranteed,
    delivery_measurement=DeliveryMeasurement(provider="Test"),
    pricing_options=[
        FlatRatePricingOption(
            pricing_option_id="flat_1",
            pricing_model="flat_rate",
            fixed_price=100.0,
            currency="USD",
        )
    ],
)

po = product.pricing_options[0]
print(type(po))
# <class 'adcp.types.generated_poc.core.pricing_option.PricingOption'>

print(po.pricing_option_id)
# AttributeError: 'PricingOption' object has no attribute 'pricing_option_id'

print(po.root.pricing_option_id)
# 'flat_1' — works but requires knowing the RootModel internals

Expected Behavior

po.pricing_option_id should work directly. The RootModel wrapper should be transparent to consumers.

Suggested Fix

Add __getattr__ delegation to the PricingOption RootModel:

class PricingOption(RootModel[CpmPricingOption | FlatRatePricingOption | ...]):
    def __getattr__(self, name: str):
        return getattr(self.root, name)

This is backward-compatible — existing .root access still works, but direct attribute access also works.

Relationship to Prior Issues

Impact

Anyone building a sales agent needs to iterate pricing options to validate create_media_buy requests. The .root workaround is non-obvious and not documented in any example.

Environment

  • adcp: 3.6.0
  • Python: 3.12
  • Pydantic: 2.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions