redis-simplify 0.1.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.
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def _get_version():
|
|
5
|
+
try:
|
|
6
|
+
from importlib.metadata import version
|
|
7
|
+
return version("redis-simplify")
|
|
8
|
+
except Exception:
|
|
9
|
+
# Fallback: lê do pyproject.toml
|
|
10
|
+
try:
|
|
11
|
+
pyproject = Path(__file__).parent.parent / "pyproject.toml"
|
|
12
|
+
if pyproject.exists():
|
|
13
|
+
data = tomllib.loads(pyproject.read_text())
|
|
14
|
+
return data["project"]["version"]
|
|
15
|
+
except Exception:
|
|
16
|
+
pass
|
|
17
|
+
return "0.1.0" # último recurso
|
|
18
|
+
|
|
19
|
+
__version__ = _get_version()
|
redis_simplify/client.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# app/core/redis_client.py
|
|
2
|
+
import redis
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Optional, Set, List
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
logging.basicConfig(level=logging.INFO)
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class RedisClient:
|
|
12
|
+
"""Cliente Redis genérico SÍNCRONO - pode ser usado por qualquer parte do sistema"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
host: str,
|
|
17
|
+
port: int = 6379,
|
|
18
|
+
password: Optional[str] = None,
|
|
19
|
+
db: int = 0,
|
|
20
|
+
decode_responses: bool = True,
|
|
21
|
+
socket_keepalive: bool = True,
|
|
22
|
+
health_check_interval: int = 30
|
|
23
|
+
):
|
|
24
|
+
# Configuração obrigatória via parâmetros (não lê .env)
|
|
25
|
+
self.host = host
|
|
26
|
+
self.port = port
|
|
27
|
+
self.password = password
|
|
28
|
+
self.db = db
|
|
29
|
+
|
|
30
|
+
self.decode_responses = decode_responses
|
|
31
|
+
self.socket_keepalive = socket_keepalive
|
|
32
|
+
self.health_check_interval = health_check_interval
|
|
33
|
+
|
|
34
|
+
self.client: Optional[redis.Redis] = None
|
|
35
|
+
self._connect()
|
|
36
|
+
|
|
37
|
+
def _connect(self):
|
|
38
|
+
"""Estabelece conexão com Redis (síncrona)"""
|
|
39
|
+
try:
|
|
40
|
+
self.client = redis.Redis(
|
|
41
|
+
host=self.host,
|
|
42
|
+
port=self.port,
|
|
43
|
+
password=self.password or None,
|
|
44
|
+
db=self.db,
|
|
45
|
+
decode_responses=self.decode_responses,
|
|
46
|
+
socket_keepalive=self.socket_keepalive,
|
|
47
|
+
health_check_interval=self.health_check_interval
|
|
48
|
+
)
|
|
49
|
+
# Testa conexão
|
|
50
|
+
self.client.ping()
|
|
51
|
+
logger.info(f"RedisClient síncrono conectado: {self.host}:{self.port}")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.error(f"Erro ao conectar Redis: {e}")
|
|
54
|
+
self.client = None
|
|
55
|
+
|
|
56
|
+
def _ensure_connection(self) -> bool:
|
|
57
|
+
"""Verifica conexão e tenta reconectar se necessário"""
|
|
58
|
+
if self.client:
|
|
59
|
+
try:
|
|
60
|
+
self.client.ping()
|
|
61
|
+
return True
|
|
62
|
+
except Exception:
|
|
63
|
+
self.client = None
|
|
64
|
+
|
|
65
|
+
self._connect()
|
|
66
|
+
return self.client is not None
|
|
67
|
+
|
|
68
|
+
def ping(self) -> bool:
|
|
69
|
+
"""Testa conectividade"""
|
|
70
|
+
if not self._ensure_connection():
|
|
71
|
+
return False
|
|
72
|
+
try:
|
|
73
|
+
return self.client.ping()
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Erro no ping: {e}")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def scan(self, cursor: int = 0, match: Optional[str] = None, count: Optional[int] = None) -> tuple:
|
|
81
|
+
"""
|
|
82
|
+
Implementação do comando SCAN do Redis
|
|
83
|
+
Retorna (nova_cursor, lista_de_chaves)
|
|
84
|
+
"""
|
|
85
|
+
if not self._ensure_connection():
|
|
86
|
+
return 0, []
|
|
87
|
+
try:
|
|
88
|
+
# Redis-py retorna (cursor, keys)
|
|
89
|
+
return self.client.scan(cursor=cursor, match=match, count=count)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Erro no scan: {e}")
|
|
92
|
+
return 0, []
|
|
93
|
+
# ========== OPERAÇÕES DE STRING ==========
|
|
94
|
+
|
|
95
|
+
def set(self, key: str, value: str, expire_seconds: Optional[int] = None) -> bool:
|
|
96
|
+
"""Define valor de uma chave"""
|
|
97
|
+
if not self._ensure_connection():
|
|
98
|
+
return False
|
|
99
|
+
try:
|
|
100
|
+
if expire_seconds:
|
|
101
|
+
self.client.setex(key, expire_seconds, value)
|
|
102
|
+
else:
|
|
103
|
+
self.client.set(key, value)
|
|
104
|
+
return True
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Erro no set {key}: {e}")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def get(self, key: str) -> Optional[str]:
|
|
110
|
+
"""Obtém valor de uma chave"""
|
|
111
|
+
if not self._ensure_connection():
|
|
112
|
+
return None
|
|
113
|
+
try:
|
|
114
|
+
return self.client.get(key)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Erro no get {key}: {e}")
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def delete(self, *keys: str) -> int:
|
|
120
|
+
"""Deleta uma ou mais chaves"""
|
|
121
|
+
if not self._ensure_connection():
|
|
122
|
+
return 0
|
|
123
|
+
try:
|
|
124
|
+
return self.client.delete(*keys)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Erro no delete {keys}: {e}")
|
|
127
|
+
return 0
|
|
128
|
+
|
|
129
|
+
def exists(self, key: str) -> bool:
|
|
130
|
+
"""Verifica se chave existe"""
|
|
131
|
+
if not self._ensure_connection():
|
|
132
|
+
return False
|
|
133
|
+
try:
|
|
134
|
+
result = self.client.exists(key)
|
|
135
|
+
return bool(result)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Erro no exists {key}: {e}")
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def expire(self, key: str, seconds: int) -> bool:
|
|
141
|
+
"""Define tempo de expiração"""
|
|
142
|
+
if not self._ensure_connection():
|
|
143
|
+
return False
|
|
144
|
+
try:
|
|
145
|
+
result = self.client.expire(key, seconds)
|
|
146
|
+
return bool(result)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Erro no expire {key}: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def incr(self, key: str) -> int:
|
|
152
|
+
"""Incrementa contador"""
|
|
153
|
+
if not self._ensure_connection():
|
|
154
|
+
return 0
|
|
155
|
+
try:
|
|
156
|
+
return self.client.incr(key)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Erro no incr {key}: {e}")
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
def decr(self, key: str) -> int:
|
|
162
|
+
"""Decrementa contador"""
|
|
163
|
+
if not self._ensure_connection():
|
|
164
|
+
return 0
|
|
165
|
+
try:
|
|
166
|
+
return self.client.decr(key)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error(f"Erro no decr {key}: {e}")
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
# ========== OPERAÇÕES DE SET (CONJUNTOS) ==========
|
|
172
|
+
|
|
173
|
+
def sadd(self, key: str, *values: str) -> int:
|
|
174
|
+
"""Adiciona valores a um set"""
|
|
175
|
+
if not self._ensure_connection():
|
|
176
|
+
return 0
|
|
177
|
+
try:
|
|
178
|
+
return self.client.sadd(key, *values)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(f"Erro no sadd {key}: {e}")
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
def srem(self, key: str, *values: str) -> int:
|
|
184
|
+
"""Remove valores de um set"""
|
|
185
|
+
if not self._ensure_connection():
|
|
186
|
+
return 0
|
|
187
|
+
try:
|
|
188
|
+
return self.client.srem(key, *values)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Erro no srem {key}: {e}")
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
def smembers(self, key: str) -> Set[str]:
|
|
194
|
+
"""Retorna todos os membros de um set"""
|
|
195
|
+
if not self._ensure_connection():
|
|
196
|
+
return set()
|
|
197
|
+
try:
|
|
198
|
+
return self.client.smembers(key)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.error(f"Erro no smembers {key}: {e}")
|
|
201
|
+
return set()
|
|
202
|
+
|
|
203
|
+
def sismember(self, key: str, value: str) -> bool:
|
|
204
|
+
"""Verifica se valor está no set"""
|
|
205
|
+
if not self._ensure_connection():
|
|
206
|
+
return False
|
|
207
|
+
try:
|
|
208
|
+
result = self.client.sismember(key, value)
|
|
209
|
+
return bool(result)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"Erro no sismember {key}: {e}")
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def scard(self, key: str) -> int:
|
|
215
|
+
"""Retorna tamanho do set"""
|
|
216
|
+
if not self._ensure_connection():
|
|
217
|
+
return 0
|
|
218
|
+
try:
|
|
219
|
+
return self.client.scard(key)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Erro no scard {key}: {e}")
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
# ========== OPERAÇÕES DE HASH ==========
|
|
225
|
+
|
|
226
|
+
def hset(self, key: str, field: str, value: str) -> int:
|
|
227
|
+
"""Define campo em hash"""
|
|
228
|
+
if not self._ensure_connection():
|
|
229
|
+
return 0
|
|
230
|
+
try:
|
|
231
|
+
return self.client.hset(key, field, value)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Erro no hset {key}: {e}")
|
|
234
|
+
return 0
|
|
235
|
+
|
|
236
|
+
def hget(self, key: str, field: str) -> Optional[str]:
|
|
237
|
+
"""Obtém campo de hash"""
|
|
238
|
+
if not self._ensure_connection():
|
|
239
|
+
return None
|
|
240
|
+
try:
|
|
241
|
+
return self.client.hget(key, field)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error(f"Erro no hget {key}: {e}")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def hgetall(self, key: str) -> dict:
|
|
247
|
+
"""Obtém todo hash"""
|
|
248
|
+
if not self._ensure_connection():
|
|
249
|
+
return {}
|
|
250
|
+
try:
|
|
251
|
+
return self.client.hgetall(key)
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(f"Erro no hgetall {key}: {e}")
|
|
254
|
+
return {}
|
|
255
|
+
|
|
256
|
+
# ========== OPERAÇÕES DE LISTA ==========
|
|
257
|
+
|
|
258
|
+
def lpush(self, key: str, *values: str) -> int:
|
|
259
|
+
"""Adiciona ao início da lista"""
|
|
260
|
+
if not self._ensure_connection():
|
|
261
|
+
return 0
|
|
262
|
+
try:
|
|
263
|
+
return self.client.lpush(key, *values)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Erro no lpush {key}: {e}")
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
def rpush(self, key: str, *values: str) -> int:
|
|
269
|
+
"""Adiciona ao final da lista"""
|
|
270
|
+
if not self._ensure_connection():
|
|
271
|
+
return 0
|
|
272
|
+
try:
|
|
273
|
+
return self.client.rpush(key, *values)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error(f"Erro no rpush {key}: {e}")
|
|
276
|
+
return 0
|
|
277
|
+
|
|
278
|
+
def lrange(self, key: str, start: int, end: int) -> List[str]:
|
|
279
|
+
"""Retorna range da lista"""
|
|
280
|
+
if not self._ensure_connection():
|
|
281
|
+
return []
|
|
282
|
+
try:
|
|
283
|
+
return self.client.lrange(key, start, end)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.error(f"Erro no lrange {key}: {e}")
|
|
286
|
+
return []
|
|
287
|
+
|
|
288
|
+
# ========== OPERAÇÕES COM JSON ==========
|
|
289
|
+
|
|
290
|
+
def set_json(self, key: str, data: dict, expire_seconds: Optional[int] = None) -> bool:
|
|
291
|
+
"""Armazena dados JSON"""
|
|
292
|
+
return self.set(key, json.dumps(data), expire_seconds)
|
|
293
|
+
|
|
294
|
+
def get_json(self, key: str) -> Optional[dict]:
|
|
295
|
+
"""Recupera dados JSON"""
|
|
296
|
+
value = self.get(key)
|
|
297
|
+
return json.loads(value) if value else None
|
|
298
|
+
|
|
299
|
+
# ========== OPERAÇÕES DE PIPELINE ==========
|
|
300
|
+
|
|
301
|
+
def pipeline(self):
|
|
302
|
+
"""Retorna pipeline para operações em lote"""
|
|
303
|
+
if not self._ensure_connection():
|
|
304
|
+
return None
|
|
305
|
+
return self.client.pipeline()
|
|
306
|
+
|
|
307
|
+
# ========== OPERAÇÕES DE ADMIN ==========
|
|
308
|
+
|
|
309
|
+
def flush_all(self):
|
|
310
|
+
"""Limpa tudo (CUIDADO: apenas para testes!)"""
|
|
311
|
+
if not self._ensure_connection():
|
|
312
|
+
return
|
|
313
|
+
try:
|
|
314
|
+
self.client.flushall()
|
|
315
|
+
logger.warning("Redis flushall executado!")
|
|
316
|
+
except Exception as e:
|
|
317
|
+
logger.error(f"Erro no flushall: {e}")
|
|
318
|
+
|
|
319
|
+
def close(self):
|
|
320
|
+
"""Fecha conexão"""
|
|
321
|
+
if self.client:
|
|
322
|
+
self.client.close()
|
|
323
|
+
logger.info("Conexão Redis fechada")
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: redis-simplify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wrapper de conveniência para Redis - conexão, JSON helpers e reconexão automática
|
|
5
|
+
Author-email: Paulo Ricardo Tebet Lyrio <tebetpaulo91@yahoo.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Paulouuul/redis-simplify
|
|
8
|
+
Project-URL: Repository, https://github.com/Paulouuul/redis-simplify
|
|
9
|
+
Project-URL: Issues, https://github.com/Paulouuul/redis-simplify/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: redis>=4.0.0
|
|
22
|
+
|
|
23
|
+
# redis-simplify
|
|
24
|
+
|
|
25
|
+
[](https://pypi.org/project/redis-simplify/)
|
|
26
|
+
[](https://pypi.org/project/redis-simplify/)
|
|
27
|
+
[](LICENSE)
|
|
28
|
+
|
|
29
|
+
A lightweight synchronous convenience wrapper for Redis built on top of **redis-py**.
|
|
30
|
+
|
|
31
|
+
`redis-simplify` was created to reduce repetitive Redis boilerplate in Python applications by providing a simple and consistent interface with automatic reconnection, JSON helpers, centralized logging, and defensive error handling.
|
|
32
|
+
|
|
33
|
+
> This package is not a Redis client replacement. It is a convenience layer built on top of `redis-py` to simplify common Redis operations.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
* Explicit Redis configuration (`host`, `port`, `password`, `db`)
|
|
40
|
+
* Does **not** automatically read `.env` files
|
|
41
|
+
* Automatic reconnection when Redis becomes unavailable
|
|
42
|
+
* Centralized logging and error handling
|
|
43
|
+
* JSON helpers for storing Python dictionaries
|
|
44
|
+
* Safe fallback values on failures
|
|
45
|
+
* Fully tested with `pytest`
|
|
46
|
+
* Lightweight implementation
|
|
47
|
+
* Synchronous API
|
|
48
|
+
* Support for the most commonly used Redis operations:
|
|
49
|
+
|
|
50
|
+
* Strings
|
|
51
|
+
* Sets
|
|
52
|
+
* Hashes
|
|
53
|
+
* Lists
|
|
54
|
+
* Pipelines
|
|
55
|
+
* SCAN iteration
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install redis-simplify
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Requirements
|
|
68
|
+
|
|
69
|
+
* Python >= 3.8
|
|
70
|
+
* redis-py >= 4.0.0
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from redis_simplify import RedisClient
|
|
78
|
+
|
|
79
|
+
client = RedisClient(
|
|
80
|
+
host="localhost",
|
|
81
|
+
port=6379
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Basic Usage
|
|
88
|
+
|
|
89
|
+
### Strings
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from redis_simplify import RedisClient
|
|
93
|
+
|
|
94
|
+
client = RedisClient(host="localhost", port=6379)
|
|
95
|
+
|
|
96
|
+
client.set("chave", "valor")
|
|
97
|
+
|
|
98
|
+
print(client.get("chave"))
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Output:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
valor
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### JSON Helpers
|
|
110
|
+
|
|
111
|
+
Store Python dictionaries directly in Redis.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
client.set_json(
|
|
115
|
+
"usuario:1",
|
|
116
|
+
{
|
|
117
|
+
"nome": "João",
|
|
118
|
+
"idade": 30
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
print(client.get_json("usuario:1"))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Output:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
{
|
|
129
|
+
"nome": "João",
|
|
130
|
+
"idade": 30
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### Sets
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
client.sadd("tags", "python", "redis")
|
|
140
|
+
|
|
141
|
+
print(client.smembers("tags"))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Possible output:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
{"python", "redis"}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Connection Check
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
if client.ping():
|
|
156
|
+
print("Redis online")
|
|
157
|
+
else:
|
|
158
|
+
print("Redis unavailable")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Automatic Reconnection
|
|
164
|
+
|
|
165
|
+
Before executing operations, the client verifies the connection status.
|
|
166
|
+
|
|
167
|
+
If Redis becomes unavailable, the wrapper automatically attempts to reconnect before executing the requested command.
|
|
168
|
+
|
|
169
|
+
This behavior is transparent to application code and helps reduce connection-management boilerplate.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Error Handling
|
|
174
|
+
|
|
175
|
+
All operations include consistent exception handling and logging.
|
|
176
|
+
|
|
177
|
+
Instead of propagating Redis exceptions, the wrapper logs errors and returns safe fallback values whenever possible.
|
|
178
|
+
|
|
179
|
+
### Fallback Values
|
|
180
|
+
|
|
181
|
+
| Return Type | Fallback |
|
|
182
|
+
| --------------- | -------- |
|
|
183
|
+
| Object / String | `None` |
|
|
184
|
+
| Boolean | `False` |
|
|
185
|
+
| Numeric | `0` |
|
|
186
|
+
| List | `[]` |
|
|
187
|
+
| Dictionary | `{}` |
|
|
188
|
+
| Set | `set()` |
|
|
189
|
+
|
|
190
|
+
This approach helps keep application code clean and reduces repetitive `try/except` blocks.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Available Methods
|
|
195
|
+
|
|
196
|
+
### Strings
|
|
197
|
+
|
|
198
|
+
| Method | Description |
|
|
199
|
+
| -------------------------------------- | ----------------------- |
|
|
200
|
+
| `set(key, value, expire_seconds=None)` | Set a value |
|
|
201
|
+
| `get(key)` | Retrieve a value |
|
|
202
|
+
| `delete(*keys)` | Delete one or more keys |
|
|
203
|
+
| `exists(key)` | Check if a key exists |
|
|
204
|
+
| `expire(key, seconds)` | Set expiration time |
|
|
205
|
+
| `incr(key)` | Increment a value |
|
|
206
|
+
| `decr(key)` | Decrement a value |
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### JSON
|
|
211
|
+
|
|
212
|
+
| Method | Description |
|
|
213
|
+
| ------------------------------------------ | ----------------------------- |
|
|
214
|
+
| `set_json(key, data, expire_seconds=None)` | Store a dictionary as JSON |
|
|
215
|
+
| `get_json(key)` | Retrieve and deserialize JSON |
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### Sets
|
|
220
|
+
|
|
221
|
+
| Method | Description |
|
|
222
|
+
| ----------------------- | -------------------- |
|
|
223
|
+
| `sadd(key, *values)` | Add members |
|
|
224
|
+
| `srem(key, *values)` | Remove members |
|
|
225
|
+
| `smembers(key)` | Retrieve all members |
|
|
226
|
+
| `sismember(key, value)` | Check membership |
|
|
227
|
+
| `scard(key)` | Count members |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Hashes
|
|
232
|
+
|
|
233
|
+
| Method | Description |
|
|
234
|
+
| ------------------------- | ------------------- |
|
|
235
|
+
| `hset(key, field, value)` | Set a hash field |
|
|
236
|
+
| `hget(key, field)` | Retrieve a field |
|
|
237
|
+
| `hgetall(key)` | Retrieve all fields |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### Lists
|
|
242
|
+
|
|
243
|
+
| Method | Description |
|
|
244
|
+
| ------------------------- | ---------------------------- |
|
|
245
|
+
| `lpush(key, *values)` | Push values to the beginning |
|
|
246
|
+
| `rpush(key, *values)` | Push values to the end |
|
|
247
|
+
| `lrange(key, start, end)` | Retrieve a range of values |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Utilities
|
|
252
|
+
|
|
253
|
+
| Method | Description |
|
|
254
|
+
| ---------------------------------------- | -------------------------- |
|
|
255
|
+
| `ping()` | Verify connectivity |
|
|
256
|
+
| `pipeline()` | Create a Redis pipeline |
|
|
257
|
+
| `scan(cursor=0, match=None, count=None)` | Iterate keys using SCAN |
|
|
258
|
+
| `flush_all()` | Remove all Redis databases |
|
|
259
|
+
| `close()` | Close the connection |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Pipeline Example
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
pipe = client.pipeline()
|
|
267
|
+
|
|
268
|
+
pipe.set("user:1", "John")
|
|
269
|
+
pipe.set("user:2", "Jane")
|
|
270
|
+
|
|
271
|
+
pipe.execute()
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## SCAN Example
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
cursor = 0
|
|
280
|
+
|
|
281
|
+
while True:
|
|
282
|
+
cursor, keys = client.scan(
|
|
283
|
+
cursor=cursor,
|
|
284
|
+
match="user:*",
|
|
285
|
+
count=100
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
print(keys)
|
|
289
|
+
|
|
290
|
+
if cursor == 0:
|
|
291
|
+
break
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Shared Instance Pattern
|
|
297
|
+
|
|
298
|
+
`redis-simplify` does not enforce a Singleton pattern.
|
|
299
|
+
|
|
300
|
+
However, many applications create a single shared instance and reuse it throughout the project:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from redis_simplify import RedisClient
|
|
304
|
+
|
|
305
|
+
redis_client = RedisClient(
|
|
306
|
+
host="localhost",
|
|
307
|
+
port=6379
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Why redis-simplify?
|
|
314
|
+
|
|
315
|
+
Many projects repeatedly implement:
|
|
316
|
+
|
|
317
|
+
* Redis connection setup
|
|
318
|
+
* Health checks
|
|
319
|
+
* Reconnection logic
|
|
320
|
+
* JSON serialization and deserialization
|
|
321
|
+
* Logging
|
|
322
|
+
* Defensive exception handling
|
|
323
|
+
|
|
324
|
+
`redis-simplify` centralizes these concerns into a small reusable wrapper while preserving the familiar Redis workflow provided by `redis-py`.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Running Tests
|
|
329
|
+
|
|
330
|
+
The project includes automated tests built with `pytest`.
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
pytest
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Contributing
|
|
339
|
+
|
|
340
|
+
Contributions are welcome.
|
|
341
|
+
|
|
342
|
+
To contribute:
|
|
343
|
+
|
|
344
|
+
1. Fork the repository
|
|
345
|
+
2. Create a feature branch
|
|
346
|
+
3. Make your changes
|
|
347
|
+
4. Add or update tests when applicable
|
|
348
|
+
5. Open a Pull Request
|
|
349
|
+
|
|
350
|
+
Bug reports, improvements, and feature suggestions are appreciated.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
|
|
356
|
+
This project is licensed under the MIT License.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Author
|
|
361
|
+
|
|
362
|
+
**Paulo Ricardo Tebet Lyrio**
|
|
363
|
+
|
|
364
|
+
GitHub: https://github.com/Paulouuul/redis-simplify
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
redis_simplify/__init__.py,sha256=uAcB3kC0h5vr1_zKZx_GMa7lCY7MDOGKozjlsrbZtB0,584
|
|
2
|
+
redis_simplify/client.py,sha256=IYjt7UYmA69z-1A2JBvxoWByjO7r2SOtwjalSYjlUjM,10796
|
|
3
|
+
redis_simplify-0.1.0.dist-info/METADATA,sha256=SgLtV4BlfXTxOgswVYv5uPIzSGjC9NmeybaHDlAyvts,8620
|
|
4
|
+
redis_simplify-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
redis_simplify-0.1.0.dist-info/top_level.txt,sha256=5exHW7LkFNhYgELOqOPbubOiiSJfLdqJdpjZsPwPSRE,15
|
|
6
|
+
redis_simplify-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
redis_simplify
|