Implement ParamModel: A Parameterized subclass based on type annotations#1133
Implement ParamModel: A Parameterized subclass based on type annotations#1133philippjfr wants to merge 6 commits intomainfrom
Conversation
|
Some suggestions instead of
|
|
|
|
Alternatively |
|
And if we're already riffing on more succinct naming for the Parameter factory, then I'd also suggest |
|
Okay, my favorite proposal now is |
I guess it would literally be ParameterizedFromTypeDeclarations?
I don't personally love Model since it is used to mean so many things that it's lost all meaning for me, but I don't object since Pydantic uses that term. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1133 +/- ##
==========================================
- Coverage 86.72% 86.38% -0.35%
==========================================
Files 9 10 +1
Lines 5290 5427 +137
==========================================
+ Hits 4588 4688 +100
- Misses 702 739 +37 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Based on discussion in #1066 I prototyped
ParamModel.Summary
Adds
ParamModel, a newParameterizedsubclass that synthesisesParameterinstances automatically from PEP 526 type annotations. This gives users a dataclass-like authoring experience without requiring manualParameterinstantiation for common cases.Motivation
After investigating
@dataclass_transform, it became clear that the decorator cannot bind RHSParameterdescriptor types to LHS attribute types in a way that satisfies type checkers, you'd still need to repeat yourself (string_param: str = String()) and even then checkers would seeString[str], notstr. Additionally,dataclass_transformrequires every field specifier to be enumerated upfront, which is impossible atParameterizedMetaclassdefinition time because not allParametersubclasses exist yet.'ParamField',sidesteps these constraints entirely: the metaclass handles synthesis at class creation time, and a dedicatedParamField()factory covers the cases where users need to supply explicit parameter configuration.Changes
ParamModel(decorated with@dataclass_transform(field_specifiers=(ParamField,))) overrides__new__to walk the class__annotations__dict at class creation time. For each non-private, non-ClassVarannotation it calls_annotation_parameter_factoryto map the annotation to an appropriateParametersubclass and infer constructor kwargs, then calls_build_parameter_from_fieldto construct the finalParameterobject. String annotations areeval'd against the module globals to supportfrom __future__ import annotations._annotation_parameter_factoryhandles the following mappings:bool→Booleanint→Integerfloat→Numberstr→Stringlist[T]→List(item_type=T)tuple[...]→Tuple(length=N)(fixed-length) orTuple(variable)dict[...]→Dictset→ClassSelector(class_=set)Literal[...]→Selector(objects=[...])T | None/Optional[T]→ wraps the inner type withallow_None=TrueAnnotated[T, {...}]→ passes the mapping as extra kwargs to the parameter constructorAny/object→ bareParameterParamField()is a typed factory (three overloads) that returns a_FieldSpecsentinel. It acceptsdefault,default_factory, aparameteroverride (class, callable, or instance), and arbitrary kwargs forwarded to the parameter constructor. Type checker overloads ensure thatdefault: FTanddefault_factory: Callable[[], FT]both propagateFTto the attribute type, matching dataclass semantics._build_parameter_from_fieldmerges the inferred factory and kwargs with anything supplied viaField, giving explicitFieldvalues precedence. If aParameterinstance is passed as theparameterargument it is shallow-copied and mutated rather than re-instantiated.Usage