cachier 2.0.1__tar.gz → 2.1.0__tar.gz
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.
- {cachier-2.0.1 → cachier-2.1.0}/.github/workflows/test.yml +0 -5
- {cachier-2.0.1 → cachier-2.1.0}/PKG-INFO +53 -7
- {cachier-2.0.1 → cachier-2.1.0}/README.rst +52 -6
- {cachier-2.0.1 → cachier-2.1.0}/cachier/_version.py +2 -2
- {cachier-2.0.1 → cachier-2.1.0}/cachier/base_core.py +22 -8
- {cachier-2.0.1 → cachier-2.1.0}/cachier/core.py +27 -11
- {cachier-2.0.1 → cachier-2.1.0}/cachier/memory_core.py +2 -2
- {cachier-2.0.1 → cachier-2.1.0}/cachier/mongo_core.py +2 -4
- {cachier-2.0.1 → cachier-2.1.0}/cachier/pickle_core.py +3 -7
- {cachier-2.0.1 → cachier-2.1.0}/cachier.egg-info/PKG-INFO +53 -7
- {cachier-2.0.1 → cachier-2.1.0}/cachier.egg-info/SOURCES.txt +4 -2
- {cachier-2.0.1 → cachier-2.1.0}/cachier.egg-info/requires.txt +6 -1
- {cachier-2.0.1 → cachier-2.1.0}/setup.py +5 -4
- {cachier-2.0.1 → cachier-2.1.0}/tests/speed_eval.py +1 -1
- cachier-2.1.0/tests/standalone_script.py +11 -0
- {cachier-2.0.1 → cachier-2.1.0}/tests/test_general.py +105 -26
- {cachier-2.0.1 → cachier-2.1.0}/tests/test_memory_core.py +38 -14
- {cachier-2.0.1 → cachier-2.1.0}/tests/test_mongo_core.py +67 -59
- {cachier-2.0.1 → cachier-2.1.0}/tests/test_pickle_core.py +53 -31
- cachier-2.1.0/tests/test_quality.py +17 -0
- cachier-2.1.0/tests/test_security.py +20 -0
- cachier-2.0.1/tests/conftest.py +0 -22
- {cachier-2.0.1 → cachier-2.1.0}/.codecov.yml +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.coveragerc +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.deepsource.toml +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.fdignore +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.flake8 +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.gitattributes +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.github/workflows/checkdocs.yml +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.github/workflows/lint.yml +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.gitignore +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.ignore +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/.pylintrc +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/LICENSE +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/MANIFEST.in +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/cachier/__init__.py +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/cachier/scripts/__init__.py +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/cachier/scripts/cli.py +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/cachier.egg-info/dependency_links.txt +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/cachier.egg-info/entry_points.txt +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/cachier.egg-info/top_level.txt +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/pytest.ini +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/setup.cfg +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/tests/__init__.py +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/tests/test_core_lookup.py +0 -0
- {cachier-2.0.1 → cachier-2.1.0}/versioneer.py +0 -0
@@ -32,11 +32,6 @@ jobs:
|
|
32
32
|
python -m pip install --upgrade pip
|
33
33
|
python -m pip install -e ".[test]"
|
34
34
|
- name: Unit tests
|
35
|
-
env:
|
36
|
-
CACHIER_TEST_HOST: ${{ secrets.CACHIER_TEST_HOST }}
|
37
|
-
CACHIER_TEST_DB: ${{ secrets.CACHIER_TEST_DB }}
|
38
|
-
CACHIER_TEST_USERNAME: ${{ secrets.CACHIER_TEST_USERNAME }}
|
39
|
-
CACHIER_TEST_PASSWORD: ${{ secrets.CACHIER_TEST_PASSWORD }}
|
40
35
|
run: |
|
41
36
|
pytest
|
42
37
|
- name: "Upload coverage to Codecov"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: cachier
|
3
|
-
Version: 2.0
|
3
|
+
Version: 2.1.0
|
4
4
|
Summary: Persistent, stale-free, local and cross-machine caching for Python functions.
|
5
5
|
Home-page: https://github.com/python-cachier/cachier
|
6
6
|
Author: Shay Palachy
|
@@ -111,6 +111,33 @@ You can add a default, pickle-based, persistent cache to your function - meaning
|
|
111
111
|
"""Your function now has a persistent cache mapped by argument values!"""
|
112
112
|
return {'arg1': arg1, 'arg2': arg2}
|
113
113
|
|
114
|
+
Class and object methods can also be cached. Cachier will automatically ignore the `self` parameter when determining the cache key for an object method. **This means that methods will be cached across all instances of an object, which may not be what you want.**
|
115
|
+
|
116
|
+
.. code-block:: python
|
117
|
+
|
118
|
+
from cachier import cachier
|
119
|
+
|
120
|
+
class Foo():
|
121
|
+
@staticmethod
|
122
|
+
@cachier()
|
123
|
+
def good_static_usage(arg_1, arg_2):
|
124
|
+
return arg_1 + arg_2
|
125
|
+
|
126
|
+
# Instance method does not depend on object's internal state, so good to cache
|
127
|
+
@cachier()
|
128
|
+
def good_usage_1(self, arg_1, arg_2)
|
129
|
+
return arg_1 + arg_2
|
130
|
+
|
131
|
+
# Instance method is calling external service, probably okay to cache
|
132
|
+
@cachier()
|
133
|
+
def good_usage_2(self, arg_1, arg_2)
|
134
|
+
result = self.call_api(arg_1, arg_2)
|
135
|
+
return result
|
136
|
+
|
137
|
+
# Instance method relies on object attribute, NOT good to cache
|
138
|
+
@cachier()
|
139
|
+
def bad_usage(self, arg_1, arg_2)
|
140
|
+
return arg_1 + arg_2 + self.arg_3
|
114
141
|
|
115
142
|
|
116
143
|
Resetting a Cache
|
@@ -121,7 +148,7 @@ The Cachier wrapper adds a ``clear_cache()`` function to each wrapped function.
|
|
121
148
|
|
122
149
|
foo.clear_cache()
|
123
150
|
|
124
|
-
|
151
|
+
General Configuration
|
125
152
|
----------------------
|
126
153
|
|
127
154
|
Threads Limit
|
@@ -163,11 +190,15 @@ Further function calls made while the calculation is being performed will not tr
|
|
163
190
|
Working with unhashable arguments
|
164
191
|
---------------------------------
|
165
192
|
|
166
|
-
As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the ``
|
193
|
+
As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the ``hash_func`` parameter of the ``cachier`` decorator can be provided with a callable that gets the args and kwargs from the decorated function and returns a hash key for them.
|
167
194
|
|
168
195
|
.. code-block:: python
|
169
196
|
|
170
|
-
|
197
|
+
def calculate_hash(args, kwds):
|
198
|
+
key = ... # compute a hash key here based on arguments
|
199
|
+
return key
|
200
|
+
|
201
|
+
@cachier(hash_func=calculate_hash)
|
171
202
|
def calculate_super_complex_stuff(custom_obj):
|
172
203
|
# amazing code goes here
|
173
204
|
|
@@ -176,6 +207,23 @@ See here for an example:
|
|
176
207
|
`Question: How to work with unhashable arguments <https://github.com/python-cachier/cachier/issues/91>`_
|
177
208
|
|
178
209
|
|
210
|
+
Precaching values
|
211
|
+
---------------------------------
|
212
|
+
|
213
|
+
If you want to load a value into the cache without calling the underlying function, this can be done with the `precache_value` function.
|
214
|
+
|
215
|
+
.. code-block:: python
|
216
|
+
|
217
|
+
@cachier()
|
218
|
+
def add(arg1, arg2):
|
219
|
+
return arg1 + arg2
|
220
|
+
|
221
|
+
add.precache_value(2, 2, value_to_cache=5)
|
222
|
+
|
223
|
+
result = add(2, 2)
|
224
|
+
print(result) # prints 5
|
225
|
+
|
226
|
+
|
179
227
|
Per-function call arguments
|
180
228
|
---------------------------
|
181
229
|
|
@@ -365,7 +413,7 @@ Other major contributors:
|
|
365
413
|
|
366
414
|
* `cthoyt <https://github.com/cthoyt>`_ - Base memory core implementation.
|
367
415
|
|
368
|
-
* `amarczew <https://github.com/amarczew>`_ - The ``
|
416
|
+
* `amarczew <https://github.com/amarczew>`_ - The ``hash_func`` kwarg.
|
369
417
|
|
370
418
|
* `non-senses <https://github.com/non-senses>`_ - The ``wait_for_calc_timeout`` kwarg.
|
371
419
|
|
@@ -411,5 +459,3 @@ Notable bugfixers:
|
|
411
459
|
.. _watchdog: https://github.com/gorakhargosh/watchdog
|
412
460
|
|
413
461
|
|
414
|
-
|
415
|
-
|
@@ -83,6 +83,33 @@ You can add a default, pickle-based, persistent cache to your function - meaning
|
|
83
83
|
"""Your function now has a persistent cache mapped by argument values!"""
|
84
84
|
return {'arg1': arg1, 'arg2': arg2}
|
85
85
|
|
86
|
+
Class and object methods can also be cached. Cachier will automatically ignore the `self` parameter when determining the cache key for an object method. **This means that methods will be cached across all instances of an object, which may not be what you want.**
|
87
|
+
|
88
|
+
.. code-block:: python
|
89
|
+
|
90
|
+
from cachier import cachier
|
91
|
+
|
92
|
+
class Foo():
|
93
|
+
@staticmethod
|
94
|
+
@cachier()
|
95
|
+
def good_static_usage(arg_1, arg_2):
|
96
|
+
return arg_1 + arg_2
|
97
|
+
|
98
|
+
# Instance method does not depend on object's internal state, so good to cache
|
99
|
+
@cachier()
|
100
|
+
def good_usage_1(self, arg_1, arg_2)
|
101
|
+
return arg_1 + arg_2
|
102
|
+
|
103
|
+
# Instance method is calling external service, probably okay to cache
|
104
|
+
@cachier()
|
105
|
+
def good_usage_2(self, arg_1, arg_2)
|
106
|
+
result = self.call_api(arg_1, arg_2)
|
107
|
+
return result
|
108
|
+
|
109
|
+
# Instance method relies on object attribute, NOT good to cache
|
110
|
+
@cachier()
|
111
|
+
def bad_usage(self, arg_1, arg_2)
|
112
|
+
return arg_1 + arg_2 + self.arg_3
|
86
113
|
|
87
114
|
|
88
115
|
Resetting a Cache
|
@@ -93,7 +120,7 @@ The Cachier wrapper adds a ``clear_cache()`` function to each wrapped function.
|
|
93
120
|
|
94
121
|
foo.clear_cache()
|
95
122
|
|
96
|
-
|
123
|
+
General Configuration
|
97
124
|
----------------------
|
98
125
|
|
99
126
|
Threads Limit
|
@@ -135,11 +162,15 @@ Further function calls made while the calculation is being performed will not tr
|
|
135
162
|
Working with unhashable arguments
|
136
163
|
---------------------------------
|
137
164
|
|
138
|
-
As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the ``
|
165
|
+
As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the ``hash_func`` parameter of the ``cachier`` decorator can be provided with a callable that gets the args and kwargs from the decorated function and returns a hash key for them.
|
139
166
|
|
140
167
|
.. code-block:: python
|
141
168
|
|
142
|
-
|
169
|
+
def calculate_hash(args, kwds):
|
170
|
+
key = ... # compute a hash key here based on arguments
|
171
|
+
return key
|
172
|
+
|
173
|
+
@cachier(hash_func=calculate_hash)
|
143
174
|
def calculate_super_complex_stuff(custom_obj):
|
144
175
|
# amazing code goes here
|
145
176
|
|
@@ -148,6 +179,23 @@ See here for an example:
|
|
148
179
|
`Question: How to work with unhashable arguments <https://github.com/python-cachier/cachier/issues/91>`_
|
149
180
|
|
150
181
|
|
182
|
+
Precaching values
|
183
|
+
---------------------------------
|
184
|
+
|
185
|
+
If you want to load a value into the cache without calling the underlying function, this can be done with the `precache_value` function.
|
186
|
+
|
187
|
+
.. code-block:: python
|
188
|
+
|
189
|
+
@cachier()
|
190
|
+
def add(arg1, arg2):
|
191
|
+
return arg1 + arg2
|
192
|
+
|
193
|
+
add.precache_value(2, 2, value_to_cache=5)
|
194
|
+
|
195
|
+
result = add(2, 2)
|
196
|
+
print(result) # prints 5
|
197
|
+
|
198
|
+
|
151
199
|
Per-function call arguments
|
152
200
|
---------------------------
|
153
201
|
|
@@ -337,7 +385,7 @@ Other major contributors:
|
|
337
385
|
|
338
386
|
* `cthoyt <https://github.com/cthoyt>`_ - Base memory core implementation.
|
339
387
|
|
340
|
-
* `amarczew <https://github.com/amarczew>`_ - The ``
|
388
|
+
* `amarczew <https://github.com/amarczew>`_ - The ``hash_func`` kwarg.
|
341
389
|
|
342
390
|
* `non-senses <https://github.com/non-senses>`_ - The ``wait_for_calc_timeout`` kwarg.
|
343
391
|
|
@@ -381,5 +429,3 @@ Notable bugfixers:
|
|
381
429
|
.. links:
|
382
430
|
.. _pymongo: https://api.mongodb.com/python/current/
|
383
431
|
.. _watchdog: https://github.com/gorakhargosh/watchdog
|
384
|
-
|
385
|
-
|
@@ -11,8 +11,8 @@ version_json = '''
|
|
11
11
|
{
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "2.0
|
14
|
+
"full-revisionid": "7be769b78acf3a0e0aff8d528476913571d79749",
|
15
|
+
"version": "2.1.0"
|
16
16
|
}
|
17
17
|
''' # END VERSION_JSON
|
18
18
|
|
@@ -8,24 +8,32 @@
|
|
8
8
|
|
9
9
|
import abc # for the _BaseCore abstract base class
|
10
10
|
import functools
|
11
|
+
import hashlib
|
12
|
+
import inspect
|
13
|
+
import pickle # nosec: B403
|
11
14
|
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
def _default_hash_func(args, kwds):
|
17
|
+
# pylint: disable-next=protected-access
|
18
|
+
key = functools._make_key(args, kwds, typed=True)
|
19
|
+
hash = hashlib.sha256()
|
20
|
+
for item in key:
|
21
|
+
hash.update(pickle.dumps(item))
|
22
|
+
return hash.hexdigest()
|
15
23
|
|
16
24
|
|
17
25
|
class _BaseCore():
|
18
26
|
__metaclass__ = abc.ABCMeta
|
19
27
|
|
20
|
-
def __init__(self,
|
21
|
-
self.
|
22
|
-
self.next_time = next_time
|
23
|
-
self.hash_func = hash_params if hash_params else _default_hash_params
|
28
|
+
def __init__(self, hash_func):
|
29
|
+
self.hash_func = hash_func if hash_func else _default_hash_func
|
24
30
|
self.func = None
|
25
31
|
|
26
32
|
def set_func(self, func):
|
27
|
-
"""Sets the function this core will use. This has to be set before
|
28
|
-
|
33
|
+
"""Sets the function this core will use. This has to be set before any
|
34
|
+
method is called. Also determine if the funtion is an object method."""
|
35
|
+
func_params = list(inspect.signature(func).parameters)
|
36
|
+
self.func_is_method = func_params and func_params[0] == 'self'
|
29
37
|
self.func = func
|
30
38
|
|
31
39
|
def get_entry(self, args, kwds):
|
@@ -34,6 +42,12 @@ class _BaseCore():
|
|
34
42
|
key = self.hash_func(args, kwds)
|
35
43
|
return self.get_entry_by_key(key)
|
36
44
|
|
45
|
+
def precache_value(self, args, kwds, value_to_cache):
|
46
|
+
"""Writes a precomputed value into the cache."""
|
47
|
+
key = self.hash_func(args, kwds)
|
48
|
+
self.set_entry(key, value_to_cache)
|
49
|
+
return value_to_cache
|
50
|
+
|
37
51
|
@abc.abstractmethod
|
38
52
|
def get_entry_by_key(self, key):
|
39
53
|
"""Returns the result mapped to the given key in this core's cache,
|
@@ -14,6 +14,7 @@ from __future__ import print_function
|
|
14
14
|
|
15
15
|
import os
|
16
16
|
from functools import wraps
|
17
|
+
from warnings import warn
|
17
18
|
|
18
19
|
import datetime
|
19
20
|
from concurrent.futures import ThreadPoolExecutor
|
@@ -85,6 +86,7 @@ def cachier(
|
|
85
86
|
backend=None,
|
86
87
|
mongetter=None,
|
87
88
|
cache_dir=None,
|
89
|
+
hash_func=None,
|
88
90
|
hash_params=None,
|
89
91
|
wait_for_calc_timeout=0,
|
90
92
|
separate_files=False,
|
@@ -125,7 +127,7 @@ def cachier(
|
|
125
127
|
A fully qualified path to a file directory to be used for cache files.
|
126
128
|
The running process must have running permissions to this folder. If
|
127
129
|
not provided, a default directory at `~/.cachier/` is used.
|
128
|
-
|
130
|
+
hash_func : callable, optional
|
129
131
|
A callable that gets the args and kwargs from the decorated function
|
130
132
|
and returns a hash key for them. This parameter can be used to enable
|
131
133
|
the use of cachier with functions that get arguments that are not
|
@@ -141,15 +143,19 @@ def cachier(
|
|
141
143
|
split between several files, one for each argument set. This can help
|
142
144
|
if you per-function cache files become too large.
|
143
145
|
"""
|
146
|
+
# Check for deprecated parameters
|
147
|
+
if hash_params is not None:
|
148
|
+
message = 'hash_params will be removed in a future release, ' \
|
149
|
+
'please use hash_func instead'
|
150
|
+
warn(message, DeprecationWarning, stacklevel=2)
|
151
|
+
hash_func = hash_params
|
144
152
|
# The default is calculated dynamically to maintain previous behavior
|
145
153
|
# to default to pickle unless the ``mongetter`` argument is given.
|
146
154
|
if backend is None:
|
147
155
|
backend = 'pickle' if mongetter is None else 'mongo'
|
148
156
|
if backend == 'pickle':
|
149
157
|
core = _PickleCore( # pylint: disable=R0204
|
150
|
-
|
151
|
-
next_time=next_time,
|
152
|
-
hash_params=hash_params,
|
158
|
+
hash_func=hash_func,
|
153
159
|
reload=pickle_reload,
|
154
160
|
cache_dir=cache_dir,
|
155
161
|
separate_files=separate_files,
|
@@ -161,16 +167,12 @@ def cachier(
|
|
161
167
|
'must specify ``mongetter`` when using the mongo core')
|
162
168
|
core = _MongoCore(
|
163
169
|
mongetter=mongetter,
|
164
|
-
|
165
|
-
next_time=next_time,
|
166
|
-
hash_params=hash_params,
|
170
|
+
hash_func=hash_func,
|
167
171
|
wait_for_calc_timeout=wait_for_calc_timeout,
|
168
172
|
)
|
169
173
|
elif backend == 'memory':
|
170
174
|
core = _MemoryCore(
|
171
|
-
|
172
|
-
next_time=next_time,
|
173
|
-
hash_params=hash_params,
|
175
|
+
hash_func=hash_func,
|
174
176
|
)
|
175
177
|
elif backend == 'redis':
|
176
178
|
raise NotImplementedError(
|
@@ -194,7 +196,10 @@ def cachier(
|
|
194
196
|
_print = print
|
195
197
|
if ignore_cache:
|
196
198
|
return func(*args, **kwds)
|
197
|
-
|
199
|
+
if core.func_is_method:
|
200
|
+
key, entry = core.get_entry(args[1:], kwds)
|
201
|
+
else:
|
202
|
+
key, entry = core.get_entry(args, kwds)
|
198
203
|
if overwrite_cache:
|
199
204
|
return _calc_entry(core, key, func, args, kwds)
|
200
205
|
if entry is not None: # pylint: disable=R0101
|
@@ -259,9 +264,20 @@ def cachier(
|
|
259
264
|
except AttributeError:
|
260
265
|
return None
|
261
266
|
|
267
|
+
def precache_value(*args, value_to_cache, **kwds):
|
268
|
+
"""Add an initial value to the cache.
|
269
|
+
|
270
|
+
Arguments
|
271
|
+
---------
|
272
|
+
value : any
|
273
|
+
entry to be written into the cache
|
274
|
+
"""
|
275
|
+
return core.precache_value(args, kwds, value_to_cache)
|
276
|
+
|
262
277
|
func_wrapper.clear_cache = clear_cache
|
263
278
|
func_wrapper.clear_being_calculated = clear_being_calculated
|
264
279
|
func_wrapper.cache_dpath = cache_dpath
|
280
|
+
func_wrapper.precache_value = precache_value
|
265
281
|
return func_wrapper
|
266
282
|
|
267
283
|
return _cachier_decorator
|
@@ -17,8 +17,8 @@ class _MemoryCore(_BaseCore):
|
|
17
17
|
See :class:`_BaseCore` documentation.
|
18
18
|
"""
|
19
19
|
|
20
|
-
def __init__(self,
|
21
|
-
super().__init__(
|
20
|
+
def __init__(self, hash_func):
|
21
|
+
super().__init__(hash_func)
|
22
22
|
self.cache = {}
|
23
23
|
self.lock = threading.RLock()
|
24
24
|
|
@@ -37,14 +37,12 @@ class _MongoCore(_BaseCore):
|
|
37
37
|
|
38
38
|
_INDEX_NAME = 'func_1_key_1'
|
39
39
|
|
40
|
-
def __init__(
|
41
|
-
self, mongetter, stale_after, next_time,
|
42
|
-
hash_params, wait_for_calc_timeout):
|
40
|
+
def __init__(self, mongetter, hash_func, wait_for_calc_timeout):
|
43
41
|
if 'pymongo' not in sys.modules:
|
44
42
|
warnings.warn((
|
45
43
|
"Cachier warning: pymongo was not found. "
|
46
44
|
"MongoDB cores will not function."))
|
47
|
-
super().__init__(
|
45
|
+
super().__init__(hash_func)
|
48
46
|
self.mongetter = mongetter
|
49
47
|
self.mongo_collection = self.mongetter()
|
50
48
|
self.wait_for_calc_timeout = wait_for_calc_timeout
|
@@ -29,10 +29,6 @@ class _PickleCore(_BaseCore):
|
|
29
29
|
|
30
30
|
Parameters
|
31
31
|
----------
|
32
|
-
stale_after : datetime.timedelta, optional
|
33
|
-
See _BaseCore documentation.
|
34
|
-
next_time : bool, optional
|
35
|
-
See _BaseCore documentation.
|
36
32
|
pickle_reload : bool, optional
|
37
33
|
See core.cachier() documentation.
|
38
34
|
cache_dir : str, optional.
|
@@ -84,10 +80,10 @@ class _PickleCore(_BaseCore):
|
|
84
80
|
self._check_calculation()
|
85
81
|
|
86
82
|
def __init__(
|
87
|
-
self,
|
88
|
-
|
83
|
+
self, hash_func, reload, cache_dir,
|
84
|
+
separate_files, wait_for_calc_timeout,
|
89
85
|
):
|
90
|
-
super().__init__(
|
86
|
+
super().__init__(hash_func)
|
91
87
|
self.cache = None
|
92
88
|
self.reload = reload
|
93
89
|
self.cache_dir = DEF_CACHIER_DIR
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: cachier
|
3
|
-
Version: 2.0
|
3
|
+
Version: 2.1.0
|
4
4
|
Summary: Persistent, stale-free, local and cross-machine caching for Python functions.
|
5
5
|
Home-page: https://github.com/python-cachier/cachier
|
6
6
|
Author: Shay Palachy
|
@@ -111,6 +111,33 @@ You can add a default, pickle-based, persistent cache to your function - meaning
|
|
111
111
|
"""Your function now has a persistent cache mapped by argument values!"""
|
112
112
|
return {'arg1': arg1, 'arg2': arg2}
|
113
113
|
|
114
|
+
Class and object methods can also be cached. Cachier will automatically ignore the `self` parameter when determining the cache key for an object method. **This means that methods will be cached across all instances of an object, which may not be what you want.**
|
115
|
+
|
116
|
+
.. code-block:: python
|
117
|
+
|
118
|
+
from cachier import cachier
|
119
|
+
|
120
|
+
class Foo():
|
121
|
+
@staticmethod
|
122
|
+
@cachier()
|
123
|
+
def good_static_usage(arg_1, arg_2):
|
124
|
+
return arg_1 + arg_2
|
125
|
+
|
126
|
+
# Instance method does not depend on object's internal state, so good to cache
|
127
|
+
@cachier()
|
128
|
+
def good_usage_1(self, arg_1, arg_2)
|
129
|
+
return arg_1 + arg_2
|
130
|
+
|
131
|
+
# Instance method is calling external service, probably okay to cache
|
132
|
+
@cachier()
|
133
|
+
def good_usage_2(self, arg_1, arg_2)
|
134
|
+
result = self.call_api(arg_1, arg_2)
|
135
|
+
return result
|
136
|
+
|
137
|
+
# Instance method relies on object attribute, NOT good to cache
|
138
|
+
@cachier()
|
139
|
+
def bad_usage(self, arg_1, arg_2)
|
140
|
+
return arg_1 + arg_2 + self.arg_3
|
114
141
|
|
115
142
|
|
116
143
|
Resetting a Cache
|
@@ -121,7 +148,7 @@ The Cachier wrapper adds a ``clear_cache()`` function to each wrapped function.
|
|
121
148
|
|
122
149
|
foo.clear_cache()
|
123
150
|
|
124
|
-
|
151
|
+
General Configuration
|
125
152
|
----------------------
|
126
153
|
|
127
154
|
Threads Limit
|
@@ -163,11 +190,15 @@ Further function calls made while the calculation is being performed will not tr
|
|
163
190
|
Working with unhashable arguments
|
164
191
|
---------------------------------
|
165
192
|
|
166
|
-
As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the ``
|
193
|
+
As mentioned above, the positional and keyword arguments to the wrapped function must be hashable (i.e. Python's immutable built-in objects, not mutable containers). To get around this limitation the ``hash_func`` parameter of the ``cachier`` decorator can be provided with a callable that gets the args and kwargs from the decorated function and returns a hash key for them.
|
167
194
|
|
168
195
|
.. code-block:: python
|
169
196
|
|
170
|
-
|
197
|
+
def calculate_hash(args, kwds):
|
198
|
+
key = ... # compute a hash key here based on arguments
|
199
|
+
return key
|
200
|
+
|
201
|
+
@cachier(hash_func=calculate_hash)
|
171
202
|
def calculate_super_complex_stuff(custom_obj):
|
172
203
|
# amazing code goes here
|
173
204
|
|
@@ -176,6 +207,23 @@ See here for an example:
|
|
176
207
|
`Question: How to work with unhashable arguments <https://github.com/python-cachier/cachier/issues/91>`_
|
177
208
|
|
178
209
|
|
210
|
+
Precaching values
|
211
|
+
---------------------------------
|
212
|
+
|
213
|
+
If you want to load a value into the cache without calling the underlying function, this can be done with the `precache_value` function.
|
214
|
+
|
215
|
+
.. code-block:: python
|
216
|
+
|
217
|
+
@cachier()
|
218
|
+
def add(arg1, arg2):
|
219
|
+
return arg1 + arg2
|
220
|
+
|
221
|
+
add.precache_value(2, 2, value_to_cache=5)
|
222
|
+
|
223
|
+
result = add(2, 2)
|
224
|
+
print(result) # prints 5
|
225
|
+
|
226
|
+
|
179
227
|
Per-function call arguments
|
180
228
|
---------------------------
|
181
229
|
|
@@ -365,7 +413,7 @@ Other major contributors:
|
|
365
413
|
|
366
414
|
* `cthoyt <https://github.com/cthoyt>`_ - Base memory core implementation.
|
367
415
|
|
368
|
-
* `amarczew <https://github.com/amarczew>`_ - The ``
|
416
|
+
* `amarczew <https://github.com/amarczew>`_ - The ``hash_func`` kwarg.
|
369
417
|
|
370
418
|
* `non-senses <https://github.com/non-senses>`_ - The ``wait_for_calc_timeout`` kwarg.
|
371
419
|
|
@@ -411,5 +459,3 @@ Notable bugfixers:
|
|
411
459
|
.. _watchdog: https://github.com/gorakhargosh/watchdog
|
412
460
|
|
413
461
|
|
414
|
-
|
415
|
-
|
@@ -33,10 +33,12 @@ cachier.egg-info/top_level.txt
|
|
33
33
|
cachier/scripts/__init__.py
|
34
34
|
cachier/scripts/cli.py
|
35
35
|
tests/__init__.py
|
36
|
-
tests/conftest.py
|
37
36
|
tests/speed_eval.py
|
37
|
+
tests/standalone_script.py
|
38
38
|
tests/test_core_lookup.py
|
39
39
|
tests/test_general.py
|
40
40
|
tests/test_memory_core.py
|
41
41
|
tests/test_mongo_core.py
|
42
|
-
tests/test_pickle_core.py
|
42
|
+
tests/test_pickle_core.py
|
43
|
+
tests/test_quality.py
|
44
|
+
tests/test_security.py
|
@@ -1,14 +1,19 @@
|
|
1
1
|
watchdog
|
2
2
|
portalocker
|
3
3
|
pathtools
|
4
|
+
setuptools>=67.6.0
|
4
5
|
|
5
6
|
[test]
|
6
7
|
pytest
|
7
8
|
coverage
|
8
9
|
pytest-cov
|
9
|
-
|
10
|
+
bandit
|
11
|
+
flake8
|
12
|
+
pylint
|
13
|
+
safety
|
10
14
|
pymongo
|
11
15
|
dnspython
|
16
|
+
pymongo-inmemory
|
12
17
|
pandas
|
13
18
|
collective.checkdocs
|
14
19
|
pygments
|
@@ -18,10 +18,10 @@ import versioneer
|
|
18
18
|
TEST_REQUIRES = [
|
19
19
|
# tests and coverages
|
20
20
|
'pytest', 'coverage', 'pytest-cov',
|
21
|
-
#
|
22
|
-
'
|
21
|
+
# linting and code quality
|
22
|
+
'bandit', 'flake8', 'pylint', 'safety',
|
23
23
|
# to connect to the test mongodb server
|
24
|
-
'pymongo', 'dnspython',
|
24
|
+
'pymongo', 'dnspython', 'pymongo-inmemory',
|
25
25
|
# to test pandas dataframe as-param hashing with mongodb core
|
26
26
|
'pandas',
|
27
27
|
# to be able to run `python setup.py checkdocs`
|
@@ -44,7 +44,7 @@ setup(
|
|
44
44
|
author='Shay Palachy',
|
45
45
|
author_email='shay.palachy@gmail.com',
|
46
46
|
url='https://github.com/python-cachier/cachier',
|
47
|
-
packages=['cachier'],
|
47
|
+
packages=['cachier', 'cachier.scripts'],
|
48
48
|
entry_points='''
|
49
49
|
[console_scripts]
|
50
50
|
cachier=cachier.scripts.cli:cli
|
@@ -52,6 +52,7 @@ setup(
|
|
52
52
|
install_requires=[
|
53
53
|
'watchdog', 'portalocker',
|
54
54
|
'pathtools', # for watchdog, who has dependency spec problem
|
55
|
+
'setuptools>=67.6.0', # to avoid vulnerability in 56.0.0
|
55
56
|
],
|
56
57
|
extras_require={
|
57
58
|
'test': TEST_REQUIRES,
|
@@ -18,7 +18,7 @@ def _test_int_pickling_compare(int_1, int_2):
|
|
18
18
|
def test_pickle_speed():
|
19
19
|
"""Test speeds"""
|
20
20
|
print("Comparing speeds of decorated vs non-decorated functions...")
|
21
|
-
num_of_vals =
|
21
|
+
num_of_vals = 1000
|
22
22
|
times = []
|
23
23
|
for i in range(1, num_of_vals):
|
24
24
|
tic = time()
|