core: add helper to 'freeze' dataclasses, in order to derive a schema from the properties
This commit is contained in:
parent
86497f9b13
commit
ca4d58e4e7
5 changed files with 81 additions and 5 deletions
|
@ -67,7 +67,7 @@ class Entry:
|
||||||
|
|
||||||
|
|
||||||
# todo multiple threads? not sure if would help much... (+ need to find offset somehow?)
|
# 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())
|
inps = list(inputs())
|
||||||
|
|
||||||
base: List[PathIsh] = ['arbtt-dump', '--format=json']
|
base: List[PathIsh] = ['arbtt-dump', '--format=json']
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
|
from .common import assert_subpackage; assert_subpackage(__name__)
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
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:
|
def disable_cachew() -> None:
|
||||||
try:
|
try:
|
||||||
import cachew
|
import cachew
|
||||||
|
|
|
@ -556,3 +556,9 @@ def to_jsons(it) -> Iterable[Json]:
|
||||||
|
|
||||||
datetime_naive = datetime
|
datetime_naive = datetime
|
||||||
datetime_aware = 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
71
my/core/types.py
Normal 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
|
|
@ -16,3 +16,4 @@ from my.core.core_config import *
|
||||||
from my.core.error import *
|
from my.core.error import *
|
||||||
from my.core.util import *
|
from my.core.util import *
|
||||||
from my.core.discovery_pure import *
|
from my.core.discovery_pure import *
|
||||||
|
from my.core.types import *
|
||||||
|
|
Loading…
Add table
Reference in a new issue