cachier 3.1.2__py3-none-any.whl → 3.3.0__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.
cachier/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from ._version import * # noqa: F403
1
+ from ._version import __version__
2
2
  from .config import (
3
3
  disable_caching,
4
4
  enable_caching,
@@ -17,4 +17,5 @@ __all__ = [
17
17
  "get_global_params",
18
18
  "enable_caching",
19
19
  "disable_caching",
20
+ "__version__",
20
21
  ]
cachier/_version.py CHANGED
@@ -18,7 +18,8 @@ with open(_PATH_VERSION) as fopen:
18
18
  def _get_git_sha() -> str:
19
19
  from subprocess import DEVNULL, check_output
20
20
 
21
- out = check_output(["git", "rev-parse", "--short", "HEAD"], stderr=DEVNULL) # noqa: S603, S607
21
+ args = ["git", "rev-parse", "--short", "HEAD"]
22
+ out = check_output(args, stderr=DEVNULL) # noqa: S603
22
23
  return out.decode("utf-8").strip()
23
24
 
24
25
 
cachier/config.py CHANGED
@@ -2,7 +2,6 @@ import hashlib
2
2
  import os
3
3
  import pickle
4
4
  import threading
5
- from collections.abc import Mapping
6
5
  from dataclasses import dataclass, replace
7
6
  from datetime import datetime, timedelta
8
7
  from typing import Any, Optional, Union
@@ -65,7 +64,7 @@ def _update_with_defaults(
65
64
  return param
66
65
 
67
66
 
68
- def set_default_params(**params: Mapping) -> None:
67
+ def set_default_params(**params: Any) -> None:
69
68
  """Configure default parameters applicable to all memoized functions."""
70
69
  # It is kept for backwards compatibility with desperation warning
71
70
  import warnings
@@ -79,7 +78,7 @@ def set_default_params(**params: Mapping) -> None:
79
78
  set_global_params(**params)
80
79
 
81
80
 
82
- def set_global_params(**params: Mapping) -> None:
81
+ def set_global_params(**params: Any) -> None:
83
82
  """Configure global parameters applicable to all memoized functions.
84
83
 
85
84
  This function takes the same keyword parameters as the ones defined in the
cachier/core.py CHANGED
@@ -14,7 +14,7 @@ from collections import OrderedDict
14
14
  from concurrent.futures import ThreadPoolExecutor
15
15
  from datetime import datetime, timedelta
16
16
  from functools import wraps
17
- from typing import Any, Optional, Union
17
+ from typing import Any, Callable, Optional, Union
18
18
  from warnings import warn
19
19
 
20
20
  from .config import (
@@ -27,9 +27,11 @@ from .cores.base import RecalculationNeeded, _BaseCore
27
27
  from .cores.memory import _MemoryCore
28
28
  from .cores.mongo import _MongoCore
29
29
  from .cores.pickle import _PickleCore
30
+ from .cores.sql import _SQLCore
30
31
 
31
32
  MAX_WORKERS_ENVAR_NAME = "CACHIER_MAX_WORKERS"
32
33
  DEFAULT_MAX_WORKERS = 8
34
+ ZERO_TIMEDELTA = timedelta(seconds=0)
33
35
 
34
36
 
35
37
  def _max_workers():
@@ -107,6 +109,7 @@ def cachier(
107
109
  hash_params: Optional[HashFunc] = None,
108
110
  backend: Optional[Backend] = None,
109
111
  mongetter: Optional[Mongetter] = None,
112
+ sql_engine: Optional[Union[str, Any, Callable[[], Any]]] = None,
110
113
  stale_after: Optional[timedelta] = None,
111
114
  next_time: Optional[bool] = None,
112
115
  cache_dir: Optional[Union[str, os.PathLike]] = None,
@@ -134,13 +137,16 @@ def cachier(
134
137
  hash_params : callable, optional
135
138
  backend : str, optional
136
139
  The name of the backend to use. Valid options currently include
137
- 'pickle', 'mongo' and 'memory'. If not provided, defaults to
140
+ 'pickle', 'mongo', 'memory', and 'sql'. If not provided, defaults to
138
141
  'pickle' unless the 'mongetter' argument is passed, in which
139
142
  case the mongo backend is automatically selected.
140
143
  mongetter : callable, optional
141
144
  A callable that takes no arguments and returns a pymongo.Collection
142
145
  object with writing permissions. If unset a local pickle cache is used
143
146
  instead.
147
+ sql_engine : str, Engine, or callable, optional
148
+ SQLAlchemy connection string, Engine, or callable returning an Engine.
149
+ Used for the SQL backend.
144
150
  stale_after : datetime.timedelta, optional
145
151
  The time delta after which a cached result is considered stale. Calls
146
152
  made after the result goes stale will trigger a recalculation of the
@@ -208,14 +214,43 @@ def cachier(
208
214
  core = _MemoryCore(
209
215
  hash_func=hash_func, wait_for_calc_timeout=wait_for_calc_timeout
210
216
  )
217
+ elif backend == "sql":
218
+ core = _SQLCore(
219
+ hash_func=hash_func,
220
+ sql_engine=sql_engine,
221
+ wait_for_calc_timeout=wait_for_calc_timeout,
222
+ )
211
223
  else:
212
224
  raise ValueError("specified an invalid core: %s" % backend)
213
225
 
214
226
  def _cachier_decorator(func):
215
227
  core.set_func(func)
216
228
 
217
- @wraps(func)
218
- def func_wrapper(*args, **kwds):
229
+ # ---
230
+ # MAINTAINER NOTE: max_age parameter
231
+ #
232
+ # The _call function below supports a per-call 'max_age' parameter,
233
+ # allowing users to specify a maximum allowed age for a cached value.
234
+ # If the cached value is older than 'max_age',
235
+ # a recalculation is triggered. This is in addition to the
236
+ # per-decorator 'stale_after' parameter.
237
+ #
238
+ # The effective staleness threshold is the minimum of 'stale_after'
239
+ # and 'max_age' (if provided).
240
+ # This ensures that the strictest max age requirement is enforced.
241
+ #
242
+ # The main function wrapper is a standard function that passes
243
+ # *args and **kwargs to _call. By default, max_age is None,
244
+ # so only 'stale_after' is considered unless overridden.
245
+ #
246
+ # The user-facing API exposes:
247
+ # - Per-call: myfunc(..., max_age=timedelta(...))
248
+ #
249
+ # This design allows both one-off (per-call) and default
250
+ # (per-decorator) max age constraints.
251
+ # ---
252
+
253
+ def _call(*args, max_age: Optional[timedelta] = None, **kwds):
219
254
  nonlocal allow_none
220
255
  _allow_none = _update_with_defaults(allow_none, "allow_none", kwds)
221
256
  # print('Inside general wrapper for {}.'.format(func.__name__))
@@ -260,7 +295,23 @@ def cachier(
260
295
  if _allow_none or entry.value is not None:
261
296
  _print("Cached result found.")
262
297
  now = datetime.now()
263
- if now - entry.time <= _stale_after:
298
+ max_allowed_age = _stale_after
299
+ nonneg_max_age = True
300
+ if max_age is not None:
301
+ if max_age < ZERO_TIMEDELTA:
302
+ _print(
303
+ "max_age is negative. "
304
+ "Cached result considered stale."
305
+ )
306
+ nonneg_max_age = False
307
+ else:
308
+ max_allowed_age = (
309
+ min(_stale_after, max_age)
310
+ if max_age is not None
311
+ else _stale_after
312
+ )
313
+ # note: if max_age < 0, we always consider a value stale
314
+ if nonneg_max_age and (now - entry.time <= max_allowed_age):
264
315
  _print("And it is fresh!")
265
316
  return entry.value
266
317
  _print("But it is stale... :(")
@@ -294,6 +345,14 @@ def cachier(
294
345
  _print("No entry found. No current calc. Calling like a boss.")
295
346
  return _calc_entry(core, key, func, args, kwds)
296
347
 
348
+ # MAINTAINER NOTE: The main function wrapper is now a standard function
349
+ # that passes *args and **kwargs to _call. This ensures that user
350
+ # arguments are not shifted, and max_age is only settable via keyword
351
+ # argument.
352
+ @wraps(func)
353
+ def func_wrapper(*args, **kwargs):
354
+ return _call(*args, **kwargs)
355
+
297
356
  def _clear_cache():
298
357
  """Clear the cache."""
299
358
  core.clear_cache()
cachier/cores/base.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Defines the interface of a cachier caching core."""
2
+
2
3
  # This file is part of Cachier.
3
4
  # https://github.com/python-cachier/cachier
4
5
 
cachier/cores/mongo.py CHANGED
@@ -75,7 +75,9 @@ class _MongoCore(_BaseCore):
75
75
  )
76
76
  if not res:
77
77
  return key, None
78
- val = pickle.loads(res["value"]) if "value" in res else None # noqa: S301
78
+ val = None
79
+ if "value" in res:
80
+ val = pickle.loads(res["value"]) # noqa: S301
79
81
  entry = CacheEntry(
80
82
  value=val,
81
83
  time=res.get("time", None),
cachier/cores/sql.py ADDED
@@ -0,0 +1,288 @@
1
+ """A SQLAlchemy-based caching core for cachier."""
2
+
3
+ import pickle
4
+ import threading
5
+ from datetime import datetime
6
+ from typing import Any, Callable, Optional, Tuple, Union
7
+
8
+ try:
9
+ from sqlalchemy import (
10
+ Boolean,
11
+ Column,
12
+ DateTime,
13
+ Index,
14
+ LargeBinary,
15
+ String,
16
+ and_,
17
+ create_engine,
18
+ delete,
19
+ insert,
20
+ select,
21
+ update,
22
+ )
23
+ from sqlalchemy.engine import Engine
24
+ from sqlalchemy.orm import declarative_base, sessionmaker
25
+
26
+ SQLALCHEMY_AVAILABLE = True
27
+ except ImportError:
28
+ SQLALCHEMY_AVAILABLE = False
29
+
30
+ from .._types import HashFunc
31
+ from ..config import CacheEntry
32
+ from .base import RecalculationNeeded, _BaseCore, _get_func_str
33
+
34
+ if SQLALCHEMY_AVAILABLE:
35
+ Base = declarative_base()
36
+
37
+ class CacheTable(Base): # type: ignore[misc, valid-type]
38
+ """SQLAlchemy model for cachier cache entries."""
39
+
40
+ __tablename__ = "cachier_cache"
41
+ id = Column(String, primary_key=True)
42
+ function_id = Column(String, index=True, nullable=False)
43
+ key = Column(String, index=True, nullable=False)
44
+ value = Column(LargeBinary, nullable=True)
45
+ timestamp = Column(DateTime, nullable=False)
46
+ stale = Column(Boolean, default=False)
47
+ processing = Column(Boolean, default=False)
48
+ completed = Column(Boolean, default=False)
49
+ __table_args__ = (
50
+ Index("ix_func_key", "function_id", "key", unique=True),
51
+ )
52
+
53
+
54
+ class _SQLCore(_BaseCore):
55
+ """SQLAlchemy-based core for Cachier, supporting SQL-based backends.
56
+
57
+ This should work with SQLite, PostgreSQL and so on.
58
+
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ hash_func: Optional[HashFunc],
64
+ sql_engine: Optional[Union[str, "Engine", Callable[[], "Engine"]]],
65
+ wait_for_calc_timeout: Optional[int] = None,
66
+ ):
67
+ if not SQLALCHEMY_AVAILABLE:
68
+ raise ImportError(
69
+ "SQLAlchemy is required for the SQL core. "
70
+ "Install with `pip install SQLAlchemy`."
71
+ )
72
+ super().__init__(
73
+ hash_func=hash_func, wait_for_calc_timeout=wait_for_calc_timeout
74
+ )
75
+ self._engine = self._resolve_engine(sql_engine)
76
+ self._Session = sessionmaker(bind=self._engine)
77
+ Base.metadata.create_all(self._engine)
78
+ self._lock = threading.RLock()
79
+ self._func_str = None
80
+
81
+ def _resolve_engine(self, sql_engine):
82
+ if isinstance(sql_engine, Engine):
83
+ return sql_engine
84
+ if isinstance(sql_engine, str):
85
+ return create_engine(sql_engine, future=True)
86
+ if callable(sql_engine):
87
+ return sql_engine()
88
+ raise ValueError(
89
+ "sql_engine must be a SQLAlchemy Engine, connection string, "
90
+ "or callable returning an Engine."
91
+ )
92
+
93
+ def set_func(self, func):
94
+ super().set_func(func)
95
+ self._func_str = _get_func_str(func)
96
+
97
+ def get_entry_by_key(self, key: str) -> Tuple[str, Optional[CacheEntry]]:
98
+ with self._lock, self._Session() as session:
99
+ row = session.execute(
100
+ select(CacheTable).where(
101
+ and_(
102
+ CacheTable.function_id == self._func_str,
103
+ CacheTable.key == key,
104
+ )
105
+ )
106
+ ).scalar_one_or_none()
107
+ if not row:
108
+ return key, None
109
+ value = pickle.loads(row.value) if row.value is not None else None
110
+ entry = CacheEntry(
111
+ value=value,
112
+ time=row.timestamp,
113
+ stale=row.stale,
114
+ _processing=row.processing,
115
+ _completed=row.completed,
116
+ )
117
+ return key, entry
118
+
119
+ def set_entry(self, key: str, func_res: Any) -> None:
120
+ with self._lock, self._Session() as session:
121
+ thebytes = pickle.dumps(func_res)
122
+ now = datetime.now()
123
+ base_insert = insert(CacheTable)
124
+ stmt = (
125
+ base_insert.values(
126
+ id=f"{self._func_str}:{key}",
127
+ function_id=self._func_str,
128
+ key=key,
129
+ value=thebytes,
130
+ timestamp=now,
131
+ stale=False,
132
+ processing=False,
133
+ completed=True,
134
+ ).on_conflict_do_update(
135
+ index_elements=[CacheTable.function_id, CacheTable.key],
136
+ set_={
137
+ "value": thebytes,
138
+ "timestamp": now,
139
+ "stale": False,
140
+ "processing": False,
141
+ "completed": True,
142
+ },
143
+ )
144
+ if hasattr(base_insert, "on_conflict_do_update")
145
+ else None
146
+ )
147
+ # Fallback for non-SQLite/Postgres: try update, else insert
148
+ if stmt:
149
+ session.execute(stmt)
150
+ else:
151
+ row = session.execute(
152
+ select(CacheTable).where(
153
+ and_(
154
+ CacheTable.function_id == self._func_str,
155
+ CacheTable.key == key,
156
+ )
157
+ )
158
+ ).scalar_one_or_none()
159
+ if row:
160
+ session.execute(
161
+ update(CacheTable)
162
+ .where(
163
+ and_(
164
+ CacheTable.function_id == self._func_str,
165
+ CacheTable.key == key,
166
+ )
167
+ )
168
+ .values(
169
+ value=thebytes,
170
+ timestamp=now,
171
+ stale=False,
172
+ processing=False,
173
+ completed=True,
174
+ )
175
+ )
176
+ else:
177
+ session.add(
178
+ CacheTable(
179
+ id=f"{self._func_str}:{key}",
180
+ function_id=self._func_str,
181
+ key=key,
182
+ value=thebytes,
183
+ timestamp=now,
184
+ stale=False,
185
+ processing=False,
186
+ completed=True,
187
+ )
188
+ )
189
+ session.commit()
190
+
191
+ def mark_entry_being_calculated(self, key: str) -> None:
192
+ with self._lock, self._Session() as session:
193
+ row = session.execute(
194
+ select(CacheTable).where(
195
+ and_(
196
+ CacheTable.function_id == self._func_str,
197
+ CacheTable.key == key,
198
+ )
199
+ )
200
+ ).scalar_one_or_none()
201
+ if row:
202
+ session.execute(
203
+ update(CacheTable)
204
+ .where(
205
+ and_(
206
+ CacheTable.function_id == self._func_str,
207
+ CacheTable.key == key,
208
+ )
209
+ )
210
+ .values(processing=True)
211
+ )
212
+ else:
213
+ session.add(
214
+ CacheTable(
215
+ id=f"{self._func_str}:{key}",
216
+ function_id=self._func_str,
217
+ key=key,
218
+ value=None,
219
+ timestamp=datetime.now(),
220
+ stale=False,
221
+ processing=True,
222
+ completed=False,
223
+ )
224
+ )
225
+ session.commit()
226
+
227
+ def mark_entry_not_calculated(self, key: str) -> None:
228
+ with self._lock, self._Session() as session:
229
+ session.execute(
230
+ update(CacheTable)
231
+ .where(
232
+ and_(
233
+ CacheTable.function_id == self._func_str,
234
+ CacheTable.key == key,
235
+ )
236
+ )
237
+ .values(processing=False)
238
+ )
239
+ session.commit()
240
+
241
+ def wait_on_entry_calc(self, key: str) -> Any:
242
+ import time
243
+
244
+ time_spent = 0
245
+ while True:
246
+ with self._lock, self._Session() as session:
247
+ row = session.execute(
248
+ select(CacheTable).where(
249
+ and_(
250
+ CacheTable.function_id == self._func_str,
251
+ CacheTable.key == key,
252
+ )
253
+ )
254
+ ).scalar_one_or_none()
255
+ if not row:
256
+ raise RecalculationNeeded()
257
+ if not row.processing:
258
+ return (
259
+ pickle.loads(row.value)
260
+ if row.value is not None
261
+ else None
262
+ )
263
+ time.sleep(1)
264
+ time_spent += 1
265
+ self.check_calc_timeout(time_spent)
266
+
267
+ def clear_cache(self) -> None:
268
+ with self._lock, self._Session() as session:
269
+ session.execute(
270
+ delete(CacheTable).where(
271
+ CacheTable.function_id == self._func_str
272
+ )
273
+ )
274
+ session.commit()
275
+
276
+ def clear_being_calculated(self) -> None:
277
+ with self._lock, self._Session() as session:
278
+ session.execute(
279
+ update(CacheTable)
280
+ .where(
281
+ and_(
282
+ CacheTable.function_id == self._func_str,
283
+ CacheTable.processing,
284
+ )
285
+ )
286
+ .values(processing=False)
287
+ )
288
+ session.commit()
cachier/version.info CHANGED
@@ -1 +1 @@
1
- 3.1.2
1
+ 3.3.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cachier
3
- Version: 3.1.2
3
+ Version: 3.3.0
4
4
  Summary: Persistent, stale-free, local and cross-machine caching for Python functions.
5
5
  Author-email: Shay Palachy Affek <shay.palachy@gmail.com>
6
6
  License: MIT License
@@ -32,11 +32,11 @@ Classifier: Intended Audience :: Developers
32
32
  Classifier: License :: OSI Approved :: MIT License
33
33
  Classifier: Programming Language :: Python
34
34
  Classifier: Programming Language :: Python :: 3 :: Only
35
- Classifier: Programming Language :: Python :: 3.8
36
35
  Classifier: Programming Language :: Python :: 3.9
37
36
  Classifier: Programming Language :: Python :: 3.10
38
37
  Classifier: Programming Language :: Python :: 3.11
39
38
  Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
40
  Classifier: Topic :: Other/Nonlisted Topic
41
41
  Classifier: Topic :: Software Development :: Libraries
42
42
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -45,6 +45,7 @@ Description-Content-Type: text/x-rst
45
45
  License-File: LICENSE
46
46
  Requires-Dist: portalocker>=2.3.2
47
47
  Requires-Dist: watchdog>=2.3.1
48
+ Dynamic: license-file
48
49
 
49
50
  Cachier
50
51
  #######
@@ -92,13 +93,14 @@ Features
92
93
  ========
93
94
 
94
95
  * Pure Python.
95
- * Compatible with Python 3.8+ (Python 2.7 was discontinued in version 1.2.8).
96
+ * Compatible with Python 3.9+ (Python 2.7 was discontinued in version 1.2.8).
96
97
  * Supported and `tested on Linux, OS X and Windows <https://travis-ci.org/shaypal5/cachier>`_.
97
98
  * A simple interface.
98
99
  * Defining "shelf life" for cached values.
99
100
  * Local caching using pickle files.
100
101
  * Cross-machine caching using MongoDB.
101
102
  * Thread-safety.
103
+ * **Per-call max age:** Specify a maximum age for cached values per call.
102
104
 
103
105
  Cachier is **NOT**:
104
106
 
@@ -281,6 +283,27 @@ Per-function call arguments
281
283
 
282
284
  Cachier also accepts several keyword arguments in the calls of the function it wraps rather than in the decorator call, allowing you to modify its behaviour for a specific function call.
283
285
 
286
+ **Max Age (max_age)**
287
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288
+ You can specify a maximum allowed age for a cached value on a per-call basis using the `max_age` keyword argument. If the cached value is older than this threshold, a recalculation is triggered. This is in addition to the `stale_after` parameter set at the decorator level; the strictest (smallest) threshold is enforced.
289
+
290
+ .. code-block:: python
291
+
292
+ from datetime import timedelta
293
+ from cachier import cachier
294
+
295
+ @cachier(stale_after=timedelta(days=3))
296
+ def add(a, b):
297
+ return a + b
298
+
299
+ # Use a per-call max age:
300
+ result = add(1, 2, max_age=timedelta(seconds=10)) # Only use cache if value is <10s old
301
+
302
+ **How it works:**
303
+ - The effective max age threshold is the minimum of `stale_after` (from the decorator) and `max_age` (from the call).
304
+ - If the cached value is older than this threshold, a new calculation is triggered and the cache is updated.
305
+ - If not, the cached value is returned as usual.
306
+
284
307
  Ignore Cache
285
308
  ~~~~~~~~~~~~
286
309
 
@@ -390,6 +413,64 @@ You can set an in-memory cache by assigning the ``backend`` parameter with ``'me
390
413
 
391
414
  Note, however, that ``cachier``'s in-memory core is simple, and has no monitoring or cap on cache size, and can thus lead to memory errors on large return values - it is mainly intended to be used with future multi-core functionality. As a rule, Python's built-in ``lru_cache`` is a much better stand-alone solution.
392
415
 
416
+ SQLAlchemy (SQL) Core
417
+ ---------------------
418
+
419
+ **Note:** The SQL core requires SQLAlchemy to be installed. It is not installed by default with cachier. To use the SQL backend, run::
420
+
421
+ pip install SQLAlchemy
422
+
423
+ Cachier supports a generic SQL backend via SQLAlchemy, allowing you to use SQLite, PostgreSQL, MySQL, and other databases.
424
+
425
+ **Usage Example (SQLite in-memory):**
426
+
427
+ .. code-block:: python
428
+
429
+ from cachier import cachier
430
+
431
+ @cachier(backend="sql", sql_engine="sqlite:///:memory:")
432
+ def my_func(x):
433
+ return x * 2
434
+
435
+ **Usage Example (PostgreSQL):**
436
+
437
+ .. code-block:: python
438
+
439
+ @cachier(backend="sql", sql_engine="postgresql://user:pass@localhost/dbname")
440
+ def my_func(x):
441
+ return x * 2
442
+
443
+ **Usage Example (MySQL):**
444
+
445
+ .. code-block:: python
446
+
447
+ @cachier(backend="sql", sql_engine="mysql+pymysql://user:pass@localhost/dbname")
448
+ def my_func(x):
449
+ return x * 2
450
+
451
+ **Configuration Options:**
452
+
453
+ - ``sql_engine``: SQLAlchemy connection string, Engine, or callable returning an Engine.
454
+ - All other standard cachier options are supported.
455
+
456
+ **Table Schema:**
457
+
458
+ - ``function_id``: Unique identifier for the cached function
459
+ - ``key``: Cache key
460
+ - ``value``: Pickled result
461
+ - ``timestamp``: Datetime of cache entry
462
+ - ``stale``: Boolean, is value stale
463
+ - ``processing``: Boolean, is value being calculated
464
+ - ``completed``: Boolean, is value calculation completed
465
+
466
+ **Limitations & Notes:**
467
+
468
+ - Requires SQLAlchemy (install with ``pip install SQLAlchemy``)
469
+ - For production, use a persistent database (not ``:memory:``)
470
+ - Thread/process safety is handled via transactions and row-level locks
471
+ - Value serialization uses ``pickle``. **Warning:** `pickle` can execute arbitrary code during deserialization if the cache database is compromised. Ensure the cache is stored securely and consider using safer serialization methods like `json` if security is a concern.
472
+ - For best performance, ensure your DB supports row-level locking
473
+
393
474
 
394
475
  Contributing
395
476
  ============
@@ -0,0 +1,20 @@
1
+ cachier/__init__.py,sha256=1isxXaP2l6Vq7gC1Gob6hduRLC07dHfWze1-oCjwSP0,415
2
+ cachier/__main__.py,sha256=upg-TlHs1vngKYvkjoPpl3Pvl6xOx4ut-M1mElMiAo0,443
3
+ cachier/_types.py,sha256=EGJMiw-oCIC_cDLyzw7YC40lfo8jnD3zMmoJpA9Y8Iw,238
4
+ cachier/_version.py,sha256=jnPPRn_qmjNi-qmQjlHnzNGf3LSBTYkMmJdGjxMTOBM,1089
5
+ cachier/config.py,sha256=6hyQtn9T6UXu2UQhKJltWT0Nu4OBS4ION1x7Lt1i8Og,3838
6
+ cachier/core.py,sha256=PHLDA6Mabih-mi4y3CBsb8_vBIpvYQt547_Y57fo8uI,15825
7
+ cachier/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ cachier/version.info,sha256=Xu5sDFxpAfXlI_CkNLykEG4f9xNt60UfmCsLsaexNxY,6
9
+ cachier/cores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ cachier/cores/base.py,sha256=s7qgmDJA4LGub6ydGfMk9vVJW4fgeU0EXl-9gmpuh28,3683
11
+ cachier/cores/memory.py,sha256=fsvqq9rwwmAaMBvYo-oUNAxB6UOyfBpuf8ACW_XTaU0,3572
12
+ cachier/cores/mongo.py,sha256=pCBrxLsmGr68Q50JVD_CUPAYwhaLDrJUQs_6A-_GYLA,4993
13
+ cachier/cores/pickle.py,sha256=FgfvZWAFdWQPOo3G-L57iEV2ujEkIDH8TyGzbarsZeE,10678
14
+ cachier/cores/sql.py,sha256=nuf2-Szo7VTPRa7IC3JGWEtGsBtdkIrx0bhOm3U0mfE,9895
15
+ cachier-3.3.0.dist-info/licenses/LICENSE,sha256=-2WrMJkIa0gVP6YQHXXDT7ws-S3M2NEVEF4XF3K8qrY,1069
16
+ cachier-3.3.0.dist-info/METADATA,sha256=P8GoF2LSZKA5Tp8NunrJvS_f1XlytO4HsCmDsUy4oxQ,23136
17
+ cachier-3.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ cachier-3.3.0.dist-info/entry_points.txt,sha256=x4Y7t6Y0Qev_3fgG-Jv7TrsvVdJty3FnGAdkT8-_5mY,49
19
+ cachier-3.3.0.dist-info/top_level.txt,sha256=_rW_HiJumDCch67YT-WAgzcyvKg5RiYDMZq9d-0ZpaE,8
20
+ cachier-3.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,19 +0,0 @@
1
- cachier/__init__.py,sha256=GZeDebG0EgWIYBmRgPhO19dMiiaam8f9Pu7cWLv3ywY,400
2
- cachier/__main__.py,sha256=upg-TlHs1vngKYvkjoPpl3Pvl6xOx4ut-M1mElMiAo0,443
3
- cachier/_types.py,sha256=EGJMiw-oCIC_cDLyzw7YC40lfo8jnD3zMmoJpA9Y8Iw,238
4
- cachier/_version.py,sha256=yE6UYwvdoIRpw3HmNifiGwV3fqVea5PwZj_EvyosiZ8,1079
5
- cachier/config.py,sha256=zp5AfhDIYsYmfQCrlQP-Jg-m6r_xIsk5tK3vPmQEhTA,3882
6
- cachier/core.py,sha256=x2uHH7twqNPpO09bUf5-Vfzx-G95qggmGTHPw1xkYo8,13156
7
- cachier/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- cachier/version.info,sha256=kFNgkh5j9BnWoRHdMha_KEAvNnq5qmlETYDpdBlgDYA,6
9
- cachier/cores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- cachier/cores/base.py,sha256=q2QXw35UduQEFseNdwbBv1ZKPl1U25NKIX1NbC2i1U4,3682
11
- cachier/cores/memory.py,sha256=fsvqq9rwwmAaMBvYo-oUNAxB6UOyfBpuf8ACW_XTaU0,3572
12
- cachier/cores/mongo.py,sha256=tM1TJUA4UjknwOfcXF4l0DLDZb1ucUpL-dD75h7VgTA,4971
13
- cachier/cores/pickle.py,sha256=FgfvZWAFdWQPOo3G-L57iEV2ujEkIDH8TyGzbarsZeE,10678
14
- cachier-3.1.2.dist-info/LICENSE,sha256=-2WrMJkIa0gVP6YQHXXDT7ws-S3M2NEVEF4XF3K8qrY,1069
15
- cachier-3.1.2.dist-info/METADATA,sha256=blJgO_8gOHhLkjmMxcHyX84D_IXXCfUXPTzsZ2IScZU,20136
16
- cachier-3.1.2.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
17
- cachier-3.1.2.dist-info/entry_points.txt,sha256=x4Y7t6Y0Qev_3fgG-Jv7TrsvVdJty3FnGAdkT8-_5mY,49
18
- cachier-3.1.2.dist-info/top_level.txt,sha256=_rW_HiJumDCch67YT-WAgzcyvKg5RiYDMZq9d-0ZpaE,8
19
- cachier-3.1.2.dist-info/RECORD,,