python3-commons 0.15.21__tar.gz → 0.16.1__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 (74) hide show
  1. {python3_commons-0.15.21 → python3_commons-0.16.1}/PKG-INFO +4 -3
  2. {python3_commons-0.15.21 → python3_commons-0.16.1}/pyproject.toml +3 -2
  3. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/audit.py +1 -3
  4. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/__init__.py +13 -13
  5. python3_commons-0.16.1/src/python3_commons/log/formatters.py +118 -0
  6. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/object_storage.py +29 -11
  7. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons.egg-info/PKG-INFO +4 -3
  8. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons.egg-info/requires.txt +3 -2
  9. {python3_commons-0.15.21 → python3_commons-0.16.1}/uv.lock +44 -19
  10. python3_commons-0.15.21/src/python3_commons/log/formatters.py +0 -35
  11. {python3_commons-0.15.21 → python3_commons-0.16.1}/.coveragerc +0 -0
  12. {python3_commons-0.15.21 → python3_commons-0.16.1}/.devcontainer/Dockerfile +0 -0
  13. {python3_commons-0.15.21 → python3_commons-0.16.1}/.devcontainer/devcontainer.json +0 -0
  14. {python3_commons-0.15.21 → python3_commons-0.16.1}/.devcontainer/docker-compose.yml +0 -0
  15. {python3_commons-0.15.21 → python3_commons-0.16.1}/.env_template +0 -0
  16. {python3_commons-0.15.21 → python3_commons-0.16.1}/.github/workflows/checks.yml +0 -0
  17. {python3_commons-0.15.21 → python3_commons-0.16.1}/.github/workflows/python-publish.yaml +0 -0
  18. {python3_commons-0.15.21 → python3_commons-0.16.1}/.github/workflows/release-on-tag-push.yml +0 -0
  19. {python3_commons-0.15.21 → python3_commons-0.16.1}/.gitignore +0 -0
  20. {python3_commons-0.15.21 → python3_commons-0.16.1}/.pre-commit-config.yaml +0 -0
  21. {python3_commons-0.15.21 → python3_commons-0.16.1}/.python-version +0 -0
  22. {python3_commons-0.15.21 → python3_commons-0.16.1}/AUTHORS.rst +0 -0
  23. {python3_commons-0.15.21 → python3_commons-0.16.1}/CHANGELOG.rst +0 -0
  24. {python3_commons-0.15.21 → python3_commons-0.16.1}/LICENSE +0 -0
  25. {python3_commons-0.15.21 → python3_commons-0.16.1}/README.md +0 -0
  26. {python3_commons-0.15.21 → python3_commons-0.16.1}/README.rst +0 -0
  27. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/Makefile +0 -0
  28. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/_static/.gitignore +0 -0
  29. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/authors.rst +0 -0
  30. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/changelog.rst +0 -0
  31. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/conf.py +0 -0
  32. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/index.rst +0 -0
  33. {python3_commons-0.15.21 → python3_commons-0.16.1}/docs/license.rst +0 -0
  34. {python3_commons-0.15.21 → python3_commons-0.16.1}/setup.cfg +0 -0
  35. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/__init__.py +0 -0
  36. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/api_client.py +0 -0
  37. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/async_functools.py +0 -0
  38. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/auth.py +0 -0
  39. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/cache.py +0 -0
  40. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/conf.py +0 -0
  41. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/helpers.py +0 -0
  42. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/models/__init__.py +0 -0
  43. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/models/auth.py +0 -0
  44. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/models/common.py +0 -0
  45. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/models/rbac.py +0 -0
  46. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/db/models/users.py +0 -0
  47. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/exceptions.py +0 -0
  48. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/fs.py +0 -0
  49. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/generators.py +0 -0
  50. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/helpers.py +0 -0
  51. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/log/__init__.py +0 -0
  52. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/log/filters.py +0 -0
  53. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/permissions.py +0 -0
  54. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/serializers/__init__.py +0 -0
  55. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/serializers/common.py +0 -0
  56. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/serializers/json.py +0 -0
  57. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/serializers/msgpack.py +0 -0
  58. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons/serializers/msgspec.py +0 -0
  59. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons.egg-info/SOURCES.txt +0 -0
  60. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  61. {python3_commons-0.15.21 → python3_commons-0.16.1}/src/python3_commons.egg-info/top_level.txt +0 -0
  62. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/__init__.py +0 -0
  63. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/integration/__init__.py +0 -0
  64. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/integration/test_cache.py +0 -0
  65. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/integration/test_osc.py +0 -0
  66. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/__init__.py +0 -0
  67. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/conftest.py +0 -0
  68. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/log/__init__.py +0 -0
  69. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/log/test_formatters.py +0 -0
  70. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/test_async_functools.py +0 -0
  71. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/test_audit.py +0 -0
  72. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/test_helpers.py +0 -0
  73. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/test_msgpack.py +0 -0
  74. {python3_commons-0.15.21 → python3_commons-0.16.1}/tests/unit/test_msgspec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.15.21
3
+ Version: 0.16.1
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -12,13 +12,14 @@ Requires-Python: <3.15.0,>=3.14.4
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiobotocore~=3.4.0
15
+ Requires-Dist: aiobotocore~=3.5.0
16
16
  Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5
17
17
  Requires-Dist: asyncpg~=0.31.0
18
18
  Requires-Dist: lxml~=6.1.0
19
19
  Requires-Dist: msgpack~=1.1.2
20
20
  Requires-Dist: msgspec==0.21.1
21
- Requires-Dist: object-storage-client==0.0.20
21
+ Requires-Dist: object-storage-client==0.0.22
22
+ Requires-Dist: orjson==3.11.8
22
23
  Requires-Dist: pydantic-settings~=2.14.0
23
24
  Requires-Dist: SQLAlchemy[asyncio]~=2.0.49
24
25
  Requires-Dist: valkey[libvalkey]~=6.1.1
@@ -18,13 +18,14 @@ classifiers = [
18
18
  keywords = []
19
19
  requires-python = ">=3.14.4,<3.15.0"
20
20
  dependencies = [
21
- "aiobotocore~=3.4.0",
21
+ "aiobotocore~=3.5.0",
22
22
  "aiohttp[speedups]>=3.13.5,<3.15.0",
23
23
  "asyncpg~=0.31.0",
24
24
  "lxml~=6.1.0",
25
25
  "msgpack~=1.1.2",
26
26
  "msgspec==0.21.1",
27
- "object-storage-client==0.0.20",
27
+ "object-storage-client==0.0.22",
28
+ "orjson==3.11.8",
28
29
  "pydantic-settings~=2.14.0",
29
30
  "SQLAlchemy[asyncio]~=2.0.49",
30
31
  "valkey[libvalkey]~=6.1.1",
@@ -20,9 +20,7 @@ logger = logging.getLogger(__name__)
20
20
  async def write_audit_data(settings: S3Settings, key: str, data: bytes) -> None:
21
21
  if settings.aws_secret_access_key:
22
22
  try:
23
- absolute_path = object_storage.get_absolute_path(f'audit/{key}')
24
-
25
- await object_storage.put_object(settings.s3_bucket, absolute_path, io.BytesIO(data), len(data))
23
+ await object_storage.put_object(settings.s3_bucket, f'audit/{key}', io.BytesIO(data), len(data))
26
24
  except Exception:
27
25
  logger.exception('Failed storing object in storage.')
28
26
  else:
@@ -17,28 +17,28 @@ Base = declarative_base(metadata=metadata)
17
17
 
18
18
 
19
19
  class AsyncSessionManager:
20
- def __init__(self, db_settings: Mapping[str, DBSettings]) -> None:
21
- self.db_settings: Mapping[str, DBSettings] = db_settings
20
+ def __init__(self, db_configs: Mapping[str, DBSettings]) -> None:
21
+ self.db_configs: Mapping[str, DBSettings] = db_configs
22
22
  self.engines: dict[str, AsyncEngine] = {}
23
23
  self.session_makers: dict = {}
24
24
 
25
- def get_db_settings(self, name: str) -> DBSettings:
25
+ def get_db_config(self, name: str) -> DBSettings:
26
26
  try:
27
- return self.db_settings[name]
27
+ return self.db_configs[name]
28
28
  except KeyError:
29
29
  logger.exception('Missing database settings: %s', name)
30
30
 
31
31
  raise
32
32
 
33
- def async_engine_from_db_settings(self, name):
34
- db_settings = self.get_db_settings(name)
33
+ def async_engine_from_db_config(self, name):
34
+ db_config = self.get_db_config(name)
35
35
  configuration = {
36
- 'url': str(db_settings.dsn),
37
- 'echo': db_settings.echo,
38
- 'pool_size': db_settings.pool_size,
39
- 'max_overflow': db_settings.max_overflow,
40
- 'pool_timeout': db_settings.pool_timeout,
41
- 'pool_recycle': db_settings.pool_recycle,
36
+ 'url': str(db_config.dsn),
37
+ 'echo': db_config.echo,
38
+ 'pool_size': db_config.pool_size,
39
+ 'max_overflow': db_config.max_overflow,
40
+ 'pool_timeout': db_config.pool_timeout,
41
+ 'pool_recycle': db_config.pool_recycle,
42
42
  }
43
43
 
44
44
  return async_engine_from_config(configuration, prefix='')
@@ -48,7 +48,7 @@ class AsyncSessionManager:
48
48
  engine = self.engines[name]
49
49
  except KeyError:
50
50
  logger.debug('Creating engine: %s', name)
51
- engine = self.async_engine_from_db_settings(name)
51
+ engine = self.async_engine_from_db_config(name)
52
52
  self.engines[name] = engine
53
53
 
54
54
  return engine
@@ -0,0 +1,118 @@
1
+ import logging
2
+ import traceback
3
+ from contextvars import ContextVar
4
+ from datetime import UTC, datetime, date
5
+ from decimal import Decimal
6
+ from typing import Any, Final, Callable
7
+
8
+ import orjson
9
+
10
+ correlation_id: ContextVar[str | None] = ContextVar('correlation_id', default=None)
11
+
12
+ _DEFAULT_MAX_TB_CHARS: Final[int] = 8_000
13
+ _STD_LOG_FIELDS: Final[frozenset[str]] = frozenset(
14
+ {
15
+ 'msg',
16
+ 'args',
17
+ 'levelname',
18
+ 'levelno',
19
+ 'pathname',
20
+ 'filename',
21
+ 'module',
22
+ 'exc_info',
23
+ 'exc_text',
24
+ 'stack_info',
25
+ 'lineno',
26
+ 'funcName',
27
+ 'created',
28
+ 'msecs',
29
+ 'relativeCreated',
30
+ 'thread',
31
+ 'threadName',
32
+ 'process',
33
+ 'processName',
34
+ 'name',
35
+ }
36
+ )
37
+
38
+
39
+ # Keep normalization minimal and branch-based (faster than dict dispatch)
40
+ def _normalize(v: Any) -> Any:
41
+ if isinstance(v, datetime | date):
42
+ return v.isoformat()
43
+
44
+ if isinstance(v, bytes):
45
+ return v.decode('utf-8', errors='replace')
46
+
47
+ if isinstance(v, Decimal):
48
+ return str(v)
49
+
50
+ if isinstance(v, Exception):
51
+ return str(v)
52
+
53
+ return v
54
+
55
+
56
+ class JSONFormatter(logging.Formatter):
57
+ __slots__ = ('_get_correlation_id', '_max_tb_chars')
58
+
59
+ def __init__(
60
+ self,
61
+ *,
62
+ get_correlation_id: Callable[[], str | None] = lambda: correlation_id.get(),
63
+ max_exc_tb_chars: int = _DEFAULT_MAX_TB_CHARS,
64
+ **kwargs: Any,
65
+ ) -> None:
66
+ super().__init__(**kwargs)
67
+ self._get_correlation_id = get_correlation_id
68
+ self._max_tb_chars = max_exc_tb_chars
69
+
70
+ def format(self, record: logging.LogRecord) -> str:
71
+ # --- message (fast path, avoid Formatter.formatMessage) ---
72
+ try:
73
+ message = record.getMessage()
74
+ except Exception:
75
+ message = str(record.msg)
76
+
77
+ # --- timestamp (no Formatter.formatTime overhead) ---
78
+ timestamp = datetime.fromtimestamp(record.created, UTC).isoformat().replace('+00:00', 'Z')
79
+
80
+ log: dict[str, Any] = {
81
+ 'message': message,
82
+ 'level': record.levelname,
83
+ 'logger': record.name,
84
+ 'timestamp': timestamp,
85
+ }
86
+
87
+ if (corr_id := self._get_correlation_id()) is not None:
88
+ log['correlation_id'] = corr_id
89
+
90
+ if (exc_info := record.exc_info) and exc_info[0] is not None:
91
+ exc_type, exc_value, exc_tb = exc_info
92
+
93
+ log['exc_type'] = f'{exc_type.__module__}.{exc_type.__qualname__}'
94
+ log['exc_value'] = str(exc_value)
95
+
96
+ tb = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb)).rstrip()
97
+
98
+ cap = self._max_tb_chars
99
+
100
+ if cap and len(tb) > cap:
101
+ tb = tb[:cap] + '\n... <truncated>'
102
+
103
+ log['exc_traceback'] = tb
104
+
105
+ record_dict = record.__dict__
106
+ std_log_fields = _STD_LOG_FIELDS
107
+
108
+ if len(record_dict) > len(std_log_fields):
109
+ normalize = _normalize
110
+ out_set = log.__setitem__
111
+
112
+ for k, v in record_dict.items():
113
+ if k[0] == '_' or k in std_log_fields:
114
+ continue
115
+
116
+ out_set(k, normalize(v))
117
+
118
+ return orjson.dumps(log).decode('utf-8')
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import threading
4
5
  from contextlib import asynccontextmanager
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  import aiobotocore.session
8
9
  from botocore.config import Config
10
+ from object_storage_client import ObjectStorageClient
9
11
 
10
12
  if TYPE_CHECKING:
11
13
  import io
@@ -19,6 +21,22 @@ from python3_commons.conf import S3Settings, s3_settings
19
21
  from python3_commons.helpers import SingletonMeta
20
22
 
21
23
  logger = logging.getLogger(__name__)
24
+ _CLIENT: ObjectStorageClient | None = None
25
+ _CLIENT_LOCK = threading.Lock()
26
+
27
+
28
+ def get_client() -> ObjectStorageClient:
29
+ global _CLIENT # noqa: PLW0603
30
+
31
+ if not _CLIENT:
32
+ with _CLIENT_LOCK:
33
+ if not _CLIENT:
34
+ client = ObjectStorageClient()
35
+ _CLIENT = client
36
+ else:
37
+ client = _CLIENT
38
+
39
+ return client
22
40
 
23
41
 
24
42
  class ObjectStorage(metaclass=SingletonMeta):
@@ -58,21 +76,21 @@ def get_absolute_path(path: str) -> str:
58
76
 
59
77
 
60
78
  async def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int, part_size: int = 0) -> str | None:
61
- storage = ObjectStorage(s3_settings)
79
+ try:
80
+ client = get_client()
81
+ data.seek(0)
82
+ absolute_path = get_absolute_path(path)
83
+ url = f's3://{bucket_name}/{absolute_path}'
62
84
 
63
- async with storage.get_client() as s3_client:
64
- try:
65
- data.seek(0)
85
+ await client.put_object(url, data.getvalue())
66
86
 
67
- await s3_client.put_object(Bucket=bucket_name, Key=path, Body=data, ContentLength=length)
87
+ logger.debug('Stored object into object storage: %s:%s', bucket_name, path)
88
+ except Exception as e:
89
+ logger.exception('Failed to put object to object storage: %s:%s', bucket_name, path, exc_info=e)
68
90
 
69
- logger.debug('Stored object into object storage: %s:%s', bucket_name, path)
70
- except Exception as e:
71
- logger.exception('Failed to put object to object storage: %s:%s', bucket_name, path, exc_info=e)
72
-
73
- raise
91
+ raise
74
92
 
75
- return f's3://{bucket_name}/{path}'
93
+ return f's3://{bucket_name}/{path}'
76
94
 
77
95
 
78
96
  @asynccontextmanager
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.15.21
3
+ Version: 0.16.1
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -12,13 +12,14 @@ Requires-Python: <3.15.0,>=3.14.4
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiobotocore~=3.4.0
15
+ Requires-Dist: aiobotocore~=3.5.0
16
16
  Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5
17
17
  Requires-Dist: asyncpg~=0.31.0
18
18
  Requires-Dist: lxml~=6.1.0
19
19
  Requires-Dist: msgpack~=1.1.2
20
20
  Requires-Dist: msgspec==0.21.1
21
- Requires-Dist: object-storage-client==0.0.20
21
+ Requires-Dist: object-storage-client==0.0.22
22
+ Requires-Dist: orjson==3.11.8
22
23
  Requires-Dist: pydantic-settings~=2.14.0
23
24
  Requires-Dist: SQLAlchemy[asyncio]~=2.0.49
24
25
  Requires-Dist: valkey[libvalkey]~=6.1.1
@@ -1,10 +1,11 @@
1
- aiobotocore~=3.4.0
1
+ aiobotocore~=3.5.0
2
2
  aiohttp[speedups]<3.15.0,>=3.13.5
3
3
  asyncpg~=0.31.0
4
4
  lxml~=6.1.0
5
5
  msgpack~=1.1.2
6
6
  msgspec==0.21.1
7
- object-storage-client==0.0.20
7
+ object-storage-client==0.0.22
8
+ orjson==3.11.8
8
9
  pydantic-settings~=2.14.0
9
10
  SQLAlchemy[asyncio]~=2.0.49
10
11
  valkey[libvalkey]~=6.1.1
@@ -4,7 +4,7 @@ requires-python = ">=3.14.4, <3.15.0"
4
4
 
5
5
  [[package]]
6
6
  name = "aiobotocore"
7
- version = "3.4.0"
7
+ version = "3.5.0"
8
8
  source = { registry = "https://pypi.org/simple" }
9
9
  dependencies = [
10
10
  { name = "aiohttp" },
@@ -15,9 +15,9 @@ dependencies = [
15
15
  { name = "python-dateutil" },
16
16
  { name = "wrapt" },
17
17
  ]
18
- sdist = { url = "https://files.pythonhosted.org/packages/b8/50/a48ed11b15f926ce3dbb33e7fb0f25af17dbb99bcb7ae3b30c763723eca7/aiobotocore-3.4.0.tar.gz", hash = "sha256:a918b5cb903f81feba7e26835aed4b5e6bb2d0149d7f42bb2dd7d8089e3d9000", size = 122360, upload-time = "2026-04-07T06:12:24.884Z" }
18
+ sdist = { url = "https://files.pythonhosted.org/packages/e6/89/9533b377e9412013cc43a539d81bc5f8feeb4b6830643821ad612f78b09b/aiobotocore-3.5.0.tar.gz", hash = "sha256:d45d1c4659ad0e48b694a5aa4ff18829100386f7de96c8d146ec7757a6f12918", size = 123061, upload-time = "2026-04-21T07:25:26.993Z" }
19
19
  wheels = [
20
- { url = "https://files.pythonhosted.org/packages/df/d8/ce9386e6d76ea79e61dee15e62aa48cff6be69e89246b0ac4a11857cb02c/aiobotocore-3.4.0-py3-none-any.whl", hash = "sha256:26290eb6830ea92d8a6f5f90b56e9f5cedd6d126074d5db63b195e281d982465", size = 88018, upload-time = "2026-04-07T06:12:22.684Z" },
20
+ { url = "https://files.pythonhosted.org/packages/2d/05/6eeeadef45c24630af0ceae4d038b883e9a394786300529286ba8cc1e62d/aiobotocore-3.5.0-py3-none-any.whl", hash = "sha256:49ce35bb8b96b85d3251c2cbbb2ed7a028dc0cb0d0d0801f9ccca1ccd0d41ded", size = 88281, upload-time = "2026-04-21T07:25:25.258Z" },
21
21
  ]
22
22
 
23
23
  [[package]]
@@ -164,16 +164,16 @@ wheels = [
164
164
 
165
165
  [[package]]
166
166
  name = "botocore"
167
- version = "1.42.84"
167
+ version = "1.42.91"
168
168
  source = { registry = "https://pypi.org/simple" }
169
169
  dependencies = [
170
170
  { name = "jmespath" },
171
171
  { name = "python-dateutil" },
172
172
  { name = "urllib3" },
173
173
  ]
174
- sdist = { url = "https://files.pythonhosted.org/packages/b4/b7/1c03423843fb0d1795b686511c00ee63fed1234c2400f469aeedfd42212f/botocore-1.42.84.tar.gz", hash = "sha256:234064604c80d9272a5e9f6b3566d260bcaa053a5e05246db90d7eca1c2cf44b", size = 15148615, upload-time = "2026-04-06T19:38:56.673Z" }
174
+ sdist = { url = "https://files.pythonhosted.org/packages/21/bc/a4b7c46471c2e789ad8c4c7acfd7f302fdb481d93ff870f441249b924ae6/botocore-1.42.91.tar.gz", hash = "sha256:d252e27bc454afdbf5ed3dc617aa423f2c855c081e98b7963093399483ecc698", size = 15213010, upload-time = "2026-04-17T19:30:50.793Z" }
175
175
  wheels = [
176
- { url = "https://files.pythonhosted.org/packages/e3/37/0c0c90361c8a1b9e6c75222ca24ae12996a298c0e18822a72ab229c37207/botocore-1.42.84-py3-none-any.whl", hash = "sha256:15f3fe07dfa6545e46a60c4b049fe2bdf63803c595ae4a4eec90e8f8172764f3", size = 14827061, upload-time = "2026-04-06T19:38:53.613Z" },
176
+ { url = "https://files.pythonhosted.org/packages/b1/fc/24cc0a47c824f13933e210e9ad034b4fba22f7185b8d904c0fbf5a3b2be8/botocore-1.42.91-py3-none-any.whl", hash = "sha256:7a28c3cc6bfab5724ad18899d52402b776a0de7d87fa20c3c5270bcaaf199ce8", size = 14897344, upload-time = "2026-04-17T19:30:44.245Z" },
177
177
  ]
178
178
 
179
179
  [[package]]
@@ -478,11 +478,11 @@ wheels = [
478
478
 
479
479
  [[package]]
480
480
  name = "idna"
481
- version = "3.11"
481
+ version = "3.12"
482
482
  source = { registry = "https://pypi.org/simple" }
483
- sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
483
+ sdist = { url = "https://files.pythonhosted.org/packages/22/12/2948fbe5513d062169bd91f7d7b1cd97bc8894f32946b71fa39f6e63ca0c/idna-3.12.tar.gz", hash = "sha256:724e9952cc9e2bd7550ea784adb098d837ab5267ef67a1ab9cf7846bdbdd8254", size = 194350, upload-time = "2026-04-21T13:32:48.916Z" }
484
484
  wheels = [
485
- { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
485
+ { url = "https://files.pythonhosted.org/packages/53/b2/acc33950394b3becb2b664741a0c0889c7ef9f9ffbfa8d47eddb53a50abd/idna-3.12-py3-none-any.whl", hash = "sha256:60ffaa1858fac94c9c124728c24fcde8160f3fb4a7f79aa8cdd33a9d1af60a67", size = 68634, upload-time = "2026-04-21T13:32:47.403Z" },
486
486
  ]
487
487
 
488
488
  [[package]]
@@ -668,13 +668,36 @@ wheels = [
668
668
 
669
669
  [[package]]
670
670
  name = "object-storage-client"
671
- version = "0.0.20"
671
+ version = "0.0.22"
672
+ source = { registry = "https://pypi.org/simple" }
673
+ sdist = { url = "https://files.pythonhosted.org/packages/1a/49/9af56487b380cef1132c1aa718702095a0ab3ddb3e82bb4eab9593dde316/object_storage_client-0.0.22.tar.gz", hash = "sha256:00535617e1f50c0dab066e7dc6bdc9d1690e989febcfaacc27737988dd5a4101", size = 40622, upload-time = "2026-04-21T12:36:41.782Z" }
674
+ wheels = [
675
+ { url = "https://files.pythonhosted.org/packages/e3/0b/c9a4dc7ae9bc8f67877190d97c0efb94a0a9e7aef6423feaa45d93232215/object_storage_client-0.0.22-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db7d843f11e1d281ba16533bc573343ec9e0a8d2cf779cd43464df6adca0a5a8", size = 1922610, upload-time = "2026-04-21T12:36:40.278Z" },
676
+ { url = "https://files.pythonhosted.org/packages/7e/f3/3e34cc1e70e9f48ad0be5d7af1771d864a3ee618c07aad43978e2d3a131c/object_storage_client-0.0.22-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d7c92c2ca19f9d734be99162df7dc573535e7022e5a9b5c1dce121a102a85ae", size = 2114290, upload-time = "2026-04-21T12:36:37.82Z" },
677
+ { url = "https://files.pythonhosted.org/packages/ee/73/4af6bf9dc086e9c0a69988d2258d2f6f5d02d00ef891d9df3d6d08c3355a/object_storage_client-0.0.22-cp314-cp314-win_amd64.whl", hash = "sha256:df863a2a3f8f1c77d06bdc547d674b321878dca7cb7db94606b763cb9cc7e508", size = 2095414, upload-time = "2026-04-21T12:36:38.966Z" },
678
+ ]
679
+
680
+ [[package]]
681
+ name = "orjson"
682
+ version = "3.11.8"
672
683
  source = { registry = "https://pypi.org/simple" }
673
- sdist = { url = "https://files.pythonhosted.org/packages/af/b3/c2a0c4067d829f49d6e777a9c11f28b899226e117492c6f6ed230890b327/object_storage_client-0.0.20.tar.gz", hash = "sha256:ca4bf4ce6c7061081caeb8d11e05f34ec597829a8178e94f62b17dc7e9b12498", size = 39116, upload-time = "2026-03-20T17:27:56.377Z" }
684
+ sdist = { url = "https://files.pythonhosted.org/packages/9d/1b/2024d06792d0779f9dbc51531b61c24f76c75b9f4ce05e6f3377a1814cea/orjson-3.11.8.tar.gz", hash = "sha256:96163d9cdc5a202703e9ad1b9ae757d5f0ca62f4fa0cc93d1f27b0e180cc404e", size = 5603832, upload-time = "2026-03-31T16:16:27.878Z" }
674
685
  wheels = [
675
- { url = "https://files.pythonhosted.org/packages/30/56/5c32ba20a482dd861f6faa18372410fb4abaaca74f419d60ef925ba64d9e/object_storage_client-0.0.20-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7fe6781539b4f29aef6836411e100a946f22ac409926ab0b0ec9362a198451fd", size = 1914810, upload-time = "2026-03-20T17:27:51.906Z" },
676
- { url = "https://files.pythonhosted.org/packages/41/2d/2e5b7b195a0e4478e4ae08efb249746c7b0df8aa25f58aa913540882617e/object_storage_client-0.0.20-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0037016589a153f7c7d6e9c2b84cdd046d280420c8a4120cebe6cb1e7665a6", size = 2102642, upload-time = "2026-03-20T17:27:55.24Z" },
677
- { url = "https://files.pythonhosted.org/packages/68/84/256c571a540017429009c289b2cccf174fc0cf731bae5697a214f57e26c8/object_storage_client-0.0.20-cp314-cp314-win_amd64.whl", hash = "sha256:49003b38f22af4cdb60f62fed7b55890c93fba0e837f1c6a216f9566b4167629", size = 2095189, upload-time = "2026-03-20T17:28:00.145Z" },
686
+ { url = "https://files.pythonhosted.org/packages/6d/35/b01910c3d6b85dc882442afe5060cbf719c7d1fc85749294beda23d17873/orjson-3.11.8-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ec795530a73c269a55130498842aaa762e4a939f6ce481a7e986eeaa790e9da4", size = 229171, upload-time = "2026-03-31T16:16:00.651Z" },
687
+ { url = "https://files.pythonhosted.org/packages/c2/56/c9ec97bd11240abef39b9e5d99a15462809c45f677420fd148a6c5e6295e/orjson-3.11.8-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c492a0e011c0f9066e9ceaa896fbc5b068c54d365fea5f3444b697ee01bc8625", size = 128746, upload-time = "2026-03-31T16:16:02.673Z" },
688
+ { url = "https://files.pythonhosted.org/packages/3b/e4/66d4f30a90de45e2f0cbd9623588e8ae71eef7679dbe2ae954ed6d66a41f/orjson-3.11.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:883206d55b1bd5f5679ad5e6ddd3d1a5e3cac5190482927fdb8c78fb699193b5", size = 131867, upload-time = "2026-03-31T16:16:04.342Z" },
689
+ { url = "https://files.pythonhosted.org/packages/19/30/2a645fc9286b928675e43fa2a3a16fb7b6764aa78cc719dc82141e00f30b/orjson-3.11.8-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5774c1fdcc98b2259800b683b19599c133baeb11d60033e2095fd9d4667b82db", size = 124664, upload-time = "2026-03-31T16:16:05.837Z" },
690
+ { url = "https://files.pythonhosted.org/packages/db/44/77b9a86d84a28d52ba3316d77737f6514e17118119ade3f91b639e859029/orjson-3.11.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7381c83dd3d4a6347e6635950aa448f54e7b8406a27c7ecb4a37e9f1ae08b", size = 129701, upload-time = "2026-03-31T16:16:07.407Z" },
691
+ { url = "https://files.pythonhosted.org/packages/b3/ea/eff3d9bfe47e9bc6969c9181c58d9f71237f923f9c86a2d2f490cd898c82/orjson-3.11.8-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14439063aebcb92401c11afc68ee4e407258d2752e62d748b6942dad20d2a70d", size = 141202, upload-time = "2026-03-31T16:16:09.48Z" },
692
+ { url = "https://files.pythonhosted.org/packages/52/c8/90d4b4c60c84d62068d0cf9e4d8f0a4e05e76971d133ac0c60d818d4db20/orjson-3.11.8-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa72e71977bff96567b0f500fc5bfd2fdf915f34052c782a4c6ebbdaa97aa858", size = 127194, upload-time = "2026-03-31T16:16:11.02Z" },
693
+ { url = "https://files.pythonhosted.org/packages/8d/c7/ea9e08d1f0ba981adffb629811148b44774d935171e7b3d780ae43c4c254/orjson-3.11.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7679bc2f01bb0d219758f1a5f87bb7c8a81c0a186824a393b366876b4948e14f", size = 133639, upload-time = "2026-03-31T16:16:13.434Z" },
694
+ { url = "https://files.pythonhosted.org/packages/6c/8c/ddbbfd6ba59453c8fc7fe1d0e5983895864e264c37481b2a791db635f046/orjson-3.11.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:14f7b8fcb35ef403b42fa5ecfa4ed032332a91f3dc7368fbce4184d59e1eae0d", size = 141914, upload-time = "2026-03-31T16:16:14.955Z" },
695
+ { url = "https://files.pythonhosted.org/packages/4e/31/dbfbefec9df060d34ef4962cd0afcb6fa7a9ec65884cb78f04a7859526c3/orjson-3.11.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c2bdf7b2facc80b5e34f48a2d557727d5c5c57a8a450de122ae81fa26a81c1bc", size = 423800, upload-time = "2026-03-31T16:16:16.594Z" },
696
+ { url = "https://files.pythonhosted.org/packages/87/cf/f74e9ae9803d4ab46b163494adba636c6d7ea955af5cc23b8aaa94cfd528/orjson-3.11.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ccd7ba1b0605813a0715171d39ec4c314cb97a9c85893c2c5c0c3a3729df38bf", size = 147837, upload-time = "2026-03-31T16:16:18.585Z" },
697
+ { url = "https://files.pythonhosted.org/packages/64/e6/9214f017b5db85e84e68602792f742e5dc5249e963503d1b356bee611e01/orjson-3.11.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbc8c9c02463fef4d3c53a9ba3336d05496ec8e1f1c53326a1e4acc11f5c600", size = 136441, upload-time = "2026-03-31T16:16:20.151Z" },
698
+ { url = "https://files.pythonhosted.org/packages/24/dd/3590348818f58f837a75fb969b04cdf187ae197e14d60b5e5a794a38b79d/orjson-3.11.8-cp314-cp314-win32.whl", hash = "sha256:0b57f67710a8cd459e4e54eb96d5f77f3624eba0c661ba19a525807e42eccade", size = 131983, upload-time = "2026-03-31T16:16:21.823Z" },
699
+ { url = "https://files.pythonhosted.org/packages/3f/0f/b6cb692116e05d058f31ceee819c70f097fa9167c82f67fabe7516289abc/orjson-3.11.8-cp314-cp314-win_amd64.whl", hash = "sha256:735e2262363dcbe05c35e3a8869898022af78f89dde9e256924dc02e99fe69ca", size = 127396, upload-time = "2026-03-31T16:16:23.685Z" },
700
+ { url = "https://files.pythonhosted.org/packages/c0/d1/facb5b5051fabb0ef9d26c6544d87ef19a939a9a001198655d0d891062dd/orjson-3.11.8-cp314-cp314-win_arm64.whl", hash = "sha256:6ccdea2c213cf9f3d9490cbd5d427693c870753df41e6cb375bd79bcbafc8817", size = 127330, upload-time = "2026-03-31T16:16:25.496Z" },
678
701
  ]
679
702
 
680
703
  [[package]]
@@ -715,7 +738,7 @@ wheels = [
715
738
 
716
739
  [[package]]
717
740
  name = "pre-commit"
718
- version = "4.5.1"
741
+ version = "4.6.0"
719
742
  source = { registry = "https://pypi.org/simple" }
720
743
  dependencies = [
721
744
  { name = "cfgv" },
@@ -724,9 +747,9 @@ dependencies = [
724
747
  { name = "pyyaml" },
725
748
  { name = "virtualenv" },
726
749
  ]
727
- sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" }
750
+ sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" }
728
751
  wheels = [
729
- { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
752
+ { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" },
730
753
  ]
731
754
 
732
755
  [[package]]
@@ -999,6 +1022,7 @@ dependencies = [
999
1022
  { name = "msgpack" },
1000
1023
  { name = "msgspec" },
1001
1024
  { name = "object-storage-client" },
1025
+ { name = "orjson" },
1002
1026
  { name = "pydantic-settings" },
1003
1027
  { name = "sqlalchemy", extra = ["asyncio"] },
1004
1028
  { name = "valkey", extra = ["libvalkey"] },
@@ -1024,13 +1048,14 @@ testing = [
1024
1048
 
1025
1049
  [package.metadata]
1026
1050
  requires-dist = [
1027
- { name = "aiobotocore", specifier = "~=3.4.0" },
1051
+ { name = "aiobotocore", specifier = "~=3.5.0" },
1028
1052
  { name = "aiohttp", extras = ["speedups"], specifier = ">=3.13.5,<3.15.0" },
1029
1053
  { name = "asyncpg", specifier = "~=0.31.0" },
1030
1054
  { name = "lxml", specifier = "~=6.1.0" },
1031
1055
  { name = "msgpack", specifier = "~=1.1.2" },
1032
1056
  { name = "msgspec", specifier = "==0.21.1" },
1033
- { name = "object-storage-client", specifier = "==0.0.20" },
1057
+ { name = "object-storage-client", specifier = "==0.0.22" },
1058
+ { name = "orjson", specifier = "==3.11.8" },
1034
1059
  { name = "pydantic-settings", specifier = "~=2.14.0" },
1035
1060
  { name = "sqlalchemy", extras = ["asyncio"], specifier = "~=2.0.49" },
1036
1061
  { name = "valkey", extras = ["libvalkey"], specifier = "~=6.1.1" },
@@ -1,35 +0,0 @@
1
- import json
2
- import logging
3
- import traceback
4
- from contextvars import ContextVar
5
-
6
- from python3_commons.serializers.json import CustomJSONEncoder
7
-
8
- correlation_id: ContextVar[str | None] = ContextVar('correlation_id', default=None)
9
-
10
-
11
- class JSONFormatter(logging.Formatter):
12
- @staticmethod
13
- def format_exception(exc_info: logging._SysExcInfoType) -> str:
14
- return ''.join(traceback.format_exception(*exc_info))
15
-
16
- def format(self, record: logging.LogRecord) -> str:
17
- if corr_id := correlation_id.get():
18
- record.correlation_id = corr_id
19
-
20
- try:
21
- record.message = record.getMessage()
22
- except TypeError:
23
- record.message = str(record.msg)
24
-
25
- if record.exc_info:
26
- record.exc_text = self.format_exception(record.exc_info)
27
- else:
28
- record.exc_text = None
29
-
30
- record_dict = record.__dict__
31
-
32
- del record_dict['args']
33
- del record_dict['msg']
34
-
35
- return json.dumps(record_dict, cls=CustomJSONEncoder)