python3-commons 0.9.12__tar.gz → 0.9.14__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.
Files changed (58) hide show
  1. {python3_commons-0.9.12 → python3_commons-0.9.14}/.pre-commit-config.yaml +2 -2
  2. {python3_commons-0.9.12 → python3_commons-0.9.14}/PKG-INFO +2 -2
  3. {python3_commons-0.9.12 → python3_commons-0.9.14}/pyproject.toml +1 -1
  4. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/api_client.py +7 -2
  5. python3_commons-0.9.14/src/python3_commons/audit.py +185 -0
  6. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/helpers.py +6 -0
  7. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons.egg-info/PKG-INFO +2 -2
  8. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons.egg-info/requires.txt +1 -1
  9. python3_commons-0.9.14/tests/test_audit.py +31 -0
  10. {python3_commons-0.9.12 → python3_commons-0.9.14}/uv.lock +98 -98
  11. python3_commons-0.9.12/src/python3_commons/audit.py +0 -189
  12. python3_commons-0.9.12/tests/test_audit.py +0 -31
  13. {python3_commons-0.9.12 → python3_commons-0.9.14}/.coveragerc +0 -0
  14. {python3_commons-0.9.12 → python3_commons-0.9.14}/.github/workflows/python-publish.yaml +0 -0
  15. {python3_commons-0.9.12 → python3_commons-0.9.14}/.github/workflows/release-on-tag-push.yml +0 -0
  16. {python3_commons-0.9.12 → python3_commons-0.9.14}/.gitignore +0 -0
  17. {python3_commons-0.9.12 → python3_commons-0.9.14}/.python-version +0 -0
  18. {python3_commons-0.9.12 → python3_commons-0.9.14}/AUTHORS.rst +0 -0
  19. {python3_commons-0.9.12 → python3_commons-0.9.14}/CHANGELOG.rst +0 -0
  20. {python3_commons-0.9.12 → python3_commons-0.9.14}/LICENSE +0 -0
  21. {python3_commons-0.9.12 → python3_commons-0.9.14}/README.md +0 -0
  22. {python3_commons-0.9.12 → python3_commons-0.9.14}/README.rst +0 -0
  23. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/Makefile +0 -0
  24. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/_static/.gitignore +0 -0
  25. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/authors.rst +0 -0
  26. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/changelog.rst +0 -0
  27. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/conf.py +0 -0
  28. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/index.rst +0 -0
  29. {python3_commons-0.9.12 → python3_commons-0.9.14}/docs/license.rst +0 -0
  30. {python3_commons-0.9.12 → python3_commons-0.9.14}/setup.cfg +0 -0
  31. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/__init__.py +0 -0
  32. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/auth.py +0 -0
  33. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/cache.py +0 -0
  34. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/conf.py +0 -0
  35. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/db/__init__.py +0 -0
  36. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/db/helpers.py +0 -0
  37. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/db/models/__init__.py +0 -0
  38. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/db/models/auth.py +0 -0
  39. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/db/models/common.py +0 -0
  40. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/db/models/rbac.py +0 -0
  41. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/fs.py +0 -0
  42. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/log/__init__.py +0 -0
  43. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/log/filters.py +0 -0
  44. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/log/formatters.py +0 -0
  45. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/object_storage.py +0 -0
  46. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/permissions.py +0 -0
  47. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/serializers/__init__.py +0 -0
  48. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/serializers/json.py +0 -0
  49. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/serializers/msgpack.py +0 -0
  50. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons/serializers/msgspec.py +0 -0
  51. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons.egg-info/SOURCES.txt +0 -0
  52. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  53. {python3_commons-0.9.12 → python3_commons-0.9.14}/src/python3_commons.egg-info/top_level.txt +0 -0
  54. {python3_commons-0.9.12 → python3_commons-0.9.14}/tests/conftest.py +0 -0
  55. {python3_commons-0.9.12 → python3_commons-0.9.14}/tests/test_cache.py +0 -0
  56. {python3_commons-0.9.12 → python3_commons-0.9.14}/tests/test_helpers.py +0 -0
  57. {python3_commons-0.9.12 → python3_commons-0.9.14}/tests/test_msgpack.py +0 -0
  58. {python3_commons-0.9.12 → python3_commons-0.9.14}/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.8.13
3
+ rev: 0.8.15
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.10
9
+ rev: v0.12.12
10
10
  hooks:
11
11
  # Run the linter.
12
12
  - id: ruff-check
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.9.12
3
+ Version: 0.9.14
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
@@ -12,7 +12,7 @@ Requires-Python: ==3.13.*
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiobotocore~=2.24.0
15
+ Requires-Dist: aiobotocore~=2.24.2
16
16
  Requires-Dist: aiohttp[speedups]~=3.12.15
17
17
  Requires-Dist: asyncpg~=0.30.0
18
18
  Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
@@ -19,7 +19,7 @@ keywords = []
19
19
  requires-python = "==3.13.*"
20
20
 
21
21
  dependencies = [
22
- "aiobotocore~=2.24.0",
22
+ "aiobotocore~=2.24.2",
23
23
  "aiohttp[speedups]~=3.12.15",
24
24
  "asyncpg~=0.30.0",
25
25
  "fastapi-users-db-sqlalchemy~=7.0.0",
@@ -8,6 +8,7 @@ from typing import AsyncGenerator, Literal, Mapping, Sequence
8
8
  from uuid import uuid4
9
9
 
10
10
  from aiohttp import ClientResponse, ClientSession, ClientTimeout, client_exceptions
11
+ from aiohttp.abc import URL
11
12
 
12
13
  from python3_commons import audit
13
14
  from python3_commons.conf import s3_settings
@@ -57,12 +58,15 @@ async def request(
57
58
 
58
59
  if audit_name:
59
60
  curl_request = None
61
+ cookies = client.cookie_jar.filter_cookies(URL(base_url))
60
62
 
61
63
  if method == 'get':
62
64
  if headers or query:
63
- curl_request = request_to_curl(url, query, method, headers)
65
+ curl_request = request_to_curl(url=url, query=query, method=method, headers=headers, cookies=cookies)
64
66
  else:
65
- curl_request = request_to_curl(url, query, method, headers, json, data)
67
+ curl_request = request_to_curl(
68
+ url=url, query=query, method=method, headers=headers, cookies=cookies, json=json, data=data
69
+ )
66
70
 
67
71
  if curl_request:
68
72
  await audit.write_audit_data(
@@ -70,6 +74,7 @@ async def request(
70
74
  f'{date_path}/{audit_name}/{uri_path}/{method}_{timestamp}_{request_id}_request.txt',
71
75
  curl_request.encode('utf-8'),
72
76
  )
77
+
73
78
  client_method = getattr(client, method)
74
79
 
75
80
  logger.debug(f'Requesting {method} {url}')
@@ -0,0 +1,185 @@
1
+ import asyncio
2
+ import io
3
+ import logging
4
+ from datetime import UTC, datetime
5
+ from uuid import uuid4
6
+
7
+ from lxml import etree
8
+ from zeep.plugins import Plugin
9
+ from zeep.wsdl.definitions import AbstractOperation
10
+
11
+ from python3_commons import object_storage
12
+ from python3_commons.conf import S3Settings, s3_settings
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ # class GeneratedStream(io.BytesIO):
18
+ # def __init__(self, generator: AsyncGenerator[bytes], *args, **kwargs):
19
+ # super().__init__(*args, **kwargs)
20
+ # self.generator = generator
21
+ #
22
+ # def read(self, size: int = -1):
23
+ # if size < 0:
24
+ # while True:
25
+ # try:
26
+ # chunk = anext(self.generator)
27
+ # except StopIteration:
28
+ # break
29
+ # else:
30
+ # self.write(chunk)
31
+ # else:
32
+ # total_written_size = 0
33
+ #
34
+ # while total_written_size < size:
35
+ # try:
36
+ # chunk = anext(self.generator)
37
+ # except StopIteration:
38
+ # break
39
+ # else:
40
+ # total_written_size += self.write(chunk)
41
+ #
42
+ # self.seek(0)
43
+ #
44
+ # if chunk := super().read(size):
45
+ # pos = self.tell()
46
+ #
47
+ # buf = self.getbuffer()
48
+ # unread_data_size = len(buf) - pos
49
+ #
50
+ # if unread_data_size > 0:
51
+ # buf[:unread_data_size] = buf[pos : pos + unread_data_size]
52
+ #
53
+ # del buf
54
+ #
55
+ # self.seek(0)
56
+ # self.truncate(unread_data_size)
57
+ #
58
+ # return chunk
59
+ #
60
+ # def readable(self):
61
+ # return True
62
+ #
63
+ #
64
+ # async def generate_archive(
65
+ # objects: AsyncGenerator[tuple[str, datetime, bytes]], chunk_size: int = 4096
66
+ # ) -> AsyncGenerator[bytes]:
67
+ # buffer = deque()
68
+ #
69
+ # with tarfile.open(fileobj=buffer, mode='w') as archive:
70
+ # async for name, last_modified, content in objects:
71
+ # logger.info(f'Adding {name} to archive')
72
+ # info = tarfile.TarInfo(name)
73
+ # info.size = len(content)
74
+ # info.mtime = int(last_modified.timestamp())
75
+ # archive.addfile(info, io.BytesIO(content))
76
+ #
77
+ # buffer_length = buffer.tell()
78
+ #
79
+ # while buffer_length >= chunk_size:
80
+ # buffer.seek(0)
81
+ # chunk = buffer.read(chunk_size)
82
+ # chunk_len = len(chunk)
83
+ #
84
+ # if not chunk:
85
+ # break
86
+ #
87
+ # yield chunk
88
+ #
89
+ # buffer.seek(0)
90
+ # buffer.truncate(chunk_len)
91
+ # buffer.seek(0, io.SEEK_END)
92
+ # buffer_length = buffer.tell()
93
+ #
94
+ # while True:
95
+ # chunk = buffer.read(chunk_size)
96
+ #
97
+ # if not chunk:
98
+ # break
99
+ #
100
+ # yield chunk
101
+ #
102
+ # buffer.seek(0)
103
+ # buffer.truncate(0)
104
+ #
105
+ #
106
+ # async def generate_bzip2(chunks: AsyncGenerator[bytes]) -> AsyncGenerator[bytes]:
107
+ # compressor = BZ2Compressor()
108
+ #
109
+ # async for chunk in chunks:
110
+ # if compressed_chunk := compressor.compress(chunk):
111
+ # yield compressed_chunk
112
+ #
113
+ # if compressed_chunk := compressor.flush():
114
+ # yield compressed_chunk
115
+ #
116
+
117
+ # async def archive_audit_data(root_path: str = 'audit'):
118
+ # now = datetime.now(tz=UTC) - timedelta(days=1)
119
+ # year = now.year
120
+ # month = now.month
121
+ # day = now.day
122
+ # bucket_name = s3_settings.s3_bucket
123
+ # date_path = object_storage.get_absolute_path(f'{root_path}/{year}/{month:02}/{day:02}')
124
+ #
125
+ # if objects := object_storage.get_objects(bucket_name, date_path, recursive=True):
126
+ # logger.info(f'Compacting files in: {date_path}')
127
+ #
128
+ # generator = generate_archive(objects, chunk_size=900_000)
129
+ # bzip2_generator = generate_bzip2(generator)
130
+ # archive_stream = GeneratedStream(bzip2_generator)
131
+ #
132
+ # archive_path = object_storage.get_absolute_path(f'audit/.archive/{year}_{month:02}_{day:02}.tar.bz2')
133
+ # await object_storage.put_object(bucket_name, archive_path, archive_stream, -1, part_size=5 * 1024 * 1024)
134
+ #
135
+ # if errors := await object_storage.remove_objects(bucket_name, date_path):
136
+ # for error in errors:
137
+ # logger.error(f'Failed to delete object in {bucket_name=}: {error}')
138
+
139
+
140
+ async def write_audit_data(settings: S3Settings, key: str, data: bytes):
141
+ if settings.s3_secret_access_key:
142
+ try:
143
+ absolute_path = object_storage.get_absolute_path(f'audit/{key}')
144
+
145
+ await object_storage.put_object(settings.s3_bucket, absolute_path, io.BytesIO(data), len(data))
146
+ except Exception as e:
147
+ logger.error(f'Failed storing object in storage: {e}')
148
+ else:
149
+ logger.debug(f'Stored object in storage: {key}')
150
+ else:
151
+ logger.debug(f'S3 is not configured, not storing object in storage: {key}')
152
+
153
+
154
+ class ZeepAuditPlugin(Plugin):
155
+ def __init__(self, audit_name: str = 'zeep'):
156
+ super().__init__()
157
+ self.audit_name = audit_name
158
+
159
+ def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str):
160
+ xml = etree.tostring(envelope, encoding='UTF-8', pretty_print=True)
161
+ now = datetime.now(tz=UTC)
162
+ date_path = now.strftime('%Y/%m/%d')
163
+ timestamp = now.strftime('%H%M%S')
164
+ path = f'{date_path}/{self.audit_name}/{operation.name}/{timestamp}_{str(uuid4())[-12:]}_{direction}.xml'
165
+ coro = write_audit_data(s3_settings, path, xml)
166
+
167
+ try:
168
+ loop = asyncio.get_running_loop()
169
+ except RuntimeError:
170
+ loop = None
171
+
172
+ if loop and loop.is_running():
173
+ loop.create_task(coro)
174
+ else:
175
+ asyncio.run(coro)
176
+
177
+ def ingress(self, envelope, http_headers, operation: AbstractOperation):
178
+ self.store_audit_in_s3(envelope, operation, 'ingress')
179
+
180
+ return envelope, http_headers
181
+
182
+ def egress(self, envelope, http_headers, operation: AbstractOperation, binding_options):
183
+ self.store_audit_in_s3(envelope, operation, 'egress')
184
+
185
+ return envelope, http_headers
@@ -8,6 +8,7 @@ from abc import ABCMeta
8
8
  from collections import defaultdict
9
9
  from datetime import date, datetime, timedelta
10
10
  from decimal import ROUND_HALF_UP, Decimal
11
+ from http.cookies import BaseCookie
11
12
  from json import dumps
12
13
  from typing import Literal, Mapping, Sequence
13
14
  from urllib.parse import urlencode
@@ -86,6 +87,7 @@ def request_to_curl(
86
87
  query: Mapping | None = None,
87
88
  method: Literal['get', 'post', 'put', 'patch', 'options', 'head', 'delete'] = 'get',
88
89
  headers: Mapping | None = None,
90
+ cookies: BaseCookie[str] | None = None,
89
91
  json: Mapping | Sequence | str | None = None,
90
92
  data: bytes | None = None,
91
93
  ) -> str:
@@ -100,6 +102,10 @@ def request_to_curl(
100
102
  curl_cmd.append('-H')
101
103
  curl_cmd.append(shlex.quote(header_line))
102
104
 
105
+ if cookies:
106
+ cookie_str = '; '.join(f'{k}={v.value}' for k, v in cookies.items())
107
+ curl_cmd.extend(['-b', shlex.quote(cookie_str)])
108
+
103
109
  if json:
104
110
  curl_cmd.append('-H')
105
111
  curl_cmd.append(shlex.quote('Content-Type: application/json'))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.9.12
3
+ Version: 0.9.14
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
@@ -12,7 +12,7 @@ Requires-Python: ==3.13.*
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiobotocore~=2.24.0
15
+ Requires-Dist: aiobotocore~=2.24.2
16
16
  Requires-Dist: aiohttp[speedups]~=3.12.15
17
17
  Requires-Dist: asyncpg~=0.30.0
18
18
  Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
@@ -1,4 +1,4 @@
1
- aiobotocore~=2.24.0
1
+ aiobotocore~=2.24.2
2
2
  aiohttp[speedups]~=3.12.15
3
3
  asyncpg~=0.30.0
4
4
  fastapi-users-db-sqlalchemy~=7.0.0
@@ -0,0 +1,31 @@
1
+ # from io import BytesIO
2
+ #
3
+ # from python3_commons.audit import GeneratedStream, generate_archive, generate_bzip2
4
+ #
5
+ #
6
+ # def test_generated_stream(s3_file_objects):
7
+ # expected_data = b''
8
+ # generator = generate_archive(s3_file_objects, chunk_size=5 * 1024 * 1024)
9
+ # bzip2_generator = generate_bzip2(generator)
10
+ # archive_stream = GeneratedStream(bzip2_generator)
11
+ # archived_data = archive_stream.read()
12
+ #
13
+ # with open('/tmp/test.tar.bz2', 'wb') as f:
14
+ # f.write(archived_data)
15
+ #
16
+ # assert archived_data == expected_data
17
+ #
18
+ #
19
+ # def test_generated_stream_by_chunks(s3_file_objects):
20
+ # expected_data = b''
21
+ # generator = generate_archive(s3_file_objects, chunk_size=2)
22
+ # bzip2_generator = generate_bzip2(generator)
23
+ # archive_stream = GeneratedStream(bzip2_generator)
24
+ # archived_data = BytesIO()
25
+ #
26
+ # with open('/tmp/test_chunked.tar.bz2', 'wb') as f:
27
+ # while chunk := archive_stream.read(2):
28
+ # f.write(chunk)
29
+ # archived_data.write(chunk)
30
+ #
31
+ # assert archived_data.getvalue() == expected_data
@@ -4,7 +4,7 @@ requires-python = "==3.13.*"
4
4
 
5
5
  [[package]]
6
6
  name = "aiobotocore"
7
- version = "2.24.1"
7
+ version = "2.24.2"
8
8
  source = { registry = "https://pypi.org/simple" }
9
9
  dependencies = [
10
10
  { name = "aiohttp" },
@@ -15,9 +15,9 @@ dependencies = [
15
15
  { name = "python-dateutil" },
16
16
  { name = "wrapt" },
17
17
  ]
18
- sdist = { url = "https://files.pythonhosted.org/packages/1b/02/b4ed1af4b3437c2fc6e6111e7fdee011b34cf1c0cc8f314474f843e10019/aiobotocore-2.24.1.tar.gz", hash = "sha256:59237f1b2d4ff619f9a9e78360b691d59b92fdd4d03d054dbd2eeff8ada5667e", size = 119754, upload-time = "2025-08-15T15:49:53.209Z" }
18
+ sdist = { url = "https://files.pythonhosted.org/packages/05/93/9f5243c2fd2fc22cff92f8d8a7e98d3080171be60778d49aeabb555a463d/aiobotocore-2.24.2.tar.gz", hash = "sha256:dfb21bdb2610e8de4d22f401e91a24d50f1330a302d03c62c485757becd439a9", size = 119837, upload-time = "2025-09-05T12:13:46.963Z" }
19
19
  wheels = [
20
- { url = "https://files.pythonhosted.org/packages/20/26/c3c93209084e24990ad1b4214f67dce1c0183454cec9cd2cad9433f493bb/aiobotocore-2.24.1-py3-none-any.whl", hash = "sha256:557922823455ca65bbd065b363b54846f16b9c4b6bd0b61ecdfa01ca13a04531", size = 85216, upload-time = "2025-08-15T15:49:51.442Z" },
20
+ { url = "https://files.pythonhosted.org/packages/87/03/2330062ac4ea9fa6447e02b0625f24efd6f05b6c44d61d86610b3555ee66/aiobotocore-2.24.2-py3-none-any.whl", hash = "sha256:808c63b2bd344b91e2f2acb874831118a9f53342d248acd16a68455a226e283a", size = 85441, upload-time = "2025-09-05T12:13:45.378Z" },
21
21
  ]
22
22
 
23
23
  [[package]]
@@ -235,16 +235,16 @@ wheels = [
235
235
 
236
236
  [[package]]
237
237
  name = "botocore"
238
- version = "1.39.11"
238
+ version = "1.40.18"
239
239
  source = { registry = "https://pypi.org/simple" }
240
240
  dependencies = [
241
241
  { name = "jmespath" },
242
242
  { name = "python-dateutil" },
243
243
  { name = "urllib3" },
244
244
  ]
245
- sdist = { url = "https://files.pythonhosted.org/packages/6d/d0/9d64261186cff650fe63168441edb4f4cd33f085a74c0c54455630a71f91/botocore-1.39.11.tar.gz", hash = "sha256:953b12909d6799350e346ab038e55b6efe622c616f80aef74d7a6683ffdd972c", size = 14217749, upload-time = "2025-07-22T19:26:40.723Z" }
245
+ sdist = { url = "https://files.pythonhosted.org/packages/6a/91/2e745382793fa7d30810a7d5ca3e05f6817b6db07601ca5aaab12720caf9/botocore-1.40.18.tar.gz", hash = "sha256:afd69bdadd8c55cc89d69de0799829e555193a352d87867f746e19020271cc0f", size = 14375007, upload-time = "2025-08-26T19:21:24.996Z" }
246
246
  wheels = [
247
- { url = "https://files.pythonhosted.org/packages/1c/2c/8a0b02d60a1dbbae7faa5af30484b016aa3023f9833dfc0d19b0b770dd6a/botocore-1.39.11-py3-none-any.whl", hash = "sha256:1545352931a8a186f3e977b1e1a4542d7d434796e274c3c62efd0210b5ea76dc", size = 13876276, upload-time = "2025-07-22T19:26:35.164Z" },
247
+ { url = "https://files.pythonhosted.org/packages/1a/f5/bd57bf21fdcc4e500cc406ed2c296e626ddd160f0fee2a4932256e5d62d8/botocore-1.40.18-py3-none-any.whl", hash = "sha256:57025c46ca00cf8cec25de07a759521bfbfb3036a0f69b272654a354615dc45f", size = 14039935, upload-time = "2025-08-26T19:21:19.085Z" },
248
248
  ]
249
249
 
250
250
  [[package]]
@@ -369,68 +369,68 @@ wheels = [
369
369
 
370
370
  [[package]]
371
371
  name = "coverage"
372
- version = "7.10.5"
373
- source = { registry = "https://pypi.org/simple" }
374
- sdist = { url = "https://files.pythonhosted.org/packages/61/83/153f54356c7c200013a752ce1ed5448573dca546ce125801afca9e1ac1a4/coverage-7.10.5.tar.gz", hash = "sha256:f2e57716a78bc3ae80b2207be0709a3b2b63b9f2dcf9740ee6ac03588a2015b6", size = 821662, upload-time = "2025-08-23T14:42:44.78Z" }
375
- wheels = [
376
- { url = "https://files.pythonhosted.org/packages/9f/08/4166ecfb60ba011444f38a5a6107814b80c34c717bc7a23be0d22e92ca09/coverage-7.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef3b83594d933020f54cf65ea1f4405d1f4e41a009c46df629dd964fcb6e907c", size = 217106, upload-time = "2025-08-23T14:41:15.268Z" },
377
- { url = "https://files.pythonhosted.org/packages/25/d7/b71022408adbf040a680b8c64bf6ead3be37b553e5844f7465643979f7ca/coverage-7.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b96bfdf7c0ea9faebce088a3ecb2382819da4fbc05c7b80040dbc428df6af44", size = 217353, upload-time = "2025-08-23T14:41:16.656Z" },
378
- { url = "https://files.pythonhosted.org/packages/74/68/21e0d254dbf8972bb8dd95e3fe7038f4be037ff04ba47d6d1b12b37510ba/coverage-7.10.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:63df1fdaffa42d914d5c4d293e838937638bf75c794cf20bee12978fc8c4e3bc", size = 248350, upload-time = "2025-08-23T14:41:18.128Z" },
379
- { url = "https://files.pythonhosted.org/packages/90/65/28752c3a896566ec93e0219fc4f47ff71bd2b745f51554c93e8dcb659796/coverage-7.10.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8002dc6a049aac0e81ecec97abfb08c01ef0c1fbf962d0c98da3950ace89b869", size = 250955, upload-time = "2025-08-23T14:41:19.577Z" },
380
- { url = "https://files.pythonhosted.org/packages/a5/eb/ca6b7967f57f6fef31da8749ea20417790bb6723593c8cd98a987be20423/coverage-7.10.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63d4bb2966d6f5f705a6b0c6784c8969c468dbc4bcf9d9ded8bff1c7e092451f", size = 252230, upload-time = "2025-08-23T14:41:20.959Z" },
381
- { url = "https://files.pythonhosted.org/packages/bc/29/17a411b2a2a18f8b8c952aa01c00f9284a1fbc677c68a0003b772ea89104/coverage-7.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1f672efc0731a6846b157389b6e6d5d5e9e59d1d1a23a5c66a99fd58339914d5", size = 250387, upload-time = "2025-08-23T14:41:22.644Z" },
382
- { url = "https://files.pythonhosted.org/packages/c7/89/97a9e271188c2fbb3db82235c33980bcbc733da7da6065afbaa1d685a169/coverage-7.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3f39cef43d08049e8afc1fde4a5da8510fc6be843f8dea350ee46e2a26b2f54c", size = 248280, upload-time = "2025-08-23T14:41:24.061Z" },
383
- { url = "https://files.pythonhosted.org/packages/d1/c6/0ad7d0137257553eb4706b4ad6180bec0a1b6a648b092c5bbda48d0e5b2c/coverage-7.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2968647e3ed5a6c019a419264386b013979ff1fb67dd11f5c9886c43d6a31fc2", size = 249894, upload-time = "2025-08-23T14:41:26.165Z" },
384
- { url = "https://files.pythonhosted.org/packages/84/56/fb3aba936addb4c9e5ea14f5979393f1c2466b4c89d10591fd05f2d6b2aa/coverage-7.10.5-cp313-cp313-win32.whl", hash = "sha256:0d511dda38595b2b6934c2b730a1fd57a3635c6aa2a04cb74714cdfdd53846f4", size = 219536, upload-time = "2025-08-23T14:41:27.694Z" },
385
- { url = "https://files.pythonhosted.org/packages/fc/54/baacb8f2f74431e3b175a9a2881feaa8feb6e2f187a0e7e3046f3c7742b2/coverage-7.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:9a86281794a393513cf117177fd39c796b3f8e3759bb2764259a2abba5cce54b", size = 220330, upload-time = "2025-08-23T14:41:29.081Z" },
386
- { url = "https://files.pythonhosted.org/packages/64/8a/82a3788f8e31dee51d350835b23d480548ea8621f3effd7c3ba3f7e5c006/coverage-7.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:cebd8e906eb98bb09c10d1feed16096700b1198d482267f8bf0474e63a7b8d84", size = 218961, upload-time = "2025-08-23T14:41:30.511Z" },
387
- { url = "https://files.pythonhosted.org/packages/d8/a1/590154e6eae07beee3b111cc1f907c30da6fc8ce0a83ef756c72f3c7c748/coverage-7.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0520dff502da5e09d0d20781df74d8189ab334a1e40d5bafe2efaa4158e2d9e7", size = 217819, upload-time = "2025-08-23T14:41:31.962Z" },
388
- { url = "https://files.pythonhosted.org/packages/0d/ff/436ffa3cfc7741f0973c5c89405307fe39b78dcf201565b934e6616fc4ad/coverage-7.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d9cd64aca68f503ed3f1f18c7c9174cbb797baba02ca8ab5112f9d1c0328cd4b", size = 218040, upload-time = "2025-08-23T14:41:33.472Z" },
389
- { url = "https://files.pythonhosted.org/packages/a0/ca/5787fb3d7820e66273913affe8209c534ca11241eb34ee8c4fd2aaa9dd87/coverage-7.10.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0913dd1613a33b13c4f84aa6e3f4198c1a21ee28ccb4f674985c1f22109f0aae", size = 259374, upload-time = "2025-08-23T14:41:34.914Z" },
390
- { url = "https://files.pythonhosted.org/packages/b5/89/21af956843896adc2e64fc075eae3c1cadb97ee0a6960733e65e696f32dd/coverage-7.10.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1b7181c0feeb06ed8a02da02792f42f829a7b29990fef52eff257fef0885d760", size = 261551, upload-time = "2025-08-23T14:41:36.333Z" },
391
- { url = "https://files.pythonhosted.org/packages/e1/96/390a69244ab837e0ac137989277879a084c786cf036c3c4a3b9637d43a89/coverage-7.10.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d42b7396b605f774d4372dd9c49bed71cbabce4ae1ccd074d155709dd8f235", size = 263776, upload-time = "2025-08-23T14:41:38.25Z" },
392
- { url = "https://files.pythonhosted.org/packages/00/32/cfd6ae1da0a521723349f3129b2455832fc27d3f8882c07e5b6fefdd0da2/coverage-7.10.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b4fdc777e05c4940b297bf47bf7eedd56a39a61dc23ba798e4b830d585486ca5", size = 261326, upload-time = "2025-08-23T14:41:40.343Z" },
393
- { url = "https://files.pythonhosted.org/packages/4c/c4/bf8d459fb4ce2201e9243ce6c015936ad283a668774430a3755f467b39d1/coverage-7.10.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:42144e8e346de44a6f1dbd0a56575dd8ab8dfa7e9007da02ea5b1c30ab33a7db", size = 259090, upload-time = "2025-08-23T14:41:42.106Z" },
394
- { url = "https://files.pythonhosted.org/packages/f4/5d/a234f7409896468e5539d42234016045e4015e857488b0b5b5f3f3fa5f2b/coverage-7.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:66c644cbd7aed8fe266d5917e2c9f65458a51cfe5eeff9c05f15b335f697066e", size = 260217, upload-time = "2025-08-23T14:41:43.591Z" },
395
- { url = "https://files.pythonhosted.org/packages/f3/ad/87560f036099f46c2ddd235be6476dd5c1d6be6bb57569a9348d43eeecea/coverage-7.10.5-cp313-cp313t-win32.whl", hash = "sha256:2d1b73023854068c44b0c554578a4e1ef1b050ed07cf8b431549e624a29a66ee", size = 220194, upload-time = "2025-08-23T14:41:45.051Z" },
396
- { url = "https://files.pythonhosted.org/packages/36/a8/04a482594fdd83dc677d4a6c7e2d62135fff5a1573059806b8383fad9071/coverage-7.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:54a1532c8a642d8cc0bd5a9a51f5a9dcc440294fd06e9dda55e743c5ec1a8f14", size = 221258, upload-time = "2025-08-23T14:41:46.44Z" },
397
- { url = "https://files.pythonhosted.org/packages/eb/ad/7da28594ab66fe2bc720f1bc9b131e62e9b4c6e39f044d9a48d18429cc21/coverage-7.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:74d5b63fe3f5f5d372253a4ef92492c11a4305f3550631beaa432fc9df16fcff", size = 219521, upload-time = "2025-08-23T14:41:47.882Z" },
398
- { url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" },
372
+ version = "7.10.6"
373
+ source = { registry = "https://pypi.org/simple" }
374
+ sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" }
375
+ wheels = [
376
+ { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" },
377
+ { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" },
378
+ { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" },
379
+ { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" },
380
+ { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" },
381
+ { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" },
382
+ { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" },
383
+ { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" },
384
+ { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" },
385
+ { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" },
386
+ { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" },
387
+ { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" },
388
+ { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" },
389
+ { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" },
390
+ { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" },
391
+ { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" },
392
+ { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" },
393
+ { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" },
394
+ { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" },
395
+ { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" },
396
+ { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" },
397
+ { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" },
398
+ { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" },
399
399
  ]
400
400
 
401
401
  [[package]]
402
402
  name = "cryptography"
403
- version = "45.0.6"
403
+ version = "45.0.7"
404
404
  source = { registry = "https://pypi.org/simple" }
405
405
  dependencies = [
406
406
  { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
407
407
  ]
408
- sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" }
409
- wheels = [
410
- { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" },
411
- { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" },
412
- { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" },
413
- { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" },
414
- { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" },
415
- { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" },
416
- { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" },
417
- { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" },
418
- { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" },
419
- { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" },
420
- { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" },
421
- { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" },
422
- { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" },
423
- { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" },
424
- { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" },
425
- { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" },
426
- { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" },
427
- { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" },
428
- { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" },
429
- { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" },
430
- { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" },
431
- { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" },
432
- { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" },
433
- { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" },
408
+ sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980, upload-time = "2025-09-01T11:15:03.146Z" }
409
+ wheels = [
410
+ { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105, upload-time = "2025-09-01T11:13:59.684Z" },
411
+ { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799, upload-time = "2025-09-01T11:14:02.517Z" },
412
+ { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504, upload-time = "2025-09-01T11:14:04.522Z" },
413
+ { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542, upload-time = "2025-09-01T11:14:06.309Z" },
414
+ { url = "https://files.pythonhosted.org/packages/cd/e3/e7de4771a08620eef2389b86cd87a2c50326827dea5528feb70595439ce4/cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", size = 3889244, upload-time = "2025-09-01T11:14:08.152Z" },
415
+ { url = "https://files.pythonhosted.org/packages/96/b8/bca71059e79a0bb2f8e4ec61d9c205fbe97876318566cde3b5092529faa9/cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", size = 4461975, upload-time = "2025-09-01T11:14:09.755Z" },
416
+ { url = "https://files.pythonhosted.org/packages/58/67/3f5b26937fe1218c40e95ef4ff8d23c8dc05aa950d54200cc7ea5fb58d28/cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", size = 4209082, upload-time = "2025-09-01T11:14:11.229Z" },
417
+ { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397, upload-time = "2025-09-01T11:14:12.924Z" },
418
+ { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244, upload-time = "2025-09-01T11:14:14.431Z" },
419
+ { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862, upload-time = "2025-09-01T11:14:16.185Z" },
420
+ { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578, upload-time = "2025-09-01T11:14:17.638Z" },
421
+ { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400, upload-time = "2025-09-01T11:14:18.958Z" },
422
+ { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824, upload-time = "2025-09-01T11:14:20.954Z" },
423
+ { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233, upload-time = "2025-09-01T11:14:22.454Z" },
424
+ { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075, upload-time = "2025-09-01T11:14:24.287Z" },
425
+ { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517, upload-time = "2025-09-01T11:14:25.679Z" },
426
+ { url = "https://files.pythonhosted.org/packages/e8/ac/924a723299848b4c741c1059752c7cfe09473b6fd77d2920398fc26bfb53/cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", size = 3882893, upload-time = "2025-09-01T11:14:27.1Z" },
427
+ { url = "https://files.pythonhosted.org/packages/83/dc/4dab2ff0a871cc2d81d3ae6d780991c0192b259c35e4d83fe1de18b20c70/cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", size = 4450132, upload-time = "2025-09-01T11:14:28.58Z" },
428
+ { url = "https://files.pythonhosted.org/packages/12/dd/b2882b65db8fc944585d7fb00d67cf84a9cef4e77d9ba8f69082e911d0de/cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", size = 4204086, upload-time = "2025-09-01T11:14:30.572Z" },
429
+ { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383, upload-time = "2025-09-01T11:14:32.046Z" },
430
+ { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186, upload-time = "2025-09-01T11:14:33.95Z" },
431
+ { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639, upload-time = "2025-09-01T11:14:35.343Z" },
432
+ { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552, upload-time = "2025-09-01T11:14:36.929Z" },
433
+ { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742, upload-time = "2025-09-01T11:14:38.368Z" },
434
434
  ]
435
435
 
436
436
  [[package]]
@@ -804,11 +804,11 @@ wheels = [
804
804
 
805
805
  [[package]]
806
806
  name = "platformdirs"
807
- version = "4.3.8"
807
+ version = "4.4.0"
808
808
  source = { registry = "https://pypi.org/simple" }
809
- sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
809
+ sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
810
810
  wheels = [
811
- { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
811
+ { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
812
812
  ]
813
813
 
814
814
  [[package]]
@@ -1032,20 +1032,20 @@ wheels = [
1032
1032
 
1033
1033
  [[package]]
1034
1034
  name = "pyright"
1035
- version = "1.1.404"
1035
+ version = "1.1.405"
1036
1036
  source = { registry = "https://pypi.org/simple" }
1037
1037
  dependencies = [
1038
1038
  { name = "nodeenv" },
1039
1039
  { name = "typing-extensions" },
1040
1040
  ]
1041
- sdist = { url = "https://files.pythonhosted.org/packages/e2/6e/026be64c43af681d5632722acd100b06d3d39f383ec382ff50a71a6d5bce/pyright-1.1.404.tar.gz", hash = "sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e", size = 4065679, upload-time = "2025-08-20T18:46:14.029Z" }
1041
+ sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" }
1042
1042
  wheels = [
1043
- { url = "https://files.pythonhosted.org/packages/84/30/89aa7f7d7a875bbb9a577d4b1dc5a3e404e3d2ae2657354808e905e358e0/pyright-1.1.404-py3-none-any.whl", hash = "sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419", size = 5902951, upload-time = "2025-08-20T18:46:12.096Z" },
1043
+ { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" },
1044
1044
  ]
1045
1045
 
1046
1046
  [[package]]
1047
1047
  name = "pytest"
1048
- version = "8.4.1"
1048
+ version = "8.4.2"
1049
1049
  source = { registry = "https://pypi.org/simple" }
1050
1050
  dependencies = [
1051
1051
  { name = "colorama", marker = "sys_platform == 'win32'" },
@@ -1054,9 +1054,9 @@ dependencies = [
1054
1054
  { name = "pluggy" },
1055
1055
  { name = "pygments" },
1056
1056
  ]
1057
- sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
1057
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
1058
1058
  wheels = [
1059
- { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
1059
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
1060
1060
  ]
1061
1061
 
1062
1062
  [[package]]
@@ -1087,14 +1087,14 @@ wheels = [
1087
1087
 
1088
1088
  [[package]]
1089
1089
  name = "pytest-mock"
1090
- version = "3.14.1"
1090
+ version = "3.15.0"
1091
1091
  source = { registry = "https://pypi.org/simple" }
1092
1092
  dependencies = [
1093
1093
  { name = "pytest" },
1094
1094
  ]
1095
- sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" }
1095
+ sdist = { url = "https://files.pythonhosted.org/packages/61/99/3323ee5c16b3637b4d941c362182d3e749c11e400bea31018c42219f3a98/pytest_mock-3.15.0.tar.gz", hash = "sha256:ab896bd190316b9d5d87b277569dfcdf718b2d049a2ccff5f7aca279c002a1cf", size = 33838, upload-time = "2025-09-04T20:57:48.679Z" }
1096
1096
  wheels = [
1097
- { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" },
1097
+ { url = "https://files.pythonhosted.org/packages/2b/b3/7fefc43fb706380144bcd293cc6e446e6f637ddfa8b83f48d1734156b529/pytest_mock-3.15.0-py3-none-any.whl", hash = "sha256:ef2219485fb1bd256b00e7ad7466ce26729b30eadfc7cbcdb4fa9a92ca68db6f", size = 10050, upload-time = "2025-09-04T20:57:47.274Z" },
1098
1098
  ]
1099
1099
 
1100
1100
  [[package]]
@@ -1182,7 +1182,7 @@ testing = [
1182
1182
 
1183
1183
  [package.metadata]
1184
1184
  requires-dist = [
1185
- { name = "aiobotocore", specifier = "~=2.24.0" },
1185
+ { name = "aiobotocore", specifier = "~=2.24.2" },
1186
1186
  { name = "aiohttp", extras = ["speedups"], specifier = "~=3.12.15" },
1187
1187
  { name = "asyncpg", specifier = "~=0.30.0" },
1188
1188
  { name = "fastapi-users", extras = ["sqlalchemy"], specifier = "~=14.0.1" },
@@ -1296,28 +1296,28 @@ wheels = [
1296
1296
 
1297
1297
  [[package]]
1298
1298
  name = "ruff"
1299
- version = "0.12.10"
1300
- source = { registry = "https://pypi.org/simple" }
1301
- sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" }
1302
- wheels = [
1303
- { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" },
1304
- { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" },
1305
- { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" },
1306
- { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" },
1307
- { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" },
1308
- { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" },
1309
- { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" },
1310
- { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" },
1311
- { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" },
1312
- { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" },
1313
- { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" },
1314
- { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" },
1315
- { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" },
1316
- { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" },
1317
- { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" },
1318
- { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" },
1319
- { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" },
1320
- { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" },
1299
+ version = "0.12.12"
1300
+ source = { registry = "https://pypi.org/simple" }
1301
+ sdist = { url = "https://files.pythonhosted.org/packages/a8/f0/e0965dd709b8cabe6356811c0ee8c096806bb57d20b5019eb4e48a117410/ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6", size = 5359915, upload-time = "2025-09-04T16:50:18.273Z" }
1302
+ wheels = [
1303
+ { url = "https://files.pythonhosted.org/packages/09/79/8d3d687224d88367b51c7974cec1040c4b015772bfbeffac95face14c04a/ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc", size = 12116602, upload-time = "2025-09-04T16:49:18.892Z" },
1304
+ { url = "https://files.pythonhosted.org/packages/c3/c3/6e599657fe192462f94861a09aae935b869aea8a1da07f47d6eae471397c/ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727", size = 12868393, upload-time = "2025-09-04T16:49:23.043Z" },
1305
+ { url = "https://files.pythonhosted.org/packages/e8/d2/9e3e40d399abc95336b1843f52fc0daaceb672d0e3c9290a28ff1a96f79d/ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb", size = 12036967, upload-time = "2025-09-04T16:49:26.04Z" },
1306
+ { url = "https://files.pythonhosted.org/packages/e9/03/6816b2ed08836be272e87107d905f0908be5b4a40c14bfc91043e76631b8/ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577", size = 12276038, upload-time = "2025-09-04T16:49:29.056Z" },
1307
+ { url = "https://files.pythonhosted.org/packages/9f/d5/707b92a61310edf358a389477eabd8af68f375c0ef858194be97ca5b6069/ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e", size = 11901110, upload-time = "2025-09-04T16:49:32.07Z" },
1308
+ { url = "https://files.pythonhosted.org/packages/9d/3d/f8b1038f4b9822e26ec3d5b49cf2bc313e3c1564cceb4c1a42820bf74853/ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e", size = 13668352, upload-time = "2025-09-04T16:49:35.148Z" },
1309
+ { url = "https://files.pythonhosted.org/packages/98/0e/91421368ae6c4f3765dd41a150f760c5f725516028a6be30e58255e3c668/ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8", size = 14638365, upload-time = "2025-09-04T16:49:38.892Z" },
1310
+ { url = "https://files.pythonhosted.org/packages/74/5d/88f3f06a142f58ecc8ecb0c2fe0b82343e2a2b04dcd098809f717cf74b6c/ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5", size = 14060812, upload-time = "2025-09-04T16:49:42.732Z" },
1311
+ { url = "https://files.pythonhosted.org/packages/13/fc/8962e7ddd2e81863d5c92400820f650b86f97ff919c59836fbc4c1a6d84c/ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92", size = 13050208, upload-time = "2025-09-04T16:49:46.434Z" },
1312
+ { url = "https://files.pythonhosted.org/packages/53/06/8deb52d48a9a624fd37390555d9589e719eac568c020b27e96eed671f25f/ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45", size = 13311444, upload-time = "2025-09-04T16:49:49.931Z" },
1313
+ { url = "https://files.pythonhosted.org/packages/2a/81/de5a29af7eb8f341f8140867ffb93f82e4fde7256dadee79016ac87c2716/ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5", size = 13279474, upload-time = "2025-09-04T16:49:53.465Z" },
1314
+ { url = "https://files.pythonhosted.org/packages/7f/14/d9577fdeaf791737ada1b4f5c6b59c21c3326f3f683229096cccd7674e0c/ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4", size = 12070204, upload-time = "2025-09-04T16:49:56.882Z" },
1315
+ { url = "https://files.pythonhosted.org/packages/77/04/a910078284b47fad54506dc0af13839c418ff704e341c176f64e1127e461/ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23", size = 11880347, upload-time = "2025-09-04T16:49:59.729Z" },
1316
+ { url = "https://files.pythonhosted.org/packages/df/58/30185fcb0e89f05e7ea82e5817b47798f7fa7179863f9d9ba6fd4fe1b098/ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489", size = 12891844, upload-time = "2025-09-04T16:50:02.591Z" },
1317
+ { url = "https://files.pythonhosted.org/packages/21/9c/28a8dacce4855e6703dcb8cdf6c1705d0b23dd01d60150786cd55aa93b16/ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee", size = 13360687, upload-time = "2025-09-04T16:50:05.8Z" },
1318
+ { url = "https://files.pythonhosted.org/packages/c8/fa/05b6428a008e60f79546c943e54068316f32ec8ab5c4f73e4563934fbdc7/ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1", size = 12052870, upload-time = "2025-09-04T16:50:09.121Z" },
1319
+ { url = "https://files.pythonhosted.org/packages/85/60/d1e335417804df452589271818749d061b22772b87efda88354cf35cdb7a/ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d", size = 13178016, upload-time = "2025-09-04T16:50:12.559Z" },
1320
+ { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" },
1321
1321
  ]
1322
1322
 
1323
1323
  [[package]]
@@ -1409,11 +1409,11 @@ wheels = [
1409
1409
 
1410
1410
  [[package]]
1411
1411
  name = "typing-extensions"
1412
- version = "4.14.1"
1412
+ version = "4.15.0"
1413
1413
  source = { registry = "https://pypi.org/simple" }
1414
- sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
1414
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
1415
1415
  wheels = [
1416
- { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
1416
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
1417
1417
  ]
1418
1418
 
1419
1419
  [[package]]
@@ -1,189 +0,0 @@
1
- import asyncio
2
- import io
3
- import logging
4
- import tarfile
5
- from bz2 import BZ2Compressor
6
- from collections import deque
7
- from datetime import UTC, datetime, timedelta
8
- from typing import AsyncGenerator
9
- from uuid import uuid4
10
-
11
- from lxml import etree
12
- from zeep.plugins import Plugin
13
- from zeep.wsdl.definitions import AbstractOperation
14
-
15
- from python3_commons import object_storage
16
- from python3_commons.conf import S3Settings, s3_settings
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class GeneratedStream(io.BytesIO):
22
- def __init__(self, generator: AsyncGenerator[bytes], *args, **kwargs):
23
- super().__init__(*args, **kwargs)
24
- self.generator = generator
25
-
26
- def read(self, size: int = -1):
27
- if size < 0:
28
- while True:
29
- try:
30
- chunk = anext(self.generator)
31
- except StopIteration:
32
- break
33
- else:
34
- self.write(chunk)
35
- else:
36
- total_written_size = 0
37
-
38
- while total_written_size < size:
39
- try:
40
- chunk = anext(self.generator)
41
- except StopIteration:
42
- break
43
- else:
44
- total_written_size += self.write(chunk)
45
-
46
- self.seek(0)
47
-
48
- if chunk := super().read(size):
49
- pos = self.tell()
50
-
51
- buf = self.getbuffer()
52
- unread_data_size = len(buf) - pos
53
-
54
- if unread_data_size > 0:
55
- buf[:unread_data_size] = buf[pos : pos + unread_data_size]
56
-
57
- del buf
58
-
59
- self.seek(0)
60
- self.truncate(unread_data_size)
61
-
62
- return chunk
63
-
64
- def readable(self):
65
- return True
66
-
67
-
68
- async def generate_archive(
69
- objects: AsyncGenerator[tuple[str, datetime, bytes]], chunk_size: int = 4096
70
- ) -> AsyncGenerator[bytes]:
71
- buffer = deque()
72
-
73
- with tarfile.open(fileobj=buffer, mode='w') as archive:
74
- async for name, last_modified, content in objects:
75
- logger.info(f'Adding {name} to archive')
76
- info = tarfile.TarInfo(name)
77
- info.size = len(content)
78
- info.mtime = int(last_modified.timestamp())
79
- archive.addfile(info, io.BytesIO(content))
80
-
81
- buffer_length = buffer.tell()
82
-
83
- while buffer_length >= chunk_size:
84
- buffer.seek(0)
85
- chunk = buffer.read(chunk_size)
86
- chunk_len = len(chunk)
87
-
88
- if not chunk:
89
- break
90
-
91
- yield chunk
92
-
93
- buffer.seek(0)
94
- buffer.truncate(chunk_len)
95
- buffer.seek(0, io.SEEK_END)
96
- buffer_length = buffer.tell()
97
-
98
- while True:
99
- chunk = buffer.read(chunk_size)
100
-
101
- if not chunk:
102
- break
103
-
104
- yield chunk
105
-
106
- buffer.seek(0)
107
- buffer.truncate(0)
108
-
109
-
110
- async def generate_bzip2(chunks: AsyncGenerator[bytes]) -> AsyncGenerator[bytes]:
111
- compressor = BZ2Compressor()
112
-
113
- async for chunk in chunks:
114
- if compressed_chunk := compressor.compress(chunk):
115
- yield compressed_chunk
116
-
117
- if compressed_chunk := compressor.flush():
118
- yield compressed_chunk
119
-
120
-
121
- async def write_audit_data(settings: S3Settings, key: str, data: bytes):
122
- if settings.s3_secret_access_key:
123
- try:
124
- absolute_path = object_storage.get_absolute_path(f'audit/{key}')
125
-
126
- await object_storage.put_object(settings.s3_bucket, absolute_path, io.BytesIO(data), len(data))
127
- except Exception as e:
128
- logger.error(f'Failed storing object in storage: {e}')
129
- else:
130
- logger.debug(f'Stored object in storage: {key}')
131
- else:
132
- logger.debug(f'S3 is not configured, not storing object in storage: {key}')
133
-
134
-
135
- async def archive_audit_data(root_path: str = 'audit'):
136
- now = datetime.now(tz=UTC) - timedelta(days=1)
137
- year = now.year
138
- month = now.month
139
- day = now.day
140
- bucket_name = s3_settings.s3_bucket
141
- date_path = object_storage.get_absolute_path(f'{root_path}/{year}/{month:02}/{day:02}')
142
-
143
- if objects := object_storage.get_objects(bucket_name, date_path, recursive=True):
144
- logger.info(f'Compacting files in: {date_path}')
145
-
146
- generator = generate_archive(objects, chunk_size=900_000)
147
- bzip2_generator = generate_bzip2(generator)
148
- archive_stream = GeneratedStream(bzip2_generator)
149
-
150
- archive_path = object_storage.get_absolute_path(f'audit/.archive/{year}_{month:02}_{day:02}.tar.bz2')
151
- await object_storage.put_object(bucket_name, archive_path, archive_stream, -1, part_size=5 * 1024 * 1024)
152
-
153
- if errors := await object_storage.remove_objects(bucket_name, date_path):
154
- for error in errors:
155
- logger.error(f'Failed to delete object in {bucket_name=}: {error}')
156
-
157
-
158
- class ZeepAuditPlugin(Plugin):
159
- def __init__(self, audit_name: str = 'zeep'):
160
- super().__init__()
161
- self.audit_name = audit_name
162
-
163
- def store_audit_in_s3(self, envelope, operation: AbstractOperation, direction: str):
164
- xml = etree.tostring(envelope, encoding='UTF-8', pretty_print=True)
165
- now = datetime.now(tz=UTC)
166
- date_path = now.strftime('%Y/%m/%d')
167
- timestamp = now.strftime('%H%M%S')
168
- path = f'{date_path}/{self.audit_name}/{operation.name}/{timestamp}_{str(uuid4())[-12:]}_{direction}.xml'
169
- coro = write_audit_data(s3_settings, path, xml)
170
-
171
- try:
172
- loop = asyncio.get_running_loop()
173
- except RuntimeError:
174
- loop = None
175
-
176
- if loop and loop.is_running():
177
- loop.create_task(coro)
178
- else:
179
- asyncio.run(coro)
180
-
181
- def ingress(self, envelope, http_headers, operation: AbstractOperation):
182
- self.store_audit_in_s3(envelope, operation, 'ingress')
183
-
184
- return envelope, http_headers
185
-
186
- def egress(self, envelope, http_headers, operation: AbstractOperation, binding_options):
187
- self.store_audit_in_s3(envelope, operation, 'egress')
188
-
189
- return envelope, http_headers
@@ -1,31 +0,0 @@
1
- from io import BytesIO
2
-
3
- from python3_commons.audit import GeneratedStream, generate_archive, generate_bzip2
4
-
5
-
6
- def test_generated_stream(s3_file_objects):
7
- expected_data = b''
8
- generator = generate_archive(s3_file_objects, chunk_size=5 * 1024 * 1024)
9
- bzip2_generator = generate_bzip2(generator)
10
- archive_stream = GeneratedStream(bzip2_generator)
11
- archived_data = archive_stream.read()
12
-
13
- with open('/tmp/test.tar.bz2', 'wb') as f:
14
- f.write(archived_data)
15
-
16
- assert archived_data == expected_data
17
-
18
-
19
- def test_generated_stream_by_chunks(s3_file_objects):
20
- expected_data = b''
21
- generator = generate_archive(s3_file_objects, chunk_size=2)
22
- bzip2_generator = generate_bzip2(generator)
23
- archive_stream = GeneratedStream(bzip2_generator)
24
- archived_data = BytesIO()
25
-
26
- with open('/tmp/test_chunked.tar.bz2', 'wb') as f:
27
- while chunk := archive_stream.read(2):
28
- f.write(chunk)
29
- archived_data.write(chunk)
30
-
31
- assert archived_data.getvalue() == expected_data