add caching to compose and collate methods, add wider collation result support
This commit is contained in:
parent
545c20e26f
commit
557df15d81
@ -316,7 +316,8 @@ class CO3(metaclass=FormatRegistryMeta):
|
||||
if args is None: args = []
|
||||
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)]
|
||||
|
||||
if key not in self.key_registry:
|
||||
@ -345,7 +346,8 @@ class CO3(metaclass=FormatRegistryMeta):
|
||||
|
||||
result = method(self, *args, **kwargs)
|
||||
|
||||
self._collate_cache[(key, group)] = result
|
||||
if pure_compose:
|
||||
self._collate_cache[(key, group)] = result
|
||||
|
||||
return result
|
||||
|
||||
|
@ -9,7 +9,7 @@ from co3.engines import SQLEngine
|
||||
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;
|
||||
``TabluarAccessor[Self, C]`` is of type ``type[RelationalAccessor[Self, C]]``
|
||||
|
@ -35,6 +35,7 @@ from inspect import signature
|
||||
from typing import Callable, Any
|
||||
from collections import defaultdict
|
||||
|
||||
from co3 import util
|
||||
from co3.co3 import CO3
|
||||
from co3.schema import Schema
|
||||
from co3.collector import Collector
|
||||
@ -247,20 +248,23 @@ class Mapper[C: Component]:
|
||||
for group_name, group_method in group_dict.items():
|
||||
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
|
||||
# pass to CO3.collate to call the method
|
||||
key_collation_data = {}
|
||||
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(
|
||||
f'Equivalence class "{collation_groups}" yielded no data, skipping'
|
||||
f'Method equivalence class "{collation_groups}" yielded '
|
||||
+ 'non-dict-like result, skipping'
|
||||
)
|
||||
continue
|
||||
|
||||
key_method_collation_data = util.types.dictlike_to_dict(collation_result)
|
||||
|
||||
for collation_group in collation_groups:
|
||||
# gather connective data for collation components
|
||||
# -> 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.coll_compose_map = coll_compose_map
|
||||
|
||||
self.type_compose_cache = {}
|
||||
self._compose_cache = {}
|
||||
|
||||
def attach(self, *args, **kwargs):
|
||||
self.type_compose_cache = {}
|
||||
self._compose_cache = {}
|
||||
|
||||
super().attach(*args, **kwargs)
|
||||
|
||||
def attach_many(self, *args, **kwargs):
|
||||
self.type_compose_cache = {}
|
||||
self._compose_cache = {}
|
||||
|
||||
super().attach_many(*args, **kwargs)
|
||||
|
||||
def compose(
|
||||
self,
|
||||
co3_ref: CO3 | type[CO3],
|
||||
groups: list[str] | None = None,
|
||||
*compose_args,
|
||||
**compose_kwargs,
|
||||
co3_ref : CO3 | type[CO3],
|
||||
groups : list[str] | None = None,
|
||||
compose_args : list | None = None,
|
||||
compose_kwargs : dict | None = None,
|
||||
):
|
||||
'''
|
||||
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):
|
||||
type_ref = co3_ref.__class__
|
||||
|
||||
if type_ref in self.type_compose_cache:
|
||||
return self.type_compose_cache[type_ref]
|
||||
if groups is None: groups = []
|
||||
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
|
||||
last_attr_comp = None
|
||||
@ -477,39 +487,39 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]):
|
||||
|
||||
# compose horizontally with components from provided action groups
|
||||
coll_list = []
|
||||
if groups is not None:
|
||||
for group in groups:
|
||||
coll_comp = self.get_coll_comp(_cls, group=group)
|
||||
for group in groups:
|
||||
coll_comp = self.get_coll_comp(_cls, group=group)
|
||||
|
||||
if coll_comp is None:
|
||||
continue
|
||||
if coll_comp is None:
|
||||
continue
|
||||
|
||||
# valid collation comps added to coll_list, to be passed to the
|
||||
# coll_map in the next iteration
|
||||
coll_list.append(coll_comp)
|
||||
# valid collation comps added to coll_list, to be passed to the
|
||||
# coll_map in the next iteration
|
||||
coll_list.append(coll_comp)
|
||||
|
||||
# note how the join condition is specified using the non-composite
|
||||
# `attr_comp` and new `coll_comp`; the composite doesn't typically
|
||||
# have the same attribute access and needs a ref to a specific comp
|
||||
if len(signature(self.coll_compose_map).parameters) > 2:
|
||||
compose_condition = self.coll_compose_map(
|
||||
attr_comp,
|
||||
coll_comp,
|
||||
last_coll_comps
|
||||
)
|
||||
else:
|
||||
compose_condition = self.coll_compose_map(attr_comp, coll_comp)
|
||||
|
||||
comp_agg = comp_agg.compose(
|
||||
# note how the join condition is specified using the non-composite
|
||||
# `attr_comp` and new `coll_comp`; the composite doesn't typically
|
||||
# have the same attribute access and needs a ref to a specific comp
|
||||
if len(signature(self.coll_compose_map).parameters) > 2:
|
||||
compose_condition = self.coll_compose_map(
|
||||
attr_comp,
|
||||
coll_comp,
|
||||
compose_condition,
|
||||
*compose_args,
|
||||
**compose_kwargs,
|
||||
last_coll_comps
|
||||
)
|
||||
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_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
|
||||
|
@ -1,2 +1,3 @@
|
||||
from co3.util import db
|
||||
from co3.util import regex
|
||||
from co3.util import types
|
||||
|
@ -1,6 +1,45 @@
|
||||
from typing import TypeVar
|
||||
from collections import namedtuple
|
||||
from dataclasses import is_dataclass, asdict
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# custom types
|
||||
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