From ca4d58e4e741fb4f1fa1b5d6b2691dc8f5aa4730 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Mon, 22 Feb 2021 21:58:19 +0000 Subject: [PATCH] core: add helper to 'freeze' dataclasses, in order to derive a schema from the properties --- my/arbtt.py | 2 +- my/core/cachew.py | 6 ++-- my/core/common.py | 6 ++++ my/core/types.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++ tests/core.py | 1 + 5 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 my/core/types.py diff --git a/my/arbtt.py b/my/arbtt.py index 2ef82a4..ac1a4e3 100644 --- a/my/arbtt.py +++ b/my/arbtt.py @@ -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'] diff --git a/my/core/cachew.py b/my/core/cachew.py index 5236a7c..4ecf51d 100644 --- a/my/core/cachew.py +++ b/my/core/cachew.py @@ -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 diff --git a/my/core/common.py b/my/core/common.py index b39a10a..6e2251b 100644 --- a/my/core/common.py +++ b/my/core/common.py @@ -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' diff --git a/my/core/types.py b/my/core/types.py new file mode 100644 index 0000000..ad42b7f --- /dev/null +++ b/my/core/types.py @@ -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 diff --git a/tests/core.py b/tests/core.py index ad20b59..5684bd9 100644 --- a/tests/core.py +++ b/tests/core.py @@ -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 *