Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

## [4.0.0b1] — 2026-04-20

First 4.0 beta. See [MIGRATION_v3_to_v4.md](MIGRATION_v3_to_v4.md) for upgrade
instructions and before/after examples.

### Breaking changes

* **Removed types (no replacement stubs):** `BrandManifest`, `FormatCategory`,
`DeliverTo`, `PromotedProducts`, `PromotedOfferings`, `Pricing`,
`PackageStatus`. These were removed upstream from the AdCP spec. Inline any
dict payloads, or use the spec-current replacements (`BrandReference` for
`BrandManifest`; `MediaBuyStatus` for the former `PackageStatus`).
* **`ResolvedBrand.brand_manifest` field removed.** Use `ResolvedBrand.brand`.
The cross-populate validator that mirrored `brand` ↔ `brand_manifest` is gone.
* **`CreateMediaBuyRequest.brand_manifest` → `brand`**, matching the spec. The
request now takes a `BrandReference`, not an inline brand manifest dict.
* **Numbered discriminated-union classes renumbered.** `Assets5…Assets14`
from 3.x now correspond to higher-numbered variants (`Assets57…Assets149`
depending on the response). Import via semantic aliases from `adcp.types`
(e.g., `CreateMediaBuySuccessResponse`) instead of numbered classes. Raw
`Assets*` imports are unsupported — they are code-generation artifacts and
will shift again.

### Non-breaking

* `__version__` is now sourced from installed distribution metadata
(`importlib.metadata.version("adcp")`) so it always matches `pyproject.toml`.
* Added top-level re-exports: `TargetingOverlay`, `AdvertiserIndustry`,
`KellerType`, `BrandSource`. Any generated type not on the top-level
surface is available from `adcp.types` — `adcp.types.generated_poc.*`
is internal and not supported for direct import.

## [3.12.0](https://github.com/adcontextprotocol/adcp-client-python/compare/v3.11.0...v3.12.0) (2026-04-16)


Expand Down
219 changes: 219 additions & 0 deletions MIGRATION_v3_to_v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Migrating from v3.x to v4.0

4.0 realigns the SDK surface with the current AdCP spec. The schema redesign
removed several types and renamed fields; this guide lists each change with
before/after code.

## Audit your exposure first

```bash
grep -rnE "BrandManifest|FormatCategory|DeliverTo|PromotedProducts|PromotedOfferings|PackageStatus|from adcp import Pricing|\.brand_manifest|adcp\.types\.generated_poc" src/
```

Each match is either an import that will now raise `ImportError`, an attribute
access that will raise `AttributeError`, or a coupling to a private module.

Update your dependency pin:

```toml
# pyproject.toml
[project]
dependencies = [
"adcp>=4.0.0b1,<5",
]
```

## Removed types

The following types were removed from the AdCP spec and have no replacement
stubs in the SDK. Imports will fail at runtime with `ImportError`.

| Removed | Replacement |
| --- | --- |
| `BrandManifest` | `BrandReference(domain=...)` when constructing requests; inline dict when reading registry `brand` payloads |
| `FormatCategory` | Removed without replacement (previously an enum, now inferred from format metadata) |
| `DeliverTo` | Removed — use `publisher_properties` on the request |
| `PromotedProducts` / `PromotedOfferings` | Removed — use the `offerings` field shape from the current spec |
| `Pricing` | Use the discriminated `*PricingOption` types (e.g. `CpmFixedRatePricingOption`) |
| `PackageStatus` | Package status is now carried by `MediaBuyStatus`; per-package status was removed |

### `BrandManifest` → `BrandReference`

**Before (v3.x):**
```python
from adcp import CreateMediaBuyRequest, BrandManifest

request = CreateMediaBuyRequest(
brand_manifest=BrandManifest(
name="Coffee Co",
brand_url="https://coffeeco.com",
logo_url="https://coffeeco.com/logo.png",
),
packages=[...],
publisher_properties=...,
)
```

**After (v4.0):**
```python
from adcp import CreateMediaBuyRequest, BrandReference

request = CreateMediaBuyRequest(
brand=BrandReference(domain="coffeeco.com"),
packages=[...],
publisher_properties=...,
)
```

The spec now resolves brand identity from `/.well-known/brand.json` at the
supplied domain, or from the AdCP registry. The SDK no longer models the
brand manifest as a typed class.

### `FormatCategory` → removed

The spec no longer models category as a separate enum. Read category
information from `Format` metadata instead.

```python
# Before
from adcp import FormatCategory
if fmt.category == FormatCategory.video: ...

# After — category info lives on Format itself
if fmt.type == "video": ... # or fmt.channel, depending on what you were matching
```

### `DeliverTo` → `publisher_properties`

```python
# Before
request = CreateMediaBuyRequest(deliver_to=DeliverTo(...), ...)

# After
request = CreateMediaBuyRequest(
publisher_properties=PublisherPropertiesAll(selection_type="all"),
...,
)
```

### `PromotedProducts` / `PromotedOfferings` → `offerings`

```python
# Before
request.promoted_offerings = PromotedOfferings(...)

# After — pass the spec-current offerings shape as a dict/model
request.offerings = [...]
```

### `Pricing` → discriminated `*PricingOption`

```python
# Before
from adcp import Pricing
pricing = Pricing(model="cpm", rate=5.0, currency="USD")

# After — each pricing model has its own class
from adcp import CpmFixedRatePricingOption
pricing = CpmFixedRatePricingOption(
pricing_option_id="cpm_usd",
pricing_model="cpm",
is_fixed=True,
currency="USD",
rate=5.0,
)
```

### `PackageStatus` → `MediaBuyStatus`

Per-package status was removed. Status now lives on the media buy.

```python
# Before
if package.status == PackageStatus.active: ...

# After
if media_buy.status == MediaBuyStatus.active: ...
```

### `ResolvedBrand.brand_manifest` field removed

`RegistryClient.lookup_brand()` returns a `ResolvedBrand` whose
`brand_manifest` field and cross-populate validator are gone.

**Before (v3.x):**
```python
result = await registry.lookup_brand("nike.com")
manifest = result.brand_manifest # Either `brand` or `brand_manifest` worked
```

**After (v4.0):**
```python
result = await registry.lookup_brand("nike.com")
manifest = result.brand # Only `.brand`
```

## Numbered discriminated-union classes shifted

`datamodel-code-generator` numbers variant classes in the order they appear in
the upstream `oneOf`. When the spec reorders variants, the numbers shift.
Example: `Assets5`…`Assets14` in 3.x now correspond to higher-numbered
variants (`Assets57`…`Assets149`) across different response modules.

**Don't** import numbered classes directly:

```python
# Fragile — will break on the next spec revision:
from adcp.types.generated_poc.bundled.creative.build_creative_response import Assets9
```

**Do** import the semantic alias from `adcp.types`:

```python
from adcp.types import CreateMediaBuySuccessResponse, BuildCreativeSuccessResponse
```

Aliases for all discriminated-union success/error variants live in
`adcp/types/aliases.py`. If a variant you need isn't aliased, file an issue —
aliasing is the supported path; direct `Assets*` imports aren't.

## Public vs. internal imports

`adcp.types.generated_poc.*` is internal. Generated module paths and class
names can change with every schema regeneration. Import from `adcp.types`
instead.

**Before:**
```python
from adcp.types.generated_poc.core.context import ContextObject
from adcp.types.generated_poc.core.targeting import TargetingOverlay
```

**After:**
```python
from adcp import ContextObject, TargetingOverlay
# or
from adcp.types import ContextObject, TargetingOverlay
```

4.0 adds top-level re-exports for `TargetingOverlay`, `AdvertiserIndustry`,
`KellerType`, and `BrandSource`. If you need a type that isn't on the
top-level surface, check `from adcp.types import X` first — most generated
types are re-exported there.

## `__version__` now reflects the installed distribution

`adcp.__version__` now reads from `importlib.metadata.version("adcp")`
instead of a hardcoded constant, so it always matches `pyproject.toml`. If
you're running from a source checkout without `pip install -e .`, you'll see
`"0.0.0+unknown"` — install the package (or run `pip install -e .` in CI) to
get the real version. If your test suite asserts on `__version__`, it will
need the same install step.

## Watch for silent Pydantic field drops

`CreateMediaBuyRequest` (and most other request models) accept extra fields
without erroring. Passing `brand_manifest=...` by keyword after upgrading
won't raise at construction — the field is silently dropped, and you'll see
the failure as a server-side rejection or missing `brand` at execution time.
The `grep` in the audit section above catches these.
37 changes: 17 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ Full type hints with Pydantic validation and auto-generated types from the AdCP
```python
from adcp import (
GetProductsRequest,
BrandManifest,
BrandReference,
Package,
CpmFixedRatePricingOption,
MediaBuyStatus,
Expand Down Expand Up @@ -297,11 +297,13 @@ if media_buy.status == MediaBuyStatus.active:
```

**Exported from main package:**
- **Core domain types**: `BrandManifest`, `Creative`, `CreativeManifest`, `MediaBuy`, `Package`
- **Status enums**: `CreativeStatus`, `MediaBuyStatus`, `PackageStatus`, `PricingModel`
- **Core domain types**: `BrandReference`, `Creative`, `CreativeManifest`, `MediaBuy`, `Package`, `PackageRequest`, `TargetingOverlay`
- **AdCP status enums**: `CreativeStatus`, `DeliveryStatus`, `MediaBuyStatus`, `PricingModel`
- **All 9 pricing options**: `CpcPricingOption`, `CpmFixedRatePricingOption`, `VcpmAuctionPricingOption`, etc.
- **Request/Response types**: All 16 operations with full request/response types

For types not on the top-level surface, import from `adcp.types` (e.g., `from adcp.types import AssetStatus`). If a type you need isn't in `adcp.types`, open an issue — we'll add an alias. The `adcp.types.generated_poc.*` modules are internal; class names and module paths shift on every schema regeneration and are not a supported API.

#### Semantic Type Aliases

For discriminated union types (success/error responses), use semantic aliases for clearer code:
Expand Down Expand Up @@ -332,7 +334,7 @@ See `examples/type_aliases_demo.py` for more examples.
**Import guidelines:**
- ✅ **DO**: Import from main package: `from adcp import GetProductsRequest`
- ✅ **DO**: Use semantic aliases: `from adcp import CreateMediaBuySuccessResponse`
- ⚠️ **AVOID**: Import from internal modules: `from adcp.types._generated import CreateMediaBuyResponse1`
- ⚠️ **AVOID**: Import from `adcp.types.generated_poc.*` — paths and class names (including numbered `Assets*` variants) change on every schema regeneration.

The main package exports provide a stable API while internal generated types may change.

Expand Down Expand Up @@ -784,7 +786,7 @@ A typical media buy workflow involves discovering products, creating the buy, an

```python
from adcp import ADCPClient, AgentConfig, GetProductsRequest, CreateMediaBuyRequest
from adcp import BrandManifest, PublisherPropertiesAll
from adcp import BrandReference, PublisherPropertiesAll

# 1. Connect to agent
config = AgentConfig(id="sales_agent", agent_uri="https://...", protocol="mcp")
Expand All @@ -802,19 +804,14 @@ async with ADCPClient(config) as client:
# 3. Create media buy reservation
media_buy_result = await client.create_media_buy(
CreateMediaBuyRequest(
brand_manifest=BrandManifest(
name="Coffee Co",
brand_url="https://coffeeco.com",
logo_url="https://coffeeco.com/logo.png",
# ... additional brand details
),
brand=BrandReference(domain="coffeeco.com"),
packages=[{
"package_id": product.packages[0].package_id,
"quantity": 1000000 # impressions
"quantity": 1000000, # impressions
}],
publisher_properties=PublisherPropertiesAll(
selection_type="all" # Target all authorized properties
)
selection_type="all", # Target all authorized properties
),
)
)

Expand Down Expand Up @@ -901,8 +898,8 @@ async with ADCPClient(config) as client:
Combine both workflows for a complete campaign setup:

```python
from adcp import ADCPMultiAgentClient, AgentConfig
from adcp import GetProductsRequest, CreateMediaBuyRequest, BuildCreativeRequest
from adcp import ADCPMultiAgentClient, AgentConfig, BrandReference, PublisherPropertiesAll
from adcp import BuildCreativeRequest, CreateMediaBuyRequest

# Connect to both sales and creative agents
async with ADCPMultiAgentClient(
Expand All @@ -927,17 +924,17 @@ async with ADCPMultiAgentClient(
creative_result = await creative_agent.build_creative(
BuildCreativeRequest(
manifest=creative_manifest,
target_format_id=formats.formats[0].format_id.id
target_format_id=formats.formats[0].format_id.id,
)
)

# 4. Create media buy with creative
media_buy_result = await sales_agent.create_media_buy(
CreateMediaBuyRequest(
brand_manifest=brand_manifest,
brand=BrandReference(domain="coffeeco.com"),
packages=[{"package_id": products.products[0].packages[0].package_id}],
publisher_properties=publisher_properties,
creative_urls=[creative_result.data.assets[0].url]
publisher_properties=PublisherPropertiesAll(selection_type="all"),
creative_urls=[creative_result.data.assets[0].url],
)
)

Expand Down
8 changes: 4 additions & 4 deletions scripts/consolidate_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,10 @@ def _stem_matches_export(module_stem: str, export_name: str) -> bool:
# Kept so existing code importing them continues to work.
# Model stubs accept any payload (extra="allow").
# PromotedOfferingsRequirement is preserved as an Enum since it was one upstream.
# No backward-compat stubs. The 3.0.0-rc3 SDK surface matches the
# 3.0.0-rc3 spec. Removed types (BrandManifest, PromotedOfferings,
# DeliverTo, Pricing, FormatCategory, PackageStatus, etc.) are
# documented in the CHANGELOG migration guide.
# No backward-compat stubs. The SDK surface matches the spec directly.
# Removed types (BrandManifest, PromotedOfferings, DeliverTo, Pricing,
# FormatCategory, PackageStatus, etc.) are documented in
# MIGRATION_v3_to_v4.md.

# Format __all__ list with proper line breaks (max 100 chars per line)
# Exclude private names that are alias targets (internal intermediates only).
Expand Down
Loading
Loading