rapyer 1.1.7__py3-none-any.whl → 1.2.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.
- rapyer/__init__.py +0 -2
- rapyer/base.py +11 -120
- rapyer/scripts/__init__.py +6 -0
- rapyer/scripts/constants.py +2 -0
- rapyer/scripts/loader.py +20 -0
- rapyer/scripts/lua/dict/__init__.py +0 -0
- rapyer/scripts/lua/dict/pop.lua +13 -0
- rapyer/scripts/lua/dict/popitem.lua +29 -0
- rapyer/scripts/registry.py +15 -1
- rapyer/types/base.py +0 -13
- rapyer/types/dct.py +13 -83
- rapyer/types/integer.py +0 -7
- {rapyer-1.1.7.dist-info → rapyer-1.2.0.dist-info}/METADATA +1 -1
- {rapyer-1.1.7.dist-info → rapyer-1.2.0.dist-info}/RECORD +15 -12
- {rapyer-1.1.7.dist-info → rapyer-1.2.0.dist-info}/WHEEL +0 -0
rapyer/__init__.py
CHANGED
|
@@ -6,7 +6,6 @@ from rapyer.base import (
|
|
|
6
6
|
afind,
|
|
7
7
|
find_redis_models,
|
|
8
8
|
ainsert,
|
|
9
|
-
get,
|
|
10
9
|
alock_from_key,
|
|
11
10
|
)
|
|
12
11
|
from rapyer.init import init_rapyer, teardown_rapyer
|
|
@@ -17,7 +16,6 @@ __all__ = [
|
|
|
17
16
|
"teardown_rapyer",
|
|
18
17
|
"aget",
|
|
19
18
|
"afind",
|
|
20
|
-
"get",
|
|
21
19
|
"find_redis_models",
|
|
22
20
|
"ainsert",
|
|
23
21
|
"alock_from_key",
|
rapyer/base.py
CHANGED
|
@@ -21,7 +21,6 @@ from pydantic_core.core_schema import FieldSerializationInfo, ValidationInfo
|
|
|
21
21
|
from redis.commands.search.index_definition import IndexDefinition, IndexType
|
|
22
22
|
from redis.commands.search.query import Query
|
|
23
23
|
from redis.exceptions import NoScriptError, ResponseError
|
|
24
|
-
from typing_extensions import deprecated
|
|
25
24
|
|
|
26
25
|
from rapyer.config import RedisConfig
|
|
27
26
|
from rapyer.context import _context_var, _context_xx_pipe
|
|
@@ -104,25 +103,6 @@ def make_pickle_field_serializer(
|
|
|
104
103
|
return pickle_field_serializer, pickle_field_validator
|
|
105
104
|
|
|
106
105
|
|
|
107
|
-
# TODO: Remove in next major version (2.0) - backward compatibility for pickled data
|
|
108
|
-
# This validator handles loading old pickled data for fields that are now JSON-serializable.
|
|
109
|
-
# In 2.0, remove this function and the validator registration in __init_subclass__.
|
|
110
|
-
def make_backward_compat_validator(field: str):
|
|
111
|
-
@field_validator(field, mode="before")
|
|
112
|
-
def backward_compat_validator(v, info: ValidationInfo):
|
|
113
|
-
ctx = info.context or {}
|
|
114
|
-
should_deserialize_redis = ctx.get(REDIS_DUMP_FLAG_NAME, False)
|
|
115
|
-
if should_deserialize_redis and isinstance(v, str):
|
|
116
|
-
try:
|
|
117
|
-
return pickle.loads(base64.b64decode(v))
|
|
118
|
-
except Exception:
|
|
119
|
-
pass
|
|
120
|
-
return v
|
|
121
|
-
|
|
122
|
-
backward_compat_validator.__name__ = f"__backward_compat_{field}"
|
|
123
|
-
return backward_compat_validator
|
|
124
|
-
|
|
125
|
-
|
|
126
106
|
class AtomicRedisModel(BaseModel):
|
|
127
107
|
_pk: str = PrivateAttr(default_factory=lambda: str(uuid.uuid4()))
|
|
128
108
|
_base_model_link: Self | RedisType = PrivateAttr(default=None)
|
|
@@ -302,11 +282,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
302
282
|
)
|
|
303
283
|
setattr(cls, serializer.__name__, serializer)
|
|
304
284
|
setattr(cls, validator.__name__, validator)
|
|
305
|
-
else:
|
|
306
|
-
# TODO: Remove in 2.0 - backward compatibility for old pickled data
|
|
307
|
-
validator = make_backward_compat_validator(attr_name)
|
|
308
|
-
setattr(cls, validator.__name__, validator)
|
|
309
|
-
continue
|
|
310
285
|
|
|
311
286
|
# Update the redis model list for initialization
|
|
312
287
|
# Skip dynamically created classes from type conversion
|
|
@@ -335,12 +310,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
335
310
|
def is_inner_model(self) -> bool:
|
|
336
311
|
return bool(self.field_name)
|
|
337
312
|
|
|
338
|
-
@deprecated(
|
|
339
|
-
f"save function is deprecated and will become sync function in rapyer 1.2.0, use asave() instead"
|
|
340
|
-
)
|
|
341
|
-
async def save(self):
|
|
342
|
-
return await self.asave() # pragma: no cover
|
|
343
|
-
|
|
344
313
|
async def asave(self) -> Self:
|
|
345
314
|
model_dump = self.redis_dump()
|
|
346
315
|
await self.Meta.redis.json().set(self.key, self.json_path, model_dump)
|
|
@@ -355,12 +324,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
355
324
|
def redis_dump_json(self):
|
|
356
325
|
return self.model_dump_json(context={REDIS_DUMP_FLAG_NAME: True})
|
|
357
326
|
|
|
358
|
-
@deprecated(
|
|
359
|
-
"duplicate function is deprecated and will be removed in rapyer 1.2.0, use aduplicate instead"
|
|
360
|
-
)
|
|
361
|
-
async def duplicate(self) -> Self:
|
|
362
|
-
return await self.aduplicate() # pragma: no cover
|
|
363
|
-
|
|
364
327
|
async def aduplicate(self) -> Self:
|
|
365
328
|
if self.is_inner_model():
|
|
366
329
|
raise RuntimeError("Can only duplicate from top level model")
|
|
@@ -369,12 +332,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
369
332
|
await duplicated.asave()
|
|
370
333
|
return duplicated
|
|
371
334
|
|
|
372
|
-
@deprecated(
|
|
373
|
-
"duplicate_many function is deprecated and will be removed in rapyer 1.2.0, use aduplicate_many instead"
|
|
374
|
-
)
|
|
375
|
-
async def duplicate_many(self, num: int) -> list[Self]:
|
|
376
|
-
return await self.aduplicate_many(num) # pragma: no cover
|
|
377
|
-
|
|
378
335
|
async def aduplicate_many(self, num: int) -> list[Self]:
|
|
379
336
|
if self.is_inner_model():
|
|
380
337
|
raise RuntimeError("Can only duplicate from top level model")
|
|
@@ -401,7 +358,7 @@ class AtomicRedisModel(BaseModel):
|
|
|
401
358
|
for field_name in kwargs.keys()
|
|
402
359
|
}
|
|
403
360
|
|
|
404
|
-
async with self.Meta.redis.pipeline() as pipe:
|
|
361
|
+
async with self.Meta.redis.pipeline(transaction=True) as pipe:
|
|
405
362
|
update_keys_in_pipeline(pipe, self.key, **json_path_kwargs)
|
|
406
363
|
await pipe.execute()
|
|
407
364
|
await self.refresh_ttl_if_needed()
|
|
@@ -409,14 +366,11 @@ class AtomicRedisModel(BaseModel):
|
|
|
409
366
|
async def aset_ttl(self, ttl: int) -> None:
|
|
410
367
|
if self.is_inner_model():
|
|
411
368
|
raise RuntimeError("Can only set TTL from top level model")
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
)
|
|
418
|
-
async def get(cls, key: str) -> Self:
|
|
419
|
-
return await cls.aget(key) # pragma: no cover
|
|
369
|
+
pipeline = _context_var.get()
|
|
370
|
+
if pipeline is not None:
|
|
371
|
+
pipeline.expire(self.key, ttl)
|
|
372
|
+
else:
|
|
373
|
+
await self.Meta.redis.expire(self.key, ttl)
|
|
420
374
|
|
|
421
375
|
@classmethod
|
|
422
376
|
async def aget(cls, key: str) -> Self:
|
|
@@ -435,12 +389,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
435
389
|
await cls.Meta.redis.expire(key, cls.Meta.ttl)
|
|
436
390
|
return instance
|
|
437
391
|
|
|
438
|
-
@deprecated(
|
|
439
|
-
"load function is deprecated and will be removed in rapyer 1.2.0, use aload() instead"
|
|
440
|
-
)
|
|
441
|
-
async def load(self):
|
|
442
|
-
return await self.aload() # pragma: no cover
|
|
443
|
-
|
|
444
392
|
async def aload(self) -> Self:
|
|
445
393
|
model_dump = await self.Meta.redis.json().get(self.key, self.json_path)
|
|
446
394
|
if not model_dump:
|
|
@@ -543,24 +491,11 @@ class AtomicRedisModel(BaseModel):
|
|
|
543
491
|
pipe.expire(model.key, cls.Meta.ttl)
|
|
544
492
|
await pipe.execute()
|
|
545
493
|
|
|
546
|
-
@classmethod
|
|
547
|
-
@deprecated(
|
|
548
|
-
"function delete is deprecated and will be removed in rapyer 1.2.0, use adelete instead"
|
|
549
|
-
)
|
|
550
|
-
async def delete_by_key(cls, key: str) -> bool:
|
|
551
|
-
return await cls.adelete_by_key(key) # pragma: no cover
|
|
552
|
-
|
|
553
494
|
@classmethod
|
|
554
495
|
async def adelete_by_key(cls, key: str) -> bool:
|
|
555
496
|
client = _context_var.get() or cls.Meta.redis
|
|
556
497
|
return await client.delete(key) == 1
|
|
557
498
|
|
|
558
|
-
@deprecated(
|
|
559
|
-
"function delete is deprecated and will be removed in rapyer 1.2.0, use adelete instead"
|
|
560
|
-
)
|
|
561
|
-
async def delete(self):
|
|
562
|
-
return await self.adelete() # pragma: no cover
|
|
563
|
-
|
|
564
499
|
async def adelete(self):
|
|
565
500
|
if self.is_inner_model():
|
|
566
501
|
raise RuntimeError("Can only delete from inner model")
|
|
@@ -572,19 +507,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
572
507
|
*[model if isinstance(model, str) else model.key for model in args]
|
|
573
508
|
)
|
|
574
509
|
|
|
575
|
-
@classmethod
|
|
576
|
-
@contextlib.asynccontextmanager
|
|
577
|
-
@deprecated(
|
|
578
|
-
"lock_from_key function is deprecated and will be removed in rapyer 1.2.0, use alock_from_key instead"
|
|
579
|
-
)
|
|
580
|
-
async def lock_from_key(
|
|
581
|
-
cls, key: str, action: str = "default", save_at_end: bool = False
|
|
582
|
-
) -> AbstractAsyncContextManager[Self]:
|
|
583
|
-
async with cls.alock_from_key( # pragma: no cover
|
|
584
|
-
key, action, save_at_end # pragma: no cover
|
|
585
|
-
) as redis_model: # pragma: no cover
|
|
586
|
-
yield redis_model # pragma: no cover
|
|
587
|
-
|
|
588
510
|
@classmethod
|
|
589
511
|
@contextlib.asynccontextmanager
|
|
590
512
|
async def alock_from_key(
|
|
@@ -596,18 +518,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
596
518
|
if save_at_end:
|
|
597
519
|
await redis_model.asave()
|
|
598
520
|
|
|
599
|
-
@contextlib.asynccontextmanager
|
|
600
|
-
@deprecated(
|
|
601
|
-
"lock function is deprecated and will be removed in rapyer 1.2.0, use alock instead"
|
|
602
|
-
)
|
|
603
|
-
async def lock(
|
|
604
|
-
self, action: str = "default", save_at_end: bool = False
|
|
605
|
-
) -> AbstractAsyncContextManager[Self]:
|
|
606
|
-
async with self.alock_from_key( # pragma: no cover
|
|
607
|
-
self.key, action, save_at_end # pragma: no cover
|
|
608
|
-
) as redis_model: # pragma: no cover
|
|
609
|
-
yield redis_model # pragma: no cover
|
|
610
|
-
|
|
611
521
|
@contextlib.asynccontextmanager
|
|
612
522
|
async def alock(
|
|
613
523
|
self, action: str = "default", save_at_end: bool = False
|
|
@@ -619,21 +529,9 @@ class AtomicRedisModel(BaseModel):
|
|
|
619
529
|
self.__dict__.update(unset_fields)
|
|
620
530
|
yield redis_model
|
|
621
531
|
|
|
622
|
-
@contextlib.asynccontextmanager
|
|
623
|
-
@deprecated(
|
|
624
|
-
"pipeline function is deprecated and will be removed in rapyer 1.2.0, use apipeline instead"
|
|
625
|
-
)
|
|
626
|
-
async def pipeline(
|
|
627
|
-
self, ignore_if_deleted: bool = False
|
|
628
|
-
) -> AbstractAsyncContextManager[Self]:
|
|
629
|
-
async with self.apipeline( # pragma: no cover
|
|
630
|
-
ignore_if_deleted=ignore_if_deleted # pragma: no cover
|
|
631
|
-
) as redis_model: # pragma: no cover
|
|
632
|
-
yield redis_model # pragma: no cover
|
|
633
|
-
|
|
634
532
|
@contextlib.asynccontextmanager
|
|
635
533
|
async def apipeline(
|
|
636
|
-
self,
|
|
534
|
+
self, ignore_redis_error: bool = False
|
|
637
535
|
) -> AbstractAsyncContextManager[Self]:
|
|
638
536
|
async with self.Meta.redis.pipeline(transaction=True) as pipe:
|
|
639
537
|
try:
|
|
@@ -643,12 +541,12 @@ class AtomicRedisModel(BaseModel):
|
|
|
643
541
|
}
|
|
644
542
|
self.__dict__.update(unset_fields)
|
|
645
543
|
except (TypeError, KeyNotFound):
|
|
646
|
-
if
|
|
544
|
+
if ignore_redis_error:
|
|
647
545
|
redis_model = self
|
|
648
546
|
else:
|
|
649
547
|
raise
|
|
650
548
|
_context_var.set(pipe)
|
|
651
|
-
_context_xx_pipe.set(
|
|
549
|
+
_context_xx_pipe.set(ignore_redis_error)
|
|
652
550
|
yield redis_model
|
|
653
551
|
commands_backup = list(pipe.command_stack)
|
|
654
552
|
noscript_on_first_attempt = False
|
|
@@ -661,10 +559,10 @@ class AtomicRedisModel(BaseModel):
|
|
|
661
559
|
except NoScriptError:
|
|
662
560
|
noscript_on_first_attempt = True
|
|
663
561
|
except ResponseError as exc:
|
|
664
|
-
if
|
|
562
|
+
if ignore_redis_error:
|
|
665
563
|
logger.warning(
|
|
666
564
|
"Swallowed ResponseError during pipeline.execute() with "
|
|
667
|
-
"
|
|
565
|
+
"ignore_redis_error=True for key %r: %s",
|
|
668
566
|
getattr(self, "key", None),
|
|
669
567
|
exc,
|
|
670
568
|
)
|
|
@@ -744,13 +642,6 @@ class AtomicRedisModel(BaseModel):
|
|
|
744
642
|
REDIS_MODELS: list[type[AtomicRedisModel]] = []
|
|
745
643
|
|
|
746
644
|
|
|
747
|
-
@deprecated(
|
|
748
|
-
"get function is deprecated and will be removed in rapyer 1.2.0, use aget instead"
|
|
749
|
-
)
|
|
750
|
-
async def get(redis_key: str) -> AtomicRedisModel:
|
|
751
|
-
return await aget(redis_key) # pragma: no cover
|
|
752
|
-
|
|
753
|
-
|
|
754
645
|
async def aget(redis_key: str) -> AtomicRedisModel:
|
|
755
646
|
redis_model_mapping = {klass.__name__: klass for klass in REDIS_MODELS}
|
|
756
647
|
class_name = redis_key.split(":")[0]
|
rapyer/scripts/__init__.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from rapyer.scripts.constants import (
|
|
2
2
|
DATETIME_ADD_SCRIPT_NAME,
|
|
3
|
+
DICT_POP_SCRIPT_NAME,
|
|
4
|
+
DICT_POPITEM_SCRIPT_NAME,
|
|
3
5
|
NUM_FLOORDIV_SCRIPT_NAME,
|
|
4
6
|
NUM_MOD_SCRIPT_NAME,
|
|
5
7
|
NUM_MUL_SCRIPT_NAME,
|
|
@@ -12,6 +14,7 @@ from rapyer.scripts.constants import (
|
|
|
12
14
|
)
|
|
13
15
|
from rapyer.scripts.registry import (
|
|
14
16
|
_REGISTERED_SCRIPT_SHAS,
|
|
17
|
+
arun_sha,
|
|
15
18
|
get_scripts,
|
|
16
19
|
get_scripts_fakeredis,
|
|
17
20
|
handle_noscript_error,
|
|
@@ -24,6 +27,8 @@ SCRIPTS_FAKEREDIS = get_scripts_fakeredis()
|
|
|
24
27
|
|
|
25
28
|
__all__ = [
|
|
26
29
|
"DATETIME_ADD_SCRIPT_NAME",
|
|
30
|
+
"DICT_POP_SCRIPT_NAME",
|
|
31
|
+
"DICT_POPITEM_SCRIPT_NAME",
|
|
27
32
|
"NUM_FLOORDIV_SCRIPT_NAME",
|
|
28
33
|
"NUM_MOD_SCRIPT_NAME",
|
|
29
34
|
"NUM_MUL_SCRIPT_NAME",
|
|
@@ -35,6 +40,7 @@ __all__ = [
|
|
|
35
40
|
"SCRIPTS_FAKEREDIS",
|
|
36
41
|
"STR_APPEND_SCRIPT_NAME",
|
|
37
42
|
"STR_MUL_SCRIPT_NAME",
|
|
43
|
+
"arun_sha",
|
|
38
44
|
"handle_noscript_error",
|
|
39
45
|
"register_scripts",
|
|
40
46
|
"run_sha",
|
rapyer/scripts/constants.py
CHANGED
rapyer/scripts/loader.py
CHANGED
|
@@ -8,12 +8,32 @@ VARIANTS = {
|
|
|
8
8
|
"EXTRACT_VALUE": "local value = tonumber(cjson.decode(current_json)[1])",
|
|
9
9
|
"EXTRACT_STR": "local value = cjson.decode(current_json)[1]",
|
|
10
10
|
"EXTRACT_DATETIME": "local value = cjson.decode(current_json)[1]",
|
|
11
|
+
"DICT_EXTRACT_VALUE": "local extracted = cjson.decode(value)[1]",
|
|
12
|
+
"DICT_EXTRACT_POPITEM": """local parsed = cjson.decode(value)
|
|
13
|
+
if type(parsed) == 'table' then
|
|
14
|
+
for _, v in pairs(parsed) do
|
|
15
|
+
extracted = v
|
|
16
|
+
break
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
extracted = parsed
|
|
20
|
+
end""",
|
|
11
21
|
},
|
|
12
22
|
"fakeredis": {
|
|
13
23
|
"EXTRACT_ARRAY": "local arr = cjson.decode(arr_json)",
|
|
14
24
|
"EXTRACT_VALUE": "local value = tonumber(cjson.decode(current_json)[1])",
|
|
15
25
|
"EXTRACT_STR": "local value = cjson.decode(current_json)[1]",
|
|
16
26
|
"EXTRACT_DATETIME": "local value = cjson.decode(current_json)[1]",
|
|
27
|
+
"DICT_EXTRACT_VALUE": "local extracted = cjson.decode(value)[1]",
|
|
28
|
+
"DICT_EXTRACT_POPITEM": """local parsed = cjson.decode(value)
|
|
29
|
+
if type(parsed) == 'table' then
|
|
30
|
+
for _, v in pairs(parsed) do
|
|
31
|
+
extracted = v
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
extracted = parsed
|
|
36
|
+
end""",
|
|
17
37
|
},
|
|
18
38
|
}
|
|
19
39
|
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
local key = KEYS[1]
|
|
2
|
+
local path = ARGV[1]
|
|
3
|
+
local target_key = ARGV[2]
|
|
4
|
+
|
|
5
|
+
local value = redis.call('JSON.GET', key, path .. '.' .. target_key)
|
|
6
|
+
|
|
7
|
+
if value and value ~= '[]' and value ~= 'null' then
|
|
8
|
+
redis.call('JSON.DEL', key, path .. '.' .. target_key)
|
|
9
|
+
--[[DICT_EXTRACT_VALUE]]
|
|
10
|
+
return extracted
|
|
11
|
+
else
|
|
12
|
+
return nil
|
|
13
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
local key = KEYS[1]
|
|
2
|
+
local path = ARGV[1]
|
|
3
|
+
|
|
4
|
+
local keys = redis.call('JSON.OBJKEYS', key, path)
|
|
5
|
+
|
|
6
|
+
if not keys or #keys == 0 then
|
|
7
|
+
return nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
if type(keys[1]) == 'table' then
|
|
11
|
+
keys = keys[1]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if not keys or #keys == 0 then
|
|
15
|
+
return nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
local first_key = tostring(keys[1])
|
|
19
|
+
local value = redis.call('JSON.GET', key, path .. '.' .. first_key)
|
|
20
|
+
|
|
21
|
+
if not value then
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
redis.call('JSON.DEL', key, path .. '.' .. first_key)
|
|
26
|
+
|
|
27
|
+
local extracted
|
|
28
|
+
--[[DICT_EXTRACT_POPITEM]]
|
|
29
|
+
return {first_key, extracted}
|
rapyer/scripts/registry.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from rapyer.errors import ScriptsNotInitializedError
|
|
2
2
|
from rapyer.scripts.constants import (
|
|
3
3
|
DATETIME_ADD_SCRIPT_NAME,
|
|
4
|
+
DICT_POP_SCRIPT_NAME,
|
|
5
|
+
DICT_POPITEM_SCRIPT_NAME,
|
|
4
6
|
NUM_FLOORDIV_SCRIPT_NAME,
|
|
5
7
|
NUM_MOD_SCRIPT_NAME,
|
|
6
8
|
NUM_MUL_SCRIPT_NAME,
|
|
@@ -24,6 +26,8 @@ SCRIPT_REGISTRY: list[tuple[str, str, str]] = [
|
|
|
24
26
|
("string", "append", STR_APPEND_SCRIPT_NAME),
|
|
25
27
|
("string", "mul", STR_MUL_SCRIPT_NAME),
|
|
26
28
|
("datetime", "add", DATETIME_ADD_SCRIPT_NAME),
|
|
29
|
+
("dict", "pop", DICT_POP_SCRIPT_NAME),
|
|
30
|
+
("dict", "popitem", DICT_POPITEM_SCRIPT_NAME),
|
|
27
31
|
]
|
|
28
32
|
|
|
29
33
|
_REGISTERED_SCRIPT_SHAS: dict[str, str] = {}
|
|
@@ -52,14 +56,24 @@ async def register_scripts(redis_client, is_fakeredis: bool = False) -> None:
|
|
|
52
56
|
_REGISTERED_SCRIPT_SHAS[name] = sha
|
|
53
57
|
|
|
54
58
|
|
|
55
|
-
def
|
|
59
|
+
def get_script(script_name: str):
|
|
56
60
|
sha = _REGISTERED_SCRIPT_SHAS.get(script_name)
|
|
57
61
|
if sha is None:
|
|
58
62
|
raise ScriptsNotInitializedError(
|
|
59
63
|
f"Script '{script_name}' not loaded. Did you forget to call init_rapyer()?"
|
|
60
64
|
)
|
|
65
|
+
return sha
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_sha(pipeline, script_name: str, keys: int, *args):
|
|
69
|
+
sha = get_script(script_name)
|
|
61
70
|
pipeline.evalsha(sha, keys, *args)
|
|
62
71
|
|
|
63
72
|
|
|
73
|
+
async def arun_sha(client, script_name: str, keys: int, *args):
|
|
74
|
+
sha = get_script(script_name)
|
|
75
|
+
return await client.evalsha(sha, keys, *args)
|
|
76
|
+
|
|
77
|
+
|
|
64
78
|
async def handle_noscript_error(redis_client) -> None:
|
|
65
79
|
await register_scripts(redis_client)
|
rapyer/types/base.py
CHANGED
|
@@ -9,7 +9,6 @@ from pydantic import GetCoreSchemaHandler, TypeAdapter
|
|
|
9
9
|
from pydantic_core import core_schema
|
|
10
10
|
from pydantic_core.core_schema import ValidationInfo, CoreSchema, SerializationInfo
|
|
11
11
|
from redis.commands.search.field import TextField
|
|
12
|
-
from typing_extensions import deprecated
|
|
13
12
|
|
|
14
13
|
from rapyer.context import _context_var
|
|
15
14
|
from rapyer.errors.base import CantSerializeRedisValueError
|
|
@@ -74,12 +73,6 @@ class RedisType(ABC):
|
|
|
74
73
|
def json_field_path(self, field_name: str):
|
|
75
74
|
return f"${self.sub_field_path(field_name)}"
|
|
76
75
|
|
|
77
|
-
@deprecated(
|
|
78
|
-
f"save function is deprecated and will become sync function in rapyer 1.2.0, use asave() instead"
|
|
79
|
-
)
|
|
80
|
-
async def save(self):
|
|
81
|
-
return await self.asave() # pragma: no cover
|
|
82
|
-
|
|
83
76
|
async def asave(self) -> Self:
|
|
84
77
|
model_dump = self._adapter.dump_python(
|
|
85
78
|
self, mode="json", context={REDIS_DUMP_FLAG_NAME: True}
|
|
@@ -90,12 +83,6 @@ class RedisType(ABC):
|
|
|
90
83
|
await self.client.expire(self.key, self.Meta.ttl, nx=nx)
|
|
91
84
|
return self
|
|
92
85
|
|
|
93
|
-
@deprecated(
|
|
94
|
-
"load function is deprecated and will be removed in rapyer 1.2.0, use aload() instead"
|
|
95
|
-
)
|
|
96
|
-
async def load(self):
|
|
97
|
-
return await self.aload() # pragma: no cover
|
|
98
|
-
|
|
99
86
|
async def aload(self):
|
|
100
87
|
redis_value = await self.client.json().get(self.key, self.field_path)
|
|
101
88
|
if redis_value is None:
|
rapyer/types/dct.py
CHANGED
|
@@ -2,6 +2,7 @@ from typing import TypeVar, Generic, get_args, Any, TypeAlias, TYPE_CHECKING
|
|
|
2
2
|
|
|
3
3
|
from pydantic_core import core_schema
|
|
4
4
|
|
|
5
|
+
from rapyer.scripts import arun_sha, DICT_POP_SCRIPT_NAME, DICT_POPITEM_SCRIPT_NAME
|
|
5
6
|
from rapyer.types.base import (
|
|
6
7
|
GenericRedisType,
|
|
7
8
|
RedisType,
|
|
@@ -12,80 +13,6 @@ from rapyer.utils.redis import update_keys_in_pipeline
|
|
|
12
13
|
|
|
13
14
|
T = TypeVar("T")
|
|
14
15
|
|
|
15
|
-
# Redis Lua script for atomic get-and-delete operation
|
|
16
|
-
POP_SCRIPT = """
|
|
17
|
-
local key = KEYS[1]
|
|
18
|
-
local path = ARGV[1]
|
|
19
|
-
local target_key = ARGV[2]
|
|
20
|
-
|
|
21
|
-
-- Get the value from the JSON object
|
|
22
|
-
local value = redis.call('JSON.GET', key, path .. '.' .. target_key)
|
|
23
|
-
|
|
24
|
-
if value and value ~= '[]' and value ~= 'null' then
|
|
25
|
-
-- Delete the key from the JSON object
|
|
26
|
-
redis.call('JSON.DEL', key, path .. '.' .. target_key)
|
|
27
|
-
|
|
28
|
-
-- Parse and return the actual value
|
|
29
|
-
local parsed = cjson.decode(value)
|
|
30
|
-
return parsed[1] -- Return first element if it's an array
|
|
31
|
-
else
|
|
32
|
-
return nil
|
|
33
|
-
end
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Redis Lua script for atomic get-arbitrary-key-and-delete operation
|
|
38
|
-
POPITEM_SCRIPT = """
|
|
39
|
-
local key = KEYS[1]
|
|
40
|
-
local path = ARGV[1]
|
|
41
|
-
|
|
42
|
-
-- Get all the keys from the JSON object
|
|
43
|
-
local keys = redis.call('JSON.OBJKEYS', key, path)
|
|
44
|
-
|
|
45
|
-
-- Return nil if no keys exist
|
|
46
|
-
if not keys or #keys == 0 then
|
|
47
|
-
return nil
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
-- Handle nested arrays - Redis sometimes wraps results
|
|
51
|
-
if type(keys[1]) == 'table' then
|
|
52
|
-
keys = keys[1]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
-- Check again after unwrapping
|
|
56
|
-
if not keys or #keys == 0 then
|
|
57
|
-
return nil
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
local first_key = tostring(keys[1])
|
|
61
|
-
|
|
62
|
-
-- Get the value for this key
|
|
63
|
-
local value = redis.call('JSON.GET', key, path .. '.' .. first_key)
|
|
64
|
-
|
|
65
|
-
-- Return nil if value doesn't exist
|
|
66
|
-
if not value then
|
|
67
|
-
return nil
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
-- Delete the key from the JSON object
|
|
71
|
-
redis.call('JSON.DEL', key, path .. '.' .. first_key)
|
|
72
|
-
|
|
73
|
-
-- Parse the JSON string
|
|
74
|
-
local parsed_value = cjson.decode(value)
|
|
75
|
-
|
|
76
|
-
-- If it's a table/object, return the first value
|
|
77
|
-
if type(parsed_value) == 'table' then
|
|
78
|
-
for _, v in pairs(parsed_value) do
|
|
79
|
-
return {first_key, v} -- Return first value found
|
|
80
|
-
end
|
|
81
|
-
-- If table is empty, return nil
|
|
82
|
-
return nil
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
-- Otherwise return the parsed value as-is
|
|
86
|
-
return {first_key, parsed_value}
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
16
|
|
|
90
17
|
class RedisDict(dict[str, T], GenericRedisType, Generic[T]):
|
|
91
18
|
original_type = dict
|
|
@@ -108,10 +35,13 @@ class RedisDict(dict[str, T], GenericRedisType, Generic[T]):
|
|
|
108
35
|
|
|
109
36
|
def update(self, m=None, /, **kwargs):
|
|
110
37
|
if self.pipeline:
|
|
111
|
-
m_redis_val =
|
|
112
|
-
|
|
38
|
+
m_redis_val = (
|
|
39
|
+
self._adapter.dump_python(
|
|
40
|
+
m, mode="json", context={REDIS_DUMP_FLAG_NAME: True}
|
|
41
|
+
)
|
|
42
|
+
if m
|
|
43
|
+
else {}
|
|
113
44
|
)
|
|
114
|
-
m_redis_val = m_redis_val or {}
|
|
115
45
|
kwargs_redis_val = self._adapter.dump_python(
|
|
116
46
|
kwargs, mode="json", context={REDIS_DUMP_FLAG_NAME: True}
|
|
117
47
|
)
|
|
@@ -175,14 +105,13 @@ class RedisDict(dict[str, T], GenericRedisType, Generic[T]):
|
|
|
175
105
|
await self.refresh_ttl_if_needed()
|
|
176
106
|
|
|
177
107
|
async def apop(self, key, default=None):
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
108
|
+
result = await arun_sha(
|
|
109
|
+
self.client, DICT_POP_SCRIPT_NAME, 1, self.key, self.json_path, key
|
|
110
|
+
)
|
|
181
111
|
super().pop(key, None)
|
|
182
112
|
await self.refresh_ttl_if_needed()
|
|
183
113
|
|
|
184
114
|
if result is None:
|
|
185
|
-
# Key doesn't exist in Redis
|
|
186
115
|
return default
|
|
187
116
|
|
|
188
117
|
return self._adapter.validate_python(
|
|
@@ -190,8 +119,9 @@ class RedisDict(dict[str, T], GenericRedisType, Generic[T]):
|
|
|
190
119
|
)[key]
|
|
191
120
|
|
|
192
121
|
async def apopitem(self):
|
|
193
|
-
|
|
194
|
-
|
|
122
|
+
result = await arun_sha(
|
|
123
|
+
self.client, DICT_POPITEM_SCRIPT_NAME, 1, self.key, self.json_path
|
|
124
|
+
)
|
|
195
125
|
await self.refresh_ttl_if_needed()
|
|
196
126
|
|
|
197
127
|
if result is not None:
|
rapyer/types/integer.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from typing import TypeAlias, TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from redis.commands.search.field import NumericField
|
|
4
|
-
from typing_extensions import deprecated
|
|
5
4
|
|
|
6
5
|
from rapyer.scripts import (
|
|
7
6
|
run_sha,
|
|
@@ -20,12 +19,6 @@ class RedisInt(int, RedisType):
|
|
|
20
19
|
def redis_schema(cls, field_name: str):
|
|
21
20
|
return NumericField(f"$.{field_name}", as_name=field_name)
|
|
22
21
|
|
|
23
|
-
@deprecated(
|
|
24
|
-
f"increase function is deprecated and will become sync function in rapyer 1.2.0, use aincrease() instead"
|
|
25
|
-
)
|
|
26
|
-
async def increase(self, amount: int = 1):
|
|
27
|
-
return await self.aincrease(amount)
|
|
28
|
-
|
|
29
22
|
async def aincrease(self, amount: int = 1):
|
|
30
23
|
result = await self.client.json().numincrby(self.key, self.json_path, amount)
|
|
31
24
|
await self.refresh_ttl_if_needed()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rapyer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Pydantic models with Redis as the backend
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: redis,redis-json,pydantic,pydantic-v2,orm,database,async,nosql,cache,key-value,data-modeling,python,backend,storage,serialization,validation
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
rapyer/__init__.py,sha256=
|
|
2
|
-
rapyer/base.py,sha256=
|
|
1
|
+
rapyer/__init__.py,sha256=vgrOQkqvf0UBQMPpWSLvd9TxXXCQ8Oyt0g5E6nkhbPY,415
|
|
2
|
+
rapyer/base.py,sha256=KeaRO5btbeV95CNeJHkS18Q5jmAkyxa11Ttsu0V5C-4,27004
|
|
3
3
|
rapyer/config.py,sha256=Js1FGfyKfS16eOTgsPEhFfi1yTM_UkR8v2z_Vv6C3Bo,978
|
|
4
4
|
rapyer/context.py,sha256=yuD_EGZB04gJv9YDlqeo7VD70J0Ldx5tE3NEGTcNdwA,357
|
|
5
5
|
rapyer/errors/__init__.py,sha256=P-DIYe85wySkCpuyWufwF7zOOOL8NUiMaHZROsciG1M,530
|
|
@@ -11,12 +11,15 @@ rapyer/fields/key.py,sha256=JDE82nhSk7VPkKanNI8M5Djqm2Kocv5ENgtN_TqsmBk,554
|
|
|
11
11
|
rapyer/fields/safe_load.py,sha256=xS3PwOe9K2D7az-ctNV798cTSIb7bcviWHQ1kL1tbBo,599
|
|
12
12
|
rapyer/init.py,sha256=R6xncUnBeq07gtM_iWqH4SHLquH7MNQgTbVbC6xIA6U,2133
|
|
13
13
|
rapyer/links.py,sha256=A0usszwvwXI7FqwTEYquGYrqyNQqT4HUWhqmLteGXWU,121
|
|
14
|
-
rapyer/scripts/__init__.py,sha256=
|
|
15
|
-
rapyer/scripts/constants.py,sha256=
|
|
16
|
-
rapyer/scripts/loader.py,sha256=
|
|
14
|
+
rapyer/scripts/__init__.py,sha256=FvTJvulDxVnVqc8JK4GmHXeS-lMis4_cudaQYe7_VKg,1130
|
|
15
|
+
rapyer/scripts/constants.py,sha256=TMbBxw3Kufsmn_tWxpfzTVIdS89cHOreo2Z-RbY8koc,452
|
|
16
|
+
rapyer/scripts/loader.py,sha256=9aYRIwnmZ60lLToLKLK07vLBnWARsTkoBZQnrnjLVHw,1827
|
|
17
17
|
rapyer/scripts/lua/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
rapyer/scripts/lua/datetime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
rapyer/scripts/lua/datetime/add.lua,sha256=wMhBkAr9KoDRtDR0qG0ofA8BENO1Ykr7f437kVLIUww,1536
|
|
20
|
+
rapyer/scripts/lua/dict/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
rapyer/scripts/lua/dict/pop.lua,sha256=653j4Ty7jwvX7q2Cr6sDbCrjIUaIdhoUvNkArUBlBM0,324
|
|
22
|
+
rapyer/scripts/lua/dict/popitem.lua,sha256=3GT1eanvRmz8kuoHjRBMRcd029i2onPI7qSZL7Zm2hE,524
|
|
20
23
|
rapyer/scripts/lua/list/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
24
|
rapyer/scripts/lua/list/remove_range.lua,sha256=sH9ySbvfUtMAktt741uyX6O3yoJQbKqmr0QiFQT9vsU,816
|
|
22
25
|
rapyer/scripts/lua/numeric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -29,16 +32,16 @@ rapyer/scripts/lua/numeric/truediv.lua,sha256=lQv6YuPb7YHsg50x7b_zDuQ3S1Oa3oeydO
|
|
|
29
32
|
rapyer/scripts/lua/string/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
33
|
rapyer/scripts/lua/string/append.lua,sha256=JfSt21qCQzIMB_0dLmN58s9MFBDaOkiA8dBSBDO77bU,310
|
|
31
34
|
rapyer/scripts/lua/string/mul.lua,sha256=rABORn6gxXobY44S1JYeLnLPeTZu26otemB0s6tBfx8,328
|
|
32
|
-
rapyer/scripts/registry.py,sha256=
|
|
35
|
+
rapyer/scripts/registry.py,sha256=RWGJdye4_UsirvsFM5gfOjBtMI-6Hx6PebcJmSs2N8k,2473
|
|
33
36
|
rapyer/types/__init__.py,sha256=lM2ZdpkgWW_7eh0AXoabtgA9CGhzWUHOi9W0_RTqu6I,484
|
|
34
|
-
rapyer/types/base.py,sha256=
|
|
37
|
+
rapyer/types/base.py,sha256=AA7E0knCy9dJd78O0APG5FEUXvHSqWknzkp3Rpj1O30,6034
|
|
35
38
|
rapyer/types/byte.py,sha256=cnV-XMMPKv03tV0oKSJ1mlrpwdAygs4H_EhOVheu-2Q,1791
|
|
36
39
|
rapyer/types/convert.py,sha256=nyElDMWaWXtI_dXpAAg5elqWo439HsZ6u6wLj9WuHsI,3548
|
|
37
40
|
rapyer/types/datetime.py,sha256=EuT6XhFPfQWNtGRd_8bX3DWm6_EGo-HqMef9Fr3fiNQ,4294
|
|
38
|
-
rapyer/types/dct.py,sha256
|
|
41
|
+
rapyer/types/dct.py,sha256=-PKZoQuBXrT7wcHlss-Oc3mQqAQq2buI8aqrSoN5F7U,6431
|
|
39
42
|
rapyer/types/float.py,sha256=4TEKOJ778MFogULlfRq8iQV9bYdx-eArgRTtmeRzeP4,2864
|
|
40
43
|
rapyer/types/init.py,sha256=SGH2uH9dIkWfaTMdiD-YV053nwRPaxUs4jTG1gOA2dg,495
|
|
41
|
-
rapyer/types/integer.py,sha256=
|
|
44
|
+
rapyer/types/integer.py,sha256=TvpFehA4LNvYWapaOph9izkWuf9eAAKqkPicjsKaz9s,2381
|
|
42
45
|
rapyer/types/lst.py,sha256=RJ_IE2ZCE-NzfLRhYw2oTHy1zp_Kbx3u-wMYYOfU71Y,7160
|
|
43
46
|
rapyer/types/string.py,sha256=2JrNSF6OaodPg6e4VK5qqJYU2c-er7M6jVD0Gr8CAUk,1024
|
|
44
47
|
rapyer/typing_support.py,sha256=48ka9BxDSUdqYBQdFxxncWFOp3kdKqzVe7RXf4k-nb4,204
|
|
@@ -47,6 +50,6 @@ rapyer/utils/annotation.py,sha256=MB01l2k9g10AOSfttVfDIUc7JBzZWdH_Cn9FDe0IEGo,30
|
|
|
47
50
|
rapyer/utils/fields.py,sha256=LhIhnuRBNrtE3RyFI-AiScXTzqYmOfcm-rdtmcdaHM0,2815
|
|
48
51
|
rapyer/utils/pythonic.py,sha256=Xiv7RLqLozgLuwZSPIxBlsypIyoIYmPG_lV78TI4r80,141
|
|
49
52
|
rapyer/utils/redis.py,sha256=d4qhR7QsER0572NMis_ATUUC17Jo4h9r8TKHTF8OIds,441
|
|
50
|
-
rapyer-1.
|
|
51
|
-
rapyer-1.
|
|
52
|
-
rapyer-1.
|
|
53
|
+
rapyer-1.2.0.dist-info/METADATA,sha256=nYKTAQnSHNKFqU1x-RwLtYP3v93v-CSYinIZkabg1uI,11418
|
|
54
|
+
rapyer-1.2.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
55
|
+
rapyer-1.2.0.dist-info/RECORD,,
|
|
File without changes
|