diff --git a/co3/accessor.py b/co3/accessor.py index a7f941b..ac07414 100644 --- a/co3/accessor.py +++ b/co3/accessor.py @@ -19,10 +19,6 @@ class Accessor[C: Component](metaclass=ABCMeta): Access wrapper class for complex queries and easy integration with Composer tables. Implements high-level access to things like common constrained SELECT queries. - Parameters: - engine: SQLAlchemy engine to use for queries. Engine is initialized dynamically as - a property (based on the config) if not provided - Instance variables: access_log: time-indexed log of access queries performed ''' diff --git a/co3/collector.py b/co3/collector.py index 44fe2e9..cc977bf 100644 --- a/co3/collector.py +++ b/co3/collector.py @@ -105,7 +105,7 @@ class Collector[C: Component]: return receipt - def collect_inserts(self, receipts:list[str]|None=None): + def collect_inserts(self, receipts: list[str] | None = None): ''' Collect insert-ready dictionaries for the core primitive schema. This method is effectively a light wrapper around the File and Note-based collection logic diff --git a/co3/indexers/relational.py b/co3/indexers/relational.py new file mode 100644 index 0000000..32b23e1 --- /dev/null +++ b/co3/indexers/relational.py @@ -0,0 +1,43 @@ +from co3.indexer import Indexer + + +class RelationalIndexer(Indexer): + _cls_select_cache = {} + _cls_groupby_cache = defaultdict(dict) + + def __init__(self, accessor, cache_select=True, cache_groupby=True): + self.accessor = accessor + + # set instance caches; if remains None, methods can't index + self._select_cache = None + self._groupby_cache = None + + if cache_groupby and not cache_select: + raise ValueError('cannot cache groupbys without select caching enabled') + + if cache_select: + self._select_cache = self._cls_select_cache + + if cache_groupby: + self._groupby_cache = self._cls_groupby_cache + + self._access_lock = threading.Lock() + + def cache_clear(self, group_by_only=False): + self._groupby_cache.clear() + if not group_by_only: + self._select_cache.clear() + + def cache_block( + self, + table, + **kwargs, + ): + ''' + Provide a user-friendly, dynamically re-indexable + ''' + return CacheBlock( + indexer = self, + table = table, + **kwargs, + ) diff --git a/co3/mapper.py b/co3/mapper.py index 02c87a3..08795cb 100644 --- a/co3/mapper.py +++ b/co3/mapper.py @@ -388,6 +388,12 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]): automatic discovery structures could help constrain schema definitions to be more in-line with the CO3 operational model. That all being said, this is a large amount of complexity and should likely be avoided until necessary. + + .. admonition:: Instance variables + + - ``type_compose_cache``: index for previously computed compositions. This index + is reset if either ``attach`` or ``attach_many`` is + called to allow possible new type propagation. ''' def __init__( self, @@ -400,6 +406,18 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]): self.attr_compose_map = attr_compose_map self.coll_compose_map = coll_compose_map + self.type_compose_cache = {} + + def attach(self, *args, **kwargs): + self.type_compose_cache = {} + + super().attach(*args, **kwargs) + + def attach_many(self, *args, **kwargs): + self.type_compose_cache = {} + + super().attach_many(*args, **kwargs) + def compose( self, co3_ref: CO3 | type[CO3], @@ -426,6 +444,9 @@ 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] + comp_agg = None last_attr_comp = None last_coll_comps = None @@ -489,4 +510,6 @@ class ComposableMapper[C: ComposableComponent](Mapper[C]): last_attr_comp = attr_comp last_coll_comps = coll_list + self.type_compose_cache[type_ref] = comp_agg + return comp_agg