onecache 0.8.1__tar.gz → 0.9.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.
- onecache-0.9.0/CHANGELOG.md +144 -0
- onecache-0.9.0/MANIFEST.in +7 -0
- {onecache-0.8.1 → onecache-0.9.0}/PKG-INFO +33 -13
- onecache-0.9.0/README.md +108 -0
- onecache-0.9.0/onecache/__init__.py +122 -0
- onecache-0.9.0/onecache/_ccore.pyx +525 -0
- onecache-0.9.0/onecache/_core.py +310 -0
- onecache-0.9.0/onecache/cache_value.py +3 -0
- onecache-0.9.0/onecache.egg-info/PKG-INFO +132 -0
- onecache-0.9.0/onecache.egg-info/SOURCES.txt +16 -0
- onecache-0.9.0/onecache.egg-info/dependency_links.txt +1 -0
- onecache-0.9.0/onecache.egg-info/top_level.txt +1 -0
- {onecache-0.8.1 → onecache-0.9.0}/pyproject.toml +32 -10
- onecache-0.9.0/pytest.ini +5 -0
- onecache-0.9.0/setup.cfg +4 -0
- onecache-0.9.0/setup.py +57 -0
- onecache-0.9.0/tests/unit/test_cache.py +454 -0
- onecache-0.8.1/README.md +0 -88
- onecache-0.8.1/onecache/__init__.py +0 -241
- onecache-0.8.1/onecache/cache_value.py +0 -34
- {onecache-0.8.1 → onecache-0.9.0}/onecache/utils.py +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.9.0] 2026-10-12
|
|
10
|
+
### Added
|
|
11
|
+
- Cython implementation of the cache core, used automatically on CPython; pure Python fallback for PyPy and platforms without a prebuilt wheel
|
|
12
|
+
- `onecache.COMPILED` flag and `ONECACHE_NO_EXTENSIONS` environment variable to force the pure Python implementation
|
|
13
|
+
- Prebuilt wheels for Linux (manylinux/musllinux), macOS and Windows on x86_64 and ARM64, built with cibuildwheel
|
|
14
|
+
- Decorated functions keep the wrapped function metadata (`__name__`, `__doc__`, `__wrapped__`)
|
|
15
|
+
- `ExpirableCache.clear()` to drop every entry
|
|
16
|
+
- `onecache.monotonic()`, the clock used for `CacheValue.expire_at`
|
|
17
|
+
- Decorated functions support weak references and pickle by reference with both implementations
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- `LRUCache` now evicts the least recently used entry (previously the least frequently accessed)
|
|
21
|
+
- Expiration checks are O(1) amortized instead of scanning the whole cache on every access, and eviction no longer sorts the cache
|
|
22
|
+
- `thread_safe=True` now guards every cache operation, not only writes, with a reentrant lock
|
|
23
|
+
- `max_mem_size` evicts entries until the new value fits, instead of always keeping at least one previous entry; a value bigger than `max_mem_size` is not cached
|
|
24
|
+
- Setting an existing key again moves it to the newest position in the eviction order
|
|
25
|
+
- `ExpirableCache.cache` is a read-only view; use `expire()` or `clear()` to remove entries. Its iteration order is not the eviction order
|
|
26
|
+
- `timeout`, `max_mem_size`, `current_size` and `thread_safe` are read-only attributes; the `lock` attribute was removed
|
|
27
|
+
- `CacheValue.expire_at` is a timestamp in seconds from `onecache.monotonic()` (`float("inf")` when it does not expire) instead of a `datetime`; that clock is not always `time.monotonic()`
|
|
28
|
+
- `CacheValue.size` is only computed when `max_mem_size` is set (0 otherwise)
|
|
29
|
+
- A `timeout` of 0 or lower means entries do not expire
|
|
30
|
+
- A cache size (`maxsize`) lower than 1 raises `ValueError`
|
|
31
|
+
- Eviction is no longer customizable by overriding `_pop_one()`
|
|
32
|
+
- Cache instances (`ExpirableCache`, `LRUCache`) can't be pickled or copied; `CacheValue` entries pickle only their own data
|
|
33
|
+
- On CPython, decorated functions are `CachedFunction` objects instead of plain functions (`inspect.isfunction()` is False), and compiled cache instances don't accept arbitrary attributes or weak references
|
|
34
|
+
- Build backend switched from poetry-core to setuptools + Cython
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
- `CacheValue.set_expired()`, `CacheValue.refresh_ttl()`, `CacheValue.increment()` and the `fetch_value` argument of `ExpirableCache.get()`
|
|
38
|
+
- `onecache.IS_PYPY` and `onecache.utcnow` re-exports (still available in `onecache.utils`)
|
|
39
|
+
|
|
40
|
+
## [0.8.1] 2026-02-20
|
|
41
|
+
### Changed
|
|
42
|
+
- Removed the maximum Python version constraint from runtime requirements (`python = ">=3.8"`)
|
|
43
|
+
- Updated Python classifiers and refreshed CI/docs for newer Python versions
|
|
44
|
+
|
|
45
|
+
## [0.8.0] 2025-11-12
|
|
46
|
+
### Removed
|
|
47
|
+
- Dropped the unused `packaging` dependency from runtime requirements
|
|
48
|
+
|
|
49
|
+
## [0.7.2] 2025-10-30
|
|
50
|
+
### Added
|
|
51
|
+
* python 3.14 compatibility
|
|
52
|
+
|
|
53
|
+
## [0.7.1] 2025-01-26
|
|
54
|
+
### Added
|
|
55
|
+
* python 3.13 compatibility
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## [0.7.0] 2024-08-01
|
|
59
|
+
### Added
|
|
60
|
+
* poetry for project management
|
|
61
|
+
* utcnow() function utility for newer python versions
|
|
62
|
+
|
|
63
|
+
## [0.6.0] 2023-08-11
|
|
64
|
+
### Added
|
|
65
|
+
- Compatibility and tests for python versions 3.10 and 3.11
|
|
66
|
+
|
|
67
|
+
### Removed
|
|
68
|
+
- Removed all cython stuff
|
|
69
|
+
|
|
70
|
+
## [0.5.1] 2023-06-28
|
|
71
|
+
### Fixed
|
|
72
|
+
- Prevent access to attribute if cache_value is None
|
|
73
|
+
|
|
74
|
+
## [0.5.0] 2023-03-27
|
|
75
|
+
### Fixed
|
|
76
|
+
- Pypy onecache works
|
|
77
|
+
|
|
78
|
+
## [0.4.1] 2022-02-04
|
|
79
|
+
### Fixed
|
|
80
|
+
- Counting values size at decreasing
|
|
81
|
+
|
|
82
|
+
## [0.4.0] 2022-01-30
|
|
83
|
+
### Added
|
|
84
|
+
- C extension for cache value with cython
|
|
85
|
+
|
|
86
|
+
## [0.3.2] 2022-01-26
|
|
87
|
+
### Added
|
|
88
|
+
- Flag for thread safety
|
|
89
|
+
|
|
90
|
+
## [0.3.1] 2021-07-20
|
|
91
|
+
### Changed
|
|
92
|
+
- Better pop when doing set, and reaching max size
|
|
93
|
+
|
|
94
|
+
## [0.3.0] 2021-07-19
|
|
95
|
+
### Changed
|
|
96
|
+
- access map removed in favor of using cache map + CacheValue access attribute
|
|
97
|
+
|
|
98
|
+
## [0.2.0] 2021-07-19
|
|
99
|
+
### Added
|
|
100
|
+
- CacheValue class as data container
|
|
101
|
+
- refresh_ttl argument, for refresh ttl per key access
|
|
102
|
+
|
|
103
|
+
## [0.1.0] 2021-07-18
|
|
104
|
+
### Fixed
|
|
105
|
+
- Better serialize keys with sort
|
|
106
|
+
|
|
107
|
+
## [0.0.4] 2021-07-16
|
|
108
|
+
### Fixed
|
|
109
|
+
- TTL and LRU usage combination works as expected
|
|
110
|
+
|
|
111
|
+
## [0.0.3] 2021-07-16
|
|
112
|
+
### Added
|
|
113
|
+
- LRUCache, using it by default for decorators
|
|
114
|
+
|
|
115
|
+
## [0.0.2] 2021-07-16
|
|
116
|
+
### Fix
|
|
117
|
+
- Readme for pypi
|
|
118
|
+
|
|
119
|
+
## [0.0.1] 2021-07-16
|
|
120
|
+
### Added
|
|
121
|
+
- Cache class
|
|
122
|
+
- Sync and Async decorators
|
|
123
|
+
|
|
124
|
+
[Unreleased]: https://github.com/sonic182/onecache/compare/0.9.0..HEAD
|
|
125
|
+
[0.9.0]: https://github.com/sonic182/onecache/compare/0.8.1..0.9.0
|
|
126
|
+
[0.8.1]: https://github.com/sonic182/onecache/compare/0.8.0..0.8.1
|
|
127
|
+
[0.8.0]: https://github.com/sonic182/onecache/compare/0.7.2..0.8.0
|
|
128
|
+
[0.7.2]: https://github.com/sonic182/onecache/compare/0.7.1..0.7.2
|
|
129
|
+
[0.7.1]: https://github.com/sonic182/onecache/compare/0.7.0..0.7.1
|
|
130
|
+
[0.7.0]: https://github.com/sonic182/onecache/compare/0.6.0..0.7.0
|
|
131
|
+
[0.6.0]: https://github.com/sonic182/onecache/compare/0.5.1..0.6.0
|
|
132
|
+
[0.5.1]: https://github.com/sonic182/onecache/compare/0.5.0..0.5.1
|
|
133
|
+
[0.5.0]: https://github.com/sonic182/onecache/compare/0.4.1..0.5.0
|
|
134
|
+
[0.4.1]: https://github.com/sonic182/onecache/compare/0.4.0..0.4.1
|
|
135
|
+
[0.4.0]: https://github.com/sonic182/onecache/compare/0.3.2..0.4.0
|
|
136
|
+
[0.3.2]: https://github.com/sonic182/onecache/compare/0.3.1..0.3.2
|
|
137
|
+
[0.3.1]: https://github.com/sonic182/onecache/compare/0.3.0..0.3.1
|
|
138
|
+
[0.3.0]: https://github.com/sonic182/onecache/compare/0.2.0..0.3.0
|
|
139
|
+
[0.2.0]: https://github.com/sonic182/onecache/compare/0.1.0..0.2.0
|
|
140
|
+
[0.1.0]: https://github.com/sonic182/onecache/compare/0.0.4..0.1.0
|
|
141
|
+
[0.0.4]: https://github.com/sonic182/onecache/compare/0.0.3..0.0.4
|
|
142
|
+
[0.0.3]: https://github.com/sonic182/onecache/compare/0.0.2..0.0.3
|
|
143
|
+
[0.0.2]: https://github.com/sonic182/onecache/compare/0.0.1..0.0.2
|
|
144
|
+
[0.0.1]: https://github.com/sonic182/onecache/compare/814065637987644cc56a09028df955e001a2163b..0.0.1
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: onecache
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Python cache for sync and async code
|
|
5
|
+
Author-email: Johanderson Mogollon <johander1822@gmail.com>
|
|
5
6
|
License: MIT
|
|
6
|
-
|
|
7
|
-
Author-email: johander1822@gmail.com
|
|
8
|
-
Requires-Python: >=3.8
|
|
7
|
+
Project-URL: Homepage, https://github.com/sonic182/onecache
|
|
9
8
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
9
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
10
|
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Programming Language ::
|
|
11
|
+
Classifier: Programming Language :: Cython
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.8
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -18,8 +16,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
20
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
|
+
Dynamic: license
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
[](https://coveralls.io/github/sonic182/onecache?branch=master)
|
|
@@ -30,7 +31,9 @@ Python cache for sync and async code.
|
|
|
30
31
|
|
|
31
32
|
Cache uses an LRU algorithm and can optionally set TTLs per entry.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
On CPython the cache core is a compiled Cython extension (prebuilt wheels for Linux, macOS and Windows on x86_64 and ARM64). On PyPy, or on platforms without a prebuilt wheel, it falls back to an equivalent pure Python implementation. `onecache.COMPILED` tells which one is in use.
|
|
35
|
+
|
|
36
|
+
Tested automatically on CPython 3.10 and 3.14 and PyPy 3.11 across Linux, macOS, and Windows on x86_64 and ARM64 (see the workflow badge). Python 3.8 and 3.9 are still installable and get prebuilt wheels, but are no longer part of the test matrix.
|
|
34
37
|
|
|
35
38
|
# Usage
|
|
36
39
|
|
|
@@ -79,9 +82,15 @@ Decorator classes supports the following arguments
|
|
|
79
82
|
* **cache_class (class)**: Class to use for cache instance. default: LRUCache
|
|
80
83
|
* **refresh_ttl (bool)**: if cache with ttl, This flag makes key expiration timestamp to be refresh per access. default: False
|
|
81
84
|
* **thread_safe (bool)**: tell decorator to use thread safe lock. default=False
|
|
82
|
-
* **max_mem_size (int)**: max mem size in bytes
|
|
85
|
+
* **max_mem_size (int)**: max mem size in bytes, as the sum of `sys.getsizeof(key) + sys.getsizeof(value)` of every entry (shallow sizes). default=None which means no limit. For pypy this value is ignored as the objects can change by the JIT compilation.
|
|
86
|
+
|
|
87
|
+
If num of records exceds maxsize, it drops the least recently used one. With `max_mem_size`, a value bigger than the limit is not cached.
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
Use `thread_safe=True` when the decorated function is called from several threads.
|
|
90
|
+
|
|
91
|
+
The decorated function exposes its cache as `.cache`: `.cache.expire(*args, **kwargs)` drops one entry, `.cache.clear()` drops all of them, and `.cache.cache` is a read-only view of the entries.
|
|
92
|
+
|
|
93
|
+
Set `ONECACHE_NO_EXTENSIONS` to any non-empty value to force the pure Python implementation. If the extension failed to build when installing from source (e.g. no compiler), pip may keep reusing that pure Python build from its cache; reinstall with `pip install --no-cache-dir --force-reinstall onecache` after installing a compiler.
|
|
85
94
|
|
|
86
95
|
|
|
87
96
|
# Development
|
|
@@ -91,11 +100,23 @@ Install dependencies with Poetry (includes dev + test groups):
|
|
|
91
100
|
poetry install --with test,dev
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
|
|
103
|
+
Build the Cython extension in place (needed after any change to `onecache/_ccore.pyx`):
|
|
95
104
|
```bash
|
|
96
|
-
|
|
105
|
+
make build_ext
|
|
97
106
|
```
|
|
98
107
|
|
|
108
|
+
Run the test suite against the compiled and the pure Python implementations:
|
|
109
|
+
```bash
|
|
110
|
+
poetry run pytest
|
|
111
|
+
ONECACHE_NO_EXTENSIONS=1 poetry run pytest
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`onecache/_ccore.pyx` and `onecache/_core.py` implement the same API and must be kept in sync.
|
|
115
|
+
|
|
116
|
+
# Releasing
|
|
117
|
+
|
|
118
|
+
Pull requests only build cp310 and cp314 wheels, so first run the `Wheels` workflow by hand on `master` (Actions → Wheels → Run workflow) to build and test every Python version. Then bump `version` in `pyproject.toml` and push a tag with the same value (e.g. `0.9.0`). The `Wheels` workflow builds the wheels with cibuildwheel, the sdist and a pure Python fallback wheel, and publishes them to PyPI through trusted publishing. That needs a `pypi` environment in the GitHub repository settings and a trusted publisher on PyPI for this repository, workflow `wheels.yml` and environment `pypi`.
|
|
119
|
+
|
|
99
120
|
Lint and format checks:
|
|
100
121
|
```bash
|
|
101
122
|
poetry run flake8
|
|
@@ -109,4 +130,3 @@ poetry run autopep8 --in-place --recursive onecache tests
|
|
|
109
130
|
3. commit - push - pull request
|
|
110
131
|
|
|
111
132
|
Thanks :)
|
|
112
|
-
|
onecache-0.9.0/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
|
|
2
|
+
[](https://coveralls.io/github/sonic182/onecache?branch=master)
|
|
3
|
+

|
|
4
|
+
# OneCache
|
|
5
|
+
|
|
6
|
+
Python cache for sync and async code.
|
|
7
|
+
|
|
8
|
+
Cache uses an LRU algorithm and can optionally set TTLs per entry.
|
|
9
|
+
|
|
10
|
+
On CPython the cache core is a compiled Cython extension (prebuilt wheels for Linux, macOS and Windows on x86_64 and ARM64). On PyPy, or on platforms without a prebuilt wheel, it falls back to an equivalent pure Python implementation. `onecache.COMPILED` tells which one is in use.
|
|
11
|
+
|
|
12
|
+
Tested automatically on CPython 3.10 and 3.14 and PyPy 3.11 across Linux, macOS, and Windows on x86_64 and ARM64 (see the workflow badge). Python 3.8 and 3.9 are still installable and get prebuilt wheels, but are no longer part of the test matrix.
|
|
13
|
+
|
|
14
|
+
# Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from onecache import CacheDecorator
|
|
18
|
+
from onecache import AsyncCacheDecorator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Counter:
|
|
22
|
+
def __init__(self, count=0):
|
|
23
|
+
self.count = count
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.asyncio
|
|
27
|
+
async def test_async_cache_counter():
|
|
28
|
+
"""Test async cache, counter case."""
|
|
29
|
+
counter = Counter()
|
|
30
|
+
|
|
31
|
+
@AsyncCacheDecorator()
|
|
32
|
+
async def mycoro(counter: Counter):
|
|
33
|
+
counter.count += 1
|
|
34
|
+
return counter.count
|
|
35
|
+
|
|
36
|
+
assert 1 == (await mycoro(counter))
|
|
37
|
+
assert 1 == (await mycoro(counter))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_cache_counter():
|
|
41
|
+
"""Test async cache, counter case."""
|
|
42
|
+
counter = Counter()
|
|
43
|
+
|
|
44
|
+
@CacheDecorator()
|
|
45
|
+
def sample(counter: Counter):
|
|
46
|
+
counter.count += 1
|
|
47
|
+
return counter.count
|
|
48
|
+
|
|
49
|
+
assert 1 == (sample(counter))
|
|
50
|
+
assert 1 == (sample(counter))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Decorator classes supports the following arguments
|
|
54
|
+
|
|
55
|
+
* **maxsize (int)**: Maximun number of items to be cached. default: 512
|
|
56
|
+
* **ttl (int)**: time to expire in milliseconds, if None, it does not expire. default: None
|
|
57
|
+
* **skip_args (bool)**: apply cache as the function doesn't have any arguments, default: False
|
|
58
|
+
* **cache_class (class)**: Class to use for cache instance. default: LRUCache
|
|
59
|
+
* **refresh_ttl (bool)**: if cache with ttl, This flag makes key expiration timestamp to be refresh per access. default: False
|
|
60
|
+
* **thread_safe (bool)**: tell decorator to use thread safe lock. default=False
|
|
61
|
+
* **max_mem_size (int)**: max mem size in bytes, as the sum of `sys.getsizeof(key) + sys.getsizeof(value)` of every entry (shallow sizes). default=None which means no limit. For pypy this value is ignored as the objects can change by the JIT compilation.
|
|
62
|
+
|
|
63
|
+
If num of records exceds maxsize, it drops the least recently used one. With `max_mem_size`, a value bigger than the limit is not cached.
|
|
64
|
+
|
|
65
|
+
Use `thread_safe=True` when the decorated function is called from several threads.
|
|
66
|
+
|
|
67
|
+
The decorated function exposes its cache as `.cache`: `.cache.expire(*args, **kwargs)` drops one entry, `.cache.clear()` drops all of them, and `.cache.cache` is a read-only view of the entries.
|
|
68
|
+
|
|
69
|
+
Set `ONECACHE_NO_EXTENSIONS` to any non-empty value to force the pure Python implementation. If the extension failed to build when installing from source (e.g. no compiler), pip may keep reusing that pure Python build from its cache; reinstall with `pip install --no-cache-dir --force-reinstall onecache` after installing a compiler.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Development
|
|
73
|
+
|
|
74
|
+
Install dependencies with Poetry (includes dev + test groups):
|
|
75
|
+
```bash
|
|
76
|
+
poetry install --with test,dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Build the Cython extension in place (needed after any change to `onecache/_ccore.pyx`):
|
|
80
|
+
```bash
|
|
81
|
+
make build_ext
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run the test suite against the compiled and the pure Python implementations:
|
|
85
|
+
```bash
|
|
86
|
+
poetry run pytest
|
|
87
|
+
ONECACHE_NO_EXTENSIONS=1 poetry run pytest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`onecache/_ccore.pyx` and `onecache/_core.py` implement the same API and must be kept in sync.
|
|
91
|
+
|
|
92
|
+
# Releasing
|
|
93
|
+
|
|
94
|
+
Pull requests only build cp310 and cp314 wheels, so first run the `Wheels` workflow by hand on `master` (Actions → Wheels → Run workflow) to build and test every Python version. Then bump `version` in `pyproject.toml` and push a tag with the same value (e.g. `0.9.0`). The `Wheels` workflow builds the wheels with cibuildwheel, the sdist and a pure Python fallback wheel, and publishes them to PyPI through trusted publishing. That needs a `pypi` environment in the GitHub repository settings and a trusted publisher on PyPI for this repository, workflow `wheels.yml` and environment `pypi`.
|
|
95
|
+
|
|
96
|
+
Lint and format checks:
|
|
97
|
+
```bash
|
|
98
|
+
poetry run flake8
|
|
99
|
+
poetry run autopep8 --in-place --recursive onecache tests
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
# Contribute
|
|
103
|
+
|
|
104
|
+
1. Fork
|
|
105
|
+
2. create a branch `feature/your_feature`
|
|
106
|
+
3. commit - push - pull request
|
|
107
|
+
|
|
108
|
+
Thanks :)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Onecache main module."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from functools import update_wrapper
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
if os.environ.get("ONECACHE_NO_EXTENSIONS"):
|
|
8
|
+
from onecache._core import CacheValue, ExpirableCache, LRUCache, monotonic
|
|
9
|
+
|
|
10
|
+
COMPILED = False
|
|
11
|
+
else:
|
|
12
|
+
try:
|
|
13
|
+
from onecache._ccore import CacheValue, ExpirableCache, LRUCache, monotonic
|
|
14
|
+
|
|
15
|
+
COMPILED = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
from onecache._core import CacheValue, ExpirableCache, LRUCache, monotonic
|
|
18
|
+
|
|
19
|
+
COMPILED = False
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"AsyncCacheDecorator",
|
|
23
|
+
"CacheDecorator",
|
|
24
|
+
"CacheValue",
|
|
25
|
+
"COMPILED",
|
|
26
|
+
"ExpirableCache",
|
|
27
|
+
"LRUCache",
|
|
28
|
+
"monotonic",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
_MISSING = object()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CacheDecorator:
|
|
35
|
+
"""Decorator for ExpirableCache"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
maxsize=512,
|
|
40
|
+
ttl: Optional[int] = None,
|
|
41
|
+
skip_args: bool = False,
|
|
42
|
+
cache_class=LRUCache,
|
|
43
|
+
refresh_ttl: Optional[bool] = False,
|
|
44
|
+
thread_safe: Optional[bool] = False,
|
|
45
|
+
max_mem_size: Optional[int] = None,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Args:
|
|
49
|
+
* **maxsize (int)**: Maximun number of items to be cached. default: 512
|
|
50
|
+
* **ttl (int)**: time to expire in milliseconds, if None, it does not expire. default: None
|
|
51
|
+
* **skip_args (bool)**: apply cache as the function doesn't have any arguments, default: False
|
|
52
|
+
* **cache_class (class)**: Class to use for cache instance. default: LRUCache
|
|
53
|
+
* **refresh_ttl (bool)**: if cache with ttl, This flag makes key expiration timestamp to be
|
|
54
|
+
refresh per access. default: False
|
|
55
|
+
* **thread_safe (bool)**: tell decorator to use thread safe lock. default=False
|
|
56
|
+
* **max_mem_size (int)**: max mem size inside cache structure. default=None which means no limit.
|
|
57
|
+
For pypy this value is ignored as the objects can change by the JIT compilation.
|
|
58
|
+
"""
|
|
59
|
+
self.cache = cache_class(
|
|
60
|
+
maxsize,
|
|
61
|
+
ttl,
|
|
62
|
+
refresh_ttl=refresh_ttl,
|
|
63
|
+
thread_safe=thread_safe,
|
|
64
|
+
max_mem_size=max_mem_size,
|
|
65
|
+
)
|
|
66
|
+
self.maxsize = maxsize
|
|
67
|
+
self.skip_args = skip_args
|
|
68
|
+
self._fast = type(self.cache) in (ExpirableCache, LRUCache)
|
|
69
|
+
|
|
70
|
+
def __call__(self, func):
|
|
71
|
+
cache = self.cache
|
|
72
|
+
skip_args = self.skip_args
|
|
73
|
+
|
|
74
|
+
if self._fast:
|
|
75
|
+
return update_wrapper(cache._wrap(func, skip_args), func, updated=())
|
|
76
|
+
|
|
77
|
+
def wrapper(*args, **kwargs):
|
|
78
|
+
key = "any" if skip_args else cache.get_key(*args, **kwargs)
|
|
79
|
+
if key not in cache:
|
|
80
|
+
resp = func(*args, **kwargs)
|
|
81
|
+
cache.set(key, resp)
|
|
82
|
+
return resp
|
|
83
|
+
return cache.get(key)
|
|
84
|
+
|
|
85
|
+
wrapper.cache = cache
|
|
86
|
+
|
|
87
|
+
return update_wrapper(wrapper, func, updated=())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class AsyncCacheDecorator(CacheDecorator):
|
|
91
|
+
"""Async Decorator for ExpirableCache"""
|
|
92
|
+
|
|
93
|
+
def __call__(self, func):
|
|
94
|
+
cache = self.cache
|
|
95
|
+
skip_args = self.skip_args
|
|
96
|
+
|
|
97
|
+
if self._fast:
|
|
98
|
+
make_key = cache._make_key
|
|
99
|
+
lookup = cache._lookup
|
|
100
|
+
set_value = cache.set
|
|
101
|
+
|
|
102
|
+
async def async_wrapper(*args, **kwargs):
|
|
103
|
+
key = "any" if skip_args else make_key(args, kwargs)
|
|
104
|
+
value = lookup(key, _MISSING)
|
|
105
|
+
if value is _MISSING:
|
|
106
|
+
value = await func(*args, **kwargs)
|
|
107
|
+
set_value(key, value)
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
|
|
112
|
+
async def async_wrapper(*args, **kwargs):
|
|
113
|
+
key = "any" if skip_args else cache.get_key(*args, **kwargs)
|
|
114
|
+
if key not in cache:
|
|
115
|
+
resp = await func(*args, **kwargs)
|
|
116
|
+
cache.set(key, resp)
|
|
117
|
+
return resp
|
|
118
|
+
return cache.get(key)
|
|
119
|
+
|
|
120
|
+
async_wrapper.cache = cache
|
|
121
|
+
|
|
122
|
+
return update_wrapper(async_wrapper, func, updated=())
|