python3-commons 0.6.7__py3-none-any.whl → 0.6.9__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.
- python3_commons/api_client.py +37 -16
- python3_commons/audit.py +12 -8
- python3_commons/helpers.py +38 -18
- python3_commons/object_storage.py +1 -1
- {python3_commons-0.6.7.dist-info → python3_commons-0.6.9.dist-info}/METADATA +1 -1
- {python3_commons-0.6.7.dist-info → python3_commons-0.6.9.dist-info}/RECORD +10 -10
- {python3_commons-0.6.7.dist-info → python3_commons-0.6.9.dist-info}/AUTHORS.rst +0 -0
- {python3_commons-0.6.7.dist-info → python3_commons-0.6.9.dist-info}/LICENSE +0 -0
- {python3_commons-0.6.7.dist-info → python3_commons-0.6.9.dist-info}/WHEEL +0 -0
- {python3_commons-0.6.7.dist-info → python3_commons-0.6.9.dist-info}/top_level.txt +0 -0
python3_commons/api_client.py
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
from contextlib import asynccontextmanager
|
2
2
|
from datetime import datetime, UTC
|
3
|
-
from
|
4
|
-
from
|
3
|
+
from json import dumps
|
4
|
+
from typing import AsyncGenerator, Literal, Mapping, Sequence
|
5
5
|
|
6
|
-
import
|
6
|
+
from aiohttp import ClientSession
|
7
|
+
from aiohttp.web_response import Response
|
7
8
|
from pydantic import HttpUrl
|
8
9
|
|
9
10
|
from python3_commons import audit
|
10
11
|
from python3_commons.conf import s3_settings
|
11
12
|
from python3_commons.helpers import request_to_curl
|
13
|
+
from python3_commons.serializers.json import CustomJSONEncoder
|
12
14
|
|
13
15
|
|
14
16
|
@asynccontextmanager
|
15
17
|
async def request(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
client: ClientSession,
|
19
|
+
base_url: HttpUrl,
|
20
|
+
uri: str,
|
21
|
+
query: Mapping | None = None,
|
22
|
+
method: Literal['get', 'post', 'put', 'patch', 'options', 'head', 'delete'] = 'get',
|
23
|
+
headers: Mapping | None = None,
|
24
|
+
json: Mapping | Sequence | str | None = None,
|
25
|
+
data: bytes | None = None,
|
26
|
+
audit_name: str | None = None
|
27
|
+
) -> AsyncGenerator[Response]:
|
22
28
|
now = datetime.now(tz=UTC)
|
23
29
|
date_path = now.strftime('%Y/%m/%d')
|
24
30
|
timestamp = now.strftime('%H%M%S_%f')
|
@@ -26,19 +32,34 @@ async def request(
|
|
26
32
|
uri_path = uri_path[1:] if uri_path.startswith('/') else uri_path
|
27
33
|
url = f'{base_url}{uri}'
|
28
34
|
|
29
|
-
if
|
30
|
-
|
35
|
+
if audit_name:
|
36
|
+
curl_request = None
|
31
37
|
|
32
|
-
if
|
33
|
-
|
38
|
+
if method == 'get':
|
39
|
+
if query:
|
40
|
+
curl_request = request_to_curl(url, query, method, headers)
|
41
|
+
else:
|
42
|
+
curl_request = request_to_curl(url, query, method, headers, json, data)
|
43
|
+
|
44
|
+
if curl_request:
|
34
45
|
await audit.write_audit_data(
|
35
46
|
s3_settings,
|
36
47
|
f'{date_path}/{audit_name}/{uri_path}/{method}_{timestamp}_request.txt',
|
37
48
|
curl_request.encode('utf-8')
|
38
49
|
)
|
50
|
+
client_method = getattr(client, method)
|
51
|
+
|
52
|
+
if method == 'get':
|
53
|
+
async with client_method(url, params=query) as response:
|
54
|
+
yield response
|
55
|
+
else:
|
56
|
+
if json:
|
57
|
+
data = dumps(json, cls=CustomJSONEncoder).encode('utf-8')
|
39
58
|
|
40
|
-
|
41
|
-
|
59
|
+
if headers:
|
60
|
+
headers = {**headers, 'Content-Type': 'application/json'}
|
61
|
+
else:
|
62
|
+
headers = {'Content-Type': 'application/json'}
|
42
63
|
|
43
|
-
async with client_method(url, params=
|
64
|
+
async with client_method(url, params=query, data=data, headers=headers) as response:
|
44
65
|
yield response
|
python3_commons/audit.py
CHANGED
@@ -3,6 +3,7 @@ import io
|
|
3
3
|
import logging
|
4
4
|
import tarfile
|
5
5
|
from bz2 import BZ2Compressor
|
6
|
+
from collections import deque
|
6
7
|
from datetime import datetime, timedelta, UTC
|
7
8
|
from typing import Generator, Iterable
|
8
9
|
from uuid import uuid4
|
@@ -42,8 +43,7 @@ class GeneratedStream(io.BytesIO):
|
|
42
43
|
except StopIteration:
|
43
44
|
break
|
44
45
|
else:
|
45
|
-
self.write(chunk)
|
46
|
-
total_written_size += len(chunk)
|
46
|
+
total_written_size += self.write(chunk)
|
47
47
|
|
48
48
|
self.seek(0)
|
49
49
|
|
@@ -69,7 +69,7 @@ class GeneratedStream(io.BytesIO):
|
|
69
69
|
|
70
70
|
def generate_archive(objects: Iterable[tuple[str, datetime, bytes]],
|
71
71
|
chunk_size: int = 4096) -> Generator[bytes, None, None]:
|
72
|
-
buffer =
|
72
|
+
buffer = deque()
|
73
73
|
|
74
74
|
with tarfile.open(fileobj=buffer, mode='w') as archive:
|
75
75
|
for name, last_modified, content in objects:
|
@@ -79,18 +79,22 @@ def generate_archive(objects: Iterable[tuple[str, datetime, bytes]],
|
|
79
79
|
info.mtime = last_modified.timestamp()
|
80
80
|
archive.addfile(info, io.BytesIO(content))
|
81
81
|
|
82
|
-
buffer.
|
82
|
+
buffer_length = buffer.tell()
|
83
83
|
|
84
|
-
while
|
84
|
+
while buffer_length >= chunk_size:
|
85
|
+
buffer.seek(0)
|
85
86
|
chunk = buffer.read(chunk_size)
|
87
|
+
chunk_len = len(chunk)
|
86
88
|
|
87
89
|
if not chunk:
|
88
90
|
break
|
89
91
|
|
90
92
|
yield chunk
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
+
buffer.seek(0)
|
95
|
+
buffer.truncate(chunk_len)
|
96
|
+
buffer.seek(0, io.SEEK_END)
|
97
|
+
buffer_length = buffer.tell()
|
94
98
|
|
95
99
|
while True:
|
96
100
|
chunk = buffer.read(chunk_size)
|
@@ -145,7 +149,7 @@ async def archive_audit_data(root_path: str = 'audit'):
|
|
145
149
|
if objects := object_storage.get_objects(bucket_name, date_path, recursive=True):
|
146
150
|
logger.info(f'Compacting files in: {date_path}')
|
147
151
|
|
148
|
-
generator = generate_archive(objects, chunk_size=
|
152
|
+
generator = generate_archive(objects, chunk_size=900_000)
|
149
153
|
bzip2_generator = generate_bzip2(generator)
|
150
154
|
archive_stream = GeneratedStream(bzip2_generator)
|
151
155
|
|
python3_commons/helpers.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
import datetime
|
2
1
|
import logging
|
3
2
|
import shlex
|
4
3
|
import threading
|
5
|
-
|
4
|
+
from datetime import date, datetime, timedelta
|
6
5
|
from decimal import Decimal, ROUND_HALF_UP
|
7
|
-
from
|
6
|
+
from json import dumps
|
7
|
+
from typing import Literal, Mapping, Sequence
|
8
|
+
from urllib.parse import urlencode
|
9
|
+
|
10
|
+
from python3_commons.serializers.json import CustomJSONEncoder
|
8
11
|
|
9
12
|
logger = logging.getLogger(__name__)
|
10
13
|
|
@@ -30,23 +33,23 @@ class SingletonMeta(type):
|
|
30
33
|
return instance
|
31
34
|
|
32
35
|
|
33
|
-
def date_from_string(string: str, fmt: str = '%d.%m.%Y') ->
|
36
|
+
def date_from_string(string: str, fmt: str = '%d.%m.%Y') -> date:
|
34
37
|
try:
|
35
|
-
return datetime.
|
38
|
+
return datetime.strptime(string, fmt).date()
|
36
39
|
except ValueError:
|
37
|
-
return
|
40
|
+
return date.fromisoformat(string)
|
38
41
|
|
39
42
|
|
40
|
-
def datetime_from_string(string: str) -> datetime
|
43
|
+
def datetime_from_string(string: str) -> datetime:
|
41
44
|
try:
|
42
|
-
return datetime.
|
45
|
+
return datetime.strptime(string, '%d.%m.%Y %H:%M:%S')
|
43
46
|
except ValueError:
|
44
|
-
return datetime.
|
47
|
+
return datetime.fromisoformat(string)
|
45
48
|
|
46
49
|
|
47
50
|
def date_range(start_date, end_date):
|
48
51
|
for n in range(int((end_date - start_date).days + 1)):
|
49
|
-
yield start_date +
|
52
|
+
yield start_date + timedelta(days=n)
|
50
53
|
|
51
54
|
|
52
55
|
def tries(times):
|
@@ -72,16 +75,33 @@ def round_decimal(value: Decimal, decimal_places=2, rounding_mode=ROUND_HALF_UP)
|
|
72
75
|
return value
|
73
76
|
|
74
77
|
|
75
|
-
def request_to_curl(
|
76
|
-
|
78
|
+
def request_to_curl(
|
79
|
+
url: str,
|
80
|
+
query: Mapping | None = None,
|
81
|
+
method: Literal['get', 'post', 'put', 'patch', 'options', 'head', 'delete'] = 'get',
|
82
|
+
headers: Mapping | None = None,
|
83
|
+
json: Mapping | Sequence | str | None = None,
|
84
|
+
data: bytes | None = None
|
85
|
+
) -> str:
|
86
|
+
if query:
|
87
|
+
url = f'{url}?{urlencode(query)}'
|
88
|
+
|
89
|
+
curl_cmd = ['curl', '-i', '-X', method.upper(), shlex.quote(url)]
|
90
|
+
|
91
|
+
if headers:
|
92
|
+
for key, value in headers.items():
|
93
|
+
header_line = f'{key}: {value}'
|
94
|
+
curl_cmd.append('-H')
|
95
|
+
curl_cmd.append(shlex.quote(header_line))
|
77
96
|
|
78
|
-
|
79
|
-
header_line = f'{key}: {value}'
|
97
|
+
if json:
|
80
98
|
curl_cmd.append('-H')
|
81
|
-
curl_cmd.append(shlex.quote(
|
99
|
+
curl_cmd.append(shlex.quote('Content-Type: application/json'))
|
82
100
|
|
83
|
-
|
84
|
-
curl_cmd.append(
|
85
|
-
|
101
|
+
curl_cmd.append('-d')
|
102
|
+
curl_cmd.append(shlex.quote(dumps(json, cls=CustomJSONEncoder)))
|
103
|
+
elif data:
|
104
|
+
curl_cmd.append('-d')
|
105
|
+
curl_cmd.append(shlex.quote(data.decode('utf-8')))
|
86
106
|
|
87
107
|
return ' '.join(curl_cmd)
|
@@ -42,7 +42,7 @@ def get_absolute_path(path: str) -> str:
|
|
42
42
|
return path
|
43
43
|
|
44
44
|
|
45
|
-
def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int, part_size: int = 0) -> str:
|
45
|
+
def put_object(bucket_name: str, path: str, data: io.BytesIO, length: int, part_size: int = 0) -> str | None:
|
46
46
|
if s3_client := ObjectStorage(s3_settings).get_client():
|
47
47
|
result = s3_client.put_object(bucket_name, path, data, length, part_size=part_size)
|
48
48
|
|
@@ -1,11 +1,11 @@
|
|
1
1
|
python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
|
2
|
-
python3_commons/api_client.py,sha256=
|
3
|
-
python3_commons/audit.py,sha256=
|
2
|
+
python3_commons/api_client.py,sha256=bfAy3HoN3Olo5dlhZAm3beTNIcya3mJ4z0vGYQa-vao,2219
|
3
|
+
python3_commons/audit.py,sha256=ZQcKkQcycQv5PT3tdKwVi43oxAxKBQwqvmavOUXoyHU,6231
|
4
4
|
python3_commons/conf.py,sha256=qm2a2yWOhfawicBPjWnUett8TrsMtoyQXDxEJ_N-v-Y,637
|
5
5
|
python3_commons/db.py,sha256=qhaDIdzBWgFyeP_XPKfHZlYVlwS2bpBPYMv84yV6820,738
|
6
6
|
python3_commons/fs.py,sha256=wfLjybXndwLqNlOxTpm_HRJnuTcC4wbrHEOaEeCo9Wc,337
|
7
|
-
python3_commons/helpers.py,sha256=
|
8
|
-
python3_commons/object_storage.py,sha256=
|
7
|
+
python3_commons/helpers.py,sha256=OmuCF7UeJ6oe-rH1Y4ZYVW_uPQ973lCLsikj01wmNqs,3101
|
8
|
+
python3_commons/object_storage.py,sha256=Bte49twIJ4l6VAzIqK6tLx7DFYwijErUmmhWkrZpSJE,4074
|
9
9
|
python3_commons/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
python3_commons/logging/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
|
11
11
|
python3_commons/logging/formatters.py,sha256=UXmmh1yd5Kc2dpvSHn6uCWLDWE2LMjlYAaH8cg3siV4,720
|
@@ -13,9 +13,9 @@ python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
13
13
|
python3_commons/serializers/json.py,sha256=P288wWz9ic38QWEMrpp_uwKPYkQiOgvE1cI4WZn6ZCg,808
|
14
14
|
python3_commons/serializers/msgpack.py,sha256=tzIGGyDL3UpZnnouCtnxuYDx6InKM_C3PP1N4PN8wd4,1269
|
15
15
|
python3_commons/serializers/msgspec.py,sha256=FuZVqOLJb0-lEKrs7dtjhwEbHIpfMUk5yu1hD64zRdc,2038
|
16
|
-
python3_commons-0.6.
|
17
|
-
python3_commons-0.6.
|
18
|
-
python3_commons-0.6.
|
19
|
-
python3_commons-0.6.
|
20
|
-
python3_commons-0.6.
|
21
|
-
python3_commons-0.6.
|
16
|
+
python3_commons-0.6.9.dist-info/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
|
17
|
+
python3_commons-0.6.9.dist-info/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
|
18
|
+
python3_commons-0.6.9.dist-info/METADATA,sha256=G6rLfj6jl0oR87R4YHO8CnuMKJ_2EMwN-kI-MMQQ_NY,985
|
19
|
+
python3_commons-0.6.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
20
|
+
python3_commons-0.6.9.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
|
21
|
+
python3_commons-0.6.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|