Skip to content

Commit ab49c98

Browse files
bokelleyclaude
andauthored
feat: complete semantic alias coverage for discriminated unions (#63)
* feat: add semantic aliases for PublisherProperties discriminated union Add user-friendly type aliases for the PublisherProperties discriminated union variants to improve developer experience and avoid direct imports from generated_poc. Changes: - Add PublisherPropertiesAll (selection_type='all') - Add PublisherPropertiesById (selection_type='by_id') - Add PublisherPropertiesByTag (selection_type='by_tag') - Export PropertyId and PropertyTag types for use with the variants - Update all export paths (aliases.py, types/__init__.py, __init__.py) - Add comprehensive tests covering imports, type checking, and instantiation These aliases follow the established pattern for other discriminated unions (PreviewRender, VastAsset, etc.) and provide clear, semantic names that match the spec's discriminator values. Example usage: ```python from adcp import PublisherPropertiesByTag, PropertyTag props = PublisherPropertiesByTag( publisher_domain="example.com", selection_type="by_tag", property_tags=[PropertyTag("premium"), PropertyTag("video")] ) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: add semantic aliases for Deployment and Destination discriminated unions Complete the semantic aliasing coverage by adding aliases for signal deployment and destination types used in GetSignalsResponse. Changes: - Add PlatformDeployment (Deployment1, type='platform') - Add AgentDeployment (Deployment2, type='agent') - Add PlatformDestination (Destination1, type='platform') - Add AgentDestination (Destination2, type='agent') - Update all export paths (aliases.py, __init__.py) - Add 7 comprehensive tests covering imports, type checking, and instantiation These aliases complete coverage of all user-facing discriminated unions in the AdCP SDK. Users no longer need to import numbered types or reach into generated_poc for any common operations. Example usage: ```python from adcp import PlatformDeployment, AgentDestination deployment = PlatformDeployment( type="platform", platform="the-trade-desk", is_live=True ) destination = AgentDestination( type="agent", agent_url="https://agent.example.com" ) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: document comprehensive semantic aliasing strategy Add detailed documentation on: - Complete coverage of all 30 user-facing discriminated union aliases - List of 15 internal types that don't need aliases - Five-strategy approach to prevent numbered type usage: 1. Complete aliasing coverage checklist 2. Automated detection script (check_missing_aliases.py) 3. CI enforcement in workflows 4. Import linting with pre-commit hooks 5. Type regeneration workflow documentation This ensures downstream users never need to import from generated_poc or use numbered types, maintaining a clean and stable public API. The documentation includes: - Updated list of all current semantic aliases - Guidelines for when to alias vs skip - Bash commands for auditing numbered types - Python script templates for automation - CI/CD integration examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * style: fix import sorting in aliases.py --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d05fbf6 commit ab49c98

File tree

5 files changed

+671
-2
lines changed

5 files changed

+671
-2
lines changed

CLAUDE.md

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ Edit `scripts/post_generate_fixes.py` and add a new function. The script:
160160
- Test that aliases point to correct generated types
161161
- Test that aliases are exported from main package
162162

163-
**Current Semantic Aliases:**
163+
**Current Semantic Aliases (Complete Coverage - 30 user-facing types):**
164164

165165
- **Preview Renders** (discriminated by `output_format`):
166166
- `UrlPreviewRender` = `PreviewRender1` (output_format='url')
@@ -189,6 +189,26 @@ Edit `scripts/post_generate_fixes.py` and add a new function. The script:
189189
- **Activation Keys** (discriminated by identifier type):
190190
- `PropertyIdActivationKey`/`PropertyTagActivationKey`
191191

192+
- **Publisher Properties** (discriminated by `selection_type`):
193+
- `PublisherPropertiesAll` = `PublisherProperties` (selection_type='all')
194+
- `PublisherPropertiesById` = `PublisherProperties4` (selection_type='by_id')
195+
- `PublisherPropertiesByTag` = `PublisherProperties5` (selection_type='by_tag')
196+
- Also exports: `PropertyId`, `PropertyTag` (constraint types)
197+
198+
- **Deployment Types** (discriminated by `type`):
199+
- `PlatformDeployment` = `Deployment1` (type='platform')
200+
- `AgentDeployment` = `Deployment2` (type='agent')
201+
202+
- **Destination Types** (discriminated by `type`):
203+
- `PlatformDestination` = `Destination1` (type='platform')
204+
- `AgentDestination` = `Destination2` (type='agent')
205+
206+
**Internal Types NOT Aliased (15 types):**
207+
- Nested helper types: `Input2`, `Input4`, `Response1`, `Results1`, `Preview1`, etc.
208+
- Package update internals: `Packages1`, `Packages2`, `Packages3`
209+
- Format helpers: `AssetsRequired1`, `ViewThreshold1`
210+
- Internal enums: `Field1`, `Method1`
211+
192212
**Note on Pricing Types:**
193213

194214
Pricing option types (`CpcPricingOption`, `CpmAuctionPricingOption`, `CpmFixedRatePricingOption`, etc.) already have clear semantic names from the schema generator, so they don't need aliases. These types now include an `is_fixed` discriminator:
@@ -201,11 +221,122 @@ Pricing option types (`CpcPricingOption`, `CpmAuctionPricingOption`, `CpmFixedRa
201221
- User-facing discriminated unions used in API calls
202222
- Types where the discriminator conveys important semantic meaning
203223
- Types where numbered suffixes cause confusion
224+
- Types that appear in union fields of Product, MediaBuy, Package
225+
- Request/Response variants users construct or pattern-match
204226

205227
**DON'T create aliases for:**
206228
- Internal helper types not commonly used directly
207229
- Types where parent context makes the meaning clear
208-
- Generic helper types (Input1, Parameters2, etc.)
230+
- Generic helper types (Input2, Parameters2, etc.)
231+
- Nested container types in requests/responses
232+
- Internal enums (Field1, Method1)
233+
234+
## Preventing Direct Imports from generated_poc
235+
236+
**Goal**: Ensure downstream users NEVER have to import from `generated_poc` or use numbered types.
237+
238+
### Strategy 1: Complete Aliasing Coverage
239+
240+
**After regenerating types**, audit for new discriminated unions:
241+
242+
```bash
243+
# Find all numbered types in generated code
244+
grep -h "^class.*[0-9](" src/adcp/types/generated_poc/*.py | \
245+
sed 's/class \([^(]*\).*/\1/' | sort -u
246+
247+
# For each numbered type, determine if it needs an alias:
248+
# 1. Is it used in a Union[] in Product/MediaBuy/Package? → YES, alias it
249+
# 2. Is it a Request/Response variant? → YES, alias it
250+
# 3. Does it have a discriminator field (Literal type)? → Probably YES
251+
# 4. Is it only used internally (Input2, Results1)? → NO, skip it
252+
```
253+
254+
### Strategy 2: Automated Detection
255+
256+
Add `scripts/check_missing_aliases.py`:
257+
258+
```python
259+
"""Check for user-facing numbered types without semantic aliases."""
260+
import ast
261+
import re
262+
from pathlib import Path
263+
264+
def find_discriminated_unions():
265+
"""Find types with discriminator fields that need aliases."""
266+
generated_dir = Path("src/adcp/types/generated_poc")
267+
numbered_types = set()
268+
269+
for py_file in generated_dir.glob("*.py"):
270+
content = py_file.read_text()
271+
# Find class definitions ending in digits
272+
matches = re.findall(r"class (\w+\d+)\(", content)
273+
# Check if they have Literal discriminators
274+
for match in matches:
275+
if f"Literal[" in content: # Has discriminator
276+
numbered_types.add(match)
277+
278+
# Check which are already aliased
279+
aliases_file = Path("src/adcp/types/aliases.py")
280+
aliases_content = aliases_file.read_text()
281+
282+
missing = []
283+
for typename in numbered_types:
284+
if f"= {typename}" not in aliases_content:
285+
missing.append(typename)
286+
287+
return missing
288+
289+
if __name__ == "__main__":
290+
missing = find_discriminated_unions()
291+
if missing:
292+
print(f"❌ Found {len(missing)} numbered types without aliases:")
293+
for t in missing:
294+
print(f" - {t}")
295+
exit(1)
296+
else:
297+
print("✅ All user-facing types have semantic aliases")
298+
```
299+
300+
### Strategy 3: CI Enforcement
301+
302+
Add to `.github/workflows/ci.yml`:
303+
304+
```yaml
305+
- name: Check for missing type aliases
306+
run: uv run python scripts/check_missing_aliases.py
307+
```
308+
309+
### Strategy 4: Import Linting
310+
311+
Add custom ruff rule or pre-commit hook:
312+
313+
```python
314+
# .pre-commit-config.yaml or custom linter
315+
# Detect imports from generated_poc in user code
316+
def check_generated_imports(file_path: str) -> list[str]:
317+
"""Warn about direct generated_poc imports."""
318+
if "test_" in file_path or "aliases.py" in file_path:
319+
return [] # Allow in tests and aliases module
320+
321+
with open(file_path) as f:
322+
for line_no, line in enumerate(f, 1):
323+
if "from adcp.types.generated_poc" in line:
324+
return [f"{file_path}:{line_no}: Don't import from generated_poc - use semantic aliases"]
325+
return []
326+
```
327+
328+
### Strategy 5: Documentation
329+
330+
**In type regeneration workflow**, always check:
331+
332+
1. Run type generation: `uv run python scripts/generate_types.py`
333+
2. Check for new numbered types: `scripts/check_missing_aliases.py`
334+
3. If new types found:
335+
- Determine discriminator field
336+
- Add semantic alias in `aliases.py`
337+
- Export from `__init__.py`
338+
- Add tests in `test_type_aliases.py`
339+
4. Commit with descriptive message explaining the discriminator
209340

210341
**Type Checking Best Practices**
211342
- Use `TYPE_CHECKING` for optional dependencies to avoid runtime import errors

src/adcp/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
from adcp.types.aliases import (
102102
ActivateSignalErrorResponse,
103103
ActivateSignalSuccessResponse,
104+
AgentDeployment,
105+
AgentDestination,
104106
BothPreviewRender,
105107
BuildCreativeErrorResponse,
106108
BuildCreativeSuccessResponse,
@@ -111,14 +113,21 @@
111113
InlineDaastAsset,
112114
InlineVastAsset,
113115
MediaSubAsset,
116+
PlatformDeployment,
117+
PlatformDestination,
114118
PreviewCreativeFormatRequest,
115119
PreviewCreativeInteractiveResponse,
116120
PreviewCreativeManifestRequest,
117121
PreviewCreativeStaticResponse,
122+
PropertyId,
118123
PropertyIdActivationKey,
124+
PropertyTag,
119125
PropertyTagActivationKey,
120126
ProvidePerformanceFeedbackErrorResponse,
121127
ProvidePerformanceFeedbackSuccessResponse,
128+
PublisherPropertiesAll,
129+
PublisherPropertiesById,
130+
PublisherPropertiesByTag,
122131
SyncCreativesErrorResponse,
123132
SyncCreativesSuccessResponse,
124133
TextSubAsset,
@@ -281,6 +290,8 @@
281290
# Semantic type aliases (for better API ergonomics)
282291
"ActivateSignalSuccessResponse",
283292
"ActivateSignalErrorResponse",
293+
"AgentDeployment",
294+
"AgentDestination",
284295
"BothPreviewRender",
285296
"BuildCreativeSuccessResponse",
286297
"BuildCreativeErrorResponse",
@@ -290,14 +301,21 @@
290301
"InlineDaastAsset",
291302
"InlineVastAsset",
292303
"MediaSubAsset",
304+
"PlatformDeployment",
305+
"PlatformDestination",
293306
"PreviewCreativeFormatRequest",
294307
"PreviewCreativeManifestRequest",
295308
"PreviewCreativeStaticResponse",
296309
"PreviewCreativeInteractiveResponse",
310+
"PropertyId",
297311
"PropertyIdActivationKey",
312+
"PropertyTag",
298313
"PropertyTagActivationKey",
299314
"ProvidePerformanceFeedbackSuccessResponse",
300315
"ProvidePerformanceFeedbackErrorResponse",
316+
"PublisherPropertiesAll",
317+
"PublisherPropertiesById",
318+
"PublisherPropertiesByTag",
301319
"SyncCreativesSuccessResponse",
302320
"SyncCreativesErrorResponse",
303321
"TextSubAsset",

src/adcp/types/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
InlineDaastAsset,
1818
InlineVastAsset,
1919
MediaSubAsset,
20+
PropertyId,
21+
PropertyTag,
22+
PublisherPropertiesAll,
23+
PublisherPropertiesById,
24+
PublisherPropertiesByTag,
2025
TextSubAsset,
2126
UrlDaastAsset,
2227
UrlPreviewRender,
@@ -89,6 +94,13 @@
8994
"UrlVastAsset",
9095
# Package type aliases
9196
"CreatedPackageReference",
97+
# Publisher properties types
98+
"PropertyId",
99+
"PropertyTag",
100+
# Publisher properties aliases
101+
"PublisherPropertiesAll",
102+
"PublisherPropertiesById",
103+
"PublisherPropertiesByTag",
92104
# Stable API types (commonly used)
93105
"BrandManifest",
94106
"Creative",

0 commit comments

Comments
 (0)