Skip to content

Conversation

@bokelley
Copy link
Contributor

@bokelley bokelley commented Nov 18, 2025

Summary

This PR addresses the downstream report that pricing options were missing the is_fixed discriminator field.

Investigation revealed that:

  • The discriminator already exists in the individual pricing option schema files
  • The issue was stale generated files persisting from previous generations
  • No mechanism existed to clean the output directory before regeneration

Changes

Type Generation Fixes

  • ✅ Remove stale generated files (pricing_option.py, brand_manifest_ref.py, index.py, start_timing.py)
  • ✅ Add clean slate generation: delete entire output directory before regenerating types
  • ✅ Add timestamp change detection: restore files where only the generation timestamp changed
  • ✅ Update CLAUDE.md with patterns for preventing stale files and noisy commits

Stable Public API

  • ✅ Create src/adcp/types/stable.py with stable type aliases
  • ✅ Export stable types from adcp.types
  • ✅ Add deprecation warning to generated_poc module
  • ✅ Add BrandManifestReference union type (BrandManifest | AnyUrl)

Pricing Option Discriminators

All pricing options now correctly have the is_fixed discriminator:

Fixed-rate options (is_fixed: Annotated[Literal[True], ...]):

  • CpmFixedRatePricingOption
  • VcpmFixedRatePricingOption
  • CpcPricingOption
  • CpcvPricingOption
  • CppPricingOption
  • CpvPricingOption
  • FlatRatePricingOption

Auction options (is_fixed: Annotated[Literal[False], ...]):

  • CpmAuctionPricingOption
  • VcpmAuctionPricingOption

Stable Public API

This PR introduces a stable public API layer to shield users from internal implementation details:

Why This Matters

Generated types may have numbered suffixes (e.g., BrandManifest1, BrandManifest2) due to schema evolution. Direct imports from generated_poc would break when schemas change.

Solution

# ✅ CORRECT - Stable public API
from adcp.types import BrandManifest, BrandManifestReference, Product

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

The stable API provides:

  1. Clean names - BrandManifest not BrandManifest1
  2. Stability - Aliases are updated when schemas evolve
  3. Versioning - Breaking changes require major version bumps
  4. Union types - Proper exposure of oneOf schemas (e.g., BrandManifestReference)

BrandManifestReference

Added BrandManifestReference = BrandManifest | AnyUrl to represent the union from brand-manifest-ref.json:

  • Inline BrandManifest object, OR
  • URL string pointing to a hosted manifest

Test Plan

  • Verify all pricing option files have is_fixed discriminator
  • Verify clean slate generation deletes output directory
  • Verify timestamp restoration prevents noisy commits
  • Code review: no high or medium priority issues found
  • All 258 tests pass
  • Type checking passes with mypy
  • Deprecation warning works for direct generated_poc imports

Infrastructure Improvements

This PR includes important infrastructure improvements to prevent similar issues in the future:

  1. Clean Slate Generation - Prevents stale files from old schemas
  2. Timestamp Restoration - Prevents commits with 100+ timestamp-only changes
  3. Stable API Layer - Hides internal implementation details
  4. Documentation - Clear patterns for future type generation work

🤖 Generated with Claude Code

…ructure

This commit addresses the downstream report that pricing options were missing
the is_fixed discriminator field. Investigation revealed the discriminator
already exists in the individual pricing option schema files generated by
datamodel-code-generator.

The root causes were:
1. Stale files (pricing_option.py, brand_manifest_ref.py, index.py, start_timing.py)
   persisting from previous generations
2. No mechanism to clean output directory before regeneration
3. Timestamp-only changes creating noisy commits

Changes:
- Remove stale generated files that no longer correspond to current schemas
- Add clean slate generation: delete entire output directory before regenerating
  types to prevent stale artifacts from old schema versions
- Add timestamp change detection: restore files where only the generation
  timestamp changed to avoid noisy commits
- Update CLAUDE.md with patterns for preventing stale files and noisy commits

All pricing options now correctly have the is_fixed discriminator:
- Fixed-rate: CpmFixedRatePricingOption, VcpmFixedRatePricingOption, etc.
  (is_fixed: Annotated[Literal[True], ...])
- Auction: CpmAuctionPricingOption, VcpmAuctionPricingOption
  (is_fixed: Annotated[Literal[False], ...])

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@bokelley bokelley force-pushed the fix-pricing-discriminator branch from 938a9a1 to 9bc7f87 Compare November 18, 2025 03:46
bokelley and others added 17 commits November 17, 2025 22:54
… fixes

CI was failing because generated.py was importing the stale files we deleted:
- brand_manifest_ref.py
- index.py
- pricing_option.py
- start_timing.py

Changes:
- Remove imports of stale files from src/adcp/types/generated.py
- Remove exports of stale symbols from __all__
- Update fix_brand_manifest_references() to:
  - Fix import statement (brand_manifest_ref → brand_manifest)
  - Fix class references (BrandManifest → BrandManifest1)
- Update fix_enum_defaults() to check brand_manifest.py instead of deleted brand_manifest_ref.py

Fixes test failures in CI for all Python versions and schema validation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… changes

Problem:
- Users importing from generated_poc directly (e.g., BrandManifest1, BrandManifest2)
  creates brittle code that breaks when schemas evolve
- Generated type names with numbered suffixes expose internal implementation details
- No stable API contract prevents breaking changes in minor versions

Solution:
- Created src/adcp/types/stable.py with clean aliases (BrandManifest → BrandManifest1)
- Updated adcp.types to re-export from stable API instead of generated internals
- Added deprecation warning to generated_poc/__init__.py for direct imports
- Documented stable API patterns in CLAUDE.md

Benefits:
- Users see clean names (BrandManifest not BrandManifest1)
- Schema evolution handled via alias updates, not user code changes
- Breaking changes properly versioned per semver
- Deprecation warnings guide users to correct imports

Migration:
✅ from adcp.types import BrandManifest  # Clean, stable
❌ from adcp.types.generated_poc.brand_manifest import BrandManifest1  # Breaks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add BrandManifestReference as a union type (BrandManifest | AnyUrl) to
properly represent the oneOf schema from brand-manifest-ref.json. This
allows users to pass either an inline BrandManifest object or a URL
string pointing to a hosted manifest.

Changes:
- Add BrandManifestReference = BrandManifest1 | AnyUrl in stable.py
- Export BrandManifestReference from adcp.types
- Restore deprecation warning to generated_poc/__init__.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Synced schemas and regenerated types after upstream fixed brand-manifest.json
to be a single clean type instead of incorrectly using anyOf with different
required fields.

Changes:
- Updated schemas from upstream (brand-manifest, list-creatives-response, promoted-offerings)
- BrandManifest is now single clean type with name (required) and url (optional)
- Removed obsolete post-generation fix for BrandManifest references
- Made consolidate_exports.py aliases conditional to avoid referencing non-existent types
- Updated stable API to reflect clean BrandManifest type

All 258 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Ruff detected unsorted imports in __init__.py and stable.py. The imports
are now sorted alphabetically which is the expected format.

Changes:
- Sort imports in src/adcp/types/__init__.py
- Sort imports in src/adcp/types/stable.py

All tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Improve downstream ergonomics by exporting commonly-used types directly
from the main `adcp` package, eliminating the need to import from
`adcp.types.stable` for typical workflows.

Added exports:
- Core domain types: BrandManifest, Creative, CreativeManifest, MediaBuy, Package
- Status enums: CreativeStatus, MediaBuyStatus, PackageStatus, PricingModel
- All 9 pricing options: CpcPricingOption, CpmFixedRatePricingOption, etc.

This enables "import from adcp and go" for 90% of user workflows:

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

# Create media buy
brand = BrandManifest(name="ACME")
pricing = CpmFixedRatePricingOption(...)
```

All 258 tests pass. Addresses code-reviewer feedback for 5-star ergonomics.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Updated Type Safety section to show that commonly-used types are now
exported from the main adcp package, including:
- Core domain types (BrandManifest, Creative, MediaBuy, Package, etc.)
- Status enums (CreativeStatus, MediaBuyStatus, PackageStatus, PricingModel)
- All 9 pricing options with discriminators
- Request/Response types for all operations

Added examples showing type-safe pricing options and status enum usage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Created comprehensive plan for auto-generating API reference documentation
using pdoc3. Key benefits:

- Zero config - works with existing docstrings and type hints
- GitHub Pages ready - single command generates static HTML
- Pydantic aware - extracts Field descriptions from JSON Schema
- Searchable - adds search across all types and methods
- Fast - regenerates in ~1 second

Includes:
- Implementation plan with GitHub Actions workflow
- Cost/benefit analysis
- Example output structure
- Quick start guide for contributors

Recommended over Sphinx (overkill) and MkDocs (narrative-focused).
Setup time ~30min, near-zero maintenance overhead.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Consolidate all generated types into a private module that's clearly
internal. This gives SDK internals convenient access while signaling
to users they should use the stable public API instead.

Changes:
- Rename src/adcp/types/generated.py → _generated.py
- Update all imports throughout codebase
- Add clear warning in _generated.py header
- Update consolidate_exports.py to generate _generated.py
- Update stable.py to import from _generated

All 274 tests pass (2 pre-existing failures unrelated to this change).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix remaining imports in tests and CLI to use _generated
- Add ruff noqa comments to skip E501 and I001 in generated file
- Update consolidate script to format __all__ with proper line breaks
- Update CI workflow to use _generated import

All 274 tests pass (2 pre-existing failures unrelated to this change).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The CI workflow was still checking the old generated.py path. Update it
to use the new _generated.py path for syntax validation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Critical fixes:
- Fix test_public_api.py: Remove non-existent SimpleADCPClient, fix Format field expectations
- Update documentation: Replace generated.py → _generated.py in CLAUDE.md and README.md
- Update pyproject.toml: Fix black/ruff exclude patterns for _generated.py

All 276 tests now passing!

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The CI workflow schema drift check was still referencing the old
generated.py path. Update to _generated.py.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Regenerate types to match what CI generates. Only timestamp changed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The CI drift check was failing because it regenerates types during
validation, which updates the timestamp, then complains about the change.

This fix ignores timestamp-only changes (expected during CI regeneration)
while still catching real schema drift.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Moves testing examples from tests/examples/ (confusing location) to docs/
where users can find them.

Changes:
- Create docs/testing-guide.md with comprehensive testing best practices
- Move RECOMMENDED_TESTING_PATTERNS.py → docs/examples/testing_patterns.py
- Remove tests/examples/ directory

Benefits:
- Clear documentation for SDK users and contributors
- Executable examples demonstrating best practices
- No confusion about whether examples are actual tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@bokelley bokelley merged commit 574ec90 into main Nov 18, 2025
7 checks passed
@bokelley bokelley deleted the fix-pricing-discriminator branch November 18, 2025 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants