|
|
|
|
| """Config for the protein folding model and experiment.""" |
|
|
| from collections.abc import Mapping |
| import copy |
| import dataclasses |
| import types |
| import typing |
| from typing import Any, ClassVar, TypeVar |
|
|
|
|
| _T = TypeVar('_T') |
| _ConfigT = TypeVar('_ConfigT', bound='BaseConfig') |
|
|
|
|
| def _strip_optional(t: type[Any]) -> type[Any]: |
| """Transforms type annotations of the form `T | None` to `T`.""" |
| if typing.get_origin(t) in (typing.Union, types.UnionType): |
| args = set(typing.get_args(t)) - {types.NoneType} |
| if len(args) == 1: |
| return args.pop() |
| return t |
|
|
|
|
| _NO_UPDATE = object() |
|
|
|
|
| class _Autocreate: |
|
|
| def __init__(self, **defaults: Any): |
| self.defaults = defaults |
|
|
|
|
| def autocreate(**defaults: Any) -> Any: |
| """Marks a field as having a default factory derived from its type.""" |
| return _Autocreate(**defaults) |
|
|
|
|
| def _clone_field( |
| field: dataclasses.Field[_T], new_default: _T |
| ) -> dataclasses.Field[_T]: |
| if new_default is _NO_UPDATE: |
| return copy.copy(field) |
| return dataclasses.field( |
| default=new_default, |
| init=True, |
| kw_only=True, |
| repr=field.repr, |
| hash=field.hash, |
| compare=field.compare, |
| metadata=field.metadata, |
| ) |
|
|
|
|
| @typing.dataclass_transform() |
| class ConfigMeta(type): |
| """Metaclass that synthesizes a __post_init__ that coerces dicts to Config subclass instances.""" |
|
|
| def __new__(mcs, name, bases, classdict): |
| cls = super().__new__(mcs, name, bases, classdict) |
|
|
| def _coercable_fields(self) -> Mapping[str, tuple[ConfigMeta, Any]]: |
| type_hints = typing.get_type_hints(self.__class__) |
| fields = dataclasses.fields(self.__class__) |
| field_to_type_and_default = { |
| field.name: (_strip_optional(type_hints[field.name]), field.default) |
| for field in fields |
| } |
| coercable_fields = { |
| f: t |
| for f, t in field_to_type_and_default.items() |
| if issubclass(type(t[0]), ConfigMeta) |
| } |
| return coercable_fields |
|
|
| cls._coercable_fields = property(_coercable_fields) |
|
|
| old_post_init = getattr(cls, '__post_init__', None) |
|
|
| def _post_init(self) -> None: |
| |
| |
| for field_name, ( |
| field_type, |
| field_default, |
| ) in self._coercable_fields.items(): |
| field_value = getattr(self, field_name) |
| if field_value is None: |
| continue |
| try: |
| match field_value: |
| case _Autocreate(): |
| |
| setattr(self, field_name, field_type(**field_value.defaults)) |
| case Mapping(): |
| |
| |
| args = {} |
| |
| if isinstance(field_default, _Autocreate): |
| args.update(field_default.defaults) |
| args.update(field_value) |
| setattr(self, field_name, field_type(**args)) |
| case _: |
| pass |
| except TypeError as e: |
| raise TypeError( |
| f'Failure while coercing field {field_name!r} of' |
| f' {self.__class__.__qualname__}' |
| ) from e |
| if old_post_init: |
| old_post_init(self) |
|
|
| cls.__post_init__ = _post_init |
|
|
| return dataclasses.dataclass(kw_only=True)(cls) |
|
|
|
|
| class BaseConfig(metaclass=ConfigMeta): |
| """Config base class. |
| |
| Subclassing Config automatically makes the subclass a kw_only dataclass with |
| a `__post_init__` that coerces Config-subclass field values from mappings to |
| instances of the right type. |
| """ |
| |
| __dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]] |
|
|
| |
| @property |
| def _coercable_fields(self) -> Mapping[str, tuple[type['BaseConfig'], Any]]: |
| return {} |
|
|
| def as_dict(self) -> Mapping[str, Any]: |
| result = dataclasses.asdict(self) |
| for field_name in self._coercable_fields: |
| field_value = getattr(self, field_name, None) |
| if isinstance(field_value, BaseConfig): |
| result[field_name] = field_value.as_dict() |
| return result |
|
|