Implement Python feature and resolver metaprogramming
Problem Description
This problem requires implementing a small Python "feature" framework using metaprogramming. The goal is to define a system that can parse type-annotated classes into Feature objects, parse functions into Resolver objects, and then automatically compute desired output features by chaining these resolvers based on known input feature values.
You must implement the parts marked Implement me! so that all provided tests pass without modification.
Step 1: Parse Features from Annotated Classes
Implement the @features class decorator. This decorator should transform a class definition as follows:
- For each type-annotated attribute in the decorated class, a
Featureinstance must be created. TheFeaturedataclass is defined withname: strandtyp: type.- The
nameof theFeatureshould be"<ClassName>.<field_name>"(for example,"User.name"). - The
typof theFeatureshould be the Python type of that attribute. This includes resolving forward references (string annotations like"User"should resolve to the actualUserclass).
- The
- The decorated class must gain a class attribute named
features, which is a list of allFeatureinstances for that class, ordered by their declaration. - Each annotated attribute of the class (e.g.,
User.id,User.email) must evaluate to its correspondingFeatureinstance, such that expressions likeUser.id == Feature(name="User.id", typ=int)are true.
Step 2: Parse Resolvers from Functions
Implement the @resolver function decorator. This decorator should transform a function definition as follows:
- The original function must be wrapped within a
Resolverdataclass instance. TheResolverdataclass is defined withinputs: list[Feature],output: Feature, andfn: Callable. - The
inputslist must be extracted from the function's parameter annotations, in order. These annotations are assumed to beFeatureobjects (e.g.,id: User.id). - The
outputmust be extracted from the function's return annotation, which is also assumed to be aFeatureobject. - The original function must be stored in the
Resolver.fnattribute so it can be called later.
You may choose to register all created Resolver instances in a global registry for use by the execute function in Step 3; the exact mechanism is up to you, as long as the behavior matches the tests.
Step 3: Implement the Execution Engine
Implement the execute function with the signature execute(inputs: dict[Feature | Any, Any], outputs: list[Feature | Any]) -> dict[Feature, Any].
- The
inputsparameter is a dictionary mappingFeatureobjects (or alternative keys you choose to support) to their concrete values (e.g.,{User.id: 1}). - The
outputsparameter is a list of requested outputFeatureobjects (or something you map to aFeature). - The function must return a dictionary mapping
Featureobjects to their computed values for all requested outputs. - It must use the
Resolverobjects created by the@resolverdecorator. - It must repeatedly apply resolvers whose input features are all known, continuing until all requested outputs are computed or no further progress can be made.
- For the cases in the provided tests, it must successfully compute
User.is_fraudfromUser.idby chaining through the appropriate resolvers.
Useful Tools (Not Required)
You may find the following Python tools useful:
cls.__annotations__cls.__name__setattr(obj, key, value)inspect.signature(fn)typing.get_type_hints
AI interview prep
Core dilemma
Designing and implementing a robust, efficient, and extensible dependency resolution and execution engine using Python's metaprogramming capabilities, balancing dynamic behavior with predictable execution.
3 tradeoffs and 3 stress tests locked
Interview prep
Unlock the interviewer X-Ray rubric, tradeoff matrix, stress tests, clarifying questions, and key concepts.
Implement Python feature and resolver metaprogramming
Problem Description
This problem requires implementing a small Python "feature" framework using metaprogramming. The goal is to define a system that can parse type-annotated classes into Feature objects, parse functions into Resolver objects, and then automatically compute desired output features by chaining these resolvers based on known input feature values.
You must implement the parts marked Implement me! so that all provided tests pass without modification.
Step 1: Parse Features from Annotated Classes
Implement the @features class decorator. This decorator should transform a class definition as follows:
- For each type-annotated attribute in the decorated class, a
Featureinstance must be created. TheFeaturedataclass is defined withname: strandtyp: type.- The
nameof theFeatureshould be"<ClassName>.<field_name>"(for example,"User.name"). - The
typof theFeatureshould be the Python type of that attribute. This includes resolving forward references (string annotations like"User"should resolve to the actualUserclass).
- The
- The decorated class must gain a class attribute named
features, which is a list of allFeatureinstances for that class, ordered by their declaration. - Each annotated attribute of the class (e.g.,
User.id,User.email) must evaluate to its correspondingFeatureinstance, such that expressions likeUser.id == Feature(name="User.id", typ=int)are true.
Step 2: Parse Resolvers from Functions
Implement the @resolver function decorator. This decorator should transform a function definition as follows:
- The original function must be wrapped within a
Resolverdataclass instance. TheResolverdataclass is defined withinputs: list[Feature],output: Feature, andfn: Callable. - The
inputslist must be extracted from the function's parameter annotations, in order. These annotations are assumed to beFeatureobjects (e.g.,id: User.id). - The
outputmust be extracted from the function's return annotation, which is also assumed to be aFeatureobject. - The original function must be stored in the
Resolver.fnattribute so it can be called later.
You may choose to register all created Resolver instances in a global registry for use by the execute function in Step 3; the exact mechanism is up to you, as long as the behavior matches the tests.
Step 3: Implement the Execution Engine
Implement the execute function with the signature execute(inputs: dict[Feature | Any, Any], outputs: list[Feature | Any]) -> dict[Feature, Any].
- The
inputsparameter is a dictionary mappingFeatureobjects (or alternative keys you choose to support) to their concrete values (e.g.,{User.id: 1}). - The
outputsparameter is a list of requested outputFeatureobjects (or something you map to aFeature). - The function must return a dictionary mapping
Featureobjects to their computed values for all requested outputs. - It must use the
Resolverobjects created by the@resolverdecorator. - It must repeatedly apply resolvers whose input features are all known, continuing until all requested outputs are computed or no further progress can be made.
- For the cases in the provided tests, it must successfully compute
User.is_fraudfromUser.idby chaining through the appropriate resolvers.
Useful Tools (Not Required)
You may find the following Python tools useful:
cls.__annotations__cls.__name__setattr(obj, key, value)inspect.signature(fn)typing.get_type_hints