add caching to compose and collate methods, add wider collation result support
This commit is contained in:
parent
686e1efd3a
commit
1ed4722e77
@ -316,7 +316,8 @@ class CO3(metaclass=FormatRegistryMeta):
|
|||||||
if args is None: args = []
|
if args is None: args = []
|
||||||
if kwargs is None: kwargs = {}
|
if kwargs is None: kwargs = {}
|
||||||
|
|
||||||
if (key, group) in self._collate_cache:
|
pure_compose = not (args or kwargs)
|
||||||
|
if (key, group) in self._collate_cache and pure_compose:
|
||||||
return self._collate_cache[(key, group)]
|
return self._collate_cache[(key, group)]
|
||||||
|
|
||||||
if key not in self.key_registry:
|
if key not in self.key_registry:
|
||||||
@ -345,7 +346,8 @@ class CO3(metaclass=FormatRegistryMeta):
|
|||||||
|
|
||||||
result = method(self, *args, **kwargs)
|
result = method(self, *args, **kwargs)
|
||||||
|
|
||||||
self._collate_cache[(key, group)] = result
|
if pure_compose:
|
||||||
|
self._collate_cache[(key, group)] = result
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from co3.engines import SQLEngine
|
|||||||
from co3.components import Relation, SQLTable
|
from co3.components import Relation, SQLTable
|
||||||
|
|
||||||
|
|
||||||
class RelationalDatabase[C: RelationR](Database):
|
class RelationalDatabase[C: Relation](Database[C]):
|
||||||
'''
|
'''
|
||||||
accessor/manager assignments satisfy supertype's type settings;
|
accessor/manager assignments satisfy supertype's type settings;
|
||||||
``TabluarAccessor[Self, C]`` is of type ``type[RelationalAccessor[Self, C]]``
|
``TabluarAccessor[Self, C]`` is of type ``type[RelationalAccessor[Self, C]]``
|
||||||
|
@ -35,6 +35,7 @@ from inspect import signature
|
|||||||
from typing import Callable, Any
|
from typing import Callable, Any
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from co3 import util
|
||||||
from co3.co3 import CO3
|
from co3.co3 import CO3
|
||||||
from co3.schema import Schema
|
from co3.schema import Schema
|
||||||
from co3.collector import Collector
|
from co3.collector import Collector
|
||||||
@ -247,20 +248,23 @@ class Mapper[C: Component]:
|
|||||||
for group_name, group_method in group_dict.items():
|
for group_name, group_method in group_dict.items():
|
||||||
method_groups[group_method].append(group_name)
|
method_groups[group_method].append(group_name)
|
||||||
|
|
||||||
logger.debug(f'Equivalence classes: "{list(method_groups.values())}"')
|
logger.debug(f'Method equivalence classes: "{list(method_groups.values())}"')
|
||||||
|
|
||||||
# collate for method equivalence classes; only need on representative group to
|
# collate for method equivalence classes; only need on representative group to
|
||||||
# pass to CO3.collate to call the method
|
# pass to CO3.collate to call the method
|
||||||
key_collation_data = {}
|
key_collation_data = {}
|
||||||
for collation_method, collation_groups in method_groups.items():
|
for collation_method, collation_groups in method_groups.items():
|
||||||
key_method_collation_data = obj.collate(key, group=collation_groups[0])
|
collation_result = obj.collate(key, group=collation_groups[0])
|
||||||
|
|
||||||
if key_method_collation_data is None:
|
if not util.types.is_dictlike(collation_result):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f'Equivalence class "{collation_groups}" yielded no data, skipping'
|
f'Method equivalence class "{collation_groups}" yielded '
|
||||||
|
+ 'non-dict-like result, skipping'
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
key_method_collation_data = util.types.dictlike_to_dict(collation_result)
|
||||||
|
|
||||||
for collation_group in collation_groups:
|
for collation_group in collation_groups:
|
||||||
# gather connective data for collation components
|
# gather connective data for collation components
|
||||||
# -> we do this here as it's obj dependent
|
# -> we do this here as it's obj dependent
|
||||||
@ -406,24 +410,24 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]):
|
|||||||
self.attr_compose_map = attr_compose_map
|
self.attr_compose_map = attr_compose_map
|
||||||
self.coll_compose_map = coll_compose_map
|
self.coll_compose_map = coll_compose_map
|
||||||
|
|
||||||
self.type_compose_cache = {}
|
self._compose_cache = {}
|
||||||
|
|
||||||
def attach(self, *args, **kwargs):
|
def attach(self, *args, **kwargs):
|
||||||
self.type_compose_cache = {}
|
self._compose_cache = {}
|
||||||
|
|
||||||
super().attach(*args, **kwargs)
|
super().attach(*args, **kwargs)
|
||||||
|
|
||||||
def attach_many(self, *args, **kwargs):
|
def attach_many(self, *args, **kwargs):
|
||||||
self.type_compose_cache = {}
|
self._compose_cache = {}
|
||||||
|
|
||||||
super().attach_many(*args, **kwargs)
|
super().attach_many(*args, **kwargs)
|
||||||
|
|
||||||
def compose(
|
def compose(
|
||||||
self,
|
self,
|
||||||
co3_ref: CO3 | type[CO3],
|
co3_ref : CO3 | type[CO3],
|
||||||
groups: list[str] | None = None,
|
groups : list[str] | None = None,
|
||||||
*compose_args,
|
compose_args : list | None = None,
|
||||||
**compose_kwargs,
|
compose_kwargs : dict | None = None,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Compose tables up the type hierarchy, and across through action groups to
|
Compose tables up the type hierarchy, and across through action groups to
|
||||||
@ -444,8 +448,14 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]):
|
|||||||
if isinstance(co3_ref, CO3):
|
if isinstance(co3_ref, CO3):
|
||||||
type_ref = co3_ref.__class__
|
type_ref = co3_ref.__class__
|
||||||
|
|
||||||
if type_ref in self.type_compose_cache:
|
if groups is None: groups = []
|
||||||
return self.type_compose_cache[type_ref]
|
if compose_args is None: compose_args = []
|
||||||
|
if compose_kwargs is None: compose_kwargs = {}
|
||||||
|
|
||||||
|
idx_tup = (type_ref, tuple(groups))
|
||||||
|
pure_compose = not (compose_args or compose_kwargs)
|
||||||
|
if idx_tup in self._compose_cache and pure_compose:
|
||||||
|
return self._compose_cache[idx_tup]
|
||||||
|
|
||||||
comp_agg = None
|
comp_agg = None
|
||||||
last_attr_comp = None
|
last_attr_comp = None
|
||||||
@ -477,39 +487,39 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]):
|
|||||||
|
|
||||||
# compose horizontally with components from provided action groups
|
# compose horizontally with components from provided action groups
|
||||||
coll_list = []
|
coll_list = []
|
||||||
if groups is not None:
|
for group in groups:
|
||||||
for group in groups:
|
coll_comp = self.get_coll_comp(_cls, group=group)
|
||||||
coll_comp = self.get_coll_comp(_cls, group=group)
|
|
||||||
|
|
||||||
if coll_comp is None:
|
if coll_comp is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# valid collation comps added to coll_list, to be passed to the
|
# valid collation comps added to coll_list, to be passed to the
|
||||||
# coll_map in the next iteration
|
# coll_map in the next iteration
|
||||||
coll_list.append(coll_comp)
|
coll_list.append(coll_comp)
|
||||||
|
|
||||||
# note how the join condition is specified using the non-composite
|
# note how the join condition is specified using the non-composite
|
||||||
# `attr_comp` and new `coll_comp`; the composite doesn't typically
|
# `attr_comp` and new `coll_comp`; the composite doesn't typically
|
||||||
# have the same attribute access and needs a ref to a specific comp
|
# have the same attribute access and needs a ref to a specific comp
|
||||||
if len(signature(self.coll_compose_map).parameters) > 2:
|
if len(signature(self.coll_compose_map).parameters) > 2:
|
||||||
compose_condition = self.coll_compose_map(
|
compose_condition = self.coll_compose_map(
|
||||||
attr_comp,
|
attr_comp,
|
||||||
coll_comp,
|
|
||||||
last_coll_comps
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
compose_condition = self.coll_compose_map(attr_comp, coll_comp)
|
|
||||||
|
|
||||||
comp_agg = comp_agg.compose(
|
|
||||||
coll_comp,
|
coll_comp,
|
||||||
compose_condition,
|
last_coll_comps
|
||||||
*compose_args,
|
|
||||||
**compose_kwargs,
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
compose_condition = self.coll_compose_map(attr_comp, coll_comp)
|
||||||
|
|
||||||
|
comp_agg = comp_agg.compose(
|
||||||
|
coll_comp,
|
||||||
|
compose_condition,
|
||||||
|
*compose_args,
|
||||||
|
**compose_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
last_attr_comp = attr_comp
|
last_attr_comp = attr_comp
|
||||||
last_coll_comps = coll_list
|
last_coll_comps = coll_list
|
||||||
|
|
||||||
self.type_compose_cache[type_ref] = comp_agg
|
if not pure_compose:
|
||||||
|
self._compose_cache[idx_tup] = comp_agg
|
||||||
|
|
||||||
return comp_agg
|
return comp_agg
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
from co3.util import db
|
from co3.util import db
|
||||||
from co3.util import regex
|
from co3.util import regex
|
||||||
|
from co3.util import types
|
||||||
|
@ -1,6 +1,45 @@
|
|||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
from collections import namedtuple
|
||||||
|
from dataclasses import is_dataclass, asdict
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# custom types
|
||||||
SQLTableLike = TypeVar('SQLTableLike', bound=sa.Table | sa.Subquery | sa.Join)
|
SQLTableLike = TypeVar('SQLTableLike', bound=sa.Table | sa.Subquery | sa.Join)
|
||||||
|
|
||||||
|
|
||||||
|
# type checking/conversion methods
|
||||||
|
def is_dataclass_instance(obj) -> bool:
|
||||||
|
return is_dataclass(obj) and not isinstance(obj, type)
|
||||||
|
|
||||||
|
def is_namedtuple_instance(obj) -> bool:
|
||||||
|
return (
|
||||||
|
isinstance(obj, tuple) and
|
||||||
|
hasattr(obj, '_asdict') and
|
||||||
|
hasattr(obj, '_fields')
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_dictlike(obj) -> bool:
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return True
|
||||||
|
elif is_dataclass_instance(obj):
|
||||||
|
return True
|
||||||
|
elif is_namedtuple_instance(obj):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def dictlike_to_dict(obj) -> dict:
|
||||||
|
'''
|
||||||
|
Attempt to convert provided object to dict. Will return dict no matter what, including
|
||||||
|
an empty dict if not dict-like. Consider using ``is_dictlike`` to determine if this
|
||||||
|
method should be called.
|
||||||
|
'''
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return obj
|
||||||
|
elif is_dataclass_instance(obj):
|
||||||
|
return asdict(obj)
|
||||||
|
elif is_namedtuple_instance(obj):
|
||||||
|
return obj._asdict()
|
||||||
|
|
||||||
|
return {}
|
||||||
|
Loading…
Reference in New Issue
Block a user