from .internal import assert_subpackage; assert_subpackage(__name__) import dataclasses as dcl import inspect from typing import Any, Type, TypeVar D = TypeVar('D') def _freeze_dataclass(Orig: Type[D]): ofields = [(f.name, f.type, f) for f in dcl.fields(Orig)] # type: ignore[arg-type] # see https://github.com/python/typing_extensions/issues/115 # extract properties along with their types props = list(inspect.getmembers(Orig, lambda o: isinstance(o, property))) pfields = [(name, inspect.signature(getattr(prop, 'fget')).return_annotation) for name, prop in props] # FIXME not sure about name? # NOTE: sadly passing bases=[Orig] won't work, python won't let us override properties with fields RRR = dcl.make_dataclass('RRR', fields=[*ofields, *pfields]) # todo maybe even declare as slots? return props, RRR # todo need some decorator thingie? from typing import Generic class Freezer(Generic[D]): ''' Some magic which converts dataclass properties into fields. It could be useful for better serialization, for performance, for using type as a schema. For now only supports dataclasses. ''' def __init__(self, Orig: Type[D]) -> None: self.Orig = Orig self.props, self.Frozen = _freeze_dataclass(Orig) def freeze(self, value: D) -> D: pvalues = {name: getattr(value, name) for name, _ in self.props} return self.Frozen(**dcl.asdict(value), **pvalues) # type: ignore[call-overload] # see https://github.com/python/typing_extensions/issues/115 ### tests # this needs to be defined here to prevent a mypy bug # see https://github.com/python/mypy/issues/7281 @dcl.dataclass class _A: x: Any # TODO what about error handling? @property def typed(self) -> int: return self.x['an_int'] @property def untyped(self): return self.x['an_any'] def test_freezer() -> None: val = _A(x={ 'an_int': 123, 'an_any': [1, 2, 3], }) af = Freezer(_A) fval = af.freeze(val) fd = vars(fval) assert fd['typed'] == 123 assert fd['untyped'] == [1, 2, 3] ### # TODO shit. what to do with exceptions? # e.g. good testcase is date parsing issue. should def yield Exception in this case # fundamentally it should just be Exception aware, dunno # # TODO not entirely sure if best to use Frozen as the schema, or actually convert objects.. # guess need to experiment and see