dycw-utilities 0.112.8__py3-none-any.whl → 0.112.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.112.8
3
+ Version: 0.112.9
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -74,7 +74,7 @@ Requires-Dist: polars-lts-cpu<1.29,>=1.28.1; extra == 'zzz-test-hashlib'
74
74
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-hashlib'
75
75
  Provides-Extra: zzz-test-http
76
76
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-http'
77
- Requires-Dist: orjson<3.11,>=3.10.16; extra == 'zzz-test-http'
77
+ Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-http'
78
78
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-http'
79
79
  Provides-Extra: zzz-test-hypothesis
80
80
  Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
@@ -144,7 +144,7 @@ Provides-Extra: zzz-test-pqdm
144
144
  Requires-Dist: pqdm<0.3,>=0.2.0; extra == 'zzz-test-pqdm'
145
145
  Provides-Extra: zzz-test-pydantic
146
146
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pydantic'
147
- Requires-Dist: pydantic<2.12,>=2.11.2; extra == 'zzz-test-pydantic'
147
+ Requires-Dist: pydantic<2.12,>=2.11.4; extra == 'zzz-test-pydantic'
148
148
  Provides-Extra: zzz-test-pyinstrument
149
149
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pyinstrument'
150
150
  Requires-Dist: pyinstrument<5.1,>=5.0.0; extra == 'zzz-test-pyinstrument'
@@ -153,7 +153,7 @@ Provides-Extra: zzz-test-pyrsistent
153
153
  Requires-Dist: pyrsistent<0.21,>=0.20.0; extra == 'zzz-test-pyrsistent'
154
154
  Provides-Extra: zzz-test-pytest
155
155
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-pytest'
156
- Requires-Dist: orjson<3.11,>=3.10.16; extra == 'zzz-test-pytest'
156
+ Requires-Dist: orjson<3.11,>=3.10.18; extra == 'zzz-test-pytest'
157
157
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-pytest'
158
158
  Provides-Extra: zzz-test-pytest-regressions
159
159
  Requires-Dist: pytest-regressions<2.8,>=2.7.0; extra == 'zzz-test-pytest-regressions'
@@ -197,7 +197,7 @@ Requires-Dist: sqlalchemy<2.1,>=2.0.40; extra == 'zzz-test-sqlalchemy-polars'
197
197
  Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-sqlalchemy-polars'
198
198
  Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-sqlalchemy-polars'
199
199
  Provides-Extra: zzz-test-streamlit
200
- Requires-Dist: streamlit<1.45,>=1.44.1; extra == 'zzz-test-streamlit'
200
+ Requires-Dist: streamlit<1.46,>=1.45.0; extra == 'zzz-test-streamlit'
201
201
  Provides-Extra: zzz-test-sys
202
202
  Requires-Dist: atomicwrites<1.5,>=1.4.1; extra == 'zzz-test-sys'
203
203
  Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-sys'
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=XeC2tMugkErM_YQbJ1Iwis-CVp-V14bjMaBeziiWF1s,60
1
+ utilities/__init__.py,sha256=4oIYPUfPGTr8vXSSPtcMMTgcanNRfFAjAkM3ufwN-Mg,60
2
2
  utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
3
3
  utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
4
4
  utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
@@ -58,7 +58,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
58
58
  utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
59
59
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
60
60
  utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
61
- utilities/redis.py,sha256=fAUbfOlCmxcxhh47PXQX63w0CU5iOFKfdUJ7jDn9ntM,22096
61
+ utilities/redis.py,sha256=XYHo1Qne4j5_BSIUorF8n5EWCRwByTdbAL3NLC6e2_4,25261
62
62
  utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
63
63
  utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
64
64
  utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
87
87
  utilities/whenever.py,sha256=iLRP_-8CZtBpHKbGZGu-kjSMg1ZubJ-VSmgSy7Eudxw,17787
88
88
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
89
89
  utilities/zoneinfo.py,sha256=-Xm57PMMwDTYpxJdkiJG13wnbwK--I7XItBh5WVhD-o,1874
90
- dycw_utilities-0.112.8.dist-info/METADATA,sha256=i2cGlKTXS9uPmvL_dC8xIdqS9wlxq5NbsOgmOn6J23o,13004
91
- dycw_utilities-0.112.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
- dycw_utilities-0.112.8.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
- dycw_utilities-0.112.8.dist-info/RECORD,,
90
+ dycw_utilities-0.112.9.dist-info/METADATA,sha256=w5dmojTlh4MG3_sA2rTFFQLP-BclPCLPCQ98Rf4RF-o,13004
91
+ dycw_utilities-0.112.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
92
+ dycw_utilities-0.112.9.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
93
+ dycw_utilities-0.112.9.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.112.8"
3
+ __version__ = "0.112.9"
utilities/redis.py CHANGED
@@ -31,11 +31,18 @@ from utilities.datetime import (
31
31
  )
32
32
  from utilities.errors import ImpossibleCaseError
33
33
  from utilities.functions import ensure_int
34
- from utilities.iterables import always_iterable
34
+ from utilities.iterables import always_iterable, one
35
35
 
36
36
  if TYPE_CHECKING:
37
37
  import datetime as dt
38
- from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
38
+ from collections.abc import (
39
+ AsyncIterator,
40
+ Awaitable,
41
+ Callable,
42
+ Iterable,
43
+ Mapping,
44
+ Sequence,
45
+ )
39
46
 
40
47
  from redis.asyncio import ConnectionPool
41
48
  from redis.asyncio.client import PubSub
@@ -71,6 +78,7 @@ class RedisHashMapKey(Generic[_K, _V]):
71
78
  name: str
72
79
  key: TypeLike[_K]
73
80
  key_serializer: Callable[[_K], bytes] | None = None
81
+ key_deserializer: Callable[[bytes], _K] | None = None
74
82
  value: TypeLike[_V]
75
83
  value_serializer: Callable[[_V], bytes] | None = None
76
84
  value_deserializer: Callable[[bytes], _V] | None = None
@@ -95,46 +103,102 @@ class RedisHashMapKey(Generic[_K, _V]):
95
103
  "Awaitable[bool]", redis.hexists(self.name, cast("str", key))
96
104
  )
97
105
 
98
- async def get(self, redis: Redis, key: _K, /) -> _V | None:
106
+ async def get(self, redis: Redis, key: _K, /) -> _V:
107
+ """Get a value from a hashmap in `redis`."""
108
+ result = one(await self.get_many(redis, [key])) # skipif-ci-and-not-linux
109
+ if result is None: # skipif-ci-and-not-linux
110
+ raise KeyError(self.name, key)
111
+ return result # skipif-ci-and-not-linux
112
+
113
+ async def get_all(self, redis: Redis, /) -> Mapping[_K, _V]:
99
114
  """Get a value from a hashmap in `redis`."""
100
- ser_key = _serialize( # skipif-ci-and-not-linux
101
- key, serializer=self.key_serializer
102
- )
103
115
  async with timeout_dur( # skipif-ci-and-not-linux
104
116
  duration=self.timeout, error=self.error
105
117
  ):
106
118
  result = await cast( # skipif-ci-and-not-linux
107
- "Awaitable[bytes | None]", redis.hget(self.name, cast("Any", ser_key))
119
+ "Awaitable[Mapping[bytes, bytes]]", redis.hgetall(self.name)
108
120
  )
109
- match result: # skipif-ci-and-not-linux
110
- case None:
111
- return None
112
- case bytes() as data:
113
- return _deserialize(data, deserializer=self.value_deserializer)
114
- case _ as never:
115
- assert_never(never)
121
+ return { # skipif-ci-and-not-linux
122
+ _deserialize(key, deserializer=self.key_deserializer): _deserialize(
123
+ value, deserializer=self.value_deserializer
124
+ )
125
+ for key, value in result.items()
126
+ }
127
+
128
+ async def get_many(
129
+ self, redis: Redis, keys: Iterable[_K], /
130
+ ) -> Sequence[_V | None]:
131
+ """Get multiple values from a hashmap in `redis`."""
132
+ keys = list(keys) # skipif-ci-and-not-linux
133
+ if len(keys) == 0: # skipif-ci-and-not-linux
134
+ return []
135
+ ser = [ # skipif-ci-and-not-linux
136
+ _serialize(key, serializer=self.key_serializer) for key in keys
137
+ ]
138
+ async with timeout_dur( # skipif-ci-and-not-linux
139
+ duration=self.timeout, error=self.error
140
+ ):
141
+ result = await cast( # skipif-ci-and-not-linux
142
+ "Awaitable[Sequence[bytes | None]]", redis.hmget(self.name, ser)
143
+ )
144
+ return [ # skipif-ci-and-not-linux
145
+ None
146
+ if data is None
147
+ else _deserialize(data, deserializer=self.value_deserializer)
148
+ for data in result
149
+ ]
150
+
151
+ async def keys(self, redis: Redis, /) -> Sequence[_K]:
152
+ """Get the keys of a hashmap in `redis`."""
153
+ async with timeout_dur( # skipif-ci-and-not-linux
154
+ duration=self.timeout, error=self.error
155
+ ):
156
+ result = await cast("Awaitable[Sequence[bytes]]", redis.hkeys(self.name))
157
+ return [ # skipif-ci-and-not-linux
158
+ _deserialize(data, deserializer=self.key_deserializer) for data in result
159
+ ]
160
+
161
+ async def length(self, redis: Redis, /) -> int:
162
+ """Get the length of a hashmap in `redis`."""
163
+ async with timeout_dur( # skipif-ci-and-not-linux
164
+ duration=self.timeout, error=self.error
165
+ ):
166
+ return await cast("Awaitable[int]", redis.hlen(self.name))
116
167
 
117
168
  async def set(self, redis: Redis, key: _K, value: _V, /) -> int:
118
169
  """Set a value in a hashmap in `redis`."""
119
- ser_key = _serialize( # skipif-ci-and-not-linux
120
- key, serializer=self.key_serializer
121
- )
122
- ser_value = _serialize( # skipif-ci-and-not-linux
123
- value, serializer=self.value_serializer
124
- )
170
+ return await self.set_many(redis, {key: value}) # skipif-ci-and-not-linux
171
+
172
+ async def set_many(self, redis: Redis, mapping: Mapping[_K, _V], /) -> int:
173
+ """Set multiple value(s) in a hashmap in `redis`."""
174
+ if len(mapping) == 0: # skipif-ci-and-not-linux
175
+ return 0
176
+ ser = { # skipif-ci-and-not-linux
177
+ _serialize(key, serializer=self.key_serializer): _serialize(
178
+ value, serializer=self.value_serializer
179
+ )
180
+ for key, value in mapping.items()
181
+ }
125
182
  async with timeout_dur( # skipif-ci-and-not-linux
126
183
  duration=self.timeout, error=self.error
127
184
  ):
128
185
  result = await cast(
129
- "Awaitable[int]",
130
- redis.hset(
131
- self.name, key=cast("Any", ser_key), value=cast("Any", ser_value)
132
- ),
186
+ "Awaitable[int]", redis.hset(self.name, mapping=cast("Any", ser))
133
187
  )
134
188
  if self.ttl is not None:
135
189
  await redis.pexpire(self.name, datetime_duration_to_timedelta(self.ttl))
136
190
  return result # skipif-ci-and-not-linux
137
191
 
192
+ async def values(self, redis: Redis, /) -> Sequence[_V]:
193
+ """Get the values of a hashmap in `redis`."""
194
+ async with timeout_dur( # skipif-ci-and-not-linux
195
+ duration=self.timeout, error=self.error
196
+ ):
197
+ result = await cast("Awaitable[Sequence[bytes]]", redis.hvals(self.name))
198
+ return [ # skipif-ci-and-not-linux
199
+ _deserialize(data, deserializer=self.value_deserializer) for data in result
200
+ ]
201
+
138
202
 
139
203
  @overload
140
204
  def redis_hash_map_key(
@@ -144,6 +208,7 @@ def redis_hash_map_key(
144
208
  /,
145
209
  *,
146
210
  key_serializer: Callable[[_K], bytes] | None = None,
211
+ key_deserializer: Callable[[bytes], Any] | None = None,
147
212
  value_serializer: Callable[[_V], bytes] | None = None,
148
213
  value_deserializer: Callable[[bytes], _V] | None = None,
149
214
  timeout: Duration | None = None,
@@ -158,6 +223,7 @@ def redis_hash_map_key(
158
223
  /,
159
224
  *,
160
225
  key_serializer: Callable[[_K], bytes] | None = None,
226
+ key_deserializer: Callable[[bytes], Any] | None = None,
161
227
  value_serializer: Callable[[_V1 | _V2], bytes] | None = None,
162
228
  value_deserializer: Callable[[bytes], _V1 | _V2] | None = None,
163
229
  timeout: Duration | None = None,
@@ -172,6 +238,7 @@ def redis_hash_map_key(
172
238
  /,
173
239
  *,
174
240
  key_serializer: Callable[[_K], bytes] | None = None,
241
+ key_deserializer: Callable[[bytes], Any] | None = None,
175
242
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
176
243
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
177
244
  timeout: Duration | None = None,
@@ -186,6 +253,7 @@ def redis_hash_map_key(
186
253
  /,
187
254
  *,
188
255
  key_serializer: Callable[[_K1 | _K2], bytes] | None = None,
256
+ key_deserializer: Callable[[bytes], Any] | None = None,
189
257
  value_serializer: Callable[[_V], bytes] | None = None,
190
258
  value_deserializer: Callable[[bytes], _V] | None = None,
191
259
  timeout: Duration | None = None,
@@ -200,6 +268,7 @@ def redis_hash_map_key(
200
268
  /,
201
269
  *,
202
270
  key_serializer: Callable[[_K1 | _K2], bytes] | None = None,
271
+ key_deserializer: Callable[[bytes], Any] | None = None,
203
272
  value_serializer: Callable[[_V1 | _V2], bytes] | None = None,
204
273
  value_deserializer: Callable[[bytes], _V1 | _V2] | None = None,
205
274
  timeout: Duration | None = None,
@@ -214,6 +283,7 @@ def redis_hash_map_key(
214
283
  /,
215
284
  *,
216
285
  key_serializer: Callable[[_K1 | _K2], bytes] | None = None,
286
+ key_deserializer: Callable[[bytes], Any] | None = None,
217
287
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
218
288
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
219
289
  timeout: Duration | None = None,
@@ -228,6 +298,7 @@ def redis_hash_map_key(
228
298
  /,
229
299
  *,
230
300
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
301
+ key_deserializer: Callable[[bytes], Any] | None = None,
231
302
  value_serializer: Callable[[_V], bytes] | None = None,
232
303
  value_deserializer: Callable[[bytes], _V] | None = None,
233
304
  timeout: Duration | None = None,
@@ -242,6 +313,7 @@ def redis_hash_map_key(
242
313
  /,
243
314
  *,
244
315
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
316
+ key_deserializer: Callable[[bytes], Any] | None = None,
245
317
  value_serializer: Callable[[_V1 | _V2], bytes] | None = None,
246
318
  value_deserializer: Callable[[bytes], _V1 | _V2] | None = None,
247
319
  timeout: Duration | None = None,
@@ -256,6 +328,7 @@ def redis_hash_map_key(
256
328
  /,
257
329
  *,
258
330
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
331
+ key_deserializer: Callable[[bytes], Any] | None = None,
259
332
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
260
333
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
261
334
  timeout: Duration | None = None,
@@ -270,6 +343,7 @@ def redis_hash_map_key(
270
343
  /,
271
344
  *,
272
345
  key_serializer: Callable[[_K1 | _K2 | _K3], bytes] | None = None,
346
+ key_deserializer: Callable[[bytes], Any] | None = None,
273
347
  value_serializer: Callable[[_V1 | _V2 | _V3], bytes] | None = None,
274
348
  value_deserializer: Callable[[bytes], _V1 | _V2 | _V3] | None = None,
275
349
  timeout: Duration | None = None,
@@ -283,6 +357,7 @@ def redis_hash_map_key(
283
357
  /,
284
358
  *,
285
359
  key_serializer: Callable[[Any], bytes] | None = None,
360
+ key_deserializer: Callable[[bytes], Any] | None = None,
286
361
  value_serializer: Callable[[Any], bytes] | None = None,
287
362
  value_deserializer: Callable[[bytes], Any] | None = None,
288
363
  timeout: Duration | None = None,
@@ -294,6 +369,7 @@ def redis_hash_map_key(
294
369
  name=name,
295
370
  key=key,
296
371
  key_serializer=key_serializer,
372
+ key_deserializer=key_deserializer,
297
373
  value=value,
298
374
  value_serializer=value_serializer,
299
375
  value_deserializer=value_deserializer,
@@ -343,23 +419,15 @@ class RedisKey(Generic[_T]):
343
419
  duration=self.timeout, error=self.error
344
420
  ):
345
421
  result = cast("bytes | None", await redis.get(self.name))
346
- match result: # skipif-ci-and-not-linux
347
- case None:
348
- return None
349
- case bytes() as data:
350
- if self.deserializer is None:
351
- from utilities.orjson import deserialize
352
-
353
- return deserialize(data)
354
- return self.deserializer(data)
355
- case _ as never:
356
- assert_never(never)
422
+ if result is None: # skipif-ci-and-not-linux
423
+ raise KeyError(self.name)
424
+ return _deserialize( # skipif-ci-and-not-linux
425
+ result, deserializer=self.deserializer
426
+ )
357
427
 
358
428
  async def set(self, redis: Redis, value: _T, /) -> int:
359
429
  """Set a value in `redis`."""
360
- ser_value = _serialize( # skipif-ci-and-not-linux
361
- value, serializer=self.serializer
362
- )
430
+ ser = _serialize(value, serializer=self.serializer) # skipif-ci-and-not-linux
363
431
  ttl = ( # skipif-ci-and-not-linux
364
432
  None
365
433
  if self.ttl is None
@@ -369,7 +437,7 @@ class RedisKey(Generic[_T]):
369
437
  duration=self.timeout, error=self.error
370
438
  ):
371
439
  result = await redis.set( # skipif-ci-and-not-linux
372
- self.name, ser_value, px=ttl
440
+ self.name, ser, px=ttl
373
441
  )
374
442
  return ensure_int(result) # skipif-ci-and-not-linux
375
443