Skip to content
Snippets Groups Projects
Select Git revision
  • 1c88bf0268e940626b91f43d8c80809bb917a6e9
  • main default protected
  • f-prefill
  • dev
  • f-docs-pylib
  • f-parse-value
  • f-compare
  • f-string-ids
  • f-217-set-special-property
  • f-filesystem-import
  • f-filesystem-link
  • f-filesystem-directory
  • f-filesystem-core
  • f-filesystem-cleanup
  • f-check-merge-entities
  • f-compare-enid
  • f-select-subproperties
  • v0.18.0
  • v0.17.0
  • v0.16.0
  • v0.15.1
  • v0.15.0
  • v0.14.0
  • v0.13.2
  • v0.13.1
  • v0.13.0
  • linkahead-rename-step-2
  • linkahead-rename-step-1
  • v0.12.0
  • v0.11.2
  • v0.11.1
  • v0.11.0
  • v0.10.0
  • v0.9.0
  • v0.8.0
  • v0.7.4
  • v0.7.3
37 results

cached.py

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    cached.py 5.91 KiB
    # -*- coding: utf-8 -*-
    #
    # This file is a part of the LinkAhead Project.
    #
    # Copyright (C) 2023 IndiScale GmbH <info@indiscale.com>
    # Copyright (C) 2023 Henrik tom Wörden <h.tomwoerden@indiscale.com>
    # Copyright (C) 2023 Daniel Hornung <d.hornung@indiscale.com>
    #
    # This program is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Affero General Public License as
    # published by the Free Software Foundation, either version 3 of the
    # License, or (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU Affero General Public License for more details.
    #
    # You should have received a copy of the GNU Affero General Public License
    # along with this program. If not, see <https://www.gnu.org/licenses/>.
    #
    
    """
    This module provides some cached versions of functions that retrieve Entities from a remote server.
    
    See also
    ========
    
    - ``cache_initialize(...)`` : Re-initialize the cache.
    - ``cache_clear()`` : Clear the cache.
    - ``cached_query(query)`` : A cached version of ``execute_query(query)``.
    - ``cached_get_entity_by(...)`` : Get an Entity by name, id, ...
    """
    
    from enum import Enum
    from functools import lru_cache
    from typing import Union
    
    from .utils import get_entity
    from .common.models import execute_query, Entity, Container
    
    
    # roughly 1GB for typical entity sizes
    DEFAULT_SIZE = 33333
    
    # This dict cache is solely for filling the real cache manually (e.g. to reuse older query results)
    _DUMMY_CACHE = {}
    
    
    class AccessType(Enum):
        """Different access types for cached queries.  Needed for filling the cache manually with
    :func:`cache_fill` .
    
        """
        QUERY = 1
        PATH = 2
        EID = 3
        NAME = 4
    
    
    def cached_get_entity_by(eid: Union[str, int] = None, name: str = None, path: str = None, query:
                             str = None) -> Entity:
        """Return a single entity that is identified uniquely by one argument.
    
    You must supply exactly one argument.
    
    If a query phrase is given, the result must be unique.  If this is not what you need, use
    :func:`cached_query` instead.
    
        """
        count = 0
        if eid is not None:
            count += 1
        if name is not None:
            count += 1
        if path is not None:
            count += 1
        if query is not None:
            count += 1
        if count != 1:
            raise ValueError("You must supply exactly one argument.")
    
        if eid is not None:
            return _cached_access(AccessType.EID, eid, unique=True)
        if name is not None:
            return _cached_access(AccessType.NAME, name, unique=True)
        if path is not None:
            return _cached_access(AccessType.PATH, path, unique=True)
        if query is not None:
            return _cached_access(AccessType.QUERY, query, unique=True)
    
        raise ValueError("Not all arguments may be None.")
    
    
    def cached_query(query_string) -> Container:
        """A cached version of :func:`linkahead.execute_query<linkahead.common.models.execute_query>`.
    
    All additional arguments are at their default values.
    
        """
        return _cached_access(AccessType.QUERY, query_string, unique=False)
    
    
    @lru_cache(maxsize=DEFAULT_SIZE)
    def _cached_access(kind: AccessType, value: Union[str, int], unique=True):
        # This is the function that is actually cached.
        # Due to the arguments, the cache has kind of separate sections for cached_query and
        # cached_get_entity_by with the different AccessTypes. However, there is only one cache size.
    
        # The dummy dict cache is only for filling the cache manually, it is deleted afterwards.
        if value in _DUMMY_CACHE:
            return _DUMMY_CACHE[value]
    
        if kind == AccessType.QUERY:
            return execute_query(value, unique=unique)
        if kind == AccessType.NAME:
            return get_entity.get_entity_by_name(value)
        if kind == AccessType.EID:
            return get_entity.get_entity_by_id(value)
        if kind == AccessType.PATH:
            return get_entity.get_entity_by_path(value)
    
        raise ValueError(f"Unknown AccessType: {kind}")
    
    
    def cache_clear() -> None:
        """Empty the cache that is used by `cached_query` and `cached_get_entity_by`."""
        _cached_access.cache_clear()
    
    
    def cache_info():
        """Return info about the cache that is used by `cached_query` and `cached_get_entity_by`.
    
    Returns
    -------
    
    out: named tuple
      See the standard library :func:`functools.lru_cache` for details."""
        return _cached_access.cache_info()
    
    
    def cache_initialize(maxsize=DEFAULT_SIZE) -> None:
        """Create a new cache with the given size for `cached_query` and `cached_get_entity_by`.
    
        This implies a call of :func:`cache_clear`, the old cache is emptied.
    
        """
        cache_clear()
        global _cached_access
        _cached_access = lru_cache(maxsize=maxsize)(_cached_access.__wrapped__)
    
    
    def cache_fill(items: dict, kind: AccessType = AccessType.EID, unique: bool = True) -> None:
        """Add entries to the cache manually.
    
        This allows to fill the cache without actually submitting queries.  Note that this does not
        overwrite existing entries with the same keys.
    
    Parameters
    ----------
    
    items: dict
      A dictionary with the entries to go into the cache.  The keys must be compatible with the
      AccessType given in ``kind``
    
    kind: AccessType, optional
      The AccessType, for example ID, name, path or query.
    
    unique: bool, optional
      If True, fills the cache for :func:`cached_get_entity_by`, presumably with
      :class:`linkahead.Entity<linkahead.common.models.Entity>` objects.  If False, the cache should be
      filled with :class:`linkahead.Container<linkahead.common.models.Container>` objects, for use with
      :func:`cached_query`.
    
        """
        # 1. add the given items to the corresponding dummy dict cache
        _DUMMY_CACHE.update(items)
    
        # 2. call the cache function with each key (this only results in a dict look up)
        for key in items.keys():
            _cached_access(kind, key, unique=unique)
    
        # 3. empty the dummy dict cache again
        _DUMMY_CACHE.clear()