1"""This example creates a new dogpile.cache backend that will persist data in a
2dictionary which is local to the current session.   remove() the session and
3the cache is gone.
4
5Create a new Dogpile cache backend that will store
6cached data local to the current Session.
7
8This is an advanced example which assumes familiarity
9with the basic operation of CachingQuery.
10
11"""
12
13from dogpile.cache.api import CacheBackend
14from dogpile.cache.api import NO_VALUE
15from dogpile.cache.region import register_backend
16
17
18class ScopedSessionBackend(CacheBackend):
19    """A dogpile backend which will cache objects locally on
20    the current session.
21
22    When used with the query_cache system, the effect is that the objects
23    in the cache are the same as that within the session - the merge()
24    is a formality that doesn't actually create a second instance.
25    This makes it safe to use for updates of data from an identity
26    perspective (still not ideal for deletes though).
27
28    When the session is removed, the cache is gone too, so the cache
29    is automatically disposed upon session.remove().
30
31    """
32
33    def __init__(self, arguments):
34        self.scoped_session = arguments["scoped_session"]
35
36    def get(self, key):
37        return self._cache_dictionary.get(key, NO_VALUE)
38
39    def set(self, key, value):
40        self._cache_dictionary[key] = value
41
42    def delete(self, key):
43        self._cache_dictionary.pop(key, None)
44
45    @property
46    def _cache_dictionary(self):
47        """Return the cache dictionary linked to the current Session."""
48
49        sess = self.scoped_session()
50        try:
51            cache_dict = sess._cache_dictionary
52        except AttributeError:
53            sess._cache_dictionary = cache_dict = {}
54        return cache_dict
55
56
57register_backend("sqlalchemy.session", __name__, "ScopedSessionBackend")
58
59
60if __name__ == "__main__":
61    from .environment import Session, regions
62    from .caching_query import FromCache
63    from dogpile.cache import make_region
64
65    # set up a region based on the ScopedSessionBackend,
66    # pointing to the scoped_session declared in the example
67    # environment.
68    regions["local_session"] = make_region().configure(
69        "sqlalchemy.session", arguments={"scoped_session": Session}
70    )
71
72    from .model import Person
73
74    # query to load Person by name, with criterion
75    # of "person 10"
76    q = (
77        Session.query(Person)
78        .options(FromCache("local_session"))
79        .filter(Person.name == "person 10")
80    )
81
82    # load from DB
83    person10 = q.one()
84
85    # next call, the query is cached.
86    person10 = q.one()
87
88    # clear out the Session.  The "_cache_dictionary" dictionary
89    # disappears with it.
90    Session.remove()
91
92    # query calls from DB again
93    person10 = q.one()
94
95    # identity is preserved - person10 is the *same* object that's
96    # ultimately inside the cache.   So it is safe to manipulate
97    # the not-queried-for attributes of objects when using such a
98    # cache without the need to invalidate - however, any change
99    # that would change the results of a cached query, such as
100    # inserts, deletes, or modification to attributes that are
101    # part of query criterion, still require careful invalidation.
102    cache, key = q._get_cache_plus_key()
103    assert person10 is cache.get(key)[0]
104