cachu 0.1.2__tar.gz → 0.1.3__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.
Files changed (32) hide show
  1. {cachu-0.1.2 → cachu-0.1.3}/PKG-INFO +14 -3
  2. {cachu-0.1.2 → cachu-0.1.3}/README.md +13 -2
  3. {cachu-0.1.2 → cachu-0.1.3}/pyproject.toml +1 -1
  4. {cachu-0.1.2 → cachu-0.1.3}/setup.cfg +1 -1
  5. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/__init__.py +1 -1
  6. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/file.py +2 -2
  7. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/decorator.py +1 -6
  8. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/operations.py +24 -16
  9. {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/PKG-INFO +14 -3
  10. {cachu-0.1.2 → cachu-0.1.3}/tests/test_clearing.py +61 -0
  11. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/__init__.py +0 -0
  12. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/memory.py +0 -0
  13. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/backends/redis.py +0 -0
  14. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/config.py +0 -0
  15. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/keys.py +0 -0
  16. {cachu-0.1.2 → cachu-0.1.3}/src/cachu/types.py +0 -0
  17. {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/SOURCES.txt +0 -0
  18. {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/dependency_links.txt +0 -0
  19. {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/requires.txt +0 -0
  20. {cachu-0.1.2 → cachu-0.1.3}/src/cachu.egg-info/top_level.txt +0 -0
  21. {cachu-0.1.2 → cachu-0.1.3}/tests/test_config.py +0 -0
  22. {cachu-0.1.2 → cachu-0.1.3}/tests/test_defaultcache.py +0 -0
  23. {cachu-0.1.2 → cachu-0.1.3}/tests/test_delete_keys.py +0 -0
  24. {cachu-0.1.2 → cachu-0.1.3}/tests/test_disable.py +0 -0
  25. {cachu-0.1.2 → cachu-0.1.3}/tests/test_exclude_params.py +0 -0
  26. {cachu-0.1.2 → cachu-0.1.3}/tests/test_file_cache.py +0 -0
  27. {cachu-0.1.2 → cachu-0.1.3}/tests/test_integration.py +0 -0
  28. {cachu-0.1.2 → cachu-0.1.3}/tests/test_memory_cache.py +0 -0
  29. {cachu-0.1.2 → cachu-0.1.3}/tests/test_namespace.py +0 -0
  30. {cachu-0.1.2 → cachu-0.1.3}/tests/test_namespace_isolation.py +0 -0
  31. {cachu-0.1.2 → cachu-0.1.3}/tests/test_redis_cache.py +0 -0
  32. {cachu-0.1.2 → cachu-0.1.3}/tests/test_set_keys.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachu
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Flexible caching library built on dogpile.cache
5
5
  Author: bissli
6
6
  License-Expression: 0BSD
@@ -81,7 +81,7 @@ cachu.configure(
81
81
 
82
82
  ### Package Isolation
83
83
 
84
- Each package automatically gets isolated configuration. This prevents conflicts when multiple libraries use the cachu package:
84
+ Each package automatically gets isolated configuration, preventing conflicts when multiple libraries use cachu:
85
85
 
86
86
  ```python
87
87
  # In library_a/config.py
@@ -92,7 +92,18 @@ cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
92
92
  import cachu
93
93
  cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
94
94
 
95
- # Each library uses its own configuration automatically
95
+ # Each library's @cache calls use its own configuration automatically
96
+ ```
97
+
98
+ To override the automatic detection, specify the `package` parameter:
99
+
100
+ ```python
101
+ from cachu import cache
102
+
103
+ # This function will use library_a's configuration
104
+ @cache(ttl=300, package='library_a')
105
+ def get_shared_data(id: int) -> dict:
106
+ return fetch(id)
96
107
  ```
97
108
 
98
109
  Retrieve configuration:
@@ -62,7 +62,7 @@ cachu.configure(
62
62
 
63
63
  ### Package Isolation
64
64
 
65
- Each package automatically gets isolated configuration. This prevents conflicts when multiple libraries use the cachu package:
65
+ Each package automatically gets isolated configuration, preventing conflicts when multiple libraries use cachu:
66
66
 
67
67
  ```python
68
68
  # In library_a/config.py
@@ -73,7 +73,18 @@ cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
73
73
  import cachu
74
74
  cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
75
75
 
76
- # Each library uses its own configuration automatically
76
+ # Each library's @cache calls use its own configuration automatically
77
+ ```
78
+
79
+ To override the automatic detection, specify the `package` parameter:
80
+
81
+ ```python
82
+ from cachu import cache
83
+
84
+ # This function will use library_a's configuration
85
+ @cache(ttl=300, package='library_a')
86
+ def get_shared_data(id: int) -> dict:
87
+ return fetch(id)
77
88
  ```
78
89
 
79
90
  Retrieve configuration:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cachu"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  description = "Flexible caching library built on dogpile.cache"
5
5
  readme = "README.md"
6
6
  license = "0BSD"
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.1.2
2
+ current_version = 0.1.3
3
3
  commit = True
4
4
  tag = True
5
5
 
@@ -1,6 +1,6 @@
1
1
  """Flexible caching library with support for memory, file, and Redis backends.
2
2
  """
3
- __version__ = '0.1.2'
3
+ __version__ = '0.1.3'
4
4
 
5
5
  from .backends.redis import get_redis_client
6
6
  from .config import configure, disable, enable, get_all_configs, get_config
@@ -115,7 +115,7 @@ class FileBackend(Backend):
115
115
 
116
116
  with dbm.open(self._filepath, 'c') as db:
117
117
  keys_to_delete = [
118
- k for k in db
118
+ k for k in db.keys()
119
119
  if fnmatch.fnmatch(k.decode(), pattern)
120
120
  ]
121
121
  for key in keys_to_delete:
@@ -131,7 +131,7 @@ class FileBackend(Backend):
131
131
  with self._lock:
132
132
  try:
133
133
  with dbm.open(self._filepath, 'c') as db:
134
- all_keys = [k.decode() for k in db]
134
+ all_keys = [k.decode() for k in db.keys()]
135
135
  except Exception:
136
136
  return
137
137
 
@@ -220,12 +220,7 @@ def get_cache_info(fn: Callable[..., Any]) -> CacheInfo:
220
220
  Returns
221
221
  CacheInfo with hits, misses, and currsize
222
222
  """
223
- if hasattr(fn, '__wrapped__'):
224
- actual_fn = fn
225
- else:
226
- actual_fn = fn
227
-
228
- fn_id = id(actual_fn)
223
+ fn_id = id(fn)
229
224
 
230
225
  with _stats_lock:
231
226
  hits, misses = _stats.get(fn_id, (0, 0))
@@ -1,8 +1,8 @@
1
1
  """Cache CRUD operations.
2
2
  """
3
3
  import logging
4
- from typing import Any
5
4
  from collections.abc import Callable
5
+ from typing import Any
6
6
 
7
7
  from .backends import NO_VALUE
8
8
  from .config import _get_caller_package, get_config
@@ -124,8 +124,6 @@ def cache_clear(
124
124
  if package is None:
125
125
  package = _get_caller_package()
126
126
 
127
- cfg = get_config(package)
128
-
129
127
  if backend is not None:
130
128
  backends_to_clear = [backend]
131
129
  else:
@@ -141,19 +139,29 @@ def cache_clear(
141
139
 
142
140
  from .decorator import _backends, _backends_lock
143
141
 
144
- with _backends_lock:
145
- for (pkg, btype, bttl), backend_instance in list(_backends.items()):
146
- if pkg != package:
147
- continue
148
- if btype not in backends_to_clear:
149
- continue
150
- if ttl is not None and bttl != ttl:
151
- continue
152
-
153
- cleared = backend_instance.clear(pattern)
154
- if cleared > 0:
155
- total_cleared += cleared
156
- logger.debug(f'Cleared {cleared} entries from {btype} backend (ttl={bttl})')
142
+ # When both backend and ttl are specified, directly get/create and clear that backend.
143
+ # This is essential for distributed caches (Redis) where cache_clear may be called
144
+ # from a different process than the one that populated the cache.
145
+ if backend is not None and ttl is not None:
146
+ backend_instance = _get_backend(package, backend, ttl)
147
+ cleared = backend_instance.clear(pattern)
148
+ if cleared > 0:
149
+ total_cleared += cleared
150
+ logger.debug(f'Cleared {cleared} entries from {backend} backend (ttl={ttl})')
151
+ else:
152
+ with _backends_lock:
153
+ for (pkg, btype, bttl), backend_instance in list(_backends.items()):
154
+ if pkg != package:
155
+ continue
156
+ if btype not in backends_to_clear:
157
+ continue
158
+ if ttl is not None and bttl != ttl:
159
+ continue
160
+
161
+ cleared = backend_instance.clear(pattern)
162
+ if cleared > 0:
163
+ total_cleared += cleared
164
+ logger.debug(f'Cleared {cleared} entries from {btype} backend (ttl={bttl})')
157
165
 
158
166
  return total_cleared
159
167
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cachu
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Flexible caching library built on dogpile.cache
5
5
  Author: bissli
6
6
  License-Expression: 0BSD
@@ -81,7 +81,7 @@ cachu.configure(
81
81
 
82
82
  ### Package Isolation
83
83
 
84
- Each package automatically gets isolated configuration. This prevents conflicts when multiple libraries use the cachu package:
84
+ Each package automatically gets isolated configuration, preventing conflicts when multiple libraries use cachu:
85
85
 
86
86
  ```python
87
87
  # In library_a/config.py
@@ -92,7 +92,18 @@ cachu.configure(key_prefix='lib_a:', redis_url='redis://redis-a:6379/0')
92
92
  import cachu
93
93
  cachu.configure(key_prefix='lib_b:', redis_url='redis://redis-b:6379/0')
94
94
 
95
- # Each library uses its own configuration automatically
95
+ # Each library's @cache calls use its own configuration automatically
96
+ ```
97
+
98
+ To override the automatic detection, specify the `package` parameter:
99
+
100
+ ```python
101
+ from cachu import cache
102
+
103
+ # This function will use library_a's configuration
104
+ @cache(ttl=300, package='library_a')
105
+ def get_shared_data(id: int) -> dict:
106
+ return fetch(id)
96
107
  ```
97
108
 
98
109
  Retrieve configuration:
@@ -111,3 +111,64 @@ def test_cache_clear_redis_by_tag(redis_docker):
111
111
  get_product(1)
112
112
 
113
113
  cachu.cache_clear(tag='users', backend='redis', ttl=300)
114
+
115
+
116
+ def test_cache_clear_without_instantiated_backend():
117
+ """Verify cache_clear creates backend when none exists.
118
+
119
+ This tests that cache_clear() properly creates a backend instance when
120
+ both backend and ttl are specified, even if no cached function has been called.
121
+
122
+ This is essential for distributed caches (Redis) where cache_clear may be called
123
+ from a different process than the one that populated the cache.
124
+ """
125
+ from cachu.decorator import _backends, clear_backends
126
+
127
+ # Clear all backends to simulate a fresh process
128
+ clear_backends()
129
+
130
+ # Verify no backends exist (simulates import script that hasn't called cached functions)
131
+ assert len(_backends) == 0
132
+
133
+ # Call cache_clear with specific backend and ttl
134
+ # Before the fix, this would do nothing because no backend existed
135
+ # After the fix, this should create the backend and attempt to clear it
136
+ cachu.cache_clear(backend='memory', ttl=999, tag='test_tag')
137
+
138
+ # With the fix, a backend should have been created
139
+ assert len(_backends) == 1
140
+ key = list(_backends.keys())[0]
141
+ assert key[1] == 'memory' # backend type
142
+ assert key[2] == 999 # ttl
143
+
144
+
145
+ def test_cache_clear_creates_backend_and_clears(temp_cache_dir):
146
+ """Verify cache_clear can clear data in file backend without prior instantiation.
147
+
148
+ File backend persists data to disk, allowing us to verify that cache_clear
149
+ can find and delete cached data even when called from a 'fresh' process state.
150
+ """
151
+ from cachu.backends import NO_VALUE
152
+ from cachu.config import _get_caller_package
153
+ from cachu.decorator import _backends, _get_backend, clear_backends
154
+
155
+ package = _get_caller_package()
156
+
157
+ # First, create some cached data in a file backend
158
+ backend = _get_backend(package, 'file', 888)
159
+ backend.set('14m:test_func||file_tag||x=1', 'test_value', 888)
160
+ assert backend.get('14m:test_func||file_tag||x=1') == 'test_value'
161
+
162
+ # Clear backend instances (but file data persists on disk)
163
+ clear_backends()
164
+ assert len(_backends) == 0
165
+
166
+ # cache_clear should create a new backend instance and clear the persisted data
167
+ cleared = cachu.cache_clear(backend='file', ttl=888, tag='file_tag')
168
+
169
+ # Backend should have been created
170
+ assert len(_backends) == 1
171
+
172
+ # Data should have been cleared (verify by getting a fresh backend)
173
+ backend = _get_backend(package, 'file', 888)
174
+ assert backend.get('14m:test_func||file_tag||x=1') is NO_VALUE
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes