-
Notifications
You must be signed in to change notification settings - Fork 1
feat: improve type ergonomics for library consumers #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Great PR! The enum coercion and dict-to-model coercion will significantly improve the developer experience. Regarding list variance - the documentation is helpful, but I have one additional suggestion that could eliminate most Suggestion: Add list item coercion for fields with base typesSimilar to how enums are coerced, you could add a def coerce_list_items(base_class: type[M]) -> Callable[[Any], list[M] | None]:
"""Coerce list items to base class instances.
This handles the list variance issue by accepting any iterable of items
that are instances of (or can be coerced to) the base class.
"""
def validator(v: Any) -> list[M] | None:
if v is None:
return None
if not isinstance(v, (list, tuple)):
return v # Let Pydantic handle the error
result = []
for item in v:
if isinstance(item, base_class):
# Already correct type (or subclass) - keep as-is
result.append(item)
elif isinstance(item, dict):
# Dict coercion
result.append(base_class(**item))
else:
result.append(item) # Let Pydantic validate
return result
return validatorThen apply to _patch_field_annotation(
PackageRequest,
"creatives",
Annotated[
list[CreativeAsset] | None,
BeforeValidator(coerce_list_items(CreativeAsset))
],
)This approach:
The key insight is that the validator's input type is This wouldn't work for all use cases (e.g., function signatures in user code), but it would eliminate the most common pain point: constructing request objects with extended types. Alternatively, if you want to keep the current approach, the documentation is solid. One minor addition might be a concrete example in the class PackageRequest(BaseModel):
"""...
Note: If using extended CreativeAsset subclasses, use cast():
creatives = cast(list[CreativeAsset], my_extended_creatives)
PackageRequest(creatives=creatives, ...)
"""Either way, this PR is a big improvement! 🎉 |
a1e9f70 to
ac554d8
Compare
Add flexible input coercion for request types that reduces boilerplate
when constructing API requests. All changes are backward compatible.
Improvements:
- Enum fields accept string values (e.g., type="video")
- List[Enum] fields accept string lists (e.g., asset_types=["image", "video"])
- Context/Ext fields accept dicts (e.g., context={"key": "value"})
- FieldModel lists accept strings (e.g., fields=["creative_id", "name"])
- Sort fields accept string enums (e.g., field="name", direction="asc")
- Subclass lists accepted without cast() for all major list fields
Affected types:
- ListCreativeFormatsRequest (type, asset_types, context, ext)
- ListCreativesRequest (fields, context, ext, sort)
- GetProductsRequest (context, ext)
- PackageRequest (creatives, ext)
- CreateMediaBuyRequest (packages, context, ext)
- UpdateMediaBuyRequest.Packages (creatives, creative_assignments)
The list variance issue is now fully resolved - users can pass list[Subclass]
where list[BaseClass] is expected without needing cast().
Closes #102
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ac554d8 to
d9f1a65
Compare
Replace manually-maintained _ergonomic.py with auto-generated version. The new script introspects Pydantic models at runtime to detect fields needing coercion, eliminating manual maintenance when schemas change. Changes: - Add scripts/generate_ergonomic_coercion.py for auto-generation - Integrate into generate_types.py pipeline - Add rationale comment explaining import-time patching choice - Remove unnecessary hasattr check in _patch_field_annotation - Discover additional coercion opportunities (pacing enum, packages list) The generator detects: - Enum fields -> string coercion - list[Enum] fields -> string list coercion - ContextObject/ExtensionObject -> dict coercion - list[BaseModel] fields -> subclass list coercion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Different Python/mypy versions report different error codes for the same type issues (return-value vs no-any-return). Using plain `# type: ignore` comments avoids unused-ignore errors across versions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Apply the same BeforeValidator coercion pattern from PR #103 to response types. This eliminates the need for cast() calls when constructing response objects with subclass instances or dict coercion. Response types now support: - Dict coercion for context/ext fields - Subclass list acceptance for products, creatives, formats, packages, errors, and media_buy_deliveries Closes #105 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: extend type ergonomics to response types Apply the same BeforeValidator coercion pattern from PR #103 to response types. This eliminates the need for cast() calls when constructing response objects with subclass instances or dict coercion. Response types now support: - Dict coercion for context/ext fields - Subclass list acceptance for products, creatives, formats, packages, errors, and media_buy_deliveries Closes #105 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: improve test imports and documentation - Update response type tests to import from public API (adcp.types) instead of internal generated_poc modules - Use semantic alias CreateMediaBuySuccessResponse instead of CreateMediaBuyResponse1 for clearer intent - Add clarifying comments for # type: ignore annotations explaining Python list covariance limitation - Add tests for GetProductsResponse.products subclass acceptance - Add tests for errors field coercion - Document _ergonomic.py in CLAUDE.md import architecture section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Summary
type="video"forFormatCategory.video)asset_types=["image", "video"])context={"key": "value"})fields=["creative_id", "name"])Changes
New files
src/adcp/types/coercion.py- Validator utilities and documentation for type coercionsrc/adcp/types/_ergonomic.py- Applies coercion validators to generated types at import timetests/test_type_coercion.py- Comprehensive tests for all coercion behavior (32 tests)Modified files
src/adcp/types/__init__.py- Import_ergonomicmodule to apply coercion, add docstring examplesAffected Types
ListCreativeFormatsRequest(type, asset_types, context, ext)ListCreativesRequest(fields, context, ext, sort)GetProductsRequest(context, ext)PackageRequest(ext)Sort(field, direction)Usage Examples
Backward Compatibility
All changes are backward compatible. Existing code using explicit enum values and model instances continues to work unchanged.
Test plan
Closes #102
🤖 Generated with Claude Code