python3-commons 0.16.0__py3-none-any.whl → 0.16.2__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.
- python3_commons/db/__init__.py +13 -13
- python3_commons/log/formatters.py +108 -18
- python3_commons/object_storage.py +4 -4
- {python3_commons-0.16.0.dist-info → python3_commons-0.16.2.dist-info}/METADATA +1 -1
- {python3_commons-0.16.0.dist-info → python3_commons-0.16.2.dist-info}/RECORD +9 -9
- {python3_commons-0.16.0.dist-info → python3_commons-0.16.2.dist-info}/WHEEL +0 -0
- {python3_commons-0.16.0.dist-info → python3_commons-0.16.2.dist-info}/licenses/AUTHORS.rst +0 -0
- {python3_commons-0.16.0.dist-info → python3_commons-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {python3_commons-0.16.0.dist-info → python3_commons-0.16.2.dist-info}/top_level.txt +0 -0
python3_commons/db/__init__.py
CHANGED
|
@@ -17,28 +17,28 @@ Base = declarative_base(metadata=metadata)
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class AsyncSessionManager:
|
|
20
|
-
def __init__(self,
|
|
21
|
-
self.
|
|
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
|
|
25
|
+
def get_db_config(self, name: str) -> DBSettings:
|
|
26
26
|
try:
|
|
27
|
-
return self.
|
|
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
|
|
34
|
-
|
|
33
|
+
def async_engine_from_db_config(self, name):
|
|
34
|
+
db_config = self.get_db_config(name)
|
|
35
35
|
configuration = {
|
|
36
|
-
'url': str(
|
|
37
|
-
'echo':
|
|
38
|
-
'pool_size':
|
|
39
|
-
'max_overflow':
|
|
40
|
-
'pool_timeout':
|
|
41
|
-
'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.
|
|
51
|
+
engine = self.async_engine_from_db_config(name)
|
|
52
52
|
self.engines[name] = engine
|
|
53
53
|
|
|
54
54
|
return engine
|
|
@@ -1,35 +1,125 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import traceback
|
|
4
3
|
from contextvars import ContextVar
|
|
4
|
+
from datetime import UTC, datetime, date
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
from typing import Any, Final, Callable
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
import msgspec
|
|
7
9
|
|
|
8
10
|
correlation_id: ContextVar[str | None] = ContextVar('correlation_id', default=None)
|
|
9
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 (unchanged)
|
|
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
|
+
|
|
10
55
|
|
|
11
56
|
class JSONFormatter(logging.Formatter):
|
|
12
|
-
|
|
13
|
-
def format_exception(exc_info: logging._SysExcInfoType) -> str:
|
|
14
|
-
return ''.join(traceback.format_exception(*exc_info))
|
|
57
|
+
__slots__ = ('_get_correlation_id', '_max_tb_chars', '_encoder')
|
|
15
58
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
|
|
68
|
+
self._get_correlation_id = get_correlation_id
|
|
69
|
+
self._max_tb_chars = max_exc_tb_chars
|
|
70
|
+
self._encoder = msgspec.json.Encoder()
|
|
19
71
|
|
|
72
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
73
|
+
# -------------------------
|
|
74
|
+
# message (fast path)
|
|
75
|
+
# -------------------------
|
|
20
76
|
try:
|
|
21
|
-
|
|
22
|
-
except
|
|
23
|
-
|
|
77
|
+
message = record.getMessage()
|
|
78
|
+
except Exception:
|
|
79
|
+
message = str(record.msg)
|
|
80
|
+
|
|
81
|
+
# -------------------------
|
|
82
|
+
# timestamp (no Formatter overhead)
|
|
83
|
+
# -------------------------
|
|
84
|
+
timestamp = datetime.fromtimestamp(record.created, UTC).isoformat().replace('+00:00', 'Z')
|
|
85
|
+
|
|
86
|
+
# -------------------------
|
|
87
|
+
# base log object (plain dict)
|
|
88
|
+
# -------------------------
|
|
89
|
+
log: dict[str, Any] = {
|
|
90
|
+
'message': message,
|
|
91
|
+
'level': record.levelname,
|
|
92
|
+
'logger': record.name,
|
|
93
|
+
'timestamp': timestamp,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (corr_id := self._get_correlation_id()) is not None:
|
|
97
|
+
log['correlation_id'] = corr_id
|
|
98
|
+
|
|
99
|
+
if (exc_info := record.exc_info) and exc_info[0] is not None:
|
|
100
|
+
exc_type, exc_value, exc_tb = exc_info
|
|
24
101
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
102
|
+
log['exc_type'] = f'{exc_type.__module__}.{exc_type.__qualname__}'
|
|
103
|
+
log['exc_value'] = str(exc_value)
|
|
104
|
+
|
|
105
|
+
tb = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb)).rstrip()
|
|
106
|
+
cap = self._max_tb_chars
|
|
107
|
+
|
|
108
|
+
if cap and len(tb) > cap:
|
|
109
|
+
tb = tb[:cap] + '\n... <truncated>'
|
|
110
|
+
|
|
111
|
+
log['exc_traceback'] = tb
|
|
29
112
|
|
|
30
113
|
record_dict = record.__dict__
|
|
114
|
+
std_log_fields = _STD_LOG_FIELDS
|
|
115
|
+
|
|
116
|
+
if len(record_dict) > len(std_log_fields):
|
|
117
|
+
normalize = _normalize
|
|
118
|
+
out_set = log.__setitem__
|
|
31
119
|
|
|
32
|
-
|
|
33
|
-
|
|
120
|
+
for k, v in record_dict.items():
|
|
121
|
+
if k[0] == '_' or k in std_log_fields:
|
|
122
|
+
continue
|
|
123
|
+
out_set(k, normalize(v))
|
|
34
124
|
|
|
35
|
-
return
|
|
125
|
+
return self._encoder.encode(log).decode('utf-8')
|
|
@@ -135,7 +135,7 @@ async def list_objects(bucket_name: str, prefix: str, *, recursive: bool = True)
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
async def get_object_streams(
|
|
138
|
-
|
|
138
|
+
bucket_name: str, path: str, *, recursive: bool = True
|
|
139
139
|
) -> AsyncGenerator[tuple[str, datetime, StreamingBody]]:
|
|
140
140
|
async for obj in list_objects(bucket_name, path, recursive=recursive):
|
|
141
141
|
object_name = obj['Key']
|
|
@@ -146,7 +146,7 @@ async def get_object_streams(
|
|
|
146
146
|
|
|
147
147
|
|
|
148
148
|
async def get_objects(
|
|
149
|
-
|
|
149
|
+
bucket_name: str, path: str, *, recursive: bool = True
|
|
150
150
|
) -> AsyncGenerator[tuple[str, datetime, bytes]]:
|
|
151
151
|
async for object_name, last_modified, stream in get_object_streams(bucket_name, path, recursive=recursive):
|
|
152
152
|
data = await stream.read()
|
|
@@ -168,7 +168,7 @@ async def remove_object(bucket_name: str, object_name: str) -> None:
|
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
async def remove_objects(
|
|
171
|
-
|
|
171
|
+
bucket_name: str, prefix: str | None = None, object_names: Iterable[str] | None = None
|
|
172
172
|
) -> Sequence[Mapping] | None:
|
|
173
173
|
storage = ObjectStorage(s3_settings)
|
|
174
174
|
|
|
@@ -191,7 +191,7 @@ async def remove_objects(
|
|
|
191
191
|
chunk_size = 1000
|
|
192
192
|
|
|
193
193
|
for i in range(0, len(objects_to_delete), chunk_size):
|
|
194
|
-
chunk = objects_to_delete[i: i + chunk_size]
|
|
194
|
+
chunk = objects_to_delete[i : i + chunk_size]
|
|
195
195
|
|
|
196
196
|
response = await s3_client.delete_objects(Bucket=bucket_name, Delete={'Objects': chunk})
|
|
197
197
|
|
|
@@ -9,9 +9,9 @@ python3_commons/exceptions.py,sha256=EGjHZVBnsM6CeBfPMqhL0IPMKjDJ_2-Z-aSPXwq91LE
|
|
|
9
9
|
python3_commons/fs.py,sha256=dn8ZcwsQf9xcAEg6neoxLN6IzJbWpprfm8wV8S55BL0,337
|
|
10
10
|
python3_commons/generators.py,sha256=P6sKdCFhHT79-DZgzPU-qmwZvHg3wU8a75UFIwrycOY,1163
|
|
11
11
|
python3_commons/helpers.py,sha256=2U0XyiBhylPrPIdPkZ16jrJRDZBE-yKv9GjRIY4C-EA,4541
|
|
12
|
-
python3_commons/object_storage.py,sha256=
|
|
12
|
+
python3_commons/object_storage.py,sha256=XpsU6ZtSgcobMcNFArisc6lF8ygzLI2pkZ9iZ9u5RJk,7026
|
|
13
13
|
python3_commons/permissions.py,sha256=yIqOy98sZqK7xexcvOaKzE4re088-HiLP8DuFStA35Q,1588
|
|
14
|
-
python3_commons/db/__init__.py,sha256=
|
|
14
|
+
python3_commons/db/__init__.py,sha256=zgEVqczGZP7NiodUqV03H7Dmy7Ph8aV8HEkuZvnSRE4,2958
|
|
15
15
|
python3_commons/db/helpers.py,sha256=sXt7vwP4j_KSNScin0UG7KugDW2eeIuQIIi5v4iYlGo,1862
|
|
16
16
|
python3_commons/db/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
python3_commons/db/models/auth.py,sha256=WW2dkoD5qpm2nBSVA081RY5Xh41m0Rrwk7fe5HdGZnI,634
|
|
@@ -20,15 +20,15 @@ python3_commons/db/models/rbac.py,sha256=ARRQ1tn_GThxiBiIs_6IKkGnIIv37SmVgSi90cb
|
|
|
20
20
|
python3_commons/db/models/users.py,sha256=zNN3bNWl3cVE6dJtTPzmPSo_5d1SsBZwwkvIs-WzUBA,644
|
|
21
21
|
python3_commons/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
python3_commons/log/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
|
|
23
|
-
python3_commons/log/formatters.py,sha256=
|
|
23
|
+
python3_commons/log/formatters.py,sha256=uFZit5Xl7k2_hAVjqVG8-6nwFR3C7fKoeuW2jiz2S8M,3496
|
|
24
24
|
python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
python3_commons/serializers/common.py,sha256=VkA7C6wODvHk0QBXVX_x2JieDstihx3U__UFbTYf654,120
|
|
26
26
|
python3_commons/serializers/json.py,sha256=UPkC3ps13x2C_NxwVV-K7Ewp4VjkVHSSUkJVw5k7Wiw,712
|
|
27
27
|
python3_commons/serializers/msgpack.py,sha256=zESFBX34GsZ8rDu6Zk5V6CLT6P0mPilU0r04Ka6TblI,1474
|
|
28
28
|
python3_commons/serializers/msgspec.py,sha256=upy5CBmK66-8hYnK5bAM_sZvZY5CAqZmzCw9GIF346I,2988
|
|
29
|
-
python3_commons-0.16.
|
|
30
|
-
python3_commons-0.16.
|
|
31
|
-
python3_commons-0.16.
|
|
32
|
-
python3_commons-0.16.
|
|
33
|
-
python3_commons-0.16.
|
|
34
|
-
python3_commons-0.16.
|
|
29
|
+
python3_commons-0.16.2.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
|
|
30
|
+
python3_commons-0.16.2.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
|
|
31
|
+
python3_commons-0.16.2.dist-info/METADATA,sha256=VHzo-ZDsPUIiAdIK3N2CHiOkc6EvdUgsSUjB6yAHBTc,1021
|
|
32
|
+
python3_commons-0.16.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
33
|
+
python3_commons-0.16.2.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
|
|
34
|
+
python3_commons-0.16.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|