Skip to content

Commit 7444074

Browse files
committed
minor fixes
1 parent 9077459 commit 7444074

5 files changed

Lines changed: 133 additions & 51 deletions

File tree

examples/redis_example.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
77
Requirements:
88
pip install redis cachier
9-
109
"""
1110

1211
import time
@@ -112,7 +111,7 @@ def demo_callable_client():
112111
print("\n=== Callable Client Demo ===")
113112

114113
def get_redis_client():
115-
"""Factory function for Redis client."""
114+
"""Get a Redis client."""
116115
return redis.Redis(
117116
host="localhost", port=6379, db=0, decode_responses=False
118117
)

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ lint.per-file-ignores."tests/**" = [
128128
"S311",
129129
"S603",
130130
]
131+
lint.per-file-ignores."examples/**" = [
132+
"S101",
133+
]
131134
lint.unfixable = [
132135
"F401",
133136
]

src/cachier/core.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,8 @@ def cachier(
141141
backend : str, optional
142142
The name of the backend to use. Valid options currently include
143143
'pickle', 'mongo', 'memory', 'sql', and 'redis'. If not provided,
144-
defaults to 'pickle', unless an argument is provided for one of the
145-
other parameters that requires a different backend, in which case the
146-
backend is set to the one required by that parameter.
144+
defaults to 'pickle' unless the 'mongetter' argument is passed,
145+
in which case the mongo backend is automatically selected.
147146
mongetter : callable, optional
148147
A callable that takes no arguments and returns a pymongo.Collection
149148
object with writing permissions. If unset a local pickle cache is used

tests/redis_requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
redis>=4.0.0
22
pandas>=1.3.0
3-
birch>=0.0.35
3+
birch>=0.0.35

tests/test_redis_core.py

Lines changed: 126 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -68,49 +68,101 @@ def _test_redis_getter():
6868
client = _get_test_redis_client()
6969
if client is None:
7070
# Create a mock Redis client for testing
71-
class MockRedis:
72-
def __init__(self):
73-
self.data = {}
74-
75-
def hgetall(self, key):
76-
return self.data.get(key, {})
77-
78-
def hset(self, key, mapping=None, **kwargs):
79-
if key not in self.data:
80-
self.data[key] = {}
81-
if mapping:
82-
self.data[key].update(mapping)
83-
if kwargs:
84-
self.data[key].update(kwargs)
85-
86-
def keys(self, pattern):
87-
import re
88-
89-
pattern = pattern.replace("*", ".*")
90-
return [k for k in self.data if re.match(pattern, k.decode())]
91-
92-
def delete(self, *keys):
93-
for key in keys:
94-
self.data.pop(key, None)
95-
96-
def pipeline(self):
97-
return MockPipeline(self)
98-
99-
class MockPipeline:
100-
def __init__(self, redis_client):
101-
self.redis_client = redis_client
102-
self.commands = []
103-
104-
def hset(self, key, field, value):
105-
self.commands.append(("hset", key, field, value))
106-
return self
107-
108-
def execute(self):
109-
for cmd, key, field, value in self.commands:
110-
if cmd == "hset":
111-
self.redis_client.hset(key, field, value)
112-
113-
return MockRedis()
71+
# Use a singleton pattern to ensure the same instance is returned
72+
if not hasattr(_test_redis_getter, "_mock_client"):
73+
74+
class MockRedis:
75+
def __init__(self):
76+
self.data = {}
77+
print("DEBUG: MockRedis initialized")
78+
79+
def hgetall(self, key):
80+
result = self.data.get(key, {})
81+
# Convert string values to bytes to match Redis behavior
82+
bytes_result = {}
83+
for k, v in result.items():
84+
if isinstance(v, str):
85+
bytes_result[k.encode("utf-8")] = v.encode("utf-8")
86+
else:
87+
bytes_result[k.encode("utf-8")] = v
88+
print(
89+
f"DEBUG: hgetall({key}) = {result} -> {bytes_result}"
90+
)
91+
return bytes_result
92+
93+
def hset(
94+
self, key, field=None, value=None, mapping=None, **kwargs
95+
):
96+
if key not in self.data:
97+
self.data[key] = {}
98+
99+
# Handle different calling patterns
100+
if mapping is not None:
101+
# Called with mapping dict
102+
self.data[key].update(mapping)
103+
elif field is not None and value is not None:
104+
# Called with field, value arguments
105+
self.data[key][field] = value
106+
elif kwargs:
107+
# Called with keyword arguments
108+
self.data[key].update(kwargs)
109+
110+
print(
111+
f"DEBUG: hset({key}, field={field}, value={value}, "
112+
"mapping={mapping}, kwargs={kwargs}) -> "
113+
"{self.data[key]}"
114+
)
115+
116+
def keys(self, pattern):
117+
import re
118+
119+
pattern = pattern.replace("*", ".*")
120+
# Fix: keys are strings, not bytes, so no need to decode
121+
result = [k for k in self.data if re.match(pattern, k)]
122+
print(f"DEBUG: keys({pattern}) = {result}")
123+
return result
124+
125+
def delete(self, *keys):
126+
for key in keys:
127+
self.data.pop(key, None)
128+
print(f"DEBUG: delete({keys})")
129+
130+
def pipeline(self):
131+
return MockPipeline(self)
132+
133+
def ping(self):
134+
return True
135+
136+
def set(self, key, value):
137+
self.data[key] = value
138+
print(f"DEBUG: set({key}, {value})")
139+
140+
def get(self, key):
141+
result = self.data.get(key)
142+
if isinstance(result, str):
143+
result = result.encode("utf-8")
144+
print(f"DEBUG: get({key}) = {result}")
145+
return result
146+
147+
class MockPipeline:
148+
def __init__(self, redis_client):
149+
self.redis_client = redis_client
150+
self.commands = []
151+
152+
def hset(self, key, field, value):
153+
self.commands.append(("hset", key, field, value))
154+
return self
155+
156+
def execute(self):
157+
for cmd, key, field, value in self.commands:
158+
if cmd == "hset":
159+
self.redis_client.hset(
160+
key, field=field, value=value
161+
)
162+
163+
_test_redis_getter._mock_client = MockRedis()
164+
165+
return _test_redis_getter._mock_client
114166
return client
115167

116168

@@ -213,46 +265,75 @@ def _stale_after_redis(arg_1, arg_2):
213265

214266

215267
def _calls_takes_time_redis(res_queue):
268+
print("DEBUG: _calls_takes_time_redis started")
269+
216270
@cachier(backend="redis", redis_client=_test_redis_getter)
217271
def _takes_time(arg_1, arg_2):
218272
"""Some function."""
273+
print(
274+
f"DEBUG: _calls_takes_time_redis._takes_time({arg_1}, {arg_2})"
275+
" called"
276+
)
219277
sleep(3)
220-
return random() + arg_1 + arg_2
278+
result = random() + arg_1 + arg_2
279+
print(
280+
f"DEBUG: _calls_takes_time_redis._takes_time({arg_1}, {arg_2}) "
281+
f"returning {result}"
282+
)
283+
return result
221284

285+
print("DEBUG: _calls_takes_time_redis calling _takes_time(34, 82.3)")
222286
res = _takes_time(34, 82.3)
287+
print(f"DEBUG: _calls_takes_time_redis got result {res}, putting in queue")
223288
res_queue.put(res)
289+
print("DEBUG: _calls_takes_time_redis completed")
224290

225291

226292
@pytest.mark.redis
227293
def test_redis_being_calculated():
228294
"""Testing Redis core handling of being calculated scenarios."""
295+
print("DEBUG: test_redis_being_calculated started")
229296

230297
@cachier(backend="redis", redis_client=_test_redis_getter)
231298
def _takes_time(arg_1, arg_2):
232299
"""Some function."""
300+
print(f"DEBUG: _takes_time({arg_1}, {arg_2}) called")
233301
sleep(3)
234-
return random() + arg_1 + arg_2
302+
result = random() + arg_1 + arg_2
303+
print(f"DEBUG: _takes_time({arg_1}, {arg_2}) returning {result}")
304+
return result
235305

306+
print("DEBUG: Clearing cache")
236307
_takes_time.clear_cache()
237308
res_queue = queue.Queue()
309+
print("DEBUG: Starting thread1")
238310
thread1 = threading.Thread(
239311
target=_calls_takes_time_redis,
240312
kwargs={"res_queue": res_queue},
241313
daemon=True,
242314
)
315+
print("DEBUG: Starting thread2")
243316
thread2 = threading.Thread(
244317
target=_calls_takes_time_redis,
245318
kwargs={"res_queue": res_queue},
246319
daemon=True,
247320
)
321+
print("DEBUG: Starting thread1")
248322
thread1.start()
323+
print("DEBUG: Sleeping 1 second")
249324
sleep(1)
325+
print("DEBUG: Starting thread2")
250326
thread2.start()
327+
print("DEBUG: Waiting for thread1 to join")
251328
thread1.join()
329+
print("DEBUG: Waiting for thread2 to join")
252330
thread2.join()
331+
print("DEBUG: Getting results from queue")
253332
res1 = res_queue.get()
254333
res2 = res_queue.get()
334+
print(f"DEBUG: Results: res1={res1}, res2={res2}")
255335
assert res1 == res2
336+
print("DEBUG: test_redis_being_calculated completed successfully")
256337

257338

258339
@pytest.mark.redis

0 commit comments

Comments
 (0)