core: add helper to 'freeze' dataclasses, in order to derive a schema from the properties

This commit is contained in:
Dima Gerasimov 2021-02-22 21:58:19 +00:00 committed by karlicoss
parent 86497f9b13
commit ca4d58e4e7
5 changed files with 81 additions and 5 deletions

View file

@ -67,7 +67,7 @@ class Entry:
# todo multiple threads? not sure if would help much... (+ need to find offset somehow?)
def entries() -> Iterable[Res[Entry]]:
def entries() -> Iterable[Entry]:
inps = list(inputs())
base: List[PathIsh] = ['arbtt-dump', '--format=json']

View file

@ -1,11 +1,9 @@
from .common import assert_subpackage; assert_subpackage(__name__)
from contextlib import contextmanager
from pathlib import Path
from typing import Optional
# can lead to some unexpected issues if you 'import cachew' which being in my/core directory.. so let's protect against it
# NOTE: if we use overlay, name can be smth like my.origg.my.core.cachew ...
assert 'my.core' in __name__, f'Expected module __name__ ({__name__}) to start with my.core'
def disable_cachew() -> None:
try:
import cachew

View file

@ -556,3 +556,9 @@ def to_jsons(it) -> Iterable[Json]:
datetime_naive = datetime
datetime_aware = datetime
def assert_subpackage(name: str) -> None:
# can lead to some unexpected issues if you 'import cachew' which being in my/core directory.. so let's protect against it
# NOTE: if we use overlay, name can be smth like my.origg.my.core.cachew ...
assert 'my.core' in name, f'Expected module __name__ ({name}) to start with my.core'

71
my/core/types.py Normal file
View file

@ -0,0 +1,71 @@
from .common import assert_subpackage; assert_subpackage(__name__)
import dataclasses as dcl
import inspect
from typing import TypeVar, Type
D = TypeVar('D')
def _freeze_dataclass(Orig: Type[D]):
ofields = [(f.name, f.type, f) for f in dcl.fields(Orig)]
# 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)
### tests
def test_freezer() -> None:
from typing import Any
@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']
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?
#
# TODO not entirely sure if best to use Frozen as the schema, or actually convert objects..
# guess need to experiment and see

View file

@ -16,3 +16,4 @@ from my.core.core_config import *
from my.core.error import *
from my.core.util import *
from my.core.discovery_pure import *
from my.core.types import *