python3-commons 0.8.36__tar.gz → 0.8.38__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.
Potentially problematic release.
This version of python3-commons might be problematic. Click here for more details.
- {python3_commons-0.8.36 → python3_commons-0.8.38}/.pre-commit-config.yaml +2 -2
- python3_commons-0.8.38/.python-version +1 -0
- {python3_commons-0.8.36/src/python3_commons.egg-info → python3_commons-0.8.38}/PKG-INFO +6 -6
- {python3_commons-0.8.36 → python3_commons-0.8.38}/pyproject.toml +12 -13
- python3_commons-0.8.38/src/python3_commons/db/models/__init__.py +2 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/helpers.py +7 -9
- python3_commons-0.8.38/src/python3_commons/object_storage.py +127 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38/src/python3_commons.egg-info}/PKG-INFO +6 -6
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons.egg-info/requires.txt +4 -4
- python3_commons-0.8.38/uv.lock +1534 -0
- python3_commons-0.8.36/.python-version +0 -1
- python3_commons-0.8.36/src/python3_commons/db/models/__init__.py +0 -8
- python3_commons-0.8.36/src/python3_commons/object_storage.py +0 -183
- python3_commons-0.8.36/uv.lock +0 -1574
- {python3_commons-0.8.36 → python3_commons-0.8.38}/.coveragerc +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/.github/workflows/python-publish.yaml +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/.github/workflows/release-on-tag-push.yml +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/.gitignore +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/AUTHORS.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/CHANGELOG.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/LICENSE +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/README.md +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/README.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/Makefile +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/_static/.gitignore +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/authors.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/changelog.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/conf.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/index.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/docs/license.rst +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/setup.cfg +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/__init__.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/api_client.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/audit.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/auth.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/cache.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/conf.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/db/__init__.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/db/helpers.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/db/models/auth.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/db/models/common.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/db/models/rbac.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/fs.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/log/__init__.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/log/filters.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/log/formatters.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/permissions.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/serializers/__init__.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/serializers/json.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/serializers/msgpack.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons/serializers/msgspec.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons.egg-info/SOURCES.txt +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons.egg-info/dependency_links.txt +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/src/python3_commons.egg-info/top_level.txt +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/tests/conftest.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/tests/test_audit.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/tests/test_cache.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/tests/test_helpers.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/tests/test_msgpack.py +0 -0
- {python3_commons-0.8.36 → python3_commons-0.8.38}/tests/test_msgspec.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
3
|
-
rev: 0.7.
|
|
3
|
+
rev: 0.7.13
|
|
4
4
|
hooks:
|
|
5
5
|
- id: uv-lock
|
|
6
6
|
# - id: uv-export
|
|
7
7
|
|
|
8
8
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
9
|
-
rev: v0.12.
|
|
9
|
+
rev: v0.12.0
|
|
10
10
|
hooks:
|
|
11
11
|
# Run the linter.
|
|
12
12
|
- id: ruff-check
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
==3.14.*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.38
|
|
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
|
|
@@ -8,20 +8,20 @@ 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.
|
|
11
|
+
Requires-Python: ==3.14.*
|
|
12
12
|
Description-Content-Type: text/x-rst
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
License-File: AUTHORS.rst
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist: aiohttp[speedups]~=3.12.14
|
|
15
|
+
Requires-Dist: aiohttp[speedups]~=3.13.0
|
|
17
16
|
Requires-Dist: asyncpg~=0.30.0
|
|
18
17
|
Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
|
|
19
18
|
Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
|
|
20
19
|
Requires-Dist: lxml~=6.0.0
|
|
20
|
+
Requires-Dist: minio~=7.2.15
|
|
21
21
|
Requires-Dist: msgpack~=1.1.1
|
|
22
22
|
Requires-Dist: msgspec~=0.19.0
|
|
23
|
-
Requires-Dist: pydantic[email]~=2.
|
|
24
|
-
Requires-Dist: pydantic-settings~=2.
|
|
23
|
+
Requires-Dist: pydantic[email]~=2.12.0
|
|
24
|
+
Requires-Dist: pydantic-settings~=2.11.0
|
|
25
25
|
Requires-Dist: python-jose==3.5.0
|
|
26
26
|
Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
|
|
27
27
|
Requires-Dist: valkey[libvalkey]~=6.1.0
|
|
@@ -16,19 +16,19 @@ classifiers = [
|
|
|
16
16
|
"Programming Language :: Python"
|
|
17
17
|
]
|
|
18
18
|
keywords = []
|
|
19
|
-
requires-python = "==3.
|
|
19
|
+
requires-python = "==3.14.*"
|
|
20
20
|
|
|
21
21
|
dependencies = [
|
|
22
|
-
"
|
|
23
|
-
"aiohttp[speedups]~=3.12.14",
|
|
22
|
+
"aiohttp[speedups]~=3.13.0",
|
|
24
23
|
"asyncpg~=0.30.0",
|
|
25
24
|
"fastapi-users-db-sqlalchemy~=7.0.0",
|
|
26
25
|
"fastapi-users[sqlalchemy]~=14.0.1",
|
|
27
26
|
"lxml~=6.0.0",
|
|
27
|
+
"minio~=7.2.15",
|
|
28
28
|
"msgpack~=1.1.1",
|
|
29
29
|
"msgspec~=0.19.0",
|
|
30
|
-
"pydantic[email]~=2.
|
|
31
|
-
"pydantic-settings~=2.
|
|
30
|
+
"pydantic[email]~=2.12.0",
|
|
31
|
+
"pydantic-settings~=2.11.0",
|
|
32
32
|
"python-jose==3.5.0",
|
|
33
33
|
"SQLAlchemy[asyncio]~=2.0.40",
|
|
34
34
|
"valkey[libvalkey]~=6.1.0",
|
|
@@ -38,14 +38,13 @@ dependencies = [
|
|
|
38
38
|
[dependency-groups]
|
|
39
39
|
dev = [
|
|
40
40
|
"build",
|
|
41
|
-
"pip
|
|
42
|
-
"pre-commit
|
|
43
|
-
"pyright
|
|
44
|
-
"ruff
|
|
45
|
-
"setuptools
|
|
46
|
-
"setuptools_scm
|
|
47
|
-
"
|
|
48
|
-
"wheel==0.45.1",
|
|
41
|
+
"pip",
|
|
42
|
+
"pre-commit",
|
|
43
|
+
"pyright",
|
|
44
|
+
"ruff",
|
|
45
|
+
"setuptools",
|
|
46
|
+
"setuptools_scm",
|
|
47
|
+
"wheel",
|
|
49
48
|
]
|
|
50
49
|
testing = [
|
|
51
50
|
"pytest",
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import shlex
|
|
3
3
|
import threading
|
|
4
|
-
from abc import ABCMeta
|
|
5
|
-
from collections import defaultdict
|
|
6
4
|
from datetime import date, datetime, timedelta
|
|
7
5
|
from decimal import ROUND_HALF_UP, Decimal
|
|
8
6
|
from json import dumps
|
|
@@ -14,24 +12,24 @@ from python3_commons.serializers.json import CustomJSONEncoder
|
|
|
14
12
|
logger = logging.getLogger(__name__)
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
class SingletonMeta(
|
|
15
|
+
class SingletonMeta(type):
|
|
18
16
|
"""
|
|
19
17
|
A metaclass that creates a Singleton base class when called.
|
|
20
18
|
"""
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
_instances = {}
|
|
21
|
+
_lock = threading.Lock()
|
|
24
22
|
|
|
25
23
|
def __call__(cls, *args, **kwargs):
|
|
26
24
|
try:
|
|
27
|
-
return cls.
|
|
25
|
+
return cls._instances[cls]
|
|
28
26
|
except KeyError:
|
|
29
|
-
with cls.
|
|
27
|
+
with cls._lock:
|
|
30
28
|
try:
|
|
31
|
-
return cls.
|
|
29
|
+
return cls._instances[cls]
|
|
32
30
|
except KeyError:
|
|
33
31
|
instance = super(SingletonMeta, cls).__call__(*args, **kwargs)
|
|
34
|
-
cls.
|
|
32
|
+
cls._instances[cls] = instance
|
|
35
33
|
|
|
36
34
|
return instance
|
|
37
35
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import logging
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Generator, Iterable
|
|
6
|
+
|
|
7
|
+
from minio import Minio
|
|
8
|
+
from minio.datatypes import Object
|
|
9
|
+
from minio.deleteobjects import DeleteError, DeleteObject
|
|
10
|
+
|
|
11
|
+
from python3_commons.conf import S3Settings, s3_settings
|
|
12
|
+
from python3_commons.helpers import SingletonMeta
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ObjectStorage(metaclass=SingletonMeta):
|
|
18
|
+
def __init__(self, settings: S3Settings):
|
|
19
|
+
if not s3_settings.s3_endpoint_url:
|
|
20
|
+
raise ValueError('s3_settings.s3_endpoint_url must be set')
|
|
21
|
+
|
|
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
|
+
)
|
|
30
|
+
|
|
31
|
+
def get_client(self) -> Minio:
|
|
32
|
+
return self._client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_absolute_path(path: str) -> str:
|
|
36
|
+
if path.startswith('/'):
|
|
37
|
+
path = path[1:]
|
|
38
|
+
|
|
39
|
+
if bucket_root := s3_settings.s3_bucket_root:
|
|
40
|
+
path = f'{bucket_root[:1] if bucket_root.startswith("/") else bucket_root}/{path}'
|
|
41
|
+
|
|
42
|
+
return path
|
|
43
|
+
|
|
44
|
+
|
|
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)
|
|
48
|
+
|
|
49
|
+
logger.debug(f'Stored object into object storage: {bucket_name}:{path}')
|
|
50
|
+
|
|
51
|
+
return result.location
|
|
52
|
+
else:
|
|
53
|
+
logger.warning('No S3 client available, skipping object put')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@contextmanager
|
|
57
|
+
def get_object_stream(bucket_name: str, path: str):
|
|
58
|
+
if s3_client := ObjectStorage(s3_settings).get_client():
|
|
59
|
+
logger.debug(f'Getting object from object storage: {bucket_name}:{path}')
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
response = s3_client.get_object(bucket_name, path)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.debug(f'Failed getting object from object storage: {bucket_name}:{path}', exc_info=e)
|
|
65
|
+
|
|
66
|
+
raise
|
|
67
|
+
|
|
68
|
+
yield response
|
|
69
|
+
|
|
70
|
+
response.close()
|
|
71
|
+
response.release_conn()
|
|
72
|
+
else:
|
|
73
|
+
logger.warning('No S3 client available, skipping object put')
|
|
74
|
+
|
|
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()
|
|
79
|
+
|
|
80
|
+
logger.debug(f'Loaded object from object storage: {bucket_name}:{path}')
|
|
81
|
+
|
|
82
|
+
return body
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def list_objects(bucket_name: str, prefix: str, recursive: bool = True) -> Generator[Object, None, None]:
|
|
86
|
+
s3_client = ObjectStorage(s3_settings).get_client()
|
|
87
|
+
|
|
88
|
+
yield from s3_client.list_objects(bucket_name, prefix=prefix, recursive=recursive)
|
|
89
|
+
|
|
90
|
+
|
|
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
|
+
|
|
97
|
+
if obj.size:
|
|
98
|
+
data = get_object(bucket_name, object_name)
|
|
99
|
+
else:
|
|
100
|
+
data = b''
|
|
101
|
+
|
|
102
|
+
yield object_name, obj.last_modified, data
|
|
103
|
+
|
|
104
|
+
|
|
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)
|
|
108
|
+
|
|
109
|
+
|
|
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()
|
|
114
|
+
|
|
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
|
|
124
|
+
|
|
125
|
+
errors = s3_client.remove_objects(bucket_name, delete_object_list)
|
|
126
|
+
|
|
127
|
+
return errors
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python3-commons
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.38
|
|
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
|
|
@@ -8,20 +8,20 @@ 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.
|
|
11
|
+
Requires-Python: ==3.14.*
|
|
12
12
|
Description-Content-Type: text/x-rst
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
License-File: AUTHORS.rst
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist: aiohttp[speedups]~=3.12.14
|
|
15
|
+
Requires-Dist: aiohttp[speedups]~=3.13.0
|
|
17
16
|
Requires-Dist: asyncpg~=0.30.0
|
|
18
17
|
Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
|
|
19
18
|
Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
|
|
20
19
|
Requires-Dist: lxml~=6.0.0
|
|
20
|
+
Requires-Dist: minio~=7.2.15
|
|
21
21
|
Requires-Dist: msgpack~=1.1.1
|
|
22
22
|
Requires-Dist: msgspec~=0.19.0
|
|
23
|
-
Requires-Dist: pydantic[email]~=2.
|
|
24
|
-
Requires-Dist: pydantic-settings~=2.
|
|
23
|
+
Requires-Dist: pydantic[email]~=2.12.0
|
|
24
|
+
Requires-Dist: pydantic-settings~=2.11.0
|
|
25
25
|
Requires-Dist: python-jose==3.5.0
|
|
26
26
|
Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
|
|
27
27
|
Requires-Dist: valkey[libvalkey]~=6.1.0
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
aiohttp[speedups]~=3.12.14
|
|
1
|
+
aiohttp[speedups]~=3.13.0
|
|
3
2
|
asyncpg~=0.30.0
|
|
4
3
|
fastapi-users-db-sqlalchemy~=7.0.0
|
|
5
4
|
fastapi-users[sqlalchemy]~=14.0.1
|
|
6
5
|
lxml~=6.0.0
|
|
6
|
+
minio~=7.2.15
|
|
7
7
|
msgpack~=1.1.1
|
|
8
8
|
msgspec~=0.19.0
|
|
9
|
-
pydantic[email]~=2.
|
|
10
|
-
pydantic-settings~=2.
|
|
9
|
+
pydantic[email]~=2.12.0
|
|
10
|
+
pydantic-settings~=2.11.0
|
|
11
11
|
python-jose==3.5.0
|
|
12
12
|
SQLAlchemy[asyncio]~=2.0.40
|
|
13
13
|
valkey[libvalkey]~=6.1.0
|