python3-commons 0.3.17__py2.py3-none-any.whl → 0.4.0__py2.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.

@@ -0,0 +1,44 @@
1
+ import logging
2
+ import tarfile
3
+ from datetime import date, timedelta
4
+ from io import BytesIO
5
+
6
+ from python3_commons import object_storage
7
+ from python3_commons.conf import s3_settings
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ async def archive_audit_data(root_path: str = 'audit'):
13
+ today = date.today() - timedelta(days=1)
14
+ year = today.year
15
+ month = today.month
16
+ day = today.day
17
+ bucket_name = s3_settings.s3_bucket
18
+ fo = BytesIO()
19
+ object_names = []
20
+ date_path = object_storage.get_absolute_path(f'{root_path}/{year}/{month:02}/{day:02}')
21
+
22
+ with tarfile.open(fileobj=fo, mode='w|bz2') as archive:
23
+ if objects := object_storage.get_objects(bucket_name, date_path, recursive=True):
24
+ logger.info(f'Compacting files in: {date_path}')
25
+
26
+ for name, last_modified, content in objects:
27
+ info = tarfile.TarInfo(name)
28
+ info.size = len(content)
29
+ info.mtime = last_modified.timestamp()
30
+ archive.addfile(info, BytesIO(content))
31
+ object_names.append(name)
32
+
33
+ fo.seek(0)
34
+
35
+ if object_names:
36
+ archive_path = object_storage.get_absolute_path(
37
+ f'.archive/{year}_{month:02}/{year}_{month:02}_{day:02}.tar.bz2')
38
+ object_storage.put_object(bucket_name, archive_path, fo, fo.getbuffer().nbytes)
39
+
40
+ if errors := object_storage.remove_objects(bucket_name, object_names=object_names):
41
+ for error in errors:
42
+ logger.error(f'Failed to delete object in {bucket_name=}: {error}')
43
+ else:
44
+ logger.info('No objects to archive found.')
python3_commons/conf.py CHANGED
@@ -15,6 +15,7 @@ class S3Settings(BaseSettings):
15
15
  s3_secure: bool = True
16
16
  s3_bucket: str | None = None
17
17
  s3_bucket_root: str | None = None
18
+ s3_cert_verify: bool = True
18
19
 
19
20
 
20
21
  settings = CommonSettings()
@@ -1,29 +1,29 @@
1
+ import io
1
2
  import logging
2
3
  from datetime import datetime
3
- from io import IOBase
4
- from typing import Generator, Iterable, Any
4
+ from typing import Generator, Iterable
5
5
 
6
- from minio import Minio
6
+ from minio import Minio, S3Error
7
7
  from minio.datatypes import Object
8
- from minio.deleteobjects import DeleteObject
8
+ from minio.deleteobjects import DeleteObject, DeleteError
9
9
 
10
- from python3_commons import minio
11
- from python3_commons.conf import s3_settings
10
+ from python3_commons.conf import s3_settings, S3Settings
12
11
 
13
12
  logger = logging.getLogger(__name__)
14
13
  __CLIENT = None
15
14
 
16
15
 
17
- def get_s3_client() -> Minio:
16
+ def get_s3_client(settings: S3Settings) -> Minio:
18
17
  global __CLIENT
19
18
 
20
19
  if not __CLIENT and s3_settings.s3_endpoint_url:
21
- __CLIENT = minio.get_client(
22
- s3_settings.s3_endpoint_url,
23
- s3_settings.s3_region_name,
24
- s3_settings.s3_access_key_id,
25
- s3_settings.s3_secret_access_key,
26
- s3_settings.s3_secure
20
+ __CLIENT = Minio(
21
+ settings.s3_endpoint_url,
22
+ region=settings.s3_region_name,
23
+ access_key=settings.s3_access_key_id.get_secret_value(),
24
+ secret_key=settings.s3_secret_access_key.get_secret_value(),
25
+ secure=settings.s3_secure,
26
+ cert_check=settings.s3_cert_verify
27
27
  )
28
28
 
29
29
  return __CLIENT
@@ -34,13 +34,13 @@ def get_absolute_path(path: str) -> str:
34
34
  path = path[1:]
35
35
 
36
36
  if bucket_root := s3_settings.s3_bucket_root:
37
- path = f'{bucket_root[:1] if bucket_root.startswith("/") else bucket_root}/{path}'
37
+ path = f'{bucket_root[:1] if bucket_root.startswith('/') else bucket_root}/{path}'
38
38
 
39
39
  return path
40
40
 
41
41
 
42
- def put_object(bucket_name: str, path: str, data: IOBase, length: int) -> str:
43
- s3_client = get_s3_client()
42
+ def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int) -> str:
43
+ s3_client = get_s3_client(s3_settings)
44
44
 
45
45
  if s3_client:
46
46
  result = s3_client.put_object(bucket_name, path, data, length)
@@ -53,7 +53,7 @@ def put_object(bucket_name: str, path: str, data: IOBase, length: int) -> str:
53
53
 
54
54
 
55
55
  def get_object_stream(bucket_name: str, path: str):
56
- s3_client = get_s3_client()
56
+ s3_client = get_s3_client(s3_settings)
57
57
 
58
58
  if s3_client:
59
59
  logger.debug(f'Getting object from object storage: {bucket_name}:{path}')
@@ -85,7 +85,7 @@ def get_object(bucket_name: str, path: str) -> bytes:
85
85
 
86
86
 
87
87
  def list_objects(bucket_name: str, prefix: str, recursive: bool = True) -> Generator[Object, None, None]:
88
- s3_client = get_s3_client()
88
+ s3_client = get_s3_client(s3_settings)
89
89
 
90
90
  yield from s3_client.list_objects(bucket_name, prefix=prefix, recursive=recursive)
91
91
 
@@ -104,13 +104,13 @@ def get_objects(bucket_name: str, path: str,
104
104
 
105
105
 
106
106
  def remove_object(bucket_name: str, object_name: str):
107
- s3_client = get_s3_client()
107
+ s3_client = get_s3_client(s3_settings)
108
108
  s3_client.remove_object(bucket_name, object_name)
109
109
 
110
110
 
111
111
  def remove_objects(bucket_name: str, prefix: str = None,
112
- object_names: Iterable[str] = None) -> Generator[Any, Any, None] | None:
113
- s3_client = get_s3_client()
112
+ object_names: Iterable[str] = None) -> Iterable[DeleteError] | None:
113
+ s3_client = get_s3_client(s3_settings)
114
114
 
115
115
  if prefix:
116
116
  delete_object_list = map(
@@ -125,3 +125,17 @@ def remove_objects(bucket_name: str, prefix: str = None,
125
125
  errors = s3_client.remove_objects(bucket_name, delete_object_list)
126
126
 
127
127
  return errors
128
+
129
+
130
+ async def store_bytes_in_s3(settings: S3Settings, data: bytes, key: str):
131
+ if settings.s3_secret_access_key:
132
+ try:
133
+ client = get_s3_client(settings)
134
+
135
+ client.put_object(settings.s3_bucket, key, io.BytesIO(data), len(data))
136
+ except S3Error as e:
137
+ logger.error(f'Failed storing object in storage: {e}')
138
+ else:
139
+ logger.debug(f'Stored object in storage: {key}')
140
+ else:
141
+ logger.debug(f'S3 is not configured, not storing object in storage: {key}')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python3-commons
3
- Version: 0.3.17
3
+ Version: 0.4.0
4
4
  Summary: Re-usable Python3 code
5
5
  Home-page: https://github.com/kamikaze/python3-commons
6
6
  Author: Oleg Korsak
@@ -15,10 +15,10 @@ Description-Content-Type: text/x-rst; charset=UTF-8
15
15
  License-File: LICENSE
16
16
  License-File: AUTHORS.rst
17
17
  Requires-Dist: asyncpg ==0.29.0
18
- Requires-Dist: minio ==7.2.5
18
+ Requires-Dist: minio ==7.2.7
19
19
  Requires-Dist: msgpack ==1.0.8
20
20
  Requires-Dist: msgspec ==0.18.6
21
- Requires-Dist: pydantic[email] ==2.7.0
21
+ Requires-Dist: pydantic[email] ==2.7.2
22
22
  Requires-Dist: pydantic-settings ==2.2.1
23
23
  Provides-Extra: testing
24
24
  Requires-Dist: pytest ; extra == 'testing'
@@ -1,10 +1,10 @@
1
1
  python3_commons/__init__.py,sha256=h-KTJUaQ50E3RmkTn_GO88IRunmDTEpNc3ylpFvCTOc,339
2
- python3_commons/conf.py,sha256=yAsgMV264NXA3mktvP9C3WoZQJiuw2nuVgLigqUwaZs,566
2
+ python3_commons/audit.py,sha256=UhjaUXzsTnDMdQAJfbZR4aFMieUf2jRgTkQjRbDVS9I,1601
3
+ python3_commons/conf.py,sha256=vSXyFwXx2wb1uy8IffeeI-RoTqhUZs0RLSSG2OLc2ss,598
3
4
  python3_commons/db.py,sha256=qhaDIdzBWgFyeP_XPKfHZlYVlwS2bpBPYMv84yV6820,738
4
5
  python3_commons/fs.py,sha256=wfLjybXndwLqNlOxTpm_HRJnuTcC4wbrHEOaEeCo9Wc,337
5
6
  python3_commons/helpers.py,sha256=wI7afc-8o-SBpLBHQnWXiudw456EAVKguJaeZiSNXwE,1377
6
- python3_commons/minio.py,sha256=0WzxmfKEahxjlC2EdMC8mqy1UZTe2uv9bj1US_IqIoQ,819
7
- python3_commons/object_storage.py,sha256=ka2EIPRNyFx1N2evr7VZPCOaroXzbgYJAUIPlMTuoNY,3644
7
+ python3_commons/object_storage.py,sha256=9N_gWtEuS3cYLOObevGR1F9ARecpuKZ9dMWHyIngMT0,4320
8
8
  python3_commons/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  python3_commons/logging/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
10
10
  python3_commons/logging/formatter.py,sha256=UXmmh1yd5Kc2dpvSHn6uCWLDWE2LMjlYAaH8cg3siV4,720
@@ -12,9 +12,9 @@ python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
12
12
  python3_commons/serializers/json.py,sha256=P288wWz9ic38QWEMrpp_uwKPYkQiOgvE1cI4WZn6ZCg,808
13
13
  python3_commons/serializers/msgpack.py,sha256=P7CZoRTBeDtgALT5GTOZVCNM_3snOfCnfh5S3zcCIEY,1679
14
14
  python3_commons/serializers/msgspec.py,sha256=ZrfQWBz_67t3yjU_S6avnRJZzYN-HFzef2qGB5W21KI,2023
15
- python3_commons-0.3.17.dist-info/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
16
- python3_commons-0.3.17.dist-info/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
17
- python3_commons-0.3.17.dist-info/METADATA,sha256=ajN5RokBiGLDV9mFaM_uAoaCF18neilXXrK2PrMP2bU,919
18
- python3_commons-0.3.17.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
19
- python3_commons-0.3.17.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
20
- python3_commons-0.3.17.dist-info/RECORD,,
15
+ python3_commons-0.4.0.dist-info/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
16
+ python3_commons-0.4.0.dist-info/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
17
+ python3_commons-0.4.0.dist-info/METADATA,sha256=mhH-cOFeu0Cw0o67I6XnuhPszqgDgLdQdjM9SynTchA,918
18
+ python3_commons-0.4.0.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
19
+ python3_commons-0.4.0.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
20
+ python3_commons-0.4.0.dist-info/RECORD,,
python3_commons/minio.py DELETED
@@ -1,28 +0,0 @@
1
- import urllib3
2
- from minio import Minio
3
- from pydantic import SecretStr
4
-
5
-
6
- def get_client(endpoint_url: str, region_name: str, access_key_id: SecretStr, secret_access_key: SecretStr,
7
- secure: bool = True) -> Minio:
8
- http_client = urllib3.PoolManager(
9
- timeout=urllib3.util.Timeout(connect=300, read=300),
10
- maxsize=50,
11
- cert_reqs='CERT_REQUIRED' if secure else 'CERT_NONE',
12
- retries=urllib3.Retry(
13
- total=5,
14
- backoff_factor=0.2,
15
- status_forcelist=[500, 502, 503, 504]
16
- )
17
- )
18
-
19
- client = Minio(
20
- endpoint_url,
21
- region=region_name,
22
- access_key=access_key_id.get_secret_value(),
23
- secret_key=secret_access_key.get_secret_value(),
24
- http_client=http_client,
25
- secure=secure
26
- )
27
-
28
- return client