python3-commons 0.6.7__py3-none-any.whl → 0.6.8__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.
@@ -1,9 +1,9 @@
1
1
  from contextlib import asynccontextmanager
2
2
  from datetime import datetime, UTC
3
- from typing import Literal, Mapping
4
- from urllib.parse import urlencode
3
+ from typing import AsyncGenerator, Literal, Mapping, Sequence
5
4
 
6
- import aiohttp
5
+ from aiohttp import ClientSession
6
+ from aiohttp.web_response import Response
7
7
  from pydantic import HttpUrl
8
8
 
9
9
  from python3_commons import audit
@@ -13,12 +13,16 @@ from python3_commons.helpers import request_to_curl
13
13
 
14
14
  @asynccontextmanager
15
15
  async def request(
16
- base_url: HttpUrl,
17
- uri: str,
18
- params: Mapping | None = None,
19
- method: Literal['get', 'post', 'patch', 'put', 'delete'] = 'get',
20
- audit_name: str | None = None,
21
- ):
16
+ client: ClientSession,
17
+ base_url: HttpUrl,
18
+ uri: str,
19
+ query: Mapping | None = None,
20
+ method: Literal['get', 'post', 'put', 'patch', 'options', 'head', 'delete'] = 'get',
21
+ headers: Mapping | None = None,
22
+ json: Mapping | Sequence | str | None = None,
23
+ data: bytes | None = None,
24
+ audit_name: str | None = None
25
+ ) -> AsyncGenerator[Response]:
22
26
  now = datetime.now(tz=UTC)
23
27
  date_path = now.strftime('%Y/%m/%d')
24
28
  timestamp = now.strftime('%H%M%S_%f')
@@ -26,19 +30,27 @@ async def request(
26
30
  uri_path = uri_path[1:] if uri_path.startswith('/') else uri_path
27
31
  url = f'{base_url}{uri}'
28
32
 
29
- if params:
30
- url_with_params = f'{url}?{urlencode(params)}'
33
+ if audit_name:
34
+ curl_request = None
31
35
 
32
- if audit_name:
33
- curl_request = request_to_curl(url_with_params, method, {}, None)
36
+ if method == 'get':
37
+ if query:
38
+ curl_request = request_to_curl(url, query, method, headers)
39
+ else:
40
+ curl_request = request_to_curl(url, query, method, headers, json, data)
41
+
42
+ if curl_request:
34
43
  await audit.write_audit_data(
35
44
  s3_settings,
36
45
  f'{date_path}/{audit_name}/{uri_path}/{method}_{timestamp}_request.txt',
37
46
  curl_request.encode('utf-8')
38
47
  )
39
48
 
40
- async with aiohttp.ClientSession() as client:
41
- client_method = getattr(client, method)
49
+ client_method = getattr(client, method)
42
50
 
43
- async with client_method(url, params=params) as response:
51
+ if method == 'get':
52
+ async with client_method(url, params=query) as response:
53
+ yield response
54
+ else:
55
+ async with client_method(url, params=query, json=json, data=data) as response:
44
56
  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 = io.BytesIO()
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.seek(0)
82
+ buffer_length = buffer.tell()
83
83
 
84
- while True:
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
- buffer.seek(0)
93
- buffer.truncate(0)
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=5*1024*1024)
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
 
@@ -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 typing import Mapping
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') -> datetime.date:
36
+ def date_from_string(string: str, fmt: str = '%d.%m.%Y') -> date:
34
37
  try:
35
- return datetime.datetime.strptime(string, fmt).date()
38
+ return datetime.strptime(string, fmt).date()
36
39
  except ValueError:
37
- return datetime.date.fromisoformat(string)
40
+ return date.fromisoformat(string)
38
41
 
39
42
 
40
- def datetime_from_string(string: str) -> datetime.datetime:
43
+ def datetime_from_string(string: str) -> datetime:
41
44
  try:
42
- return datetime.datetime.strptime(string, '%d.%m.%Y %H:%M:%S')
45
+ return datetime.strptime(string, '%d.%m.%Y %H:%M:%S')
43
46
  except ValueError:
44
- return datetime.datetime.fromisoformat(string)
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 + datetime.timedelta(days=n)
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(url: str, method: str, headers: Mapping, body: bytes | None = None) -> str:
76
- curl_cmd = ['curl', '-i', '-X', method, shlex.quote(url)]
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
- for key, value in headers.items():
79
- header_line = f'{key}: {value}'
97
+ if json:
80
98
  curl_cmd.append('-H')
81
- curl_cmd.append(shlex.quote(header_line))
99
+ curl_cmd.append(shlex.quote('Content-Type: application/json'))
82
100
 
83
- if body is not None:
84
- curl_cmd.append('--data')
85
- curl_cmd.append(shlex.quote(body.decode('utf-8')))
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,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: python3-commons
3
- Version: 0.6.7
3
+ Version: 0.6.8
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License: gpl-3
@@ -1,11 +1,11 @@
1
1
  python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
2
- python3_commons/api_client.py,sha256=7DXXviUdinw80aTTn11Eh-goGmVdnAcaZsI27qVb2nQ,1420
3
- python3_commons/audit.py,sha256=cKQesMo01T_5OSxig-ZRyTXeCQjIjCepKeMgK8xtQ4o,6027
2
+ python3_commons/api_client.py,sha256=0q6pViDNpYwL7zmaw-Fyil18y9QXYsCKJkoWCUM-uW0,1860
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=hZG8M-mltBC8I9yx5ZuAM7bABFNuOsqX6FzSaQz4y9U,2480
8
- python3_commons/object_storage.py,sha256=pk2J14RL9FLTwaks-IS4EJX9TBMLzid35CroGftLNhU,4067
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.7.dist-info/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
17
- python3_commons-0.6.7.dist-info/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
18
- python3_commons-0.6.7.dist-info/METADATA,sha256=6L7NNsUmrmT5EOpjKf_dh605zkUZ8ltIMRn-lq0j5ME,985
19
- python3_commons-0.6.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
20
- python3_commons-0.6.7.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
21
- python3_commons-0.6.7.dist-info/RECORD,,
16
+ python3_commons-0.6.8.dist-info/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
17
+ python3_commons-0.6.8.dist-info/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
18
+ python3_commons-0.6.8.dist-info/METADATA,sha256=8ZUT2mhKdXeUn7SUZhkm8pXZDL1O1-QToZMSCwMwWDU,985
19
+ python3_commons-0.6.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
20
+ python3_commons-0.6.8.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
21
+ python3_commons-0.6.8.dist-info/RECORD,,