django-ormcache 1.2.2__py2.py3-none-any.whl → 1.3__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.1
2
+ Name: django-ormcache
3
+ Version: 1.3
4
+ Summary: ORM cache for Django
5
+ Home-page: https://github.com/educreations/django-ormcache
6
+ Author: Corey Farwell
7
+ Author-email: coreyf@rwell.org
8
+ Maintainer: Educreations Engineering
9
+ Maintainer-email: engineering@educreations.com
10
+ License: MIT
11
+ Project-URL: Homepage, https://github.com/educreations/django-ormcache
12
+ Project-URL: Issues, https://github.com/educreations/django-ormcache/issues
13
+ Project-URL: Changelog, https://github.com/educreations/django-ormcache/blob/master/CHANGES.md
14
+ Keywords: cache django
15
+ Classifier: Framework :: Django
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Topic :: Software Development
22
+ Requires-Python: >=3.7, <4
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: Django <5.0,>=2.0
26
+ Requires-Dist: six
27
+ Provides-Extra: test
28
+ Requires-Dist: tox ; extra == 'test'
29
+ Requires-Dist: pytest ; extra == 'test'
30
+ Requires-Dist: pytest-django ; extra == 'test'
31
+ Requires-Dist: flake8 ; extra == 'test'
32
+
33
+ # Django ORMCache
34
+
35
+ A cache manager mixin that provides some caching of objects for the ORM.
36
+
37
+
38
+ # Installation / Setup / Usage
39
+
40
+ TODO
41
+
42
+
43
+ # Testing
44
+
45
+ Run the tests with:
46
+
47
+ ```
48
+ pip install -e .[tests]
49
+ tox
50
+ ```
51
+
52
+ # License
53
+
54
+ Copyright © 2024, Educreations, Inc under the [MIT](LICENSE).
@@ -0,0 +1,5 @@
1
+ django_ormcache-1.3.dist-info/LICENSE,sha256=RVUF4JTQmXwGoB2iQ_ywDydZlJ0ILqbUgdN7DxvayiA,1085
2
+ django_ormcache-1.3.dist-info/METADATA,sha256=uiHFtyu46ZCR89bC0GNJEqGv5l9-l8KEeR9kY87z5_Y,1471
3
+ django_ormcache-1.3.dist-info/WHEEL,sha256=0XQbNV6JE5ziJsWjIU8TRRv0N6SohNonLWgP86g5fiI,109
4
+ django_ormcache-1.3.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ django_ormcache-1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: setuptools (70.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,57 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: django-ormcache
3
- Version: 1.2.2
4
- Summary: ORM cache for Django
5
- Home-page: https://github.com/educreations/django-ormcache
6
- Author: Corey Farwell
7
- Author-email: coreyf@rwell.org
8
- Maintainer: Educreations Engineering
9
- Maintainer-email: engineering@educreations.com
10
- License: MIT
11
- Keywords: cache django
12
- Platform: UNKNOWN
13
- Classifier: Framework :: Django
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python
18
- Classifier: Programming Language :: Python :: 2.7
19
- Classifier: Programming Language :: Python :: 3
20
- Classifier: Topic :: Software Development
21
- Requires-Dist: Django (<4.2,>=1.9)
22
-
23
- Django ORMCache
24
- ===============
25
-
26
- .. image:: https://travis-ci.org/educreations/django-ormcache.svg?branch=master
27
- :target: https://travis-ci.org/educreations/django-ormcache
28
-
29
- .. image:: https://coveralls.io/repos/github/educreations/django-ormcache/badge.svg?branch=master
30
- :target: https://coveralls.io/github/educreations/django-ormcache?branch=master
31
-
32
- A cache manager mixin that provides some caching of objects for the ORM.
33
-
34
-
35
- Installation / Setup / Usage
36
- ----------------------------
37
-
38
- TODO
39
-
40
-
41
- Testing
42
- -------
43
-
44
- Run the tests with:
45
-
46
- .. code:: bash
47
-
48
- pip install -r requirements.txt
49
- python runtests.py
50
-
51
-
52
- License
53
- -------
54
-
55
- Copyright © 2017, Educreations, Inc under the `MIT <LICENSE>`_.
56
-
57
-
@@ -1,12 +0,0 @@
1
- ormcache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ormcache/decorators.py,sha256=2ZMqQTIJlK0uLCf4Egk5rZhp4WrBzeGyaIScWpT3yp4,1822
3
- ormcache/managers.py,sha256=s1r9FxqN6pyxxM22aUqdI9IK22a37tTPbtB-gLBlNrQ,2034
4
- ormcache/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- ormcache/queryset.py,sha256=oFAfJtRjk8In0FrGtjrOfrlWYOuh_cSEHLxlo3RbcfA,5398
6
- ormcache/signals.py,sha256=4v7Awa3QIH9pGd-VbEk9jcjQvwBABq-29fmn9p6z1lg,147
7
- ormcache/utils.py,sha256=RSI___JrEEDEzwb-MjhqtDojYvsoX0qD-6gAObNbkBc,1792
8
- django_ormcache-1.2.2.dist-info/LICENSE,sha256=RVUF4JTQmXwGoB2iQ_ywDydZlJ0ILqbUgdN7DxvayiA,1085
9
- django_ormcache-1.2.2.dist-info/METADATA,sha256=40fZpcqPBD2dL33rL7rhSwUXhaLyg9ZG5XOB78IucCA,1431
10
- django_ormcache-1.2.2.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110
11
- django_ormcache-1.2.2.dist-info/top_level.txt,sha256=aOHDwDLnkCYseV7Vm27Flc3fKlms_YQQnElZh0fSgR8,9
12
- django_ormcache-1.2.2.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- ormcache
ormcache/__init__.py DELETED
File without changes
ormcache/decorators.py DELETED
@@ -1,57 +0,0 @@
1
- from hashlib import sha1
2
- import logging
3
-
4
- from django.core.cache import cache
5
- from django.utils.decorators import wraps
6
-
7
- log = logging.getLogger(__name__)
8
-
9
-
10
- def cache_result(ttl=60 * 5, key=None):
11
- """
12
- Cache the result of a function call using Django's caching mechanism.
13
-
14
- If the decorated function returns None, cache_result raises a
15
- RuntimeException (as the cache returns None to indicate a miss).
16
-
17
- Note: If a key is not passed, the decorator will hash the function
18
- signature. The ordering of function parameters is important. e.g.:
19
- * myFunction(x=1, y=2)
20
- * myFunction(y=2, x=1)
21
- * myFunction(1, 2)
22
- will each be cached separately.
23
-
24
- Note: Does not automatically handle invalidation
25
-
26
- Usage:
27
- --------------------------------------------------------------------------
28
- @cache_result
29
- def my_expensive_method(param1, param2, param3):
30
- ....
31
- return expensive_result
32
- --------------------------------------------------------------------------
33
- """
34
-
35
- def decorator(func):
36
- @wraps(func)
37
- def inner(*args, **kwargs):
38
- key_ = key
39
- if key_ is None:
40
- func_signature = (
41
- str(func.__module__) + str(func.__name__) + str(args) + str(kwargs)
42
- ).encode("utf8")
43
- key_ = sha1(func_signature).hexdigest()
44
- result = cache.get(key_)
45
- if result is None:
46
- result = func(*args, **kwargs)
47
- if result is None:
48
- log.error(
49
- "Function decorated by @cache_result returned " "None",
50
- extra={"function": func.__name__},
51
- )
52
- cache.set(key_, result, ttl)
53
- return result
54
-
55
- return inner
56
-
57
- return decorator
ormcache/managers.py DELETED
@@ -1,59 +0,0 @@
1
- from django.db.models.signals import class_prepared, post_delete, post_save
2
- from django.utils.functional import cached_property
3
-
4
- from ormcache.queryset import CachedQuerySet
5
-
6
-
7
- class CachedManagerMixin(object):
8
- @cached_property
9
- def __cache_enabled(self):
10
- return getattr(self.model, "cache_enabled", False)
11
-
12
- def __require_cache(func):
13
- def wrapper(self, *args, **kwargs):
14
- if not self.__cache_enabled:
15
- error = "Caching is not enabled on {}".format(str(type(self)))
16
- raise RuntimeError(error)
17
- return func(self, *args, **kwargs)
18
-
19
- return wrapper
20
-
21
- @__require_cache
22
- def from_ids(self, ids, lookup="pk__in", **kwargs):
23
- queryset = self.get_queryset()
24
- return queryset.from_ids(ids, lookup=lookup, **kwargs)
25
-
26
- @__require_cache
27
- def invalidate(self, *args, **kwargs):
28
- return self.get_queryset().invalidate(*args, **kwargs)
29
-
30
- @__require_cache
31
- def cache_key(self, *args, **kwargs):
32
- return self.get_queryset().cache_key(*args, **kwargs)
33
-
34
- # Django overrides
35
-
36
- def contribute_to_class(self, model, name):
37
- """Override Django builtin"""
38
- super(CachedManagerMixin, self).contribute_to_class(model, name)
39
- class_prepared.connect(self.__class_prepared_cache, sender=model)
40
-
41
- def get_queryset(self):
42
- """Override Django builtin"""
43
- if self.__cache_enabled:
44
- return CachedQuerySet(self.model)
45
- else:
46
- return super(CachedManagerMixin, self).get_queryset()
47
-
48
- # Signals
49
-
50
- def __class_prepared_cache(self, sender, **kwargs):
51
- if self.__cache_enabled:
52
- post_save.connect(self.__post_save_cache, sender=self.model, weak=False)
53
- post_delete.connect(self.__post_delete_cache, sender=self.model, weak=False)
54
-
55
- def __post_save_cache(self, instance, created, **kwargs):
56
- self.invalidate(instance.pk, recache=True)
57
-
58
- def __post_delete_cache(self, instance, **kwargs):
59
- self.invalidate(instance.pk)
ormcache/models.py DELETED
File without changes
ormcache/queryset.py DELETED
@@ -1,148 +0,0 @@
1
- try:
2
- from collections.abc import Iterable
3
- except ImportError:
4
- from collections import Iterable
5
-
6
- import logging
7
-
8
- import django
9
- from django.core.cache import cache
10
- from django.db.models.query import QuerySet
11
-
12
- if django.VERSION >= (1, 8):
13
- from django.db.models.expressions import Col
14
- from django.db.models.lookups import Exact
15
- elif django.VERSION >= (1, 7):
16
- from django.db.models.lookups import Exact
17
- from django.db.models.sql.datastructures import Col
18
-
19
- from ormcache.signals import cache_hit, cache_missed, cache_invalidated # noqa
20
-
21
- log = logging.getLogger(__name__)
22
-
23
-
24
- class CachedQuerySet(QuerySet):
25
-
26
- __MAXIMUM_CACHE_KEY_LENGTH = 250
27
- __CACHE_FOREVER = 2592000 # http://ur1.ca/egyvu
28
-
29
- def get(self, *args, **kwargs):
30
- """
31
- Adds a layer of caching around the Manager's built in 'get()' method.
32
- Will only cache if 'pk' or 'id' is used in kwargs. Results from the
33
- 'get()' method will be cached indefinitely (30 days) until invalidated.
34
- """
35
-
36
- orig_kwargs = kwargs.copy()
37
-
38
- # If this queryset is filtered by a single column and no arguments
39
- # were passed to get(), treat the queryset filter as an argument to
40
- # get(). This lets Model.objects.filter(pk=42).get() work like
41
- # Model.objects.get(pk=42).
42
- can_ignore_filter = False
43
- if django.VERSION >= (1, 7) and not kwargs:
44
- if len(self.query.where) == 1 and not self.query.where.negated:
45
- (child,) = self.query.where.children
46
- if isinstance(child, Exact) and isinstance(child.lhs, Col):
47
- can_ignore_filter = True
48
- kwargs[child.lhs.target.attname] = child.rhs
49
- elif (
50
- len(args) == 1
51
- and len(args[0].children) == 1
52
- and len(args[0].children[0]) == 2
53
- and args[0].children[0][0] == "id"
54
- ):
55
- can_ignore_filter = True
56
- kwargs[args[0].children[0][0]] = args[0].children[0][1]
57
-
58
- # Don't access cache if using a filtered queryset
59
- if self.query.where and not can_ignore_filter:
60
- return super(CachedQuerySet, self).get(*args, **orig_kwargs)
61
-
62
- # Don't access cache if using a deferred queryset
63
- if (
64
- len(self.query.deferred_loading[0]) > 0
65
- or not self.query.deferred_loading[1]
66
- ):
67
- return super(CachedQuerySet, self).get(*args, **orig_kwargs)
68
-
69
- # Get the cache key from the model name and pk
70
- if "pk" in kwargs:
71
- pk = kwargs["pk"]
72
- elif "pk__exact" in kwargs:
73
- pk = kwargs["pk__exact"]
74
- elif "id" in kwargs:
75
- pk = kwargs["id"]
76
- elif "id__exact" in kwargs:
77
- pk = kwargs["id__exact"]
78
- else:
79
- return super(CachedQuerySet, self).get(*args, **orig_kwargs)
80
-
81
- key = self.cache_key(pk)
82
-
83
- # Retrieve (or set) the item in the cache
84
- item = cache.get(key)
85
- if item is None:
86
- cache_missed.send(sender=self.model)
87
- item = super(CachedQuerySet, self).get(*args, **orig_kwargs)
88
- cache.set(key, item, self.__CACHE_FOREVER)
89
- else:
90
- cache_hit.send(sender=self.model)
91
-
92
- return item
93
-
94
- def from_ids(self, ids, lookup="pk__in"):
95
- assert isinstance(ids, Iterable)
96
-
97
- cache_keys = [self.cache_key(id_) for id_ in ids]
98
-
99
- instances = cache.get_many(cache_keys).values()
100
- cached_dict = {i.id: i for i in instances if i}
101
- uncached_ids = set(ids) - set(cached_dict.keys())
102
-
103
- # If there are uncached instances, retrieve and cache them
104
- if len(uncached_ids) > 0:
105
- uncached = self.filter(**{lookup: uncached_ids})
106
-
107
- # Add the uncached instances to the existing instances
108
- cached_dict.update((i.id, i) for i in uncached)
109
-
110
- # Cache the uncached instances
111
- to_cache = {self.cache_key(i.pk): i for i in uncached}
112
- cache.set_many(to_cache, self.__CACHE_FOREVER)
113
-
114
- return [cached_dict[id_] for id_ in ids if id_ in cached_dict]
115
-
116
- def cache_key(self, pk):
117
- """Generate the cache key for an individual model"""
118
- key = "{}-pk:{}".format(self.model.__name__, pk)
119
-
120
- # We shouldn't have keys that are crazy long. This might mean
121
- # we get duplicates though
122
- key = key[: self.__MAXIMUM_CACHE_KEY_LENGTH]
123
-
124
- return key
125
-
126
- def invalidate(self, pk, recache=False):
127
- """Invalidate a single item in the cache"""
128
- key = self.cache_key(pk)
129
- cache_invalidated.send(sender=self.model)
130
- if recache is True:
131
- try:
132
- entry = super(CachedQuerySet, self).get(pk=pk)
133
- except self.model.DoesNotExist:
134
- log.info(
135
- "Unable to recache model during invalidate",
136
- exc_info=True,
137
- extra={"data": {"model": self.model, "pk": pk}},
138
- )
139
- except self.model.MultipleObjectsReturned:
140
- log.error(
141
- "Error retrieving single entry from database",
142
- exc_info=True,
143
- extra={"data": {"model": self.model, "pk": pk}},
144
- )
145
- else:
146
- cache.set(key, entry, self.__CACHE_FOREVER)
147
- else:
148
- cache.delete(key)
ormcache/signals.py DELETED
@@ -1,6 +0,0 @@
1
- import django.dispatch
2
-
3
-
4
- cache_hit = django.dispatch.Signal()
5
- cache_missed = django.dispatch.Signal()
6
- cache_invalidated = django.dispatch.Signal()
ormcache/utils.py DELETED
@@ -1,54 +0,0 @@
1
- try:
2
- from django.db.models.fields.related import SingleRelatedObjectDescriptor
3
- except ImportError:
4
- from django.db.models.fields.related_descriptors import (
5
- ReverseOneToOneDescriptor as SingleRelatedObjectDescriptor,
6
- )
7
-
8
-
9
- def _distinct(l):
10
- """Given an iterable will return a list of all distinct values."""
11
- return list(set(l))
12
-
13
-
14
- def attach_foreignkey(objects, field):
15
- """
16
- Similar to the disqus's attach_foreignkey, but pulls items from the cache
17
- if caching is enabled on the model
18
- """
19
- is_foreignkey = isinstance(field, SingleRelatedObjectDescriptor)
20
-
21
- if not is_foreignkey:
22
- field = field.field
23
- accessor = "_%s_cache" % field.name
24
- model = field.remote_field.model
25
- lookup = "%s__in" % "pk"
26
- column = field.column
27
- key = "pk"
28
- else:
29
- accessor = field.cache_name
30
- field = field.related.field
31
- model = field.model
32
- lookup = "%s__in" % field.name
33
- column = "pk"
34
- key = field.column
35
-
36
- # Ensure values are unique, do not contain already present values, and
37
- # are not missing values specified in select_related
38
- values = _distinct(
39
- getattr(o, column) for o in objects if getattr(o, accessor, False) is False
40
- )
41
- if not values:
42
- return
43
-
44
- # Pull instances from cache if caching is enabled. This conditional logic
45
- # can be removed when the UserManager inherits BaseManager
46
- if getattr(model, "cache_enabled", False) is True:
47
- instances = model.objects.from_ids(values, lookup=lookup)
48
- else:
49
- instances = model.objects.filter(**{lookup: values})
50
-
51
- instances_dict = {getattr(i, key): i for i in instances}
52
-
53
- for obj in objects:
54
- setattr(obj, accessor, instances_dict.get(getattr(obj, column)))