python3-commons 0.5.16__tar.gz → 0.5.17__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.

Files changed (47) hide show
  1. {python3_commons-0.5.16/src/python3_commons.egg-info → python3_commons-0.5.17}/PKG-INFO +1 -1
  2. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/audit.py +5 -2
  3. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/helpers.py +22 -0
  4. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/object_storage.py +13 -11
  5. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/serializers/msgspec.py +6 -2
  6. python3_commons-0.5.17/src/python3_commons/stream_tar.py +102 -0
  7. {python3_commons-0.5.16 → python3_commons-0.5.17/src/python3_commons.egg-info}/PKG-INFO +1 -1
  8. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons.egg-info/SOURCES.txt +1 -0
  9. {python3_commons-0.5.16 → python3_commons-0.5.17}/.coveragerc +0 -0
  10. {python3_commons-0.5.16 → python3_commons-0.5.17}/.github/workflows/python-publish.yaml +0 -0
  11. {python3_commons-0.5.16 → python3_commons-0.5.17}/.gitignore +0 -0
  12. {python3_commons-0.5.16 → python3_commons-0.5.17}/AUTHORS.rst +0 -0
  13. {python3_commons-0.5.16 → python3_commons-0.5.17}/CHANGELOG.rst +0 -0
  14. {python3_commons-0.5.16 → python3_commons-0.5.17}/LICENSE +0 -0
  15. {python3_commons-0.5.16 → python3_commons-0.5.17}/README.md +0 -0
  16. {python3_commons-0.5.16 → python3_commons-0.5.17}/README.rst +0 -0
  17. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/Makefile +0 -0
  18. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/_static/.gitignore +0 -0
  19. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/authors.rst +0 -0
  20. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/changelog.rst +0 -0
  21. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/conf.py +0 -0
  22. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/index.rst +0 -0
  23. {python3_commons-0.5.16 → python3_commons-0.5.17}/docs/license.rst +0 -0
  24. {python3_commons-0.5.16 → python3_commons-0.5.17}/pyproject.toml +0 -0
  25. {python3_commons-0.5.16 → python3_commons-0.5.17}/requirements.txt +0 -0
  26. {python3_commons-0.5.16 → python3_commons-0.5.17}/requirements_dev.txt +0 -0
  27. {python3_commons-0.5.16 → python3_commons-0.5.17}/requirements_test.txt +0 -0
  28. {python3_commons-0.5.16 → python3_commons-0.5.17}/setup.cfg +0 -0
  29. {python3_commons-0.5.16 → python3_commons-0.5.17}/setup.py +0 -0
  30. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/__init__.py +0 -0
  31. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/conf.py +0 -0
  32. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/db.py +0 -0
  33. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/fs.py +0 -0
  34. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/logging/__init__.py +0 -0
  35. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/logging/filters.py +0 -0
  36. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/logging/formatter.py +0 -0
  37. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/serializers/__init__.py +0 -0
  38. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/serializers/json.py +0 -0
  39. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons/serializers/msgpack.py +0 -0
  40. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  41. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons.egg-info/not-zip-safe +0 -0
  42. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons.egg-info/requires.txt +0 -0
  43. {python3_commons-0.5.16 → python3_commons-0.5.17}/src/python3_commons.egg-info/top_level.txt +0 -0
  44. {python3_commons-0.5.16 → python3_commons-0.5.17}/tests/conftest.py +0 -0
  45. {python3_commons-0.5.16 → python3_commons-0.5.17}/tests/test_audit.py +0 -0
  46. {python3_commons-0.5.16 → python3_commons-0.5.17}/tests/test_msgpack.py +0 -0
  47. {python3_commons-0.5.16 → python3_commons-0.5.17}/tests/test_msgspec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python3-commons
3
- Version: 0.5.16
3
+ Version: 0.5.17
4
4
  Summary: Re-usable Python3 code
5
5
  Home-page: https://github.com/kamikaze/python3-commons
6
6
  Author: Oleg Korsak
@@ -13,7 +13,7 @@ from zeep.wsdl.definitions import AbstractOperation
13
13
 
14
14
  from python3_commons import object_storage
15
15
  from python3_commons.conf import S3Settings, s3_settings
16
- from python3_commons.object_storage import get_s3_client
16
+ from python3_commons.object_storage import ObjectStorage
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -35,6 +35,8 @@ class GeneratedStream(io.BytesIO):
35
35
 
36
36
  self.write(chunk)
37
37
 
38
+ self.seek(0)
39
+
38
40
  if chunk := super().read(size):
39
41
  pos = self.tell()
40
42
 
@@ -64,6 +66,7 @@ def generate_archive(objects: Iterable[tuple[str, datetime, bytes]],
64
66
  info.size = len(content)
65
67
  info.mtime = last_modified.timestamp()
66
68
  archive.addfile(info, io.BytesIO(content))
69
+ archive.fileobj.flush()
67
70
 
68
71
  buffer.seek(0)
69
72
 
@@ -82,7 +85,7 @@ def generate_archive(objects: Iterable[tuple[str, datetime, bytes]],
82
85
  def write_audit_data_sync(settings: S3Settings, key: str, data: bytes):
83
86
  if settings.s3_secret_access_key:
84
87
  try:
85
- client = get_s3_client(settings)
88
+ client = ObjectStorage(settings).get_client()
86
89
  absolute_path = object_storage.get_absolute_path(f'audit/{key}')
87
90
 
88
91
  client.put_object(settings.s3_bucket, absolute_path, io.BytesIO(data), len(data))
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import logging
3
3
  import shlex
4
+ import threading
4
5
 
5
6
  from decimal import Decimal, ROUND_HALF_UP
6
7
  from typing import Mapping
@@ -8,6 +9,27 @@ from typing import Mapping
8
9
  logger = logging.getLogger(__name__)
9
10
 
10
11
 
12
+ class SingletonMeta(type):
13
+ """
14
+ A metaclass that creates a Singleton base class when called.
15
+ """
16
+ _instances = {}
17
+ _lock = threading.Lock()
18
+
19
+ def __call__(cls, *args, **kwargs):
20
+ try:
21
+ return cls._instances[cls]
22
+ except KeyError:
23
+ with cls._lock:
24
+ try:
25
+ return cls._instances[cls]
26
+ except KeyError:
27
+ instance = super(SingletonMeta, cls).__call__(*args, **kwargs)
28
+ cls._instances[cls] = instance
29
+
30
+ return instance
31
+
32
+
11
33
  def date_from_string(string: str, fmt: str = '%d.%m.%Y') -> datetime.date:
12
34
  try:
13
35
  return datetime.datetime.strptime(string, fmt).date()
@@ -8,16 +8,17 @@ from minio.datatypes import Object
8
8
  from minio.deleteobjects import DeleteObject, DeleteError
9
9
 
10
10
  from python3_commons.conf import s3_settings, S3Settings
11
+ from python3_commons.helpers import SingletonMeta
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
- __CLIENT = None
14
14
 
15
15
 
16
- def get_s3_client(settings: S3Settings) -> Minio:
17
- global __CLIENT
16
+ class ObjectStorage(metaclass=SingletonMeta):
17
+ def __init__(self, settings: S3Settings):
18
+ if not s3_settings.s3_endpoint_url:
19
+ raise ValueError('s3_settings.s3_endpoint_url must be set')
18
20
 
19
- if not __CLIENT and s3_settings.s3_endpoint_url:
20
- __CLIENT = Minio(
21
+ self._client = Minio(
21
22
  settings.s3_endpoint_url,
22
23
  region=settings.s3_region_name,
23
24
  access_key=settings.s3_access_key_id.get_secret_value(),
@@ -26,7 +27,8 @@ def get_s3_client(settings: S3Settings) -> Minio:
26
27
  cert_check=settings.s3_cert_verify
27
28
  )
28
29
 
29
- return __CLIENT
30
+ def get_client(self) -> Minio:
31
+ return self._client
30
32
 
31
33
 
32
34
  def get_absolute_path(path: str) -> str:
@@ -40,7 +42,7 @@ def get_absolute_path(path: str) -> str:
40
42
 
41
43
 
42
44
  def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int, part_size: int = 0) -> str:
43
- s3_client = get_s3_client(s3_settings)
45
+ s3_client = ObjectStorage(s3_settings).get_client()
44
46
 
45
47
  if s3_client:
46
48
  result = s3_client.put_object(bucket_name, path, data, length, part_size=part_size)
@@ -53,7 +55,7 @@ def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int, part_
53
55
 
54
56
 
55
57
  def get_object_stream(bucket_name: str, path: str):
56
- s3_client = get_s3_client(s3_settings)
58
+ s3_client = ObjectStorage(s3_settings).get_client()
57
59
 
58
60
  if s3_client:
59
61
  logger.debug(f'Getting object from object storage: {bucket_name}:{path}')
@@ -85,7 +87,7 @@ def get_object(bucket_name: str, path: str) -> bytes:
85
87
 
86
88
 
87
89
  def list_objects(bucket_name: str, prefix: str, recursive: bool = True) -> Generator[Object, None, None]:
88
- s3_client = get_s3_client(s3_settings)
90
+ s3_client = ObjectStorage(s3_settings).get_client()
89
91
 
90
92
  yield from s3_client.list_objects(bucket_name, prefix=prefix, recursive=recursive)
91
93
 
@@ -104,13 +106,13 @@ def get_objects(bucket_name: str, path: str,
104
106
 
105
107
 
106
108
  def remove_object(bucket_name: str, object_name: str):
107
- s3_client = get_s3_client(s3_settings)
109
+ s3_client = ObjectStorage(s3_settings).get_client()
108
110
  s3_client.remove_object(bucket_name, object_name)
109
111
 
110
112
 
111
113
  def remove_objects(bucket_name: str, prefix: str = None,
112
114
  object_names: Iterable[str] = None) -> Iterable[DeleteError] | None:
113
- s3_client = get_s3_client(s3_settings)
115
+ s3_client = ObjectStorage(s3_settings).get_client()
114
116
 
115
117
  if prefix:
116
118
  delete_object_list = map(
@@ -40,8 +40,12 @@ def ext_hook(code: int, data: memoryview) -> Any:
40
40
  raise NotImplementedError(f'Extension type code {code} is not supported')
41
41
 
42
42
 
43
+ MSGPACK_ENCODER = msgpack.Encoder(enc_hook=enc_hook)
44
+ MSGPACK_DECODER = msgpack.Decoder(ext_hook=ext_hook)
45
+
46
+
43
47
  def serialize_msgpack(data) -> bytes:
44
- result = msgpack.encode(data, enc_hook=enc_hook)
48
+ result = MSGPACK_ENCODER.encode(data)
45
49
 
46
50
  return result
47
51
 
@@ -50,6 +54,6 @@ def deserialize_msgpack(data: bytes, data_type=None):
50
54
  if data_type:
51
55
  result = msgpack.decode(data, type=data_type)
52
56
  else:
53
- result = msgpack.decode(data, ext_hook=ext_hook)
57
+ result = MSGPACK_DECODER.decode(data)
54
58
 
55
59
  return result
@@ -0,0 +1,102 @@
1
+ import os
2
+ import sys
3
+ import tarfile
4
+ from io import BytesIO
5
+
6
+
7
+ class FileStream(object):
8
+ def __init__(self):
9
+ self.buffer = BytesIO()
10
+ self.offset = 0
11
+
12
+ def write(self, chunk):
13
+ self.buffer.write(chunk)
14
+ self.offset += len(chunk)
15
+
16
+ def tell(self):
17
+ return self.offset
18
+
19
+ def close(self):
20
+ self.buffer.close()
21
+
22
+ def pop(self):
23
+ value = self.buffer.getvalue()
24
+ self.buffer.seek(0)
25
+ self.buffer.truncate()
26
+
27
+ return value
28
+
29
+
30
+ def stream_build_tar(in_filename, streaming_fp):
31
+ tar = tarfile.TarFile.open(out_filename, 'w|gz', streaming_fp)
32
+
33
+ stat = os.stat(in_filename)
34
+
35
+ tar_info = tarfile.TarInfo(in_filename)
36
+
37
+ # Note that you can get this information from the storage backend,
38
+ # but it's valid for either to raise a NotImplementedError, so it's
39
+ # important to check.
40
+ #
41
+ # Things like the mode or ownership won't be available.
42
+ tar_info.mtime = stat.st_mtime
43
+ tar_info.size = stat.st_size
44
+
45
+ # Note that we don't pass a fileobj, so we don't write any data
46
+ # through addfile. We'll do this ourselves.
47
+ tar.addfile(tar_info)
48
+
49
+ yield
50
+
51
+ with open(in_filename, 'rb') as in_fp:
52
+ total_size = 0
53
+
54
+ while True:
55
+ s = in_fp.read(BLOCK_SIZE)
56
+
57
+ if len(s) > 0:
58
+ tar.fileobj.write(s)
59
+
60
+ yield
61
+
62
+ if len(s) < BLOCK_SIZE:
63
+ blocks, remainder = divmod(tar_info.size, tarfile.BLOCKSIZE)
64
+
65
+ if remainder > 0:
66
+ tar.fileobj.write(tarfile.NUL *
67
+ (tarfile.BLOCKSIZE - remainder))
68
+
69
+ yield
70
+
71
+ blocks += 1
72
+
73
+ tar.offset += blocks * tarfile.BLOCKSIZE
74
+ break
75
+
76
+ tar.close()
77
+
78
+ yield
79
+
80
+
81
+ BLOCK_SIZE = 4096
82
+
83
+
84
+ if len(sys.argv) != 3:
85
+ print('Usage: %s in_filename out_filename' % sys.argv[0])
86
+ sys.exit(1)
87
+
88
+ in_filename = sys.argv[1]
89
+ out_filename = sys.argv[2]
90
+
91
+ streaming_fp = FileStream()
92
+
93
+ with open(out_filename, 'wb') as out_fp:
94
+ for i in stream_build_tar(in_filename, streaming_fp):
95
+ s = streaming_fp.pop()
96
+
97
+ if len(s) > 0:
98
+ print('Writing %d bytes...' % len(s))
99
+ out_fp.write(s)
100
+ out_fp.flush()
101
+
102
+ print('Wrote tar file to %s' % out_filename)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python3-commons
3
- Version: 0.5.16
3
+ Version: 0.5.17
4
4
  Summary: Re-usable Python3 code
5
5
  Home-page: https://github.com/kamikaze/python3-commons
6
6
  Author: Oleg Korsak
@@ -26,6 +26,7 @@ src/python3_commons/db.py
26
26
  src/python3_commons/fs.py
27
27
  src/python3_commons/helpers.py
28
28
  src/python3_commons/object_storage.py
29
+ src/python3_commons/stream_tar.py
29
30
  src/python3_commons.egg-info/PKG-INFO
30
31
  src/python3_commons.egg-info/SOURCES.txt
31
32
  src/python3_commons.egg-info/dependency_links.txt