python3-commons 0.0.0__py3-none-any.whl → 0.2.16__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.
Potentially problematic release.
This version of python3-commons might be problematic. Click here for more details.
- python3_commons/api_client.py +44 -16
- python3_commons/audit.py +127 -138
- python3_commons/auth.py +53 -47
- python3_commons/cache.py +36 -38
- python3_commons/conf.py +37 -6
- python3_commons/db/__init__.py +15 -10
- python3_commons/db/helpers.py +5 -7
- python3_commons/db/models/__init__.py +8 -2
- python3_commons/db/models/auth.py +2 -2
- python3_commons/db/models/common.py +8 -6
- python3_commons/db/models/rbac.py +5 -5
- python3_commons/fs.py +2 -2
- python3_commons/helpers.py +44 -13
- python3_commons/object_storage.py +135 -73
- python3_commons/permissions.py +2 -4
- python3_commons/serializers/common.py +8 -0
- python3_commons/serializers/json.py +5 -7
- python3_commons/serializers/msgpack.py +19 -21
- python3_commons/serializers/msgspec.py +50 -27
- {python3_commons-0.0.0.dist-info → python3_commons-0.2.16.dist-info}/METADATA +13 -13
- python3_commons-0.2.16.dist-info/RECORD +30 -0
- {python3_commons-0.0.0.dist-info → python3_commons-0.2.16.dist-info}/WHEEL +1 -1
- python3_commons-0.0.0.dist-info/RECORD +0 -29
- /python3_commons/{logging → log}/__init__.py +0 -0
- /python3_commons/{logging → log}/filters.py +0 -0
- /python3_commons/{logging → log}/formatters.py +0 -0
- {python3_commons-0.0.0.dist-info → python3_commons-0.2.16.dist-info}/licenses/AUTHORS.rst +0 -0
- {python3_commons-0.0.0.dist-info → python3_commons-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {python3_commons-0.0.0.dist-info → python3_commons-0.2.16.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
|
-
from contextlib import
|
|
4
|
-
from
|
|
5
|
-
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import aiobotocore.session
|
|
8
|
+
from botocore.config import Config
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import io
|
|
12
|
+
from collections.abc import AsyncGenerator, Iterable, Mapping, Sequence
|
|
13
|
+
from datetime import datetime
|
|
6
14
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from minio.deleteobjects import DeleteError, DeleteObject
|
|
15
|
+
from aiobotocore.response import StreamingBody
|
|
16
|
+
from types_aiobotocore_s3.client import S3Client
|
|
10
17
|
|
|
11
18
|
from python3_commons.conf import S3Settings, s3_settings
|
|
12
19
|
from python3_commons.helpers import SingletonMeta
|
|
@@ -16,25 +23,33 @@ logger = logging.getLogger(__name__)
|
|
|
16
23
|
|
|
17
24
|
class ObjectStorage(metaclass=SingletonMeta):
|
|
18
25
|
def __init__(self, settings: S3Settings):
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
self._session = aiobotocore.session.get_session()
|
|
27
|
+
config = {
|
|
28
|
+
'region_name': settings.aws_region,
|
|
29
|
+
'use_ssl': settings.s3_secure,
|
|
30
|
+
'verify': settings.s3_cert_verify,
|
|
31
|
+
'config': Config(s3={'addressing_style': settings.s3_addressing_style}, signature_version='s3v4'),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if s3_endpoint_url := settings.s3_endpoint_url:
|
|
35
|
+
config['endpoint_url'] = s3_endpoint_url
|
|
36
|
+
|
|
37
|
+
if aws_access_key_id := settings.aws_access_key_id:
|
|
38
|
+
config['aws_access_key_id'] = aws_access_key_id.get_secret_value()
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
region=settings.s3_region_name,
|
|
25
|
-
access_key=settings.s3_access_key_id.get_secret_value(),
|
|
26
|
-
secret_key=settings.s3_secret_access_key.get_secret_value(),
|
|
27
|
-
secure=settings.s3_secure,
|
|
28
|
-
cert_check=settings.s3_cert_verify,
|
|
29
|
-
)
|
|
40
|
+
if aws_secret_access_key := settings.aws_secret_access_key:
|
|
41
|
+
config['aws_secret_access_key'] = aws_secret_access_key.get_secret_value()
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
self._config = config
|
|
44
|
+
|
|
45
|
+
@asynccontextmanager
|
|
46
|
+
async def get_client(self) -> AsyncGenerator[S3Client]:
|
|
47
|
+
async with self._session.create_client('s3', **self._config) as client:
|
|
48
|
+
yield client
|
|
33
49
|
|
|
34
50
|
|
|
35
51
|
def get_absolute_path(path: str) -> str:
|
|
36
|
-
|
|
37
|
-
path = path[1:]
|
|
52
|
+
path = path.removeprefix('/')
|
|
38
53
|
|
|
39
54
|
if bucket_root := s3_settings.s3_bucket_root:
|
|
40
55
|
path = f'{bucket_root[:1] if bucket_root.startswith("/") else bucket_root}/{path}'
|
|
@@ -42,86 +57,133 @@ def get_absolute_path(path: str) -> str:
|
|
|
42
57
|
return path
|
|
43
58
|
|
|
44
59
|
|
|
45
|
-
def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int, part_size: int = 0) -> str | None:
|
|
46
|
-
|
|
47
|
-
result = s3_client.put_object(bucket_name, path, data, length, part_size=part_size)
|
|
60
|
+
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)
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
async with storage.get_client() as s3_client:
|
|
64
|
+
try:
|
|
65
|
+
data.seek(0)
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
else:
|
|
53
|
-
logger.warning('No S3 client available, skipping object put')
|
|
67
|
+
await s3_client.put_object(Bucket=bucket_name, Key=path, Body=data, ContentLength=length)
|
|
54
68
|
|
|
69
|
+
logger.debug(f'Stored object into object storage: {bucket_name}:{path}')
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.exception(f'Failed to put object to object storage: {bucket_name}:{path}', exc_info=e)
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
raise
|
|
74
|
+
|
|
75
|
+
return f's3://{bucket_name}/{path}'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@asynccontextmanager
|
|
79
|
+
async def get_object_stream(bucket_name: str, path: str) -> AsyncGenerator[StreamingBody]:
|
|
80
|
+
storage = ObjectStorage(s3_settings)
|
|
81
|
+
|
|
82
|
+
async with storage.get_client() as s3_client:
|
|
59
83
|
logger.debug(f'Getting object from object storage: {bucket_name}:{path}')
|
|
60
84
|
|
|
61
85
|
try:
|
|
62
|
-
response = s3_client.get_object(bucket_name, path)
|
|
86
|
+
response = await s3_client.get_object(Bucket=bucket_name, Key=path)
|
|
87
|
+
|
|
88
|
+
async with response['Body'] as stream:
|
|
89
|
+
yield stream
|
|
63
90
|
except Exception as e:
|
|
64
|
-
logger.
|
|
91
|
+
logger.exception(f'Failed getting object from object storage: {bucket_name}:{path}', exc_info=e)
|
|
65
92
|
|
|
66
93
|
raise
|
|
67
94
|
|
|
68
|
-
yield response
|
|
69
|
-
|
|
70
|
-
response.close()
|
|
71
|
-
response.release_conn()
|
|
72
|
-
else:
|
|
73
|
-
logger.warning('No S3 client available, skipping object put')
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
body = stream.read()
|
|
96
|
+
async def get_object(bucket_name: str, path: str) -> bytes:
|
|
97
|
+
async with get_object_stream(bucket_name, path) as stream:
|
|
98
|
+
body = await stream.read()
|
|
79
99
|
|
|
80
100
|
logger.debug(f'Loaded object from object storage: {bucket_name}:{path}')
|
|
81
101
|
|
|
82
102
|
return body
|
|
83
103
|
|
|
84
104
|
|
|
85
|
-
def list_objects(bucket_name: str, prefix: str, recursive: bool = True) ->
|
|
86
|
-
|
|
105
|
+
async def list_objects(bucket_name: str, prefix: str, *, recursive: bool = True) -> AsyncGenerator[Mapping]:
|
|
106
|
+
storage = ObjectStorage(s3_settings)
|
|
107
|
+
|
|
108
|
+
async with storage.get_client() as s3_client:
|
|
109
|
+
paginator = s3_client.get_paginator('list_objects_v2')
|
|
87
110
|
|
|
88
|
-
|
|
111
|
+
page_iterator = paginator.paginate(Bucket=bucket_name, Prefix=prefix, Delimiter='' if recursive else '/')
|
|
89
112
|
|
|
113
|
+
async for page in page_iterator:
|
|
114
|
+
if 'Contents' in page:
|
|
115
|
+
for obj in page['Contents']:
|
|
116
|
+
yield dict(obj)
|
|
90
117
|
|
|
91
|
-
def get_objects(
|
|
92
|
-
bucket_name: str, path: str, recursive: bool = True
|
|
93
|
-
) -> Generator[tuple[str, datetime, bytes], None, None]:
|
|
94
|
-
for obj in list_objects(bucket_name, path, recursive):
|
|
95
|
-
object_name = obj.object_name
|
|
96
118
|
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
async def get_object_streams(
|
|
120
|
+
bucket_name: str, path: str, *, recursive: bool = True
|
|
121
|
+
) -> AsyncGenerator[tuple[str, datetime, StreamingBody]]:
|
|
122
|
+
async for obj in list_objects(bucket_name, path, recursive=recursive):
|
|
123
|
+
object_name = obj['Key']
|
|
124
|
+
last_modified = obj['LastModified']
|
|
125
|
+
|
|
126
|
+
async with get_object_stream(bucket_name, path) as stream:
|
|
127
|
+
yield object_name, last_modified, stream
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def get_objects(
|
|
131
|
+
bucket_name: str, path: str, *, recursive: bool = True
|
|
132
|
+
) -> AsyncGenerator[tuple[str, datetime, bytes]]:
|
|
133
|
+
async for object_name, last_modified, stream in get_object_streams(bucket_name, path, recursive=recursive):
|
|
134
|
+
data = await stream.read()
|
|
135
|
+
|
|
136
|
+
yield object_name, last_modified, data
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def remove_object(bucket_name: str, object_name: str):
|
|
140
|
+
storage = ObjectStorage(s3_settings)
|
|
141
|
+
|
|
142
|
+
async with storage.get_client() as s3_client:
|
|
143
|
+
try:
|
|
144
|
+
await s3_client.delete_object(Bucket=bucket_name, Key=object_name)
|
|
145
|
+
logger.debug(f'Removed object from object storage: {bucket_name}:{object_name}')
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.exception(f'Failed to remove object from object storage: {bucket_name}:{object_name}', exc_info=e)
|
|
148
|
+
|
|
149
|
+
raise
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def remove_objects(
|
|
153
|
+
bucket_name: str, prefix: str | None = None, object_names: Iterable[str] | None = None
|
|
154
|
+
) -> Sequence[Mapping] | None:
|
|
155
|
+
storage = ObjectStorage(s3_settings)
|
|
156
|
+
|
|
157
|
+
async with storage.get_client() as s3_client:
|
|
158
|
+
if prefix:
|
|
159
|
+
objects_to_delete = tuple(
|
|
160
|
+
{'Key': obj['Key']} async for obj in list_objects(bucket_name, prefix, recursive=True)
|
|
161
|
+
)
|
|
162
|
+
elif object_names:
|
|
163
|
+
objects_to_delete = tuple({'Key': name} for name in object_names)
|
|
99
164
|
else:
|
|
100
|
-
|
|
165
|
+
return None
|
|
101
166
|
|
|
102
|
-
|
|
167
|
+
if not objects_to_delete:
|
|
168
|
+
return None
|
|
103
169
|
|
|
170
|
+
try:
|
|
171
|
+
errors = []
|
|
172
|
+
# S3 delete_objects can handle up to 1000 objects at once
|
|
173
|
+
chunk_size = 1000
|
|
104
174
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
s3_client.remove_object(bucket_name, object_name)
|
|
175
|
+
for i in range(0, len(objects_to_delete), chunk_size):
|
|
176
|
+
chunk = objects_to_delete[i : i + chunk_size]
|
|
108
177
|
|
|
178
|
+
response = await s3_client.delete_objects(Bucket=bucket_name, Delete={'Objects': chunk})
|
|
109
179
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
) -> Iterable[DeleteError] | None:
|
|
113
|
-
s3_client = ObjectStorage(s3_settings).get_client()
|
|
180
|
+
if 'Errors' in response:
|
|
181
|
+
errors.extend(response['Errors'])
|
|
114
182
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
s3_client.list_objects(bucket_name, prefix=prefix, recursive=True),
|
|
119
|
-
)
|
|
120
|
-
elif object_names:
|
|
121
|
-
delete_object_list = map(DeleteObject, object_names)
|
|
122
|
-
else:
|
|
123
|
-
return None
|
|
183
|
+
logger.debug(f'Removed {len(objects_to_delete)} objects from object storage: {bucket_name}')
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.exception(f'Failed to remove objects from object storage: {bucket_name}', exc_info=e)
|
|
124
186
|
|
|
125
|
-
|
|
187
|
+
raise
|
|
126
188
|
|
|
127
|
-
|
|
189
|
+
return errors if errors else None
|
python3_commons/permissions.py
CHANGED
|
@@ -24,9 +24,8 @@ async def has_api_key_permission(session: AsyncSession, api_key_uid: UUID, permi
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
cursor = await session.execute(query)
|
|
27
|
-
result = cursor.scalar()
|
|
28
27
|
|
|
29
|
-
return
|
|
28
|
+
return bool(cursor.scalar())
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
async def has_user_permission(session: AsyncSession, user_id: UUID, permission: str) -> bool:
|
|
@@ -43,6 +42,5 @@ async def has_user_permission(session: AsyncSession, user_id: UUID, permission:
|
|
|
43
42
|
)
|
|
44
43
|
|
|
45
44
|
cursor = await session.execute(query)
|
|
46
|
-
result = cursor.scalar()
|
|
47
45
|
|
|
48
|
-
return
|
|
46
|
+
return bool(cursor.scalar())
|
|
@@ -10,17 +10,15 @@ from typing import Any
|
|
|
10
10
|
class CustomJSONEncoder(json.JSONEncoder):
|
|
11
11
|
def default(self, o) -> Any:
|
|
12
12
|
try:
|
|
13
|
-
return super(
|
|
13
|
+
return super().default(o)
|
|
14
14
|
except TypeError:
|
|
15
|
-
if isinstance(o, datetime):
|
|
15
|
+
if isinstance(o, (datetime, date)):
|
|
16
16
|
return o.isoformat()
|
|
17
|
-
|
|
18
|
-
return o.isoformat()
|
|
19
|
-
elif isinstance(o, bytes):
|
|
17
|
+
if isinstance(o, bytes):
|
|
20
18
|
return base64.b64encode(o).decode('ascii')
|
|
21
|
-
|
|
19
|
+
if dataclasses.is_dataclass(o):
|
|
22
20
|
return dataclasses.asdict(o)
|
|
23
|
-
|
|
21
|
+
if isinstance(o, (Decimal, socket, type, Exception)):
|
|
24
22
|
return str(o)
|
|
25
23
|
|
|
26
24
|
return type(o).__name__
|
|
@@ -7,6 +7,7 @@ from decimal import Decimal
|
|
|
7
7
|
import msgpack
|
|
8
8
|
from msgpack import ExtType
|
|
9
9
|
|
|
10
|
+
from python3_commons.serializers.common import ExtendedType
|
|
10
11
|
from python3_commons.serializers.json import CustomJSONEncoder
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
@@ -14,37 +15,34 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
def msgpack_encoder(obj):
|
|
16
17
|
if isinstance(obj, Decimal):
|
|
17
|
-
return ExtType(
|
|
18
|
-
|
|
19
|
-
return ExtType(
|
|
20
|
-
|
|
21
|
-
return ExtType(
|
|
22
|
-
|
|
23
|
-
return ExtType(
|
|
18
|
+
return ExtType(ExtendedType.DECIMAL, str(obj).encode())
|
|
19
|
+
if isinstance(obj, datetime):
|
|
20
|
+
return ExtType(ExtendedType.DATETIME, obj.isoformat().encode())
|
|
21
|
+
if isinstance(obj, date):
|
|
22
|
+
return ExtType(ExtendedType.DATE, obj.isoformat().encode())
|
|
23
|
+
if dataclasses.is_dataclass(obj):
|
|
24
|
+
return ExtType(ExtendedType.DATACLASS, json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode())
|
|
24
25
|
|
|
25
26
|
return f'no encoder for {obj}'
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def msgpack_decoder(code, data):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
match code:
|
|
31
|
+
case ExtendedType.DECIMAL:
|
|
32
|
+
return Decimal(data.decode())
|
|
33
|
+
case ExtendedType.DATETIME:
|
|
34
|
+
return datetime.fromisoformat(data.decode())
|
|
35
|
+
case ExtendedType.DATE:
|
|
36
|
+
return date.fromisoformat(data.decode())
|
|
37
|
+
case ExtendedType.DATACLASS:
|
|
38
|
+
return json.loads(data)
|
|
37
39
|
|
|
38
40
|
return f'no decoder for type {code}'
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def serialize_msgpack(data) -> bytes:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return result
|
|
44
|
+
return msgpack.packb(data, default=msgpack_encoder)
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def deserialize_msgpack(data: bytes):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return result
|
|
48
|
+
return msgpack.unpackb(data, ext_hook=msgpack_decoder)
|
|
@@ -2,42 +2,53 @@ import dataclasses
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import struct
|
|
5
|
-
from _decimal import Decimal
|
|
6
5
|
from datetime import date, datetime
|
|
7
|
-
from
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from typing import Any, TypeVar
|
|
8
8
|
|
|
9
9
|
from msgspec import msgpack
|
|
10
10
|
from msgspec.msgpack import Ext, encode
|
|
11
|
+
from pydantic import BaseModel
|
|
11
12
|
|
|
13
|
+
from python3_commons.serializers.common import ExtendedType
|
|
12
14
|
from python3_commons.serializers.json import CustomJSONEncoder
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
17
|
+
T = TypeVar('T')
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
def enc_hook(obj: Any) -> Any:
|
|
18
21
|
if isinstance(obj, Decimal):
|
|
19
|
-
return Ext(
|
|
20
|
-
|
|
21
|
-
return Ext(
|
|
22
|
-
|
|
23
|
-
return Ext(
|
|
24
|
-
|
|
25
|
-
return Ext(
|
|
22
|
+
return Ext(ExtendedType.DECIMAL, struct.pack('b', str(obj).encode()))
|
|
23
|
+
if isinstance(obj, datetime):
|
|
24
|
+
return Ext(ExtendedType.DATETIME, struct.pack('b', obj.isoformat().encode()))
|
|
25
|
+
if isinstance(obj, date):
|
|
26
|
+
return Ext(ExtendedType.DATE, struct.pack('b', obj.isoformat().encode()))
|
|
27
|
+
if dataclasses.is_dataclass(obj):
|
|
28
|
+
return Ext(
|
|
29
|
+
ExtendedType.DATACLASS,
|
|
30
|
+
struct.pack('b', json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode()),
|
|
31
|
+
)
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
msg = f'Objects of type {type(obj)} are not supported'
|
|
34
|
+
|
|
35
|
+
raise NotImplementedError(msg)
|
|
28
36
|
|
|
29
37
|
|
|
30
38
|
def ext_hook(code: int, data: memoryview) -> Any:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
match code:
|
|
40
|
+
case ExtendedType.DECIMAL:
|
|
41
|
+
return Decimal(data.tobytes().decode())
|
|
42
|
+
case ExtendedType.DATETIME:
|
|
43
|
+
return datetime.fromisoformat(data.tobytes().decode())
|
|
44
|
+
case ExtendedType.DATE:
|
|
45
|
+
return date.fromisoformat(data.tobytes().decode())
|
|
46
|
+
case ExtendedType.DATACLASS:
|
|
47
|
+
return json.loads(data.tobytes())
|
|
48
|
+
case _:
|
|
49
|
+
msg = f'Extension type code {code} is not supported'
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
raise NotImplementedError(msg)
|
|
41
52
|
|
|
42
53
|
|
|
43
54
|
MSGPACK_ENCODER = msgpack.Encoder(enc_hook=enc_hook)
|
|
@@ -45,28 +56,40 @@ MSGPACK_DECODER = msgpack.Decoder(ext_hook=ext_hook)
|
|
|
45
56
|
MSGPACK_DECODER_NATIVE = msgpack.Decoder()
|
|
46
57
|
|
|
47
58
|
|
|
48
|
-
def serialize_msgpack_native(data) -> bytes:
|
|
59
|
+
def serialize_msgpack_native(data: Any) -> bytes:
|
|
60
|
+
if isinstance(data, BaseModel):
|
|
61
|
+
data = data.model_dump()
|
|
62
|
+
|
|
49
63
|
return encode(data)
|
|
50
64
|
|
|
51
65
|
|
|
52
|
-
def deserialize_msgpack_native(data: bytes, data_type=None):
|
|
66
|
+
def deserialize_msgpack_native[T](data: bytes, data_type: type[T] | None = None) -> T | Any:
|
|
53
67
|
if data_type:
|
|
54
|
-
|
|
68
|
+
if issubclass(data_type, BaseModel):
|
|
69
|
+
decoded = MSGPACK_DECODER_NATIVE.decode(data)
|
|
70
|
+
result = data_type.model_validate(decoded)
|
|
71
|
+
else:
|
|
72
|
+
result = msgpack.decode(data, type=data_type)
|
|
55
73
|
else:
|
|
56
74
|
result = MSGPACK_DECODER_NATIVE.decode(data)
|
|
57
75
|
|
|
58
76
|
return result
|
|
59
77
|
|
|
60
78
|
|
|
61
|
-
def serialize_msgpack(data) -> bytes:
|
|
62
|
-
|
|
79
|
+
def serialize_msgpack(data: Any) -> bytes:
|
|
80
|
+
if isinstance(data, BaseModel):
|
|
81
|
+
data = data.model_dump()
|
|
63
82
|
|
|
64
|
-
return
|
|
83
|
+
return MSGPACK_ENCODER.encode(data)
|
|
65
84
|
|
|
66
85
|
|
|
67
|
-
def deserialize_msgpack(data: bytes, data_type=None):
|
|
86
|
+
def deserialize_msgpack[T](data: bytes, data_type: type[T] | None = None) -> T | Any:
|
|
68
87
|
if data_type:
|
|
69
|
-
|
|
88
|
+
if issubclass(data_type, BaseModel):
|
|
89
|
+
decoded = MSGPACK_DECODER.decode(data)
|
|
90
|
+
result = data_type.model_validate(decoded)
|
|
91
|
+
else:
|
|
92
|
+
result = msgpack.decode(data, type=data_type)
|
|
70
93
|
else:
|
|
71
94
|
result = MSGPACK_DECODER.decode(data)
|
|
72
95
|
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.16
|
|
4
4
|
Summary: Re-usable Python3 code
|
|
5
5
|
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
|
6
|
-
License:
|
|
6
|
+
License-Expression: GPL-3.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/kamikaze/python3-commons
|
|
8
8
|
Project-URL: Documentation, https://github.com/kamikaze/python3-commons/wiki
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Programming Language :: Python
|
|
11
|
-
Requires-Python:
|
|
11
|
+
Requires-Python: <3.15.0,>=3.13.9
|
|
12
12
|
Description-Content-Type: text/x-rst
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
License-File: AUTHORS.rst
|
|
15
|
-
Requires-Dist:
|
|
15
|
+
Requires-Dist: aiobotocore~=2.25.0
|
|
16
|
+
Requires-Dist: aiohttp[speedups]<3.15.0,>=3.12.0
|
|
16
17
|
Requires-Dist: asyncpg~=0.30.0
|
|
17
18
|
Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
|
|
18
19
|
Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
|
|
19
|
-
Requires-Dist: lxml~=
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist: msgpack~=1.1.0
|
|
20
|
+
Requires-Dist: lxml~=6.0.2
|
|
21
|
+
Requires-Dist: msgpack~=1.1.2
|
|
22
22
|
Requires-Dist: msgspec~=0.19.0
|
|
23
|
-
Requires-Dist: pydantic[email]~=2.
|
|
24
|
-
Requires-Dist: pydantic-settings~=2.
|
|
25
|
-
Requires-Dist: python-jose==3.
|
|
26
|
-
Requires-Dist: SQLAlchemy[asyncio]~=2.0.
|
|
27
|
-
Requires-Dist: valkey[libvalkey]~=6.1.
|
|
28
|
-
Requires-Dist: zeep~=4.3.
|
|
23
|
+
Requires-Dist: pydantic[email]~=2.12.3
|
|
24
|
+
Requires-Dist: pydantic-settings~=2.11.0
|
|
25
|
+
Requires-Dist: python-jose==3.5.0
|
|
26
|
+
Requires-Dist: SQLAlchemy[asyncio]~=2.0.44
|
|
27
|
+
Requires-Dist: valkey[libvalkey]~=6.1.1
|
|
28
|
+
Requires-Dist: zeep~=4.3.2
|
|
29
29
|
Dynamic: license-file
|
|
30
30
|
|
|
31
31
|
Re-usable Python3 code
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
|
|
2
|
+
python3_commons/api_client.py,sha256=0PE8iYW5zq7n89veC6afSkwj_fzD_nldBc5wdXyg5jo,5266
|
|
3
|
+
python3_commons/audit.py,sha256=vPwGR0s7RoDpxhK1Q2N44T1pdannxmSgpqcA5hn5bPE,6078
|
|
4
|
+
python3_commons/auth.py,sha256=uJRxsmeQpJxfUs1bAbshb3wn58OdD2UjVcXAYB3hnQc,2948
|
|
5
|
+
python3_commons/cache.py,sha256=-3CvGo1FH5H8N_gUClEcvWgz5VWU5YlgQa48PXfdmMY,7728
|
|
6
|
+
python3_commons/conf.py,sha256=77nqTBL5bAk8iOEdtdHWjYx8wLKtHBj6TCylSo3BWQk,2392
|
|
7
|
+
python3_commons/fs.py,sha256=dn8ZcwsQf9xcAEg6neoxLN6IzJbWpprfm8wV8S55BL0,337
|
|
8
|
+
python3_commons/helpers.py,sha256=RpbHU04MXQ-b2GC0RZlmkcpu33KCU4Mq8-qa2hbNbgA,4027
|
|
9
|
+
python3_commons/object_storage.py,sha256=dhTCHz_SbttB9e4wkzp3jxs-fASFa2L-F-PdkG5bedI,6584
|
|
10
|
+
python3_commons/permissions.py,sha256=n6q4mSlBkiIeQMNFydQoXCkyu-TovAEzYK_Cvg2rU6g,1527
|
|
11
|
+
python3_commons/db/__init__.py,sha256=gizEDoNNWMWyaLSpb01qXmyViKPaz1XRrk_fOZ9AveU,2918
|
|
12
|
+
python3_commons/db/helpers.py,sha256=n56yYCE0fvzvU7nL1936NfZhbaQmvfumzRsGimBlNV4,1776
|
|
13
|
+
python3_commons/db/models/__init__.py,sha256=zjZCf0DNDkqmPZ49quJ6KZohtKH87viI_ijDG3E0PVE,554
|
|
14
|
+
python3_commons/db/models/auth.py,sha256=NMHirujigpaRR0Bhhe2gzy8Q8PPABuaA-D8ZY7aaqeE,1177
|
|
15
|
+
python3_commons/db/models/common.py,sha256=nRLQVi7Y0SsXo3qMIwQX6GuDO9kHnlma4O_mYXQVtHQ,1512
|
|
16
|
+
python3_commons/db/models/rbac.py,sha256=BIB7nJXQkCes0XA-fg-oCHP6YU0_rXIm29O73j4pNUg,3160
|
|
17
|
+
python3_commons/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
python3_commons/log/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
|
|
19
|
+
python3_commons/log/formatters.py,sha256=p2AtZD4Axp3Em0e9gWzW8U_yOR5entD7xn7Edvc-IuM,719
|
|
20
|
+
python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
python3_commons/serializers/common.py,sha256=VkA7C6wODvHk0QBXVX_x2JieDstihx3U__UFbTYf654,120
|
|
22
|
+
python3_commons/serializers/json.py,sha256=UPkC3ps13x2C_NxwVV-K7Ewp4VjkVHSSUkJVw5k7Wiw,712
|
|
23
|
+
python3_commons/serializers/msgpack.py,sha256=mKQwfjPHh3BiXYCZLAhBsEhys8DxKHMZJIq-gDgqGDM,1451
|
|
24
|
+
python3_commons/serializers/msgspec.py,sha256=CAqlaZnDh-jiT6B_TsxXF9AojROMcV3bBd0Kp8ZFAzY,2952
|
|
25
|
+
python3_commons-0.2.16.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
|
|
26
|
+
python3_commons-0.2.16.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
|
|
27
|
+
python3_commons-0.2.16.dist-info/METADATA,sha256=sb_YFXSqR_VJUydRe_5exjDz2MxDchNInjlTkDzcPX8,1149
|
|
28
|
+
python3_commons-0.2.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
python3_commons-0.2.16.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
|
|
30
|
+
python3_commons-0.2.16.dist-info/RECORD,,
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
|
|
2
|
-
python3_commons/api_client.py,sha256=LT7_YmnYVHK2ucKxIhUJCZrmxgfy-lfOxx08-R0WvW0,4505
|
|
3
|
-
python3_commons/audit.py,sha256=osx2ywZXf-V0zOkrhlNgSyzCBvojXQwSYBQ4-ze1xiM,6249
|
|
4
|
-
python3_commons/auth.py,sha256=X8mo7L0dhy1EwzHjOkjBAPvHCCqeTS6yhkcVfVi89cU,2459
|
|
5
|
-
python3_commons/cache.py,sha256=lf27LTD4Z9Iqi5GaK8jH8UC0cL9sHH8wicZ88YDp6Mg,7725
|
|
6
|
-
python3_commons/conf.py,sha256=1VebgIPy195dX4mHDSsDRt-8rE1WrloxKGR-dP0PzBw,1466
|
|
7
|
-
python3_commons/fs.py,sha256=wfLjybXndwLqNlOxTpm_HRJnuTcC4wbrHEOaEeCo9Wc,337
|
|
8
|
-
python3_commons/helpers.py,sha256=ygnTv3KYoiibOFIi99-g8EXaETKHLt5i3jvykGrv6aE,3079
|
|
9
|
-
python3_commons/object_storage.py,sha256=nQsXca0zzzeSY35qhnjE6pLfkLuxn7jDul0-hw0jizE,3985
|
|
10
|
-
python3_commons/permissions.py,sha256=bhjTp-tq-oaTGFMHNnSBlcVX5XQCTL0nWcu6SdPEAB4,1555
|
|
11
|
-
python3_commons/db/__init__.py,sha256=ONlvuAYEagLeSdU9oX02JBpECvWdqNx0OoR9BMCvAIQ,2741
|
|
12
|
-
python3_commons/db/helpers.py,sha256=PY0h08aLiGx-J54wmP3GHPCgGCcLd60rayAUnR3aWdI,1742
|
|
13
|
-
python3_commons/db/models/__init__.py,sha256=Utr5AJf1FwcrxNtdesgjq92WMK4zpK4VL_8z1JEkJw0,185
|
|
14
|
-
python3_commons/db/models/auth.py,sha256=dmyD3BX7LVBgKiepPN-bxlY6J3PhcmUfVdQwhNR45fU,1187
|
|
15
|
-
python3_commons/db/models/common.py,sha256=8JUDUBJLf59xLNhF57zZBgcgrmvcWAYXGYO4P-LKzHY,1512
|
|
16
|
-
python3_commons/db/models/rbac.py,sha256=7NNTUbS8whuPUHpm4oba_UWDdNiJlHrm8HBO7oGtk64,3185
|
|
17
|
-
python3_commons/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
python3_commons/logging/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
|
|
19
|
-
python3_commons/logging/formatters.py,sha256=p2AtZD4Axp3Em0e9gWzW8U_yOR5entD7xn7Edvc-IuM,719
|
|
20
|
-
python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
python3_commons/serializers/json.py,sha256=91UaXLGKGj0yPyrnuMeNrkG2GuPUgcgAsmIokUgEwpU,808
|
|
22
|
-
python3_commons/serializers/msgpack.py,sha256=WrvaPE187shSK8zkH4UHHMimEZNMv9RaDSwsBE2HlCw,1269
|
|
23
|
-
python3_commons/serializers/msgspec.py,sha256=5SS_wQXcrThPmCWjI7k1a_cHWLz46Jzn3pit89SYJGY,2038
|
|
24
|
-
python3_commons-0.0.0.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
|
|
25
|
-
python3_commons-0.0.0.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
|
|
26
|
-
python3_commons-0.0.0.dist-info/METADATA,sha256=RuVBD9YvT9Q0aRW7aEUDoYEpnXzUeqsgLu83xG0PKck,1113
|
|
27
|
-
python3_commons-0.0.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
28
|
-
python3_commons-0.0.0.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
|
|
29
|
-
python3_commons-0.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|