archipy-redis 5.11.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.
- archipy_redis-5.11.0/.gitignore +245 -0
- archipy_redis-5.11.0/PKG-INFO +20 -0
- archipy_redis-5.11.0/README.md +9 -0
- archipy_redis-5.11.0/archipy/adapters/redis/__init__.py +1 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/__init__.py +59 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/_shared.py +102 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/arrays.py +151 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/cluster.py +97 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/connection.py +617 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/hashes.py +306 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/keys.py +581 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/lists.py +244 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/pubsub.py +94 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/sets.py +317 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapter_mixins/sorted_sets.py +563 -0
- archipy_redis-5.11.0/archipy/adapters/redis/adapters.py +74 -0
- archipy_redis-5.11.0/archipy/adapters/redis/mocks.py +195 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/__init__.py +52 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/arrays.py +187 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/cluster.py +133 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/connection.py +202 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/hashes.py +315 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/keys.py +690 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/lists.py +273 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/pubsub.py +110 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/sets.py +331 -0
- archipy_redis-5.11.0/archipy/adapters/redis/port_mixins/sorted_sets.py +643 -0
- archipy_redis-5.11.0/archipy/adapters/redis/ports.py +66 -0
- archipy_redis-5.11.0/archipy/adapters/redis/search.py +906 -0
- archipy_redis-5.11.0/archipy/adapters/redis/search_ports.py +226 -0
- archipy_redis-5.11.0/pyproject.toml +24 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
### JetBrains template
|
|
2
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
3
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
4
|
+
.idea
|
|
5
|
+
# User-specific stuff
|
|
6
|
+
.idea/**/workspace.xml
|
|
7
|
+
.idea/**/tasks.xml
|
|
8
|
+
.idea/**/usage.statistics.xml
|
|
9
|
+
.idea/**/dictionaries
|
|
10
|
+
.idea/**/shelf
|
|
11
|
+
dist
|
|
12
|
+
# AWS User-specific
|
|
13
|
+
.idea/**/aws.xml
|
|
14
|
+
|
|
15
|
+
# Generated files
|
|
16
|
+
.idea/**/contentModel.xml
|
|
17
|
+
|
|
18
|
+
# Sensitive or high-churn files
|
|
19
|
+
.idea/**/dataSources/
|
|
20
|
+
.idea/**/dataSources.ids
|
|
21
|
+
.idea/**/dataSources.local.xml
|
|
22
|
+
.idea/**/sqlDataSources.xml
|
|
23
|
+
.idea/**/dynamic.xml
|
|
24
|
+
.idea/**/uiDesigner.xml
|
|
25
|
+
.idea/**/dbnavigator.xml
|
|
26
|
+
|
|
27
|
+
# Gradle
|
|
28
|
+
.idea/**/gradle.xml
|
|
29
|
+
.idea/**/libraries
|
|
30
|
+
|
|
31
|
+
# Gradle and Maven with auto-import
|
|
32
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
33
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
34
|
+
# auto-import.
|
|
35
|
+
# .idea/artifacts
|
|
36
|
+
# .idea/compiler.xml
|
|
37
|
+
# .idea/jarRepositories.xml
|
|
38
|
+
# .idea/modules.xml
|
|
39
|
+
# .idea/*.iml
|
|
40
|
+
# .idea/modules
|
|
41
|
+
# *.iml
|
|
42
|
+
# *.ipr
|
|
43
|
+
|
|
44
|
+
# CMake
|
|
45
|
+
cmake-build-*/
|
|
46
|
+
|
|
47
|
+
# Mongo Explorer plugin
|
|
48
|
+
.idea/**/mongoSettings.xml
|
|
49
|
+
|
|
50
|
+
# File-based project format
|
|
51
|
+
*.iws
|
|
52
|
+
|
|
53
|
+
# IntelliJ
|
|
54
|
+
out/
|
|
55
|
+
|
|
56
|
+
# mpeltonen/sbt-idea plugin
|
|
57
|
+
.idea_modules/
|
|
58
|
+
|
|
59
|
+
# JIRA plugin
|
|
60
|
+
atlassian-ide-plugin.xml
|
|
61
|
+
|
|
62
|
+
# Cursive Clojure plugin
|
|
63
|
+
.idea/replstate.xml
|
|
64
|
+
|
|
65
|
+
# SonarLint plugin
|
|
66
|
+
.idea/sonarlint/
|
|
67
|
+
|
|
68
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
69
|
+
com_crashlytics_export_strings.xml
|
|
70
|
+
crashlytics.properties
|
|
71
|
+
crashlytics-build.properties
|
|
72
|
+
fabric.properties
|
|
73
|
+
|
|
74
|
+
# Editor-based Rest Client
|
|
75
|
+
.idea/httpRequests
|
|
76
|
+
|
|
77
|
+
# Android studio 3.1+ serialized cache file
|
|
78
|
+
.idea/caches/build_file_checksums.ser
|
|
79
|
+
|
|
80
|
+
### Python template
|
|
81
|
+
# Byte-compiled / optimized / DLL files
|
|
82
|
+
__pycache__/
|
|
83
|
+
*.py[cod]
|
|
84
|
+
*$py.class
|
|
85
|
+
|
|
86
|
+
# C extensions
|
|
87
|
+
*.so
|
|
88
|
+
|
|
89
|
+
# Distribution / packaging
|
|
90
|
+
.Python
|
|
91
|
+
build/
|
|
92
|
+
develop-eggs/
|
|
93
|
+
dist/
|
|
94
|
+
downloads/
|
|
95
|
+
eggs/
|
|
96
|
+
.eggs/
|
|
97
|
+
lib/
|
|
98
|
+
lib64/
|
|
99
|
+
parts/
|
|
100
|
+
sdist/
|
|
101
|
+
var/
|
|
102
|
+
wheels/
|
|
103
|
+
share/python-wheels/
|
|
104
|
+
*.egg-info/
|
|
105
|
+
.installed.cfg
|
|
106
|
+
*.egg
|
|
107
|
+
MANIFEST
|
|
108
|
+
|
|
109
|
+
# PyInstaller
|
|
110
|
+
# Usually these files are written by a python script from a template
|
|
111
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
112
|
+
*.manifest
|
|
113
|
+
*.spec
|
|
114
|
+
|
|
115
|
+
# Installer logs
|
|
116
|
+
pip-log.txt
|
|
117
|
+
pip-delete-this-directory.txt
|
|
118
|
+
|
|
119
|
+
# Unit test / coverage reports
|
|
120
|
+
htmlcov/
|
|
121
|
+
.tox/
|
|
122
|
+
.nox/
|
|
123
|
+
.coverage
|
|
124
|
+
.coverage.*
|
|
125
|
+
.cache
|
|
126
|
+
nosetests.xml
|
|
127
|
+
coverage.xml
|
|
128
|
+
*.cover
|
|
129
|
+
*.py,cover
|
|
130
|
+
.hypothesis/
|
|
131
|
+
.pytest_cache/
|
|
132
|
+
cover/
|
|
133
|
+
|
|
134
|
+
# Translations
|
|
135
|
+
*.mo
|
|
136
|
+
*.pot
|
|
137
|
+
|
|
138
|
+
# Django stuff:
|
|
139
|
+
*.log
|
|
140
|
+
local_settings.py
|
|
141
|
+
db.sqlite3
|
|
142
|
+
db.sqlite3-journal
|
|
143
|
+
|
|
144
|
+
# Flask stuff:
|
|
145
|
+
instance/
|
|
146
|
+
.webassets-cache
|
|
147
|
+
|
|
148
|
+
# Scrapy stuff:
|
|
149
|
+
.scrapy
|
|
150
|
+
|
|
151
|
+
# Sphinx documentation
|
|
152
|
+
docs/_build/
|
|
153
|
+
|
|
154
|
+
# PyBuilder
|
|
155
|
+
.pybuilder/
|
|
156
|
+
target/
|
|
157
|
+
|
|
158
|
+
# Jupyter Notebook
|
|
159
|
+
.ipynb_checkpoints
|
|
160
|
+
|
|
161
|
+
# IPython
|
|
162
|
+
profile_default/
|
|
163
|
+
ipython_config.py
|
|
164
|
+
|
|
165
|
+
# pyenv
|
|
166
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
167
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
168
|
+
# .python-version
|
|
169
|
+
|
|
170
|
+
# pipenv
|
|
171
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
172
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
173
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
174
|
+
# install all needed dependencies.
|
|
175
|
+
#Pipfile.lock
|
|
176
|
+
|
|
177
|
+
# poetry
|
|
178
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
179
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
180
|
+
# commonly ignored for libraries.
|
|
181
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
182
|
+
#poetry.lock
|
|
183
|
+
|
|
184
|
+
# pdm
|
|
185
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
186
|
+
#pdm.lock
|
|
187
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
188
|
+
# in version control.
|
|
189
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
190
|
+
.pdm.toml
|
|
191
|
+
|
|
192
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
193
|
+
__pypackages__/
|
|
194
|
+
|
|
195
|
+
# Celery stuff
|
|
196
|
+
celerybeat-schedule
|
|
197
|
+
celerybeat.pid
|
|
198
|
+
|
|
199
|
+
# SageMath parsed files
|
|
200
|
+
*.sage.py
|
|
201
|
+
|
|
202
|
+
# Environments
|
|
203
|
+
.env
|
|
204
|
+
.venv
|
|
205
|
+
env/
|
|
206
|
+
venv/
|
|
207
|
+
ENV/
|
|
208
|
+
env.bak/
|
|
209
|
+
venv.bak/
|
|
210
|
+
|
|
211
|
+
# Spyder project settings
|
|
212
|
+
.spyderproject
|
|
213
|
+
.spyproject
|
|
214
|
+
|
|
215
|
+
# Rope project settings
|
|
216
|
+
.ropeproject
|
|
217
|
+
|
|
218
|
+
# mkdocs documentation
|
|
219
|
+
/site
|
|
220
|
+
|
|
221
|
+
# mypy
|
|
222
|
+
.mypy_cache/
|
|
223
|
+
.dmypy.json
|
|
224
|
+
dmypy.json
|
|
225
|
+
|
|
226
|
+
# Pyre type checker
|
|
227
|
+
.pyre/
|
|
228
|
+
|
|
229
|
+
# pytype static type analyzer
|
|
230
|
+
.pytype/
|
|
231
|
+
|
|
232
|
+
# Cython debug symbols
|
|
233
|
+
cython_debug/
|
|
234
|
+
|
|
235
|
+
# PyCharm
|
|
236
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
237
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
238
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
239
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
240
|
+
#.idea/
|
|
241
|
+
|
|
242
|
+
.ruff_cache
|
|
243
|
+
bandit-report.json
|
|
244
|
+
|
|
245
|
+
.gitattributes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: archipy-redis
|
|
3
|
+
Version: 5.11.0
|
|
4
|
+
Summary: ArchiPy Redis adapter.
|
|
5
|
+
Author-email: Hossein Nejati <hosseinnejati14@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Requires-Python: <4,>=3.14
|
|
8
|
+
Requires-Dist: archipy==5.11.0
|
|
9
|
+
Requires-Dist: redis[hiredis]>=8.1.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# archipy-redis
|
|
13
|
+
|
|
14
|
+
Redis adapter for [ArchiPy](https://syntaxarc.github.io/ArchiPy/). Install via the core extra:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv add "archipy[redis]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Imports stay the same: `from archipy.adapters.redis.adapters import RedisAdapter`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Redis adapters for ArchiPy."""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Redis adapter mixins package."""
|
|
2
|
+
|
|
3
|
+
from archipy.adapters.redis.adapter_mixins.arrays import (
|
|
4
|
+
AsyncRedisArraysMixin,
|
|
5
|
+
RedisArraysMixin,
|
|
6
|
+
)
|
|
7
|
+
from archipy.adapters.redis.adapter_mixins.cluster import (
|
|
8
|
+
AsyncRedisClusterMixin,
|
|
9
|
+
RedisClusterMixin,
|
|
10
|
+
)
|
|
11
|
+
from archipy.adapters.redis.adapter_mixins.connection import (
|
|
12
|
+
AsyncRedisConnectionMixin,
|
|
13
|
+
RedisConnectionMixin,
|
|
14
|
+
)
|
|
15
|
+
from archipy.adapters.redis.adapter_mixins.hashes import (
|
|
16
|
+
AsyncRedisHashesMixin,
|
|
17
|
+
RedisHashesMixin,
|
|
18
|
+
)
|
|
19
|
+
from archipy.adapters.redis.adapter_mixins.keys import (
|
|
20
|
+
AsyncRedisKeysMixin,
|
|
21
|
+
RedisKeysMixin,
|
|
22
|
+
)
|
|
23
|
+
from archipy.adapters.redis.adapter_mixins.lists import (
|
|
24
|
+
AsyncRedisListsMixin,
|
|
25
|
+
RedisListsMixin,
|
|
26
|
+
)
|
|
27
|
+
from archipy.adapters.redis.adapter_mixins.pubsub import (
|
|
28
|
+
AsyncRedisPubSubMixin,
|
|
29
|
+
RedisPubSubMixin,
|
|
30
|
+
)
|
|
31
|
+
from archipy.adapters.redis.adapter_mixins.sets import (
|
|
32
|
+
AsyncRedisSetsMixin,
|
|
33
|
+
RedisSetsMixin,
|
|
34
|
+
)
|
|
35
|
+
from archipy.adapters.redis.adapter_mixins.sorted_sets import (
|
|
36
|
+
AsyncRedisSortedSetsMixin,
|
|
37
|
+
RedisSortedSetsMixin,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AsyncRedisArraysMixin",
|
|
42
|
+
"AsyncRedisClusterMixin",
|
|
43
|
+
"AsyncRedisConnectionMixin",
|
|
44
|
+
"AsyncRedisHashesMixin",
|
|
45
|
+
"AsyncRedisKeysMixin",
|
|
46
|
+
"AsyncRedisListsMixin",
|
|
47
|
+
"AsyncRedisPubSubMixin",
|
|
48
|
+
"AsyncRedisSetsMixin",
|
|
49
|
+
"AsyncRedisSortedSetsMixin",
|
|
50
|
+
"RedisArraysMixin",
|
|
51
|
+
"RedisClusterMixin",
|
|
52
|
+
"RedisConnectionMixin",
|
|
53
|
+
"RedisHashesMixin",
|
|
54
|
+
"RedisKeysMixin",
|
|
55
|
+
"RedisListsMixin",
|
|
56
|
+
"RedisPubSubMixin",
|
|
57
|
+
"RedisSetsMixin",
|
|
58
|
+
"RedisSortedSetsMixin",
|
|
59
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Shared helpers and attribute bases for Redis adapter mixins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Iterable, Mapping
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from redis import RedisCluster
|
|
10
|
+
from redis.asyncio import RedisCluster as AsyncRedisCluster
|
|
11
|
+
from redis.asyncio.client import Redis as AsyncRedis
|
|
12
|
+
from redis.client import Redis
|
|
13
|
+
|
|
14
|
+
from archipy.configs.config_template import RedisConfig
|
|
15
|
+
|
|
16
|
+
_set = set
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyncRedisMixinBase:
|
|
20
|
+
"""Shared attribute/method declarations for sync Redis adapter mixins."""
|
|
21
|
+
|
|
22
|
+
client: Redis | RedisCluster
|
|
23
|
+
read_only_client: Redis | RedisCluster
|
|
24
|
+
_configs: RedisConfig
|
|
25
|
+
_search_client: Redis | RedisCluster | None
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def _ensure_sync_int(value: int | Awaitable[int]) -> int:
|
|
29
|
+
"""Coerce sync Redis int responses that may be typed as Awaitable."""
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AsyncRedisMixinBase:
|
|
34
|
+
"""Shared attribute/method declarations for async Redis adapter mixins."""
|
|
35
|
+
|
|
36
|
+
client: AsyncRedis | AsyncRedisCluster
|
|
37
|
+
read_only_client: AsyncRedis | AsyncRedisCluster
|
|
38
|
+
_configs: RedisConfig
|
|
39
|
+
_search_client: AsyncRedis | AsyncRedisCluster | None
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
async def _ensure_async_int(value: int | Awaitable[int]) -> int:
|
|
43
|
+
"""Coerce async Redis int responses that may be typed as Awaitable."""
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
async def _ensure_async_bool(value: bool | Awaitable[bool]) -> bool:
|
|
48
|
+
"""Coerce async Redis bool responses that may be typed as Awaitable."""
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
async def _ensure_async_str(
|
|
53
|
+
value: str | bytes | Awaitable[str | bytes | None] | None,
|
|
54
|
+
) -> str | None:
|
|
55
|
+
"""Coerce async Redis string responses that may be typed as Awaitable."""
|
|
56
|
+
raise NotImplementedError
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
async def _ensure_async_list(value: list[Any] | Awaitable[list[Any]]) -> list[Any]:
|
|
60
|
+
"""Coerce async Redis list responses that may be typed as Awaitable."""
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _redis_connection_kwargs(configs: RedisConfig, *, decode_responses: bool | None = None) -> dict[str, Any]:
|
|
65
|
+
"""Build common Redis client connection kwargs from config."""
|
|
66
|
+
return {
|
|
67
|
+
"password": configs.PASSWORD,
|
|
68
|
+
"decode_responses": (configs.DECODE_RESPONSES if decode_responses is None else decode_responses),
|
|
69
|
+
"health_check_interval": configs.HEALTH_CHECK_INTERVAL,
|
|
70
|
+
"max_connections": configs.MAX_CONNECTIONS,
|
|
71
|
+
"socket_connect_timeout": configs.SOCKET_CONNECT_TIMEOUT,
|
|
72
|
+
"socket_timeout": configs.SOCKET_TIMEOUT,
|
|
73
|
+
"protocol": configs.PROTOCOL,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _sentinel_redis_kwargs(configs: RedisConfig) -> dict[str, Any]:
|
|
78
|
+
"""Build Redis kwargs for sentinel master_for/slave_for connections."""
|
|
79
|
+
return {
|
|
80
|
+
"socket_timeout": configs.SOCKET_TIMEOUT,
|
|
81
|
+
"socket_connect_timeout": configs.SOCKET_CONNECT_TIMEOUT,
|
|
82
|
+
"max_connections": configs.MAX_CONNECTIONS,
|
|
83
|
+
"password": configs.PASSWORD,
|
|
84
|
+
"decode_responses": configs.DECODE_RESPONSES,
|
|
85
|
+
"protocol": configs.PROTOCOL,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _sentinel_kwargs(configs: RedisConfig) -> dict[str, str] | None:
|
|
90
|
+
"""Build sentinel-node auth kwargs when SENTINEL_PASSWORD is set."""
|
|
91
|
+
if configs.SENTINEL_PASSWORD:
|
|
92
|
+
return {"password": configs.SENTINEL_PASSWORD}
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _normalize_zset_keys(
|
|
97
|
+
keys: Mapping[bytes | str, float] | Iterable[bytes | str],
|
|
98
|
+
) -> dict[str, float] | list[str]:
|
|
99
|
+
"""Normalize sorted-set keys for zunion/zinter into a form the Redis client accepts."""
|
|
100
|
+
if isinstance(keys, Mapping):
|
|
101
|
+
return {str(key): float(value) for key, value in keys.items()}
|
|
102
|
+
return [str(key) for key in keys]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Redis adapter mixins for arrays operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable
|
|
6
|
+
|
|
7
|
+
from archipy.adapters.redis.adapter_mixins._shared import (
|
|
8
|
+
AsyncRedisMixinBase,
|
|
9
|
+
SyncRedisMixinBase,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RedisArraysMixin(SyncRedisMixinBase):
|
|
14
|
+
"""Sync Redis mixin for arrays operations."""
|
|
15
|
+
|
|
16
|
+
def arset(self, name: bytes | str, index: int, *values: bytes | str | float) -> int:
|
|
17
|
+
"""Set one or more contiguous values in an array.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name (bytes | str): The key of the array.
|
|
21
|
+
index (int): The starting index to set values at.
|
|
22
|
+
*values (bytes | str | float): Values to store at consecutive indices.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
RedisResponseType: The number of previously empty slots that were set.
|
|
26
|
+
"""
|
|
27
|
+
result = self.client.arset(name, index, *values)
|
|
28
|
+
return self._ensure_sync_int(result)
|
|
29
|
+
|
|
30
|
+
def arget(self, name: bytes | str, index: int) -> bytes | str | None:
|
|
31
|
+
"""Get the value at an index in an array.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name (bytes | str): The key of the array.
|
|
35
|
+
index (int): The index to read.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
RedisResponseType: The value at the index, or None if unset.
|
|
39
|
+
"""
|
|
40
|
+
return self.read_only_client.arget(name, index)
|
|
41
|
+
|
|
42
|
+
def arlen(self, name: bytes | str) -> int:
|
|
43
|
+
"""Get the number of populated elements in an array.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
name (bytes | str): The key of the array.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
RedisResponseType: The number of populated elements.
|
|
50
|
+
"""
|
|
51
|
+
result = self.read_only_client.arlen(name)
|
|
52
|
+
return self._ensure_sync_int(result)
|
|
53
|
+
|
|
54
|
+
def ardel(self, name: bytes | str, *indices: int) -> int:
|
|
55
|
+
"""Delete one or more indices from an array.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name (bytes | str): The key of the array.
|
|
59
|
+
*indices (int): Indices to delete.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
RedisResponseType: The number of elements deleted.
|
|
63
|
+
"""
|
|
64
|
+
result = self.client.ardel(name, *indices)
|
|
65
|
+
return self._ensure_sync_int(result)
|
|
66
|
+
|
|
67
|
+
def arring(self, name: bytes | str, size: int, *values: bytes | str | float) -> int:
|
|
68
|
+
"""Insert values into an array as a fixed-size ring buffer.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
name (bytes | str): The key of the array.
|
|
72
|
+
size (int): The fixed size of the ring buffer.
|
|
73
|
+
*values (bytes | str | float): Values to insert.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
RedisResponseType: The last index where a value was inserted.
|
|
77
|
+
"""
|
|
78
|
+
result = self.client.arring(name, size, *values)
|
|
79
|
+
return self._ensure_sync_int(result)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AsyncRedisArraysMixin(AsyncRedisMixinBase):
|
|
83
|
+
"""Async Redis mixin for arrays operations."""
|
|
84
|
+
|
|
85
|
+
async def arset(self, name: bytes | str, index: int, *values: bytes | str | float) -> int:
|
|
86
|
+
"""Set one or more contiguous values in an array asynchronously.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
name (bytes | str): The key of the array.
|
|
90
|
+
index (int): The starting index to set values at.
|
|
91
|
+
*values (bytes | str | float): Values to store at consecutive indices.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
RedisResponseType: The number of previously empty slots that were set.
|
|
95
|
+
"""
|
|
96
|
+
result = self.client.arset(name, index, *values)
|
|
97
|
+
return await self._ensure_async_int(result)
|
|
98
|
+
|
|
99
|
+
async def arget(self, name: bytes | str, index: int) -> bytes | str | None:
|
|
100
|
+
"""Get the value at an index in an array asynchronously.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
name (bytes | str): The key of the array.
|
|
104
|
+
index (int): The index to read.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
RedisResponseType: The value at the index, or None if unset.
|
|
108
|
+
"""
|
|
109
|
+
result = self.read_only_client.arget(name, index)
|
|
110
|
+
if isinstance(result, Awaitable):
|
|
111
|
+
return await result
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
async def arlen(self, name: bytes | str) -> int:
|
|
115
|
+
"""Get the number of populated elements in an array asynchronously.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
name (bytes | str): The key of the array.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
RedisResponseType: The number of populated elements.
|
|
122
|
+
"""
|
|
123
|
+
result = self.read_only_client.arlen(name)
|
|
124
|
+
return await self._ensure_async_int(result)
|
|
125
|
+
|
|
126
|
+
async def ardel(self, name: bytes | str, *indices: int) -> int:
|
|
127
|
+
"""Delete one or more indices from an array asynchronously.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
name (bytes | str): The key of the array.
|
|
131
|
+
*indices (int): Indices to delete.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
RedisResponseType: The number of elements deleted.
|
|
135
|
+
"""
|
|
136
|
+
result = self.client.ardel(name, *indices)
|
|
137
|
+
return await self._ensure_async_int(result)
|
|
138
|
+
|
|
139
|
+
async def arring(self, name: bytes | str, size: int, *values: bytes | str | float) -> int:
|
|
140
|
+
"""Insert values into an array as a fixed-size ring buffer asynchronously.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
name (bytes | str): The key of the array.
|
|
144
|
+
size (int): The fixed size of the ring buffer.
|
|
145
|
+
*values (bytes | str | float): Values to insert.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
RedisResponseType: The last index where a value was inserted.
|
|
149
|
+
"""
|
|
150
|
+
result = self.client.arring(name, size, *values)
|
|
151
|
+
return await self._ensure_async_int(result)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Redis adapter mixins for cluster operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from redis import RedisCluster
|
|
8
|
+
from redis.asyncio import RedisCluster as AsyncRedisCluster
|
|
9
|
+
|
|
10
|
+
from archipy.adapters.redis.adapter_mixins._shared import (
|
|
11
|
+
AsyncRedisMixinBase,
|
|
12
|
+
SyncRedisMixinBase,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RedisClusterMixin(SyncRedisMixinBase):
|
|
17
|
+
"""Sync Redis mixin for cluster operations."""
|
|
18
|
+
|
|
19
|
+
def cluster_info(self) -> dict[str, str] | None:
|
|
20
|
+
"""Get cluster information."""
|
|
21
|
+
if isinstance(self.client, RedisCluster):
|
|
22
|
+
return self.client.cluster_info()
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
def cluster_nodes(
|
|
26
|
+
self,
|
|
27
|
+
) -> dict[str, dict[str, str | bool | list[list[str]] | list[dict[str, str]]]] | None:
|
|
28
|
+
"""Get cluster nodes information."""
|
|
29
|
+
if isinstance(self.client, RedisCluster):
|
|
30
|
+
return self.client.cluster_nodes()
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def cluster_slots(self) -> list[Any] | None:
|
|
34
|
+
"""Get cluster slots mapping."""
|
|
35
|
+
if isinstance(self.client, RedisCluster):
|
|
36
|
+
return self.client.cluster_slots()
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def cluster_key_slot(self, key: str) -> int | None:
|
|
40
|
+
"""Get the hash slot for a key."""
|
|
41
|
+
if isinstance(self.client, RedisCluster):
|
|
42
|
+
return self.client.cluster_keyslot(key)
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def cluster_count_keys_in_slot(self, slot: int) -> int | None:
|
|
46
|
+
"""Count keys in a specific slot."""
|
|
47
|
+
if isinstance(self.client, RedisCluster):
|
|
48
|
+
return self.client.cluster_countkeysinslot(slot)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def cluster_get_keys_in_slot(self, slot: int, count: int) -> list[bytes | str] | None:
|
|
52
|
+
"""Get keys in a specific slot."""
|
|
53
|
+
if isinstance(self.client, RedisCluster):
|
|
54
|
+
return self.client.cluster_get_keys_in_slot(slot, count)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AsyncRedisClusterMixin(AsyncRedisMixinBase):
|
|
59
|
+
"""Async Redis mixin for cluster operations."""
|
|
60
|
+
|
|
61
|
+
async def cluster_info(self) -> dict[str, str] | None:
|
|
62
|
+
"""Get cluster information asynchronously."""
|
|
63
|
+
if isinstance(self.client, AsyncRedisCluster):
|
|
64
|
+
return await self.client.cluster_info()
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
async def cluster_nodes(
|
|
68
|
+
self,
|
|
69
|
+
) -> dict[str, dict[str, str | bool | list[list[str]] | list[dict[str, str]]]] | None:
|
|
70
|
+
"""Get cluster nodes information asynchronously."""
|
|
71
|
+
if isinstance(self.client, AsyncRedisCluster):
|
|
72
|
+
return await self.client.cluster_nodes()
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
async def cluster_slots(self) -> list[Any] | None:
|
|
76
|
+
"""Get cluster slots mapping asynchronously."""
|
|
77
|
+
if isinstance(self.client, AsyncRedisCluster):
|
|
78
|
+
return await self.client.cluster_slots()
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
async def cluster_key_slot(self, key: str) -> int | None:
|
|
82
|
+
"""Get the hash slot for a key asynchronously."""
|
|
83
|
+
if isinstance(self.client, AsyncRedisCluster):
|
|
84
|
+
return await self.client.cluster_keyslot(key)
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
async def cluster_count_keys_in_slot(self, slot: int) -> int | None:
|
|
88
|
+
"""Count keys in a specific slot asynchronously."""
|
|
89
|
+
if isinstance(self.client, AsyncRedisCluster):
|
|
90
|
+
return await self.client.cluster_countkeysinslot(slot)
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
async def cluster_get_keys_in_slot(self, slot: int, count: int) -> list[bytes | str] | None:
|
|
94
|
+
"""Get keys in a specific slot asynchronously."""
|
|
95
|
+
if isinstance(self.client, AsyncRedisCluster):
|
|
96
|
+
return await self.client.cluster_get_keys_in_slot(slot, count)
|
|
97
|
+
return None
|