python3-commons 0.0.0__py3-none-any.whl → 0.2.17__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.

@@ -1,12 +1,19 @@
1
- import io
1
+ from __future__ import annotations
2
+
2
3
  import logging
3
- from contextlib import contextmanager
4
- from datetime import datetime
5
- from typing import Generator, Iterable
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 minio import Minio
8
- from minio.datatypes import Object
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
- if not s3_settings.s3_endpoint_url:
20
- raise ValueError('s3_settings.s3_endpoint_url must be set')
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
- self._client = Minio(
23
- settings.s3_endpoint_url,
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
- def get_client(self) -> Minio:
32
- return self._client
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
- if path.startswith('/'):
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
- if s3_client := ObjectStorage(s3_settings).get_client():
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
- logger.debug(f'Stored object into object storage: {bucket_name}:{path}')
63
+ async with storage.get_client() as s3_client:
64
+ try:
65
+ data.seek(0)
50
66
 
51
- return result.location
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
- @contextmanager
57
- def get_object_stream(bucket_name: str, path: str):
58
- if s3_client := ObjectStorage(s3_settings).get_client():
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.debug(f'Failed getting object from object storage: {bucket_name}:{path}', exc_info=e)
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
- def get_object(bucket_name: str, path: str) -> bytes:
77
- with get_object_stream(bucket_name, path) as stream:
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) -> Generator[Object, None, None]:
86
- s3_client = ObjectStorage(s3_settings).get_client()
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
- yield from s3_client.list_objects(bucket_name, prefix=prefix, recursive=recursive)
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
- if obj.size:
98
- data = get_object(bucket_name, object_name)
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
- data = b''
165
+ return None
101
166
 
102
- yield object_name, obj.last_modified, data
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
- def remove_object(bucket_name: str, object_name: str):
106
- s3_client = ObjectStorage(s3_settings).get_client()
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
- def remove_objects(
111
- bucket_name: str, prefix: str = None, object_names: Iterable[str] = None
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
- if prefix:
116
- delete_object_list = map(
117
- lambda obj: DeleteObject(obj.object_name),
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
- errors = s3_client.remove_objects(bucket_name, delete_object_list)
187
+ raise
126
188
 
127
- return errors
189
+ return errors if errors else None
@@ -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 result
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 result
46
+ return bool(cursor.scalar())
@@ -0,0 +1,8 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class ExtendedType(IntEnum):
5
+ DECIMAL = 1
6
+ DATETIME = 2
7
+ DATE = 3
8
+ DATACLASS = 4
@@ -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(CustomJSONEncoder, self).default(o)
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
- elif isinstance(o, date):
18
- return o.isoformat()
19
- elif isinstance(o, bytes):
17
+ if isinstance(o, bytes):
20
18
  return base64.b64encode(o).decode('ascii')
21
- elif dataclasses.is_dataclass(o):
19
+ if dataclasses.is_dataclass(o):
22
20
  return dataclasses.asdict(o)
23
- elif isinstance(o, (Decimal, socket, type, Exception)):
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(1, str(obj).encode())
18
- elif isinstance(obj, datetime):
19
- return ExtType(2, obj.isoformat().encode())
20
- elif isinstance(obj, date):
21
- return ExtType(3, obj.isoformat().encode())
22
- elif dataclasses.is_dataclass(obj):
23
- return ExtType(4, json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode())
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
- if code == 1:
30
- return Decimal(data.decode())
31
- elif code == 2:
32
- return datetime.fromisoformat(data.decode())
33
- elif code == 3:
34
- return date.fromisoformat(data.decode())
35
- elif code == 4:
36
- return json.loads(data)
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
- result = msgpack.packb(data, default=msgpack_encoder)
43
-
44
- return result
44
+ return msgpack.packb(data, default=msgpack_encoder)
45
45
 
46
46
 
47
47
  def deserialize_msgpack(data: bytes):
48
- result = msgpack.unpackb(data, ext_hook=msgpack_decoder)
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 typing import Any
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(1, struct.pack('b', str(obj).encode()))
20
- elif isinstance(obj, datetime):
21
- return Ext(2, struct.pack('b', obj.isoformat().encode()))
22
- elif isinstance(obj, date):
23
- return Ext(3, struct.pack('b', obj.isoformat().encode()))
24
- elif dataclasses.is_dataclass(obj):
25
- return Ext(4, struct.pack('b', json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode()))
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
- raise NotImplementedError(f'Objects of type {type(obj)} are not supported')
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
- if code == 1:
32
- return Decimal(data.tobytes().decode())
33
- elif code == 2:
34
- return datetime.fromisoformat(data.tobytes().decode())
35
- elif code == 3:
36
- return date.fromisoformat(data.tobytes().decode())
37
- elif code == 4:
38
- return json.loads(data.tobytes())
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
- raise NotImplementedError(f'Extension type code {code} is not supported')
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
- result = msgpack.decode(data, type=data_type)
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
- result = MSGPACK_ENCODER.encode(data)
79
+ def serialize_msgpack(data: Any) -> bytes:
80
+ if isinstance(data, BaseModel):
81
+ data = data.model_dump()
63
82
 
64
- return result
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
- result = msgpack.decode(data, type=data_type)
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.0.0
3
+ Version: 0.2.17
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
- License: gpl-3
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: ==3.13.*
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: aiohttp[speedups]~=3.11.16
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~=5.4.0
20
- Requires-Dist: minio~=7.2.15
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.11.3
24
- Requires-Dist: pydantic-settings~=2.9.1
25
- Requires-Dist: python-jose==3.4.0
26
- Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
27
- Requires-Dist: valkey[libvalkey]~=6.1.0
28
- Requires-Dist: zeep~=4.3.1
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.17.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
26
+ python3_commons-0.2.17.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
27
+ python3_commons-0.2.17.dist-info/METADATA,sha256=0S9iDndAdxv5rAPxiLK4w2xisEcoMHXSGwKdO4Za1-s,1149
28
+ python3_commons-0.2.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ python3_commons-0.2.17.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
30
+ python3_commons-0.2.17.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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