Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9bc7f87
fix: remove stale generated files and improve type generation infrast…
bokelley Nov 18, 2025
c34c184
fix: remove imports of deleted stale files and update post-generation…
bokelley Nov 18, 2025
c6455c1
feat: add stable public API layer to shield users from generated type…
bokelley Nov 18, 2025
7990017
feat: expose BrandManifestReference union type in stable API
bokelley Nov 18, 2025
c9c04ff
feat: integrate upstream BrandManifest schema consolidation
bokelley Nov 18, 2025
6611cbd
fix: sort imports to satisfy ruff linter
bokelley Nov 18, 2025
4d2a37e
feat: export core domain types and pricing options from main package
bokelley Nov 18, 2025
9f321c1
docs: update README with ergonomic type import improvements
bokelley Nov 18, 2025
bfd6fb9
docs: add API documentation strategy recommendation
bokelley Nov 18, 2025
a7217e2
refactor: rename generated.py to _generated.py to signal internal API
bokelley Nov 18, 2025
176740e
fix: update imports and add linter config for _generated.py
bokelley Nov 18, 2025
360a1cb
fix: update CI workflow to use _generated.py path
bokelley Nov 18, 2025
01f520c
fix: address code review issues and update documentation
bokelley Nov 18, 2025
0cc0bf5
fix: update schema drift check to use _generated.py path
bokelley Nov 18, 2025
5b3ebd7
chore: regenerate types to sync with CI
bokelley Nov 18, 2025
e3cedf0
fix: improve schema drift check to ignore timestamp-only changes
bokelley Nov 18, 2025
dded08a
docs: move testing patterns to docs/ and create testing guide
bokelley Nov 18, 2025
c6e5e8c
chore: remove temporary testing documentation files
bokelley Nov 18, 2025
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
21 changes: 12 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ jobs:
- name: Validate generated code syntax
run: |
echo "Validating generated code can be parsed..."
python -m py_compile src/adcp/types/generated.py
python -m py_compile src/adcp/types/_generated.py
echo "✓ Syntax validation passed"

- name: Validate generated code imports
run: |
echo "Validating generated code can be imported..."
python -c "from adcp.types import generated; print(f'✓ Successfully imported {len(dir(generated))} symbols')"
python -c "from adcp.types import _generated as generated; print(f'✓ Successfully imported {len(dir(generated))} symbols')"

- name: Run code generation tests
run: |
Expand All @@ -133,11 +133,14 @@ jobs:

- name: Check for schema drift
run: |
if git diff --exit-code src/adcp/types/generated.py schemas/cache/; then
echo "✓ Schemas are up-to-date"
else
echo "✗ Schemas are out of date!"
echo "Run: make regenerate-schemas"
git diff src/adcp/types/generated.py
exit 1
# Check if only generation timestamp changed (expected when CI regenerates)
if git diff --exit-code src/adcp/types/_generated.py schemas/cache/ | grep -v "^[-+]Generation date:"; then
# Real changes detected (not just timestamp)
if git diff src/adcp/types/_generated.py | grep -v "^[-+]Generation date:" | grep "^[-+]" | grep -v "^[-+][-+][-+]" | grep -v "^[-+]@@" > /dev/null; then
echo "✗ Schemas are out of date!"
echo "Run: make regenerate-schemas"
git diff src/adcp/types/_generated.py
exit 1
fi
fi
echo "✓ Schemas are up-to-date"
55 changes: 48 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,32 @@ FormatId = str
PackageRequest = dict[str, Any]
```

**Stable Public API Layer**

**CRITICAL**: The `generated_poc` directory is internal implementation. **Never import directly from it**.

Generated types in `src/adcp/types/generated_poc/` may have:
- Numbered suffixes (e.g., `BrandManifest1`, `BrandManifest2`) due to schema evolution
- Structural changes between minor versions
- Files added/removed as schemas evolve

**Always use the stable API:**
```python
# ✅ CORRECT - Stable public API
from adcp.types import BrandManifest, Product, CpmFixedRatePricingOption
from adcp.types.stable import BrandManifest, Product

# ❌ WRONG - Internal generated types (will break)
from adcp.types.generated_poc.brand_manifest import BrandManifest1
from adcp.types._generated import BrandManifest1
```

The stable API (`src/adcp/types/stable.py`) provides:
1. **Clean names** - `BrandManifest` not `BrandManifest1`
2. **Stability** - Aliases are updated when schemas evolve
3. **Versioning** - Breaking changes require major version bumps
4. **Deprecation warnings** - Direct `generated_poc` imports trigger warnings

**NEVER Modify Generated Files Directly**

Files in `src/adcp/types/generated_poc/` and `src/adcp/types/generated.py` are auto-generated by `scripts/generate_types.py`. Any manual edits will be lost on regeneration.
Expand All @@ -27,6 +53,14 @@ Files in `src/adcp/types/generated_poc/` and `src/adcp/types/generated.py` are a

We use `scripts/post_generate_fixes.py` which runs automatically after type generation to apply necessary modifications that can't be generated.

**Preventing Stale Files:**

The generation script (`scripts/generate_types.py`) **deletes the entire output directory** before regenerating types. This prevents stale files from persisting when schemas are renamed or removed. Without this, old generated files could remain checked in indefinitely, causing import errors and confusion about which types are actually current.

**Avoiding Noisy Commits:**

After generation, the script automatically restores files where only the timestamp changed (e.g., `# timestamp: 2025-11-18T03:32:03+00:00`). This prevents commits with 100+ file changes where the only difference is the generation timestamp, making actual changes easier to review.

**Type Name Collisions:**

The upstream AdCP schemas define multiple types with the same name (e.g., `Contact`, `Asset`, `Status`) in different schema files. These are **genuinely different types** with different fields, not duplicates.
Expand All @@ -35,7 +69,7 @@ When consolidating exports in `generated.py`, we use a "first wins" strategy (al

```python
# Access the "winning" version
from adcp.types.generated import Asset
from adcp.types._generated import Asset

# Access specific versions
from adcp.types.generated_poc.brand_manifest import Asset as BrandAsset
Expand All @@ -48,17 +82,24 @@ from adcp.types.generated_poc.format import Asset as FormatAsset
- Using discriminated unions where appropriate

**Current fixes applied:**
1. **Model validators** - Injects `@model_validator` decorators into:
- `PublisherProperty.validate_mutual_exclusivity()` - enforces property_ids/property_tags mutual exclusivity
- `Product.validate_publisher_properties_items()` - validates all publisher_properties items

2. **Self-referential types** - Fixes `preview_render.py` if it contains module-qualified self-references
1. **Self-referential types** - Fixes `preview_render.py` if it contains module-qualified self-references

3. **Forward references** - Fixes BrandManifest imports in:
2. **Forward references** - Fixes BrandManifest imports in:
- `promoted_offerings.py`
- `create_media_buy_request.py`
- `get_products_request.py`

3. **~~Publisher properties validation~~ (DEPRECATED)** - After PR #213 added explicit discriminator to `publisher_properties` schema, Pydantic now generates proper discriminated union variants (`PublisherProperties`, `PublisherProperties4`, `PublisherProperties5`) with automatic validation. Manual validator injection is no longer needed.

**Note on Pricing Options:**

The code generator creates individual files for each pricing option (e.g., `cpm_fixed_option.py`, `cpm_auction_option.py`) with the `is_fixed` discriminator field already included:
- Fixed-rate options: `is_fixed: Annotated[Literal[True], ...]`
- Auction options: `is_fixed: Annotated[Literal[False], ...]`

These are used via union types in `Product.pricing_options`. No post-generation fix is needed for pricing options.

**To add new post-generation fixes:**
Edit `scripts/post_generate_fixes.py` and add a new function. The script:
- Runs automatically via `generate_types.py`
Expand All @@ -79,7 +120,7 @@ Edit `scripts/post_generate_fixes.py` and add a new function. The script:
3. **Create semantic aliases in `aliases.py`**:
```python
# Import the generated types
from adcp.types.generated import PreviewRender1, PreviewRender2, PreviewRender3
from adcp.types._generated import PreviewRender1, PreviewRender2, PreviewRender3

# Create semantic aliases based on discriminator values
UrlPreviewRender = PreviewRender1 # output_format='url'
Expand Down
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,16 @@ client = ADCPClient(config)

### Type Safety

Full type hints with Pydantic validation and auto-generated types from the AdCP spec:
Full type hints with Pydantic validation and auto-generated types from the AdCP spec. All commonly-used types are exported from the main `adcp` package for convenience:

```python
from adcp import GetProductsRequest
from adcp import (
GetProductsRequest,
BrandManifest,
Package,
CpmFixedRatePricingOption,
MediaBuyStatus,
)

# All methods require typed request objects
request = GetProductsRequest(brief="Coffee brands", max_results=10)
Expand All @@ -220,8 +226,27 @@ result = await agent.get_products(request)
if result.success:
for product in result.data.products:
print(product.name, product.pricing_options) # Full IDE autocomplete!

# Type-safe pricing with discriminators
pricing = CpmFixedRatePricingOption(
pricing_option_id="cpm_usd",
pricing_model="cpm",
is_fixed=True, # Literal[True] - type checked!
currency="USD",
rate=5.0
)

# Type-safe status enums
if media_buy.status == MediaBuyStatus.active:
print("Media buy is active")
```

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

#### Semantic Type Aliases

For discriminated union types (success/error responses), use semantic aliases for clearer code:
Expand Down Expand Up @@ -252,7 +277,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 internal modules: `from adcp.types._generated import CreateMediaBuyResponse1`

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

Expand Down
Loading