python3-commons 0.8.9__tar.gz → 0.8.11__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.
- {python3_commons-0.8.9/src/python3_commons.egg-info → python3_commons-0.8.11}/PKG-INFO +4 -3
- {python3_commons-0.8.9 → python3_commons-0.8.11}/pyproject.toml +4 -3
- {python3_commons-0.8.9 → python3_commons-0.8.11}/requirements.txt +3 -2
- python3_commons-0.8.11/src/python3_commons/cache.py +249 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/conf.py +9 -1
- {python3_commons-0.8.9 → python3_commons-0.8.11/src/python3_commons.egg-info}/PKG-INFO +4 -3
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons.egg-info/SOURCES.txt +1 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons.egg-info/requires.txt +3 -2
- {python3_commons-0.8.9 → python3_commons-0.8.11}/.coveragerc +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/.github/workflows/python-publish.yaml +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/.gitignore +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/AUTHORS.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/CHANGELOG.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/LICENSE +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/README.md +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/README.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/Makefile +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/_static/.gitignore +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/authors.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/changelog.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/conf.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/index.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/docs/license.rst +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/requirements_dev.txt +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/requirements_test.txt +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/setup.cfg +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/setup.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/api_client.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/audit.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/db/__init__.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/db/helpers.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/db/models/__init__.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/db/models/auth.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/db/models/common.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/db/models/rbac.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/fs.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/helpers.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/logging/__init__.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/logging/filters.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/logging/formatters.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/object_storage.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/permissions.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/serializers/json.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/serializers/msgpack.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/serializers/msgspec.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons.egg-info/top_level.txt +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/tests/conftest.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/tests/test_audit.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/tests/test_helpers.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/tests/test_msgpack.py +0 -0
- {python3_commons-0.8.9 → python3_commons-0.8.11}/tests/test_msgspec.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: python3-commons
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.11
|
4
4
|
Summary: Re-usable Python3 code
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
6
6
|
License: gpl-3
|
@@ -20,9 +20,10 @@ Requires-Dist: lxml~=5.3.1
|
|
20
20
|
Requires-Dist: minio~=7.2.15
|
21
21
|
Requires-Dist: msgpack~=1.1.0
|
22
22
|
Requires-Dist: msgspec~=0.19.0
|
23
|
-
Requires-Dist: pydantic[email]~=2.
|
23
|
+
Requires-Dist: pydantic[email]~=2.11.0
|
24
24
|
Requires-Dist: pydantic-settings~=2.8.1
|
25
|
-
Requires-Dist: SQLAlchemy[asyncio]~=2.0.
|
25
|
+
Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
|
26
|
+
Requires-Dist: valkey[libvalkey]~=6.1.0
|
26
27
|
Requires-Dist: zeep~=4.3.1
|
27
28
|
Provides-Extra: testing
|
28
29
|
Requires-Dist: pytest; extra == "testing"
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "python3-commons"
|
7
|
-
version = "0.8.
|
7
|
+
version = "0.8.11"
|
8
8
|
description = "Re-usable Python3 code"
|
9
9
|
authors = [
|
10
10
|
{name = "Oleg Korsak", email = "kamikaze.is.waiting.you@gmail.com"}
|
@@ -25,9 +25,10 @@ dependencies = [
|
|
25
25
|
"minio~=7.2.15",
|
26
26
|
"msgpack~=1.1.0",
|
27
27
|
"msgspec~=0.19.0",
|
28
|
-
"pydantic[email]~=2.
|
28
|
+
"pydantic[email]~=2.11.0",
|
29
29
|
"pydantic-settings~=2.8.1",
|
30
|
-
"SQLAlchemy[asyncio]~=2.0.
|
30
|
+
"SQLAlchemy[asyncio]~=2.0.40",
|
31
|
+
"valkey[libvalkey]~=6.1.0",
|
31
32
|
"zeep~=4.3.1"
|
32
33
|
]
|
33
34
|
requires-python = ">=3.13"
|
@@ -0,0 +1,249 @@
|
|
1
|
+
import logging
|
2
|
+
import socket
|
3
|
+
from platform import platform
|
4
|
+
from typing import Any, Mapping, Sequence
|
5
|
+
|
6
|
+
import valkey
|
7
|
+
from pydantic import RedisDsn
|
8
|
+
from valkey.asyncio import Valkey, StrictValkey, ConnectionPool, Sentinel
|
9
|
+
from valkey.asyncio.retry import Retry
|
10
|
+
from valkey.backoff import FullJitterBackoff
|
11
|
+
|
12
|
+
from python3_commons.conf import valkey_settings
|
13
|
+
from python3_commons.helpers import SingletonMeta
|
14
|
+
from python3_commons.serializers.msgspec import (
|
15
|
+
serialize_msgpack, deserialize_msgpack, deserialize_msgpack_native, serialize_msgpack_native
|
16
|
+
)
|
17
|
+
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
class AsyncValkeyClient(metaclass=SingletonMeta):
|
22
|
+
def __init__(self, dsn: RedisDsn, sentinel_dsn: RedisDsn | None):
|
23
|
+
self._valkey_pool = None
|
24
|
+
self._valkey = None
|
25
|
+
|
26
|
+
if sentinel_dsn:
|
27
|
+
self._initialize_sentinel(sentinel_dsn)
|
28
|
+
else:
|
29
|
+
self._initialize_standard_pool(dsn)
|
30
|
+
|
31
|
+
@staticmethod
|
32
|
+
def _get_keepalive_options():
|
33
|
+
if platform == 'linux' or platform == 'darwin':
|
34
|
+
return {
|
35
|
+
socket.TCP_KEEPIDLE: 10,
|
36
|
+
socket.TCP_KEEPINTVL: 5,
|
37
|
+
socket.TCP_KEEPCNT: 5
|
38
|
+
}
|
39
|
+
else:
|
40
|
+
return {}
|
41
|
+
|
42
|
+
def _initialize_sentinel(self, dsn: RedisDsn):
|
43
|
+
sentinel = Sentinel(
|
44
|
+
[(dsn.host, dsn.port)],
|
45
|
+
socket_connect_timeout=10,
|
46
|
+
socket_timeout=60,
|
47
|
+
password=dsn.password,
|
48
|
+
sentinel_kwargs={'password': dsn.password}
|
49
|
+
)
|
50
|
+
|
51
|
+
ka_options = self._get_keepalive_options()
|
52
|
+
|
53
|
+
self._valkey = sentinel.master_for(
|
54
|
+
'myprimary',
|
55
|
+
valkey_class=StrictValkey,
|
56
|
+
socket_connect_timeout=10,
|
57
|
+
socket_timeout=60,
|
58
|
+
health_check_interval=30,
|
59
|
+
retry_on_timeout=True,
|
60
|
+
retry=Retry(FullJitterBackoff(cap=5, base=1), 5),
|
61
|
+
socket_keepalive=True,
|
62
|
+
socket_keepalive_options=ka_options
|
63
|
+
)
|
64
|
+
|
65
|
+
def _initialize_standard_pool(self, dsn: RedisDsn):
|
66
|
+
self._valkey_pool = ConnectionPool.from_url(str(dsn))
|
67
|
+
self._valkey = StrictValkey(connection_pool=self._valkey_pool)
|
68
|
+
|
69
|
+
def get_client(self) -> Valkey:
|
70
|
+
return self._valkey
|
71
|
+
|
72
|
+
|
73
|
+
def get_valkey_client() -> Valkey:
|
74
|
+
return AsyncValkeyClient(valkey_settings.dsn, valkey_settings.sentinel_dsn).get_client()
|
75
|
+
|
76
|
+
|
77
|
+
async def delete(name: str):
|
78
|
+
await get_valkey_client().delete(name)
|
79
|
+
|
80
|
+
|
81
|
+
async def store_bytes(name: str, data: bytes, ttl: int = None, if_not_set: bool = False):
|
82
|
+
r = get_valkey_client()
|
83
|
+
|
84
|
+
return r.set(name, data, ex=ttl, nx=if_not_set)
|
85
|
+
|
86
|
+
|
87
|
+
async def get_bytes(name: str) -> bytes | None:
|
88
|
+
r = get_valkey_client()
|
89
|
+
|
90
|
+
return r.get(name)
|
91
|
+
|
92
|
+
|
93
|
+
async def store(name: str, obj: Any, ttl: int = None, if_not_set: bool = False):
|
94
|
+
return store_bytes(name, serialize_msgpack_native(obj), ttl, if_not_set)
|
95
|
+
|
96
|
+
|
97
|
+
async def get(name: str, default=None, data_type: Any = None) -> Any:
|
98
|
+
if data := await get_bytes(name):
|
99
|
+
return deserialize_msgpack_native(data, data_type)
|
100
|
+
|
101
|
+
return default
|
102
|
+
|
103
|
+
|
104
|
+
async def store_string(name: str, data: str, ttl: int = None):
|
105
|
+
await store_bytes(name, data.encode(), ttl)
|
106
|
+
|
107
|
+
|
108
|
+
async def get_string(name: str) -> str | None:
|
109
|
+
if data := await get_bytes(name):
|
110
|
+
return data.decode('utf-8')
|
111
|
+
|
112
|
+
return None
|
113
|
+
|
114
|
+
|
115
|
+
async def store_sequence(name: str, data: Sequence, ttl: int = None):
|
116
|
+
if data:
|
117
|
+
try:
|
118
|
+
r = get_valkey_client()
|
119
|
+
await r.rpush(name, *map(serialize_msgpack_native, data))
|
120
|
+
|
121
|
+
if ttl:
|
122
|
+
await r.expire(name, ttl)
|
123
|
+
except valkey.exceptions.ConnectionError as e:
|
124
|
+
logger.error(f'Failed to store sequence in cache: {e}')
|
125
|
+
|
126
|
+
|
127
|
+
async def get_sequence(name: str, _type: type = list) -> Sequence:
|
128
|
+
r = get_valkey_client()
|
129
|
+
|
130
|
+
return _type(map(deserialize_msgpack_native, r.lrange(name, 0, -1)))
|
131
|
+
|
132
|
+
|
133
|
+
async def store_dict(name: str, data: Mapping, ttl: int = None):
|
134
|
+
if data:
|
135
|
+
try:
|
136
|
+
r = get_valkey_client()
|
137
|
+
data = {k: serialize_msgpack_native(v) for k, v in data.items()}
|
138
|
+
await r.hset(name, mapping=data)
|
139
|
+
|
140
|
+
if ttl:
|
141
|
+
await r.expire(name, ttl)
|
142
|
+
except valkey.exceptions.ConnectionError as e:
|
143
|
+
logger.error(f'Failed to store dict in cache: {e}')
|
144
|
+
|
145
|
+
|
146
|
+
async def get_dict(name: str, value_data_type=None) -> dict | None:
|
147
|
+
r = get_valkey_client()
|
148
|
+
|
149
|
+
if data := r.hgetall(name):
|
150
|
+
data = {k.decode(): deserialize_msgpack(v, value_data_type) for k, v in data.items()}
|
151
|
+
|
152
|
+
return data
|
153
|
+
|
154
|
+
return None
|
155
|
+
|
156
|
+
|
157
|
+
async def set_dict(name: str, mapping: dict, ttl: int = None):
|
158
|
+
if mapping:
|
159
|
+
try:
|
160
|
+
r = get_valkey_client()
|
161
|
+
mapping = {str(k): serialize_msgpack(v) for k, v in mapping.items()}
|
162
|
+
await r.hset(name, mapping=mapping)
|
163
|
+
|
164
|
+
if ttl:
|
165
|
+
await r.expire(name, ttl)
|
166
|
+
except valkey.exceptions.ConnectionError as e:
|
167
|
+
logger.error(f'Failed to set dict in cache: {e}')
|
168
|
+
|
169
|
+
|
170
|
+
async def get_dict_item(name: str, key: str, data_type=None, default=None):
|
171
|
+
try:
|
172
|
+
r = get_valkey_client()
|
173
|
+
|
174
|
+
if data := await r.hget(name, key):
|
175
|
+
return deserialize_msgpack_native(data, data_type)
|
176
|
+
|
177
|
+
return default
|
178
|
+
except valkey.exceptions.ConnectionError as e:
|
179
|
+
logger.error(f'Failed to get dict item from cache: {e}')
|
180
|
+
|
181
|
+
return None
|
182
|
+
|
183
|
+
|
184
|
+
async def set_dict_item(name: str, key: str, obj: Any):
|
185
|
+
try:
|
186
|
+
r = get_valkey_client()
|
187
|
+
await r.hset(name, key, serialize_msgpack_native(obj))
|
188
|
+
except valkey.exceptions.ConnectionError as e:
|
189
|
+
logger.error(f'Failed to set dict item in cache: {e}')
|
190
|
+
|
191
|
+
|
192
|
+
async def delete_dict_item(name: str, *keys):
|
193
|
+
try:
|
194
|
+
r = get_valkey_client()
|
195
|
+
await r.hdel(name, *keys)
|
196
|
+
except valkey.exceptions.ConnectionError as e:
|
197
|
+
logger.error(f'Failed to delete dict item from cache: {e}')
|
198
|
+
|
199
|
+
|
200
|
+
async def store_set(name: str, value: set, ttl: int = None):
|
201
|
+
try:
|
202
|
+
r = get_valkey_client()
|
203
|
+
await r.sadd(name, *map(serialize_msgpack_native, value))
|
204
|
+
|
205
|
+
if ttl:
|
206
|
+
await r.expire(name, ttl)
|
207
|
+
except valkey.exceptions.ConnectionError as e:
|
208
|
+
logger.error(f'Failed to store set in cache: {e}')
|
209
|
+
|
210
|
+
|
211
|
+
async def has_set_item(name: str, value: str) -> bool:
|
212
|
+
try:
|
213
|
+
r = get_valkey_client()
|
214
|
+
|
215
|
+
return r.sismember(name, serialize_msgpack_native(value)) == 1
|
216
|
+
except valkey.exceptions.ConnectionError as e:
|
217
|
+
logger.error(f'Failed to check if set has item in cache: {e}')
|
218
|
+
|
219
|
+
return False
|
220
|
+
|
221
|
+
|
222
|
+
async def add_set_item(name: str, *values: str):
|
223
|
+
try:
|
224
|
+
r = get_valkey_client()
|
225
|
+
await r.sadd(name, *map(serialize_msgpack_native, values))
|
226
|
+
except valkey.exceptions.ConnectionError as e:
|
227
|
+
logger.error(f'Failed to add set item into cache: {e}')
|
228
|
+
|
229
|
+
|
230
|
+
async def delete_set_item(name: str, value: str):
|
231
|
+
r = get_valkey_client()
|
232
|
+
await r.srem(name, serialize_msgpack_native(value))
|
233
|
+
|
234
|
+
|
235
|
+
async def get_set_members(name: str) -> set[str] | None:
|
236
|
+
try:
|
237
|
+
r = get_valkey_client()
|
238
|
+
|
239
|
+
return set(map(deserialize_msgpack_native, r.smembers(name)))
|
240
|
+
except valkey.exceptions.ConnectionError as e:
|
241
|
+
logger.error(f'Failed to get set members from cache: {e}')
|
242
|
+
|
243
|
+
return None
|
244
|
+
|
245
|
+
|
246
|
+
async def exists(name: str) -> bool:
|
247
|
+
r = get_valkey_client()
|
248
|
+
|
249
|
+
return r.exists(name) == 1
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from pydantic import SecretStr, PostgresDsn, Field
|
1
|
+
from pydantic import SecretStr, PostgresDsn, Field, RedisDsn
|
2
2
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
3
3
|
|
4
4
|
|
@@ -8,6 +8,13 @@ class CommonSettings(BaseSettings):
|
|
8
8
|
logging_formatter: str = 'default'
|
9
9
|
|
10
10
|
|
11
|
+
class ValkeySettings(BaseSettings):
|
12
|
+
model_config = SettingsConfigDict(env_prefix='VALKEY_')
|
13
|
+
|
14
|
+
dsn: RedisDsn | None = None
|
15
|
+
sentinel_dsn: RedisDsn | None = None
|
16
|
+
|
17
|
+
|
11
18
|
class DBSettings(BaseSettings):
|
12
19
|
model_config = SettingsConfigDict(env_prefix='DB_')
|
13
20
|
|
@@ -31,5 +38,6 @@ class S3Settings(BaseSettings):
|
|
31
38
|
|
32
39
|
|
33
40
|
settings = CommonSettings()
|
41
|
+
valkey_settings = ValkeySettings()
|
34
42
|
db_settings = DBSettings()
|
35
43
|
s3_settings = S3Settings()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: python3-commons
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.11
|
4
4
|
Summary: Re-usable Python3 code
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
6
6
|
License: gpl-3
|
@@ -20,9 +20,10 @@ Requires-Dist: lxml~=5.3.1
|
|
20
20
|
Requires-Dist: minio~=7.2.15
|
21
21
|
Requires-Dist: msgpack~=1.1.0
|
22
22
|
Requires-Dist: msgspec~=0.19.0
|
23
|
-
Requires-Dist: pydantic[email]~=2.
|
23
|
+
Requires-Dist: pydantic[email]~=2.11.0
|
24
24
|
Requires-Dist: pydantic-settings~=2.8.1
|
25
|
-
Requires-Dist: SQLAlchemy[asyncio]~=2.0.
|
25
|
+
Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
|
26
|
+
Requires-Dist: valkey[libvalkey]~=6.1.0
|
26
27
|
Requires-Dist: zeep~=4.3.1
|
27
28
|
Provides-Extra: testing
|
28
29
|
Requires-Dist: pytest; extra == "testing"
|
@@ -6,9 +6,10 @@ lxml~=5.3.1
|
|
6
6
|
minio~=7.2.15
|
7
7
|
msgpack~=1.1.0
|
8
8
|
msgspec~=0.19.0
|
9
|
-
pydantic[email]~=2.
|
9
|
+
pydantic[email]~=2.11.0
|
10
10
|
pydantic-settings~=2.8.1
|
11
|
-
SQLAlchemy[asyncio]~=2.0.
|
11
|
+
SQLAlchemy[asyncio]~=2.0.40
|
12
|
+
valkey[libvalkey]~=6.1.0
|
12
13
|
zeep~=4.3.1
|
13
14
|
|
14
15
|
[testing]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons/serializers/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{python3_commons-0.8.9 → python3_commons-0.8.11}/src/python3_commons.egg-info/dependency_links.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|