|
def create_dataclass_from_callable( |
|
callable_obj: Callable, |
|
overrides: Optional[Dict[str, Tuple[Type, Any]]] = None, |
|
) -> List[Tuple[str, Type, Any]]: |
|
""" |
|
Creates the fields of a dataclass from a `Callable` that includes all |
|
parameters of the callable as typed fields with default values inferred or |
|
taken from type hints. The function also accepts a dictionary containing |
|
parameter names together with a tuple of a type and default to allow |
|
specification of or override (un)typed defaults from the target callable. |
|
|
|
Args: |
|
callable_obj (Callable): The callable object to create a dataclass from. |
|
overrides (Optional[Dict[str, Tuple[Type, Any]]]): Dictionary to |
|
override inferred types and default values. Each dict value is a tuple |
|
(Type, default_value). |
|
|
|
Returns: |
|
Fields that can be used to construct a new dataclass type that |
|
represents the interface of the callable. |
|
|
|
Examples: |
|
>>> from pprint import pprint |
|
>>> custom_types_defaults: Dict[str, Tuple[Type, Any]] = { |
|
... "penalty": (str, "l2"), |
|
... "class_weight": (Optional[dict], None), |
|
... "random_state": (Optional[int], None), |
|
... "max_iter": (int, 2000), |
|
... "n_jobs": (Optional[int], None), |
|
... "l1_ratio": (Optional[float], None), |
|
... } |
|
>>> fields = create_dataclass_from_callable(LogisticRegression, custom_types_defaults) |
|
>>> LogisticRegressionInterface = dataclasses.make_dataclass( |
|
... "LogisticRegressionInterface", fields, bases=(DataClassJSONMixin,) |
|
... ) |
|
>>> lr_instance = LogisticRegressionInterface() |
|
>>> isinstance(lr_instance, DataClassJSONMixin) |
|
True |
|
>>> pprint(lr_instance) |
|
LogisticRegressionInterface(penalty='l2', |
|
dual=False, |
|
tol=0.0001, |
|
C=1.0, |
|
fit_intercept=True, |
|
intercept_scaling=1, |
|
class_weight=None, |
|
random_state=None, |
|
solver='lbfgs', |
|
max_iter=2000, |
|
multi_class='auto', |
|
verbose=0, |
|
warm_start=False, |
|
n_jobs=None, |
|
l1_ratio=None) |
|
""" |
|
if inspect.isclass(callable_obj): |
|
func = callable_obj.__init__ |
|
else: |
|
func = callable_obj |
|
|
|
signature = inspect.signature(func) |
|
type_hints = get_type_hints(func) |
|
|
|
fields = [] |
|
for name, param in signature.parameters.items(): |
|
if name == "self": |
|
continue |
|
|
|
if overrides and name in overrides: |
|
field_type, default_value = overrides[name] |
|
else: |
|
inferred_type = infer_type_from_default(param.default) |
|
field_type = type_hints.get(name, inferred_type) |
|
default_value = ( |
|
param.default |
|
if param.default is not inspect.Parameter.empty |
|
else dataclasses.field(default_factory=lambda: None) |
|
) |
|
|
|
fields.append((name, field_type, default_value)) |
|
|
|
return fields |
flytekit's dataclass transformer requires JSON-serializable dataclasses (docs). Currently, we construct JSON-serializable dataclasses for arguments or whole function interfaces along the lines of the pseudocode
approximating the usage in the logistic regression example, which, accounting for the dependency on
flytezen/src/flytezen/configuration.py
Lines 36 to 117 in 526203d
create_dataclass_from_callable, is verbose, even though it behaves as expected.Based on the documentation for hydra_zen.builds zen_dataclass argument it seems like it should be possible to use the dataclasses constructed by hydra-zen instead
and eliminate
create_dataclass_from_callablefrom flytezen altogether, but this produces