77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
from .internal import assert_subpackage; assert_subpackage(__name__)
|
|
|
|
import dataclasses as dcl
|
|
import inspect
|
|
from typing import TypeVar, Type, Any
|
|
|
|
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=dict(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
|