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