Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cb5e6fa
feat: Add proper Pydantic models for Format nested types
bokelley Nov 15, 2025
d0123af
refactor: Extract inline Format schemas to separate files
bokelley Nov 15, 2025
ef5cda0
refactor: Migrate to datamodel-code-generator for type generation
bokelley Nov 15, 2025
269640a
refactor: Replace ETag caching with SHA-256 content hashing
bokelley Nov 15, 2025
a528682
fix: Regenerate consolidated exports to handle import conflicts
bokelley Nov 15, 2025
225325b
feat!: Release v2.0.0 with breaking changes
bokelley Nov 15, 2025
8d7f25b
chore: sync schemas to AdCP v2.4.0 with discriminated unions
bokelley Nov 15, 2025
9a77cbb
fix: update all tests for AdCP v2.4.0 schema changes
bokelley Nov 15, 2025
0fcca7c
fix: update tests for datamodel-code-generator migration
bokelley Nov 15, 2025
9400b19
chore: remove schema validation documentation files
bokelley Nov 15, 2025
f03ae1b
chore: revert version to 1.6.1 (release-please will manage version bu…
bokelley Nov 15, 2025
a11eac9
ci: update workflow to use generate_types.py instead of removed script
bokelley Nov 15, 2025
952360f
fix: add datamodel-code-generator dependency and fix linting errors
bokelley Nov 15, 2025
19eefca
fix: add email-validator dependency for datamodel-code-generator
bokelley Nov 15, 2025
20e86d7
refactor: improve code quality in type generation scripts
bokelley Nov 15, 2025
59643a1
fix: add model_validator to PublisherProperty for automatic validation
bokelley Nov 15, 2025
d4716b8
feat: add comprehensive model validators for all types requiring vali…
bokelley Nov 15, 2025
4fc5b1d
fix: repair generated type imports and restore model validators
bokelley Nov 15, 2025
dd8a7a0
feat: add post-generation script for model validators and fixes
bokelley Nov 15, 2025
3b70613
fix: improve post-generation script robustness and documentation
bokelley Nov 15, 2025
724898a
chore: set up pre-commit hooks with ruff and black
bokelley Nov 15, 2025
44b02b5
fix: resolve mypy type errors (74 → 4)
bokelley Nov 15, 2025
7366769
docs: document type name collision issue
bokelley Nov 15, 2025
e6d72e8
refactor: remove dead code file tasks.py
bokelley Nov 15, 2025
6c82879
fix: resolve all mypy type errors and enable strict type checking
bokelley Nov 15, 2025
bc4a2d0
fix: resolve CI failures (ruff line length and post-generation script)
bokelley Nov 15, 2025
ab464fb
fix: align black and ruff line length to 100 chars consistently
bokelley Nov 15, 2025
317ee67
refactor: make post-generation script fail loudly instead of silently
bokelley Nov 15, 2025
595a294
fix: exclude generated_poc directory from ruff linting
bokelley Nov 15, 2025
b135e0e
fix: align datamodel-code-generator version and remove unreliable Pub…
bokelley Nov 15, 2025
3e8ac4b
test: add missing test_code_generation.py test suite
bokelley Nov 15, 2025
c0eccd6
fix: update schemas to v1.0.0 and adapt to discriminated union changes
bokelley Nov 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ jobs:
run: python scripts/fix_schema_refs.py

- name: Generate models
run: python scripts/generate_models_simple.py
run: python scripts/generate_types.py

- name: Validate generated code syntax
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,6 @@ Thumbs.db
# Environment variables
.env
uv.lock

# Temporary schema processing directory
.schema_temp/
48 changes: 14 additions & 34 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
# Pre-commit hooks for AdCP Python client
# See https://pre-commit.com for more information
# Installation: pip install pre-commit && pre-commit install
# Installation: uv add --dev pre-commit && uv run pre-commit install

repos:
# Black code formatting
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
language_version: python3.10
language_version: python3.11
args: [--line-length=100]

# Ruff linting
# Ruff linting (ignoring line-length for now - black handles it)
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
exclude: ^src/adcp/types/generated\.py$
args: [--fix, --exit-non-zero-on-fix, --ignore=E501]
exclude: ^src/adcp/types/generated_poc/

# Mypy type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.14.0
# Mypy type checking (local hook to use project dependencies)
- repo: local
hooks:
- id: mypy
additional_dependencies:
- pydantic>=2.0.0
- types-requests
args: [--config-file=pyproject.toml]
files: ^src/adcp/
exclude: ^src/adcp/types/generated\.py$
name: mypy
entry: uv run mypy
language: system
types: [python]
pass_filenames: false
args: [src/adcp]

# Basic file checks
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand All @@ -48,28 +47,9 @@ repos:
- id: check-case-conflict
- id: detect-private-key

# Validate generated code after schema changes
- repo: local
hooks:
- id: validate-generated-code
name: Validate generated Pydantic models
entry: python -m py_compile
language: system
files: ^src/adcp/types/generated\.py$
pass_filenames: true
description: Ensures generated code is syntactically valid Python

- id: test-code-generation
name: Test code generator
entry: pytest tests/test_code_generation.py -v --tb=short
language: system
files: ^scripts/generate_models_simple\.py$
pass_filenames: false
description: Run code generation tests when generator changes

# Configuration
default_language_version:
python: python3.10
python: python3.11

# Run hooks on all files during manual runs
fail_fast: false
47 changes: 47 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,53 @@ FormatId = str
PackageRequest = dict[str, Any]
```

**NEVER Modify Generated Files Directly**

Files in `src/adcp/types/generated_poc/` are auto-generated by `scripts/generate_types.py`. Any manual edits will be lost on regeneration.

**Post-Generation Fix System:**

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

**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.

When consolidating exports in `generated.py`, we use a "first wins" strategy (alphabetical by module name) and warn about collisions. Users can still access all versions via module-qualified imports:

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

# Access specific versions
from adcp.types.generated_poc.brand_manifest import Asset as BrandAsset
from adcp.types.generated_poc.format import Asset as FormatAsset
```

**Upstream Issue:** This should ideally be fixed in the AdCP schema definitions by either:
- Using unique names (e.g., `BrandAsset` vs `FormatAsset`)
- Sharing common types via `$ref`
- 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

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

**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`
- Is idempotent (safe to run multiple times)
- Validates fixes were successfully applied
- Fails loudly if schema changes break the fix patterns

**Type Checking Best Practices**
- Use `TYPE_CHECKING` for optional dependencies to avoid runtime import errors
- Use `cast()` for JSON deserialization to satisfy mypy's `no-any-return` checks
Expand Down
15 changes: 11 additions & 4 deletions examples/adagents_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ def example_manual_verification():

print("\nScenario 4: Agent with empty properties = authorized for all")
result = verify_agent_authorization(
adagents_data, "https://another-agent.com", "website", [{"type": "domain", "value": "any.com"}]
adagents_data,
"https://another-agent.com",
"website",
[{"type": "domain", "value": "any.com"}],
)
print(f" Result: {result}")

Expand Down Expand Up @@ -225,7 +228,9 @@ def example_domain_matching():
print(f" example.com == example.com: {domain_matches('example.com', 'example.com')}")

print("\n2. Common subdomains (www, m) match bare domain:")
print(f" www.example.com matches example.com: {domain_matches('www.example.com', 'example.com')}")
print(
f" www.example.com matches example.com: {domain_matches('www.example.com', 'example.com')}"
)
print(f" m.example.com matches example.com: {domain_matches('m.example.com', 'example.com')}")

print("\n3. Other subdomains DON'T match bare domain:")
Expand Down Expand Up @@ -262,7 +267,8 @@ async def main():
print("\n\n" + "=" * 60)
print("Summary")
print("=" * 60)
print("""
print(
"""
Key Functions:
1. fetch_adagents(domain) - Fetch and validate adagents.json
2. verify_agent_authorization(data, agent_url, ...) - Check authorization
Expand All @@ -279,7 +285,8 @@ async def main():
- Developer tools: Build validators and testing utilities

See the full API documentation for more details.
""")
"""
)


if __name__ == "__main__":
Expand Down
1 change: 1 addition & 0 deletions examples/basic_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import asyncio

from adcp import ADCPClient
from adcp.types import AgentConfig, Protocol

Expand Down
18 changes: 10 additions & 8 deletions examples/fetch_preview_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,23 @@ async def main():
print(f" Expires: {preview.get('expires_at', 'N/A')}")
print()

preview_data.append({
"format_id": format_id,
"name": name,
"preview_url": preview_url,
"width": 300,
"height": 400,
})
preview_data.append(
{
"format_id": format_id,
"name": name,
"preview_url": preview_url,
"width": 300,
"height": 400,
}
)

# Save to JSON for the web component demo
output_file = Path(__file__).parent / "preview_urls.json"
with open(output_file, "w") as f:
json.dump(preview_data, f, indent=2)

print(f"💾 Saved preview URLs to: {output_file}")
print(f"\n🌐 Open examples/web_component_demo.html in a browser to see the previews!")
print("\n🌐 Open examples/web_component_demo.html in a browser to see the previews!")

except Exception as e:
print(f"❌ Error: {e}")
Expand Down
3 changes: 2 additions & 1 deletion examples/multi_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import asyncio

from adcp import ADCPMultiAgentClient
from adcp.types import AgentConfig, Protocol

Expand Down Expand Up @@ -53,7 +54,7 @@ async def main():
sync_count = sum(1 for r in results if r.status == "completed")
async_count = sum(1 for r in results if r.status == "submitted")

print(f"\n📊 Results:")
print("\n📊 Results:")
print(f" ✅ Sync completions: {sync_count}")
print(f" ⏳ Async (webhooks pending): {async_count}")

Expand Down
2 changes: 1 addition & 1 deletion examples/preview_urls.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
"width": 160,
"height": 600
}
]
]
5 changes: 1 addition & 4 deletions examples/test_helpers_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
create_test_agent,
test_agent,
test_agent_a2a,
test_agent_a2a_no_auth,
test_agent_client,
test_agent_no_auth,
)
Expand Down Expand Up @@ -219,9 +218,7 @@ async def various_operations() -> None:

# List creative formats
print("2. Listing creative formats...")
formats = await test_agent.list_creative_formats(
ListCreativeFormatsRequest()
)
formats = await test_agent.list_creative_formats(ListCreativeFormatsRequest())
success = "✅" if formats.success else "❌"
count = len(formats.data.formats) if formats.data else 0
print(f" {success} Formats: {count}")
Expand Down
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ dev = [
"mypy>=1.0.0",
"black>=23.0.0",
"ruff>=0.1.0",
"datamodel-code-generator[http]>=0.35.0",
"email-validator>=2.0.0",
]

[project.urls]
Expand All @@ -63,7 +65,11 @@ extend-exclude = "/(generated|tasks)\\.py$"
[tool.ruff]
line-length = 100
target-version = "py310"
extend-exclude = ["src/adcp/types/generated.py", "src/adcp/types/tasks.py"]
extend-exclude = [
"src/adcp/types/generated.py",
"src/adcp/types/tasks.py",
"src/adcp/types/generated_poc/",
]

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
Expand All @@ -86,3 +92,9 @@ ignore_errors = true
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[dependency-groups]
dev = [
"datamodel-code-generator>=0.35.0",
"pre-commit>=4.4.0",
]
Loading