Compare commits

..

5 Commits

17 changed files with 906 additions and 273 deletions

View File

@@ -8,11 +8,12 @@ authors = [
license = "LGPL-3.0-or-later" license = "LGPL-3.0-or-later"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"megasniff>=0.2.3", "megasniff>=0.2.4",
"breakshaft>=0.1.6", "breakshaft>=0.1.6",
"case-insensitive-dictionary>=0.2.1", "case-insensitive-dictionary>=0.2.1",
"mypy>=1.17.0", "mypy>=1.17.0",
"jinja2>=3.1.6", "jinja2>=3.1.6",
"python-multipart>=0.0.20",
] ]
[tool.uv.sources] [tool.uv.sources]

View File

@@ -1,20 +1,27 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Optional, Literal, Annotated
import uvicorn import uvicorn
from python_multipart.multipart import File, Field
from turbosloth import SlothApp from turbosloth import SlothApp
from turbosloth.doc.openapi_app import OpenAPIApp from turbosloth.doc.openapi_app import OpenAPIApp
from turbosloth.doc.openapi_models import Info from turbosloth.doc.openapi_models import Info
from turbosloth.interfaces.base import BasicResponse from turbosloth.interfaces.base import BasicResponse, BasicRequest
from turbosloth.interfaces.serialize_selector import SerializeSelector from turbosloth.interfaces.serialize_selector import SerializeSelector
from turbosloth.interfaces.serialized import SerializedResponse from turbosloth.interfaces.serialized import SerializedResponse
from turbosloth.schema import RequestBody, HeaderParam, QueryParam from turbosloth.interfaces.serialized.multipart_form_data import MultipartFormSerializedRequest
from turbosloth.schema import RequestBody, HeaderParam, QueryParam, Resp
# from turbosloth.types import HTTPResponse
app = SlothApp(di_autodoc_prefix='/didoc', app = SlothApp(di_autodoc_prefix='/didoc',
serialize_selector=SerializeSelector(default_content_type='application/json'), serialize_selector=SerializeSelector(
default_content_type='application/json',
default_accept_type='application/json'
),
openapi_app=OpenAPIApp(Info('asdf', '1.0.0'))) openapi_app=OpenAPIApp(Info('asdf', '1.0.0')))
@@ -101,6 +108,32 @@ def auth_user(user: RequestBody(UserPostSchema)) -> User:
return User(user.user_id, f'user {user.user_id}') return User(user.user_id, f'user {user.user_id}')
@dataclass
class TestResp:
req: UserPostSchema
q1: str
h1: str
a: str
db: list[int]
user: User
q2: int
@dataclass
class TestResp1(TestResp):
hehe: str = 'haha'
@dataclass
class HeadersResp:
head1: str
@dataclass
class ErrResp:
err_info: str
@app.post("/test/body/{a}") @app.post("/test/body/{a}")
async def test_body(r: RequestBody(UserPostSchema), async def test_body(r: RequestBody(UserPostSchema),
q1: str, q1: str,
@@ -108,7 +141,9 @@ async def test_body(r: RequestBody(UserPostSchema),
h1: HeaderParam(str, 'header1'), h1: HeaderParam(str, 'header1'),
db: DummyDbConnection, db: DummyDbConnection,
user: User, user: User,
q2: int = 321) -> SerializedResponse: q2: int = 321) -> (Resp((TestResp, HeadersResp), 200)
| Resp(TestResp1, 201)
| Resp(ErrResp, 500)):
print(r.user_id) print(r.user_id)
resp = { resp = {
'req': r, 'req': r,
@@ -116,10 +151,61 @@ async def test_body(r: RequestBody(UserPostSchema),
'h1': h1, 'h1': h1,
'a': a, 'a': a,
'db': db.constructions, 'db': db.constructions,
'user': user.__dict__, 'user': user,
'q2': q2 'q2': q2
} }
return SerializedResponse(resp) if a == '123':
return ErrResp('hehe'), {}
if q1 == 'a':
return TestResp1(**resp)
return TestResp(**resp), HeadersResp('asdf')
@dataclass
class FieldData:
type_: Annotated[str, 'type']
name: str
value: str
@dataclass
class FileData:
type_: Annotated[str, 'type']
name: str
fname: str
@dataclass
class FileRespSchema:
fields: list[FileData | FieldData]
@app.post('/upload_multipart')
async def upload_multipart(req: MultipartFormSerializedRequest) -> Resp(FileRespSchema, 200):
fields = []
fields_raw = []
async for e in req:
ee = {}
if isinstance(e, File):
f = FileData('file', e.field_name.decode(), e.file_name.decode())
fields.append(f)
ee = f.__dict__
fields_raw.append(ee)
if e.in_memory:
# оно в любом случае будет сохраняться на диск если слишком большое, что мне не очень нравится
# TODO: кастомизация поведения File и Field полей, потоковое чтение multipart/*
e.flush_to_disk()
print(e.actual_file_name)
elif isinstance(e, Field):
f = FieldData('field', e.field_name.decode(), e.value.decode())
fields.append(f)
ee = f.__dict__
fields_raw.append(ee)
else:
pass
print(e)
return FileRespSchema(fields)
@app.get('/openapi.json') @app.get('/openapi.json')
@@ -141,14 +227,54 @@ async def stoplight() -> BasicResponse:
<!-- Embed elements Elements via Web Component --> <!-- Embed elements Elements via Web Component -->
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script> <script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css"> <link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
<style>.sl-elements {height: 100vh}</style> <style>
.sl-elements {height: 100vh}
body {background-color: #1c1f26;}
/* :root :not(sl-code-highlight) */
:root {
/* Canvas */
--color-canvas-50: #0d1117;
--color-canvas-100: #1b2432;
--color-canvas-200: #2c3e55;
--color-canvas-300: #3e5a78;
--color-canvas: #1c1f26;
--color-canvas-tint:#252a34;
--color-canvas-pure:#0a0f16;
/* Text */
--color-text: #e9eef7;
--color-text-heading: #f5f7fb;
--color-text-paragraph: #d7deeb;
--color-text-muted: #9caec9;
--color-text-primary: #8cbff2;
--color-text-light: #cfd9ec;
}
.sl-overflow-y-auto {
color-scheme: dark; /* For scrollbar */
}
/* Синтаксис JSON в Examples */
pre code {
color: #e9eef7 !important; /* общий текст */
}
.token.property { color: #8cbff2 !important; } /* ключи (пастельный голубой) */
.token.string { color: #f2a6a6 !important; } /* строки (мягкий красно-розовый) */
.token.number { color: #a6d8f2 !important; } /* числа (светлый голубой) */
.token.boolean { color: #f7c97f !important; } /* true/false (пастельный жёлтый) */
.token.null { color: #cba6f7 !important; } /* null (сиреневый) */
.token.punctuation { color: #9caec9 !important; } /* скобки, запятые */
.token.operator { color: #9caec9 !important; }
</style>
</head> </head>
<body> <body>
<elements-api <elements-api
apiDescriptionUrl="/openapi.json" apiDescriptionUrl="/openapi.json"
router="hash" router="hash"
layout="sidebar" layout="responsive"
/> />
</body> </body>

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import html import html
import traceback
import typing import typing
from dataclasses import dataclass from dataclasses import dataclass
from types import NoneType from types import NoneType
@@ -9,7 +10,8 @@ from typing import Optional, Callable, Awaitable, Protocol, get_type_hints, get_
import breakshaft.util_mermaid import breakshaft.util_mermaid
import megasniff.exceptions import megasniff.exceptions
from breakshaft.models import ConversionPoint, Callgraph from breakshaft.models import ConversionPoint, Callgraph
from megasniff import SchemaInflatorGenerator from breakshaft.util import is_basic_type_annot
from megasniff import SchemaInflatorGenerator, SchemaDeflatorGenerator
from .didoc import create_di_autodoc_handler from .didoc import create_di_autodoc_handler
from .doc.openapi_app import OpenAPIApp from .doc.openapi_app import OpenAPIApp
@@ -21,8 +23,10 @@ from .interfaces.serialized import SerializedResponse, SerializedRequest
from .interfaces.serialized.text import TextSerializedResponse from .interfaces.serialized.text import TextSerializedResponse
from .req_schema import UnwrappedRequest from .req_schema import UnwrappedRequest
from .router import Router, Route from .router import Router, Route
from .schema import EndpointConfig, ParamSchema from .schema import EndpointConfig, ParamSchema, ReturnSchemaItem
from .types import HandlerType, InternalHandlerType, ContentType, Accept from .types import HandlerType, InternalHandlerType, ContentType, Accept, HTTPReturnCode, \
HTTPResponseBodyPlaceholder, HTTPResponseHeadersPlaceholder, PreSerializedResponseBody, \
PreSerializedResponseHeader
from .internal_types import Scope, Receive, Send, MethodType, QTYPE, BTYPE, PTYPE, HTYPE from .internal_types import Scope, Receive, Send, MethodType, QTYPE, BTYPE, PTYPE, HTYPE
from breakshaft.convertor import ConvRepo from breakshaft.convertor import ConvRepo
@@ -33,7 +37,7 @@ import breakshaft.util
class ASGIApp(Protocol): class ASGIApp(Protocol):
router: Router router: Router
def route(self, method: MethodType, path_pattern: str): def route(self, method: MethodType, path_pattern: str, ok_return_code: str):
raise RuntimeError('stub!') raise RuntimeError('stub!')
def add_subroute(self, subr: Route | Router, basepath: str) -> None: def add_subroute(self, subr: Route | Router, basepath: str) -> None:
@@ -67,11 +71,16 @@ class HTTPApp(ASGIApp):
contenttype_header = req.headers.get('Accept') contenttype_header = req.headers.get('Accept')
if contenttype_header is None: if contenttype_header is None:
return ct return ct
ctypes = set(map(str.strip, contenttype_header.split(',')))
if (self.serialize_selector.default_content_type in ctypes
and self.serialize_selector.default_content_type is not None):
contenttype = self.serialize_selector.default_content_type
charset = None
else:
properties: dict[str, str]
contenttype, properties = parse_content_type(contenttype_header)
properties: dict[str, str] charset = properties.get('charset')
contenttype, properties = parse_content_type(contenttype_header)
charset = properties.get('charset')
if charset is None: if charset is None:
# TODO: extract default charsets based on content type # TODO: extract default charsets based on content type
@@ -82,18 +91,24 @@ class HTTPApp(ASGIApp):
return ContentType(contenttype, charset) return ContentType(contenttype, charset)
def serialize_request(self, ct: ContentType, req: BasicRequest) -> SerializedRequest: async def serialize_request(self, ct: ContentType, req: BasicRequest) -> SerializedRequest:
ser = self.serialize_selector.select(ct.contenttype, ct.charset) ser = self.serialize_selector.select_req(ct.contenttype)
return ser.req.deserialize(req, ct.charset) return await ser.deserialize(req, ct.charset)
def serialize_response(self, req: BasicRequest, sresp: SerializedResponse, ct: Accept) -> BasicResponse: def serialize_response(self, req: BasicRequest, sresp: SerializedResponse, ct: Accept) -> BasicResponse:
ser = self.serialize_selector.select(ct.contenttype, ct.charset) if type(sresp) is SerializedResponse:
sresponser = ser.resp ser = self.serialize_selector.select_resp(ct.contenttype)
sresponser = ser
try: try:
return sresponser.into_basic(sresp, ct.charset) return sresponser.into_basic(sresp, ct.charset)
except UnicodeEncodeError: except UnicodeEncodeError:
return sresponser.into_basic(sresp, 'utf-8') return sresponser.into_basic(sresp, 'utf-8')
else:
try:
return sresp.into_basic(ct.charset)
except UnicodeEncodeError:
return sresp.into_basic('utf-8')
async def send_answer(self, send: Send, resp: BasicResponse): async def send_answer(self, send: Send, resp: BasicResponse):
await send(resp.into_start_message()) await send(resp.into_start_message())
@@ -106,15 +121,15 @@ class HTTPApp(ASGIApp):
method = scope['method'] method = scope['method']
path = scope['path'] path = scope['path']
body = b'' # body = b''
while True: # while True:
event = await receive() # event = await receive()
body += event.get('body', b'') # body += event.get('body', b'')
if not event.get('more_body', False): # if not event.get('more_body', False):
break # break
scope['body'] = body # scope['body'] = body
req = BasicRequest.from_scope(scope) req = BasicRequest.from_scope(scope, receive)
sresponser: type[SerializedResponse] = TextSerializedResponse sresponser: type[SerializedResponse] = TextSerializedResponse
charset = 'latin1' charset = 'latin1'
@@ -178,35 +193,48 @@ class LifespanApp:
class MethodRoutersApp(ASGIApp): class MethodRoutersApp(ASGIApp):
def get(self, path_pattern: str): def get(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('GET', path_pattern) return self.route('GET', path_pattern, ok_return_code)
def post(self, path_pattern: str): def post(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('POST', path_pattern) return self.route('POST', path_pattern, ok_return_code)
def push(self, path_pattern: str): def put(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('PUSH', path_pattern) return self.route('PUT', path_pattern, ok_return_code)
def put(self, path_pattern: str): def patch(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('PUT', path_pattern) return self.route('PATCH', path_pattern, ok_return_code)
def patch(self, path_pattern: str): def delete(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('PATCH', path_pattern) return self.route('DELETE', path_pattern, ok_return_code)
def delete(self, path_pattern: str): def head(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('DELETE', path_pattern) return self.route('HEAD', path_pattern, ok_return_code)
def head(self, path_pattern: str): def connect(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('HEAD', path_pattern) return self.route('CONNECT', path_pattern, ok_return_code)
def connect(self, path_pattern: str): def options(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('CONNECT', path_pattern) return self.route('OPTIONS', path_pattern, ok_return_code)
def options(self, path_pattern: str): def trace(self, path_pattern: str, ok_return_code: str = '200'):
return self.route('OPTIONS', path_pattern) return self.route('TRACE', path_pattern, ok_return_code)
def trace(self, path_pattern: str):
return self.route('TRACE', path_pattern) def _gen_body_header_retcode_extraction(handler_hash):
def _body_header_retcode_extraction(ret):
if isinstance(ret, tuple):
body, headers = ret
else:
body = ret
headers = {}
code_map = getattr(body, '__turbosloth_http_code_mapping__', {})
http_ret_code = code_map.get(handler_hash, 200)
return (body, headers), http_ret_code
return _body_header_retcode_extraction
class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp): class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
@@ -227,6 +255,7 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
serialize_selector = SerializeSelector() serialize_selector = SerializeSelector()
self.serialize_selector = serialize_selector self.serialize_selector = serialize_selector
self.infl_generator = SchemaInflatorGenerator(strict_mode=True, store_sources=True) self.infl_generator = SchemaInflatorGenerator(strict_mode=True, store_sources=True)
self.defl_generator = SchemaDeflatorGenerator(strict_mode=True, explicit_casts=False, store_sources=True)
self.di_autodoc_prefix = di_autodoc_prefix self.di_autodoc_prefix = di_autodoc_prefix
self.openapi_app = openapi_app self.openapi_app = openapi_app
@@ -236,16 +265,8 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
self.inj_repo = ConvRepo(store_callseq=True) self.inj_repo = ConvRepo(store_callseq=True)
@self.inj_repo.mark_injector() @self.inj_repo.mark_injector()
def extract_query(req: BasicRequest | SerializedRequest) -> QTYPE: def unwrap_request(req: BasicRequest | SerializedRequest) -> tuple[QTYPE, PTYPE, HTYPE]:
return req.query return req.query, req.path_matches, req.headers
@self.inj_repo.mark_injector()
def extract_path_matches(req: BasicRequest | SerializedRequest) -> PTYPE:
return req.path_matches
@self.inj_repo.mark_injector()
def extract_headers(req: BasicRequest | SerializedRequest) -> HTYPE:
return req.headers
@self.inj_repo.mark_injector() @self.inj_repo.mark_injector()
def extract_body(req: SerializedRequest) -> BTYPE: def extract_body(req: SerializedRequest) -> BTYPE:
@@ -255,11 +276,30 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
def provide_sloth_app() -> SlothApp: def provide_sloth_app() -> SlothApp:
return self return self
@self.inj_repo.mark_injector()
def construct_response(b: HTTPResponseBodyPlaceholder,
h: HTTPResponseHeadersPlaceholder,
c: HTTPReturnCode) -> SerializedResponse:
return SerializedResponse(b, int(c), h)
self.inj_repo.add_injector(self.extract_content_type) self.inj_repo.add_injector(self.extract_content_type)
self.inj_repo.add_injector(self.extract_accept_type) self.inj_repo.add_injector(self.extract_accept_type)
self.inj_repo.add_injector(self.serialize_request) self.inj_repo.add_injector(self.serialize_request)
self.inj_repo.add_injector(self.serialize_response) self.inj_repo.add_injector(self.serialize_response)
added_req_serializers = set()
for ser in self.serialize_selector.req_serializers.values():
if ser in added_req_serializers:
continue
added_req_serializers.add(ser)
async def _serialize(ct: ContentType, req: BasicRequest):
return await ser.deserialize(req, ct.charset)
hints = get_type_hints(_serialize, include_extras=True)
hints['return'] = ser
self.inj_repo.add_injector(_serialize, type_remap=hints)
async def __call__(self, scope: Scope, receive: Receive, send: Send): async def __call__(self, scope: Scope, receive: Receive, send: Send):
t = scope['type'] t = scope['type']
if t == 'http': if t == 'http':
@@ -279,7 +319,7 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
) )
type_remap = { type_remap = {
'from_data': from_type_override, 'from_data': from_type_override,
'return': Tuple[tuple(t.replacement_type for t in schemas)] 'return': tuple[tuple(t.replacement_type for t in schemas)]
} }
return infl, set(ConversionPoint.from_fn(infl, type_remap=type_remap)) return infl, set(ConversionPoint.from_fn(infl, type_remap=type_remap))
return None, None return None, None
@@ -442,19 +482,24 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
return ret return ret
def route(self, method: MethodType, path_pattern: str): def route(self, method: MethodType, path_pattern: str, ok_return_code: str):
def decorator(fn: HandlerType): def decorator(fn: HandlerType):
path_substs = self.router.find_pattern_substs(path_pattern) path_substs = self.router.find_pattern_substs(path_pattern)
fork_with, fn_type_hints = self._integrate_func(fn, path_substs) config, fork_with, fn_type_hints = self._integrate_func(fn, path_substs, ok_return_code=ok_return_code)
tmp_repo = self.inj_repo.fork(fork_with) tmp_repo = self.inj_repo.fork(fork_with)
p = tmp_repo.create_pipeline( try:
(Send, BasicRequest), p = tmp_repo.create_pipeline(
[ConversionPoint.from_fn(fn, type_remap=fn_type_hints), self.send_answer], (Send, BasicRequest),
force_async=True, [ConversionPoint.from_fn(fn, type_remap=fn_type_hints), self.send_answer],
) force_async=True,
)
self.router.add(method, path_pattern, p) self.router.add(method, path_pattern, p)
except Exception as e:
print(f'Error: unable to register handler {method} {path_pattern}: {e}')
traceback.print_exc()
p = None
if self.di_autodoc_prefix is not None and not path_pattern.startswith( if self.di_autodoc_prefix is not None and not path_pattern.startswith(
self.di_autodoc_prefix + '/' + method + self.di_autodoc_prefix): self.di_autodoc_prefix + '/' + method + self.di_autodoc_prefix):
@@ -471,22 +516,36 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
) )
) )
self.route('GET', self.di_autodoc_prefix + '/' + method + path_pattern)( self.get(self.di_autodoc_prefix + '/' + method + path_pattern)(
create_di_autodoc_handler(method, path_pattern, p, depgraph)) create_di_autodoc_handler(method, path_pattern, p, depgraph))
if self.openapi_app is not None: if self.openapi_app is not None:
responses = {}
if config.return_schema is not None:
for k, v in config.return_schema.code_map.items():
schema = self.schema_from_type(v.body)
self.openapi_app.register_component(v.body)
content = {}
for ctype in self.serialize_selector.resp_serializers.keys():
content[ctype] = MediaType(schema=schema)
headers = None
if v.headers_provided:
pass
responses[k] = Response(
description=f'{v.body}',
content=content,
headers=headers
)
req_body = {}
req_schema = self.construct_req_body(p)
for ctype in self.serialize_selector.req_serializers.keys():
req_body[ctype] = MediaType(req_schema)
self.openapi_app.register_endpoint( self.openapi_app.register_endpoint(
method, method,
path_pattern, path_pattern,
self.construct_req_params(p), self.construct_req_params(p),
{ responses,
'200': Response('desc', content={'application/json': MediaType(schema=Schema(type='boolean'))}) request_body=OpenApiRequestBody(req_body)
},
request_body=OpenApiRequestBody(
{
'application/json': MediaType(self.construct_req_body(p))
}
)
) )
self.register_endpoint_components(p) self.register_endpoint_components(p)
@@ -504,12 +563,13 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
def _integrate_func(self, def _integrate_func(self,
func: Callable, func: Callable,
path_params: Optional[Iterable[str]] = None): path_params: Optional[Iterable[str]] = None,
ok_return_code: Optional[str] = None):
injectors = self.inj_repo.filtered_injectors(True, True) injectors = self.inj_repo.filtered_injectors(True, True)
injected_types = list(map(lambda x: x.injects, injectors)) injected_types = list(map(lambda x: x.injects, injectors)) + [BasicRequest, SerializedRequest]
config = EndpointConfig.from_handler(func, path_params or set(), injected_types) config = EndpointConfig.from_handler(func, path_params or set(), injected_types, ok_return_code=ok_return_code)
fork_with = set() fork_with = set()
@@ -523,6 +583,74 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
if infl is not None: if infl is not None:
fork_with |= infl fork_with |= infl
if config.return_schema is not None and len(config.return_schema.code_map) > 0:
def process_class(current_class: type, key, value):
if '__turbosloth_http_code_mapping__' not in current_class.__dict__:
current_class.__turbosloth_http_code_mapping__ = {}
current_class.__turbosloth_http_code_mapping__[key] = value
for subclass in current_class.__subclasses__():
process_class(subclass, key, value)
ret_map = config.return_schema.code_map
return_schemas_to_unwrap = set()
combined_body_return_types = []
combined_header_return_types = []
handler_hash = hash(func)
for http_code, schema in ret_map.items():
process_class(schema.body, handler_hash, int(http_code))
combined_body_return_types.append(schema.body)
combined_header_return_types.append(schema.headers)
if len(combined_body_return_types) > 0:
combined_body_return_type = Union[*combined_body_return_types]
combined_header_return_type = Union[*combined_header_return_types]
combined_return_type = tuple[combined_body_return_type, combined_header_return_type]
fork_with |= set(
ConversionPoint.from_fn(self.defl_generator.schema_to_deflator(combined_body_return_type),
rettype=HTTPResponseBodyPlaceholder,
type_remap={
'return': HTTPResponseBodyPlaceholder,
'from_data': combined_body_return_type
})
)
is_only_dict_headers = True
for t in combined_header_return_types:
origin = t
while o := get_origin(origin):
origin = o
if isinstance(origin, dict):
is_only_dict_headers = False
break
if len(combined_header_return_types) > 0 and not is_only_dict_headers:
fork_with |= set(
ConversionPoint.from_fn(self.defl_generator.schema_to_deflator(combined_header_return_type),
rettype=HTTPResponseHeadersPlaceholder,
type_remap={
'return': HTTPResponseHeadersPlaceholder,
'from_data': combined_header_return_type
})
)
else:
combined_return_type = tuple[combined_body_return_type, HTTPResponseHeadersPlaceholder]
fork_with |= set(
ConversionPoint.from_fn(
_gen_body_header_retcode_extraction(handler_hash),
type_remap={
'return': tuple[combined_return_type, HTTPReturnCode],
'ret': config.type_replacement['return'],
})
)
i_b = None i_b = None
if config.body_schema is not None: if config.body_schema is not None:
i_b = self.infl_generator.schema_to_inflator( i_b = self.infl_generator.schema_to_inflator(
@@ -566,10 +694,10 @@ class SlothApp(HTTPApp, WSApp, LifespanApp, MethodRoutersApp):
setattr(func, '__turbosloth_config__', config) setattr(func, '__turbosloth_config__', config)
return fork_with, fn_type_hints return config, fork_with, fn_type_hints
def add_injector(self, func: Callable): def add_injector(self, func: Callable):
fork_with, _ = self._integrate_func(func) _, fork_with, _ = self._integrate_func(func)
self.inj_repo.add_conversion_points(fork_with) self.inj_repo.add_conversion_points(fork_with)
def mark_injector(self): def mark_injector(self):

View File

@@ -63,6 +63,6 @@ def create_di_autodoc_handler(method: str,
) )
async def _h() -> SerializedResponse: async def _h() -> SerializedResponse:
return HTMLSerializedResponse(200, {}, html_content) return HTMLSerializedResponse(html_content, 200, {})
return _h return _h

View File

@@ -3,11 +3,15 @@ from __future__ import annotations
import typing import typing
import urllib.parse import urllib.parse
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Mapping from typing import Any, Mapping, Awaitable, Callable
from case_insensitive_dict import CaseInsensitiveDict from case_insensitive_dict import CaseInsensitiveDict
from turbosloth.internal_types import MethodType, Scope, ASGIMessage from turbosloth.internal_types import MethodType, Scope, ASGIMessage, Receive
async def _dummy_recv() -> ASGIMessage:
return {}
@dataclass @dataclass
@@ -16,24 +20,49 @@ class BasicRequest:
path: str path: str
headers: CaseInsensitiveDict[str, str] headers: CaseInsensitiveDict[str, str]
query: dict[str, list[Any] | Any] query: dict[str, list[Any] | Any]
body: bytes
path_matches: dict[str, str] path_matches: dict[str, str]
_do_body_recv: Receive
_body_recv_all = False
@property
def is_body_recv_done(self):
return self._body_recv_all
async def fetch_full_body(self) -> bytes:
is_done = False
buf = b''
while not is_done:
b, is_done = await self.fetch_body_part()
buf += b
return buf
async def fetch_body_part(self) -> tuple[bytes | bytearray, bool]:
if self._body_recv_all:
return b'', True
event = await self._do_body_recv()
buf = event.get('body', b'')
is_done = not event.get('more_body', False)
if is_done:
self._body_recv_all = True
return buf, is_done
def __init__(self, def __init__(self,
method: MethodType, method: MethodType,
path: str, path: str,
headers: Mapping[str, str], headers: Mapping[str, str],
query: dict[str, list[Any] | Any], query: dict[str, list[Any] | Any],
body: bytes): body_recv: Receive):
self.method = method self.method = method
self.path = path self.path = path
self.headers = CaseInsensitiveDict(headers) self.headers = CaseInsensitiveDict(headers)
self.query = query self.query = query
self.body = body self._do_body_recv = body_recv
self.path_matches = {} self.path_matches = {}
@classmethod @classmethod
def from_scope(cls, scope: Scope) -> BasicRequest: def from_scope(cls,
scope: Scope,
recv: Receive) -> BasicRequest:
path = scope['path'] path = scope['path']
method = typing.cast(MethodType, scope['method']) method = typing.cast(MethodType, scope['method'])
headers = {} headers = {}
@@ -50,9 +79,7 @@ class BasicRequest:
query[k] = v query[k] = v
query = typing.cast(dict[str, list[Any] | Any], query) query = typing.cast(dict[str, list[Any] | Any], query)
body = scope['body'] return BasicRequest(method, path, headers, query, recv)
return BasicRequest(method, path, headers, query, body)
@dataclass @dataclass

View File

@@ -4,42 +4,60 @@ from typing import NamedTuple, Optional
from case_insensitive_dict import CaseInsensitiveDict from case_insensitive_dict import CaseInsensitiveDict
from turbosloth.exceptions import NotAcceptableException from turbosloth.exceptions import NotAcceptableException
from turbosloth.interfaces.serialized import SerializedRequest, SerializedResponse, default_serializers from turbosloth.interfaces.serialized import SerializedRequest, SerializedResponse, default_req_serializers, \
default_resp_serializers
from turbosloth.util import parse_content_type from turbosloth.util import parse_content_type
type SerializeReqChoise = tuple[type[SerializedRequest], str]
class SerializeChoise(NamedTuple): type SerializeRespChoise = tuple[type[SerializedResponse], str]
req: type[SerializedRequest]
resp: type[SerializedResponse]
charset: str
class SerializeSelector: class SerializeSelector:
default_content_type: Optional[str] default_content_type: Optional[str]
serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]] default_accept_type: Optional[str]
req_serializers: dict[str, type[SerializedRequest]]
resp_serializers: dict[str, type[SerializedResponse]]
def __init__(self, def __init__(self,
default_content_type: Optional[str] = 'text/plain', default_content_type: Optional[str] = 'text/plain',
filter_content_types: Optional[list[str]] = None): default_accept_type: Optional[str] = 'text/plain',
filter_content_types: Optional[list[str]] = None,
filter_accept_types: Optional[list[str]] = None):
self.default_content_type = default_content_type self.default_content_type = default_content_type
ser = {} self.default_accept_type = default_accept_type
req_ser = {}
resp_ser = {}
if filter_content_types is None: if filter_content_types is None:
filter_content_types = list(default_serializers.keys()) filter_content_types = list(default_req_serializers.keys())
for k, v in default_serializers.items(): if filter_accept_types is None:
filter_accept_types = list(default_resp_serializers.keys())
for k, v in default_req_serializers.items():
if k in filter_content_types: if k in filter_content_types:
ser[k] = v req_ser[k] = v
self.serializers = ser for k, v in default_resp_serializers.items():
if k in filter_accept_types:
resp_ser[k] = v
self.req_serializers = req_ser
self.resp_serializers = resp_ser
def select(self, contenttype: str, charset: str) -> SerializeChoise: def select_req(self, contenttype: str) -> type[SerializedRequest]:
choise = self.req_serializers.get(typing.cast(str, contenttype))
choise = self.serializers.get(typing.cast(str, contenttype)) if choise is None and self.default_accept_type is not None:
if choise is None and self.default_content_type is not None: choise = self.req_serializers.get(self.default_accept_type)
choise = self.serializers.get(self.default_content_type)
if choise is None: if choise is None:
raise NotAcceptableException('acceptable content types: ' + ', '.join(self.serializers.keys())) raise NotAcceptableException('acceptable content types: ' + ', '.join(self.req_serializers.keys()))
req, resp = choise
return SerializeChoise(req, resp, charset) return choise
def select_resp(self, accepttype: str) -> type[SerializedResponse]:
choise = self.resp_serializers.get(typing.cast(str, accepttype))
if choise is None and self.default_content_type is not None:
choise = self.resp_serializers.get(self.default_content_type)
if choise is None:
raise NotAcceptableException('acceptable content types: ' + ', '.join(self.resp_serializers.keys()))
return choise

View File

@@ -1,31 +1,44 @@
from .base import SerializedRequest, SerializedResponse from .base import SerializedRequest, SerializedResponse
default_serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]] = {} default_req_serializers: dict[str, type[SerializedRequest]] = {}
default_resp_serializers: dict[str, type[SerializedResponse]] = {}
try: try:
from .text import TextSerializedRequest, TextSerializedResponse from .text import TextSerializedRequest, TextSerializedResponse
default_serializers['text/plain'] = (TextSerializedRequest, TextSerializedResponse) default_req_serializers['text/plain'] = TextSerializedRequest
default_resp_serializers['text/plain'] = TextSerializedResponse
except: except:
pass pass
try: try:
from .json import JsonSerializedRequest, JsonSerializedResponse from .json import JsonSerializedRequest, JsonSerializedResponse
default_serializers['application/json'] = (JsonSerializedRequest, JsonSerializedResponse) default_req_serializers['application/json'] = JsonSerializedRequest
default_resp_serializers['application/json'] = JsonSerializedResponse
except: except:
pass pass
try: try:
from .xml import XMLSerializedRequest, XMLSerializedResponse from .xml import XMLSerializedRequest, XMLSerializedResponse
default_serializers['application/xml'] = (XMLSerializedRequest, XMLSerializedResponse) default_req_serializers['application/xml'] = XMLSerializedRequest
default_resp_serializers['application/xml'] = XMLSerializedResponse
except: except:
pass pass
try: try:
from .msgpack import MessagePackSerializedRequest, MessagePackSerializedResponse from .msgpack import MessagePackSerializedRequest, MessagePackSerializedResponse
default_serializers['application/vnd.msgpack'] = (MessagePackSerializedRequest, MessagePackSerializedResponse) default_req_serializers['application/vnd.msgpack'] = MessagePackSerializedRequest
default_resp_serializers['application/vnd.msgpack'] = MessagePackSerializedResponse
except:
pass
try:
from .multipart_form_data import MultipartFormSerializedRequest
default_req_serializers['application/octet-stream'] = MultipartFormSerializedRequest
default_req_serializers['application/x-www-form-urlencoded'] = MultipartFormSerializedRequest
except: except:
pass pass

View File

@@ -35,7 +35,7 @@ class SerializedRequest:
return self.basic.path_matches return self.basic.path_matches
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: async def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
raise NotImplementedError() raise NotImplementedError()

View File

@@ -7,13 +7,14 @@ from .base import SerializedRequest, SerializedResponse
class JsonSerializedRequest(SerializedRequest): class JsonSerializedRequest(SerializedRequest):
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: async def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
if len(basic.body) == 0: body = await basic.fetch_full_body()
if len(body) == 0:
b = None b = None
elif charset.lower() in {'utf-8', 'utf8'}: elif charset.lower() in {'utf-8', 'utf8'}:
b = orjson.loads(basic.body) b = orjson.loads(body)
else: else:
btxt = basic.body.decode(charset) btxt = body.decode(charset)
b = orjson.loads(btxt.encode('utf-8')) b = orjson.loads(btxt.encode('utf-8'))
return cls(b, basic, charset) return cls(b, basic, charset)

View File

@@ -7,11 +7,12 @@ from .base import SerializedRequest, SerializedResponse
class MessagePackSerializedRequest(SerializedRequest): class MessagePackSerializedRequest(SerializedRequest):
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: async def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
if len(basic.body) == 0: body = await basic.fetch_full_body()
if len(body) == 0:
b = None b = None
else: else:
b = msgpack.unpackb(basic.body) b = msgpack.unpackb(body)
return cls(b, basic, charset) return cls(b, basic, charset)

View File

@@ -0,0 +1,49 @@
import asyncio
from dataclasses import dataclass
from queue import Queue, Empty
from typing import AsyncIterable
import python_multipart
from case_insensitive_dict import CaseInsensitiveDict
from python_multipart import MultipartParser, FormParser
from turbosloth.interfaces.base import BasicRequest, BasicResponse
from .base import SerializedRequest, SerializedResponse
@dataclass
class MultipartFormSerializedRequest(SerializedRequest):
_parser: FormParser
_parse_q: Queue
async def _fetch_part(self) -> bool:
was_done = self.basic.is_body_recv_done
buf, _ = await self.basic.fetch_body_part()
self._parser.write(buf)
return was_done
async def __aiter__(self) -> AsyncIterable:
is_done = False
while not is_done:
try:
it = self._parse_q.get(block=False)
except Empty:
is_done = await self._fetch_part()
else:
yield it
def __init__(self, basic: BasicRequest, charset: str, ):
content_type = basic.headers.get('content-type', 'multipart/form-data')
self.basic = basic
self.body = None
self.charset = charset
self._parse_q = Queue()
self._parser = python_multipart.create_form_parser(
basic.headers,
on_field=self._parse_q.put,
on_file=self._parse_q.put,
)
@classmethod
async def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
return MultipartFormSerializedRequest(basic, charset)

View File

@@ -6,8 +6,9 @@ from .base import SerializedRequest, SerializedResponse
class TextSerializedRequest(SerializedRequest): class TextSerializedRequest(SerializedRequest):
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: async def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
b = basic.body.decode(charset) body = await basic.fetch_full_body()
b = body.decode(charset)
return cls(b, basic, charset) return cls(b, basic, charset)

View File

@@ -9,11 +9,12 @@ from .base import SerializedRequest, SerializedResponse
class XMLSerializedRequest(SerializedRequest): class XMLSerializedRequest(SerializedRequest):
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: async def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
if len(basic.body) == 0: body = await basic.fetch_full_body()
if len(body) == 0:
b = {} b = {}
else: else:
btxt = basic.body.decode(charset) btxt = body.decode(charset)
parsed = etree.fromstring(btxt) parsed = etree.fromstring(btxt)
b = {child.tag: child.text for child in parsed} b = {child.tag: child.text for child in parsed}

View File

@@ -10,7 +10,6 @@ type Send = Callable[[ASGIMessage], Awaitable[None]]
type MethodType = ( type MethodType = (
Literal['GET'] | Literal['GET'] |
Literal['POST'] | Literal['POST'] |
Literal['PUSH'] |
Literal['PUT'] | Literal['PUT'] |
Literal['PATCH'] | Literal['PATCH'] |
Literal['DELETE'] | Literal['DELETE'] |

View File

@@ -1,19 +1,43 @@
from __future__ import annotations from __future__ import annotations
import inspect import inspect
import typing
from types import UnionType
from typing import TYPE_CHECKING, overload, Any, TypeAlias, Optional, get_origin, get_args, Callable, get_type_hints, \ from typing import TYPE_CHECKING, overload, Any, TypeAlias, Optional, get_origin, get_args, Callable, get_type_hints, \
Iterable Iterable, Union, Type, Tuple
from dataclasses import dataclass from dataclasses import dataclass
from typing import TypeVar, Generic, Annotated from typing import TypeVar, Generic, Annotated
from megasniff.utils import TupleSchemaItem from megasniff.utils import TupleSchemaItem
from turbosloth.doc.openapi_app import resolve_type
from turbosloth.interfaces.base import BasicResponse
from turbosloth.interfaces.serialized import SerializedResponse
from turbosloth.types import HTTPCodeType
T = TypeVar("T") T = TypeVar("T")
T1 = TypeVar("T1")
T2 = TypeVar("T2")
from typing import Annotated, TypeAlias from typing import Annotated, TypeAlias
def Resp[T1, T2](tps: type[T1] | tuple[type[T1], type[T2]],
return_code: HTTPCodeType = 200) -> tuple[type[T1], type[T2]] | type[T1]:
if isinstance(tps, tuple):
tp = tps[0]
htp = tps[1]
else:
tp = tps
htp = None
if htp is None:
return Annotated[tp, "__turbosloth__response_schema__", int(return_code)]
else:
return Annotated[tuple[tp, htp], "__turbosloth__response_schema__", int(return_code)]
def RequestBody[T](tp: type[T]) -> type[T]: def RequestBody[T](tp: type[T]) -> type[T]:
return Annotated[tp, "__turbosloth__request_body__"] return Annotated[tp, "__turbosloth__request_body__"]
@@ -63,22 +87,169 @@ def get_endpoint_params_info(func) -> dict[str, ParamSchema]:
return result return result
@dataclass
class ReturnSchemaItem:
body: type
_headers: type
http_code: HTTPCodeType
def __init__(self, body: type, headers: type, http_code: HTTPCodeType):
self.body = body
self._headers = headers
self.http_code = http_code
@property
def headers(self) -> type:
if self._headers is None:
return dict
else:
return self._headers
@property
def headers_provided(self) -> bool:
return self._headers is not None
@property
def restore_func_ret_schema(self) -> type:
if self._headers is None:
return self.body
else:
return tuple[self.body, self.headers]
def __hash__(self):
return hash((self.body, self.headers))
@dataclass
class ReturnSchema:
code_map: dict[str, type | ReturnSchemaItem]
@classmethod
def from_raw(cls, tp: type):
origin = get_origin(tp)
if origin == Union:
schemas = get_args(tp)
else:
schemas = [tp]
schema_map = {}
for schema in schemas:
origin = get_origin(schema)
if origin == Annotated:
args = get_args(schema)
schema_type = args[1]
if schema_type != '__turbosloth__response_schema__':
continue
body_header = args[0]
http_code = str(int(args[2]))
body_schema = body_header
header_schema = None
b_origin = get_origin(body_header)
if b_origin == Tuple or b_origin == tuple:
args = get_args(body_header)
body_schema = args[0]
header_schema = args[1]
if http_code in schema_map.keys():
raise ValueError(f'Duplicate return code {http_code}')
schema_map[http_code] = ReturnSchemaItem(body_schema,
header_schema,
typing.cast(HTTPCodeType, http_code))
else:
continue
return ReturnSchema(schema_map)
#
# @classmethod
# def from_handler(cls, h: Callable, default_ok_code: str = '200') -> ReturnSchema:
# handle_hints = get_type_hints(h)
# ret_schema = handle_hints.get('return', None)
# ret_type = resolve_type(ret_schema)
# origin = get_origin(ret_type)
#
# ret = {}
#
# if origin is Union or origin is UnionType:
# for return_variant in get_args(ret_type):
# variant_origin = get_origin(return_variant)
# variant = return_variant
#
# if variant_origin is not None and issubclass(variant_origin, HTTPResponse):
# body_t, code_literal, header_t = get_args(return_variant)
# if code_literal is None:
# code_literal = default_ok_code
# else:
# code_literal, = get_args(code_literal)
# variant = ReturnSchemaItem(body_t, header_t)
# else:
# code_literal = default_ok_code
#
# if code_literal in ret.keys():
# raise ValueError(f'Duplicate return code: {code_literal}')
# ret[code_literal] = variant
# # TODO: union response schemas, dosctring :raises
# # чтобы разбирать возвращаемые схемы вида A|B|C|Annotated[D,"HTTPCode"]|SerializedResponse|BasicResponse
# # будет необходимо научить breakshaft делать разбиение A|B|C->D на множество isinstance
# # при условии что существуют все преобразования {A->D,B->D,C->D}
# # также, для определения исключений через :raises в docstring метода, необходимо научить breakshaft
# # преобразовывать A->B(raises C|D) в A->B|C|D
# # raise NotImplementedError('Union return schemas does not implemented yet')
# else:
# variant_origin = get_origin(ret_type)
# if variant_origin is not None and issubclass(variant_origin, HTTPResponse):
# body_t, code_literal, header_t = get_args(ret_type)
# if code_literal is None:
# code_literal = default_ok_code
# else:
# code_literal, = get_args(code_literal)
# variant = ReturnSchemaItem(body_t, header_t)
# else:
# code_literal = default_ok_code
# variant = ret_type
#
# if code_literal in ret.keys():
# raise ValueError(f'Duplicate return code: {code_literal}')
# ret[code_literal] = variant
#
# if len(list(ret.values())) != len(set(ret.values())):
# raise ValueError(f'Duplicate return schema on different return codes: {list(ret.values())}')
#
# for v in ret.values():
# orig = get_origin(v)
# if orig is not None:
# v = orig
# if not isinstance(v, ReturnSchemaItem) and issubclass(v, (SerializedResponse, BasicResponse)):
# raise NotImplementedError(
# 'Mixing autoserialized and manually serialized responses are not supported yet')
#
# return ReturnSchema(ret)
@dataclass @dataclass
class EndpointConfig: class EndpointConfig:
body_schema: ParamSchema | None body_schema: ParamSchema | None
query_schemas: dict[str, ParamSchema] query_schemas: dict[str, ParamSchema]
path_schemas: dict[str, ParamSchema] path_schemas: dict[str, ParamSchema]
header_schemas: dict[str, ParamSchema] header_schemas: dict[str, ParamSchema]
return_schema: ReturnSchema | None
fn: Callable fn: Callable
type_replacement: dict[str, type] type_replacement: dict[str, type]
@classmethod @classmethod
def from_handler(cls, h: Callable, path_substituts: set[str], ignore_types: Iterable[type]) -> EndpointConfig: def from_handler(cls,
h: Callable,
path_substituts: set[str],
ignore_types: Iterable[type],
ok_return_code: Optional[str] = None) -> EndpointConfig:
body_schema = None body_schema = None
query_schemas = {} query_schemas = {}
path_schemas = {} path_schemas = {}
header_schemas = {} header_schemas = {}
type_replacement = {} type_replacement = {}
return_schema = None
handle_hints = get_endpoint_params_info(h) handle_hints = get_endpoint_params_info(h)
for argname, s in handle_hints.items(): for argname, s in handle_hints.items():
@@ -87,9 +258,6 @@ class EndpointConfig:
continue continue
type_replacement[argname] = s.replacement_type type_replacement[argname] = s.replacement_type
if argname == 'return':
continue
if get_origin(tp) == Annotated: if get_origin(tp) == Annotated:
args = get_args(tp) args = get_args(tp)
t = args[0] t = args[0]
@@ -120,4 +288,26 @@ class EndpointConfig:
else: else:
query_schemas[argname] = s query_schemas[argname] = s
return EndpointConfig(body_schema, query_schemas, path_schemas, header_schemas, h, type_replacement) ret_type = get_type_hints(h, include_extras=True).get('return')
if ret_type is not None:
return_schema = ReturnSchema.from_raw(ret_type)
if len(return_schema.code_map) > 0:
type CustomReturnTypeReplacement = ret_type
type_replacement['return'] = CustomReturnTypeReplacement
# if origin is not None:
# ret_type = origin
# if ok_return_code is not None and (ret_type is Union or ret_type is UnionType or not issubclass(ret_type,
# (SerializedResponse,
# BasicResponse))):
# return_schema = ReturnSchema.from_handler(h, ok_return_code)
# type ReturnTypeReplacement = object
# type_replacement['return'] = ReturnTypeReplacement
return EndpointConfig(body_schema,
query_schemas,
path_schemas,
header_schemas,
return_schema,
h,
type_replacement)

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable, Awaitable, Literal, Any from typing import Callable, Awaitable, Literal, Any, Generic, TypeVar
from turbosloth.interfaces.base import BasicRequest from turbosloth.interfaces.base import BasicRequest
from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest
@@ -19,3 +19,50 @@ class ContentType:
type Accept = ContentType type Accept = ContentType
type HTTPReturnCode = str | int
type PreSerializedResponseBody = dict | list | int | str | float | bool | None
type PreSerializedResponseHeader = dict[str, str]
type ResponseHeaders = dict[str, str]
type HTTPResponseBodyPlaceholder = Any
type HTTPResponseHeadersPlaceholder = Any
BodyT = TypeVar('BodyT')
HeaderT = TypeVar('HeaderT', default=dict[str, str])
type HTTPCodeType = (Literal["100"] | Literal["101"] | Literal["102"] | Literal["103"] | Literal["200"] |
Literal["201"] | Literal["202"] | Literal["203"] | Literal["204"] | Literal["205"] |
Literal["206"] | Literal["207"] | Literal["208"] | Literal["226"] | Literal["300"] |
Literal["301"] | Literal["302"] | Literal["303"] | Literal["304"] | Literal["305"] |
Literal["306"] | Literal["307"] | Literal["308"] | Literal["400"] | Literal["401"] |
Literal["402"] | Literal["403"] | Literal["404"] | Literal["405"] | Literal["406"] |
Literal["407"] | Literal["408"] | Literal["409"] | Literal["410"] | Literal["411"] |
Literal["412"] | Literal["413"] | Literal["414"] | Literal["415"] | Literal["416"] |
Literal["417"] | Literal["418"] | Literal["421"] | Literal["422"] | Literal["423"] |
Literal["424"] | Literal["425"] | Literal["426"] | Literal["428"] | Literal["429"] |
Literal["431"] | Literal["451"] | Literal["500"] | Literal["501"] | Literal["502"] |
Literal["503"] | Literal["504"] | Literal["505"] | Literal["506"] | Literal["507"] |
Literal["508"] | Literal["510"] | Literal["511"] |
Literal[100] | Literal[101] | Literal[102] | Literal[103] | Literal[200] |
Literal[201] | Literal[202] | Literal[203] | Literal[204] | Literal[205] |
Literal[206] | Literal[207] | Literal[208] | Literal[226] | Literal[300] |
Literal[301] | Literal[302] | Literal[303] | Literal[304] | Literal[305] |
Literal[306] | Literal[307] | Literal[308] | Literal[400] | Literal[401] |
Literal[402] | Literal[403] | Literal[404] | Literal[405] | Literal[406] |
Literal[407] | Literal[408] | Literal[409] | Literal[410] | Literal[411] |
Literal[412] | Literal[413] | Literal[414] | Literal[415] | Literal[416] |
Literal[417] | Literal[418] | Literal[421] | Literal[422] | Literal[423] |
Literal[424] | Literal[425] | Literal[426] | Literal[428] | Literal[429] |
Literal[431] | Literal[451] | Literal[500] | Literal[501] | Literal[502] |
Literal[503] | Literal[504] | Literal[505] | Literal[506] | Literal[507] |
Literal[508] | Literal[510] | Literal[511]
)
# HTTPCodeT = TypeVar('HTTPCodeT', bound=HTTPCodeType | None, default=None)
#
#
# class HTTPResponse(Generic[BodyT, HTTPCodeT, HeaderT]):
# body: BodyT
# header: HeaderT
#
# def __init__(self, body: BodyT, header: HeaderT):
# self.body = body
# self.header = header

287
uv.lock generated
View File

@@ -1,18 +1,18 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.13" requires-python = ">=3.13"
[[package]] [[package]]
name = "breakshaft" name = "breakshaft"
version = "0.1.6.post2" version = "0.1.6.post4"
source = { registry = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" } source = { registry = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }
dependencies = [ dependencies = [
{ name = "hatchling" }, { name = "hatchling" },
{ name = "jinja2" }, { name = "jinja2" },
] ]
sdist = { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.6.post2/breakshaft-0.1.6.post2.tar.gz", hash = "sha256:523cdbd55f7fcea3a3f664bbc3ec7d524be09f1f45c4c3b39f3de18164fad37d" } sdist = { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.6.post4/breakshaft-0.1.6.post4.tar.gz", hash = "sha256:d971832bbf7fc08785684355b69c1af8782bbc1fe0502d8057db53cbff8faaf6" }
wheels = [ wheels = [
{ url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.6.post2/breakshaft-0.1.6.post2-py3-none-any.whl", hash = "sha256:a355b4546ef94f92e792e14a6cfcd029298a299b453c7ba47e13c32e5a82ff4b" }, { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/breakshaft/0.1.6.post4/breakshaft-0.1.6.post4-py3-none-any.whl", hash = "sha256:cb7b206330fcb7da37f6e5fc010ddc3dd4503603327b3d10e40d3095e1795d41" },
] ]
[[package]] [[package]]
@@ -47,55 +47,55 @@ wheels = [
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.10.4" version = "7.10.6"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/4e/08b493f1f1d8a5182df0044acc970799b58a8d289608e0d891a03e9d269a/coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27", size = 823798, upload-time = "2025-08-17T00:26:43.314Z" } 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" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/46/b0/4a3662de81f2ed792a4e425d59c4ae50d8dd1d844de252838c200beed65a/coverage-7.10.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b8e1d2015d5dfdbf964ecef12944c0c8c55b885bb5c0467ae8ef55e0e151233", size = 216735, upload-time = "2025-08-17T00:25:08.617Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/c5/e8/e2dcffea01921bfffc6170fb4406cffb763a3b43a047bbd7923566708193/coverage-7.10.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:25735c299439018d66eb2dccf54f625aceb78645687a05f9f848f6e6c751e169", size = 216982, upload-time = "2025-08-17T00:25:10.384Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/9d/59/cc89bb6ac869704d2781c2f5f7957d07097c77da0e8fdd4fd50dbf2ac9c0/coverage-7.10.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:715c06cb5eceac4d9b7cdf783ce04aa495f6aff657543fea75c30215b28ddb74", size = 247981, upload-time = "2025-08-17T00:25:11.854Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/aa/23/3da089aa177ceaf0d3f96754ebc1318597822e6387560914cc480086e730/coverage-7.10.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e017ac69fac9aacd7df6dc464c05833e834dc5b00c914d7af9a5249fcccf07ef", size = 250584, upload-time = "2025-08-17T00:25:13.483Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/ad/82/e8693c368535b4e5fad05252a366a1794d481c79ae0333ed943472fd778d/coverage-7.10.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bad180cc40b3fccb0f0e8c702d781492654ac2580d468e3ffc8065e38c6c2408", size = 251856, upload-time = "2025-08-17T00:25:15.27Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/56/19/8b9cb13292e602fa4135b10a26ac4ce169a7fc7c285ff08bedd42ff6acca/coverage-7.10.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:becbdcd14f685fada010a5f792bf0895675ecf7481304fe159f0cd3f289550bd", size = 250015, upload-time = "2025-08-17T00:25:16.759Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/10/e7/e5903990ce089527cf1c4f88b702985bd65c61ac245923f1ff1257dbcc02/coverage-7.10.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0b485ca21e16a76f68060911f97ebbe3e0d891da1dbbce6af7ca1ab3f98b9097", size = 247908, upload-time = "2025-08-17T00:25:18.232Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/dd/c9/7d464f116df1df7fe340669af1ddbe1a371fc60f3082ff3dc837c4f1f2ab/coverage-7.10.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6c1d098ccfe8e1e0a1ed9a0249138899948afd2978cbf48eb1cc3fcd38469690", size = 249525, upload-time = "2025-08-17T00:25:20.141Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/ce/42/722e0cdbf6c19e7235c2020837d4e00f3b07820fd012201a983238cc3a30/coverage-7.10.4-cp313-cp313-win32.whl", hash = "sha256:8630f8af2ca84b5c367c3df907b1706621abe06d6929f5045fd628968d421e6e", size = 219173, upload-time = "2025-08-17T00:25:21.56Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/97/7e/aa70366f8275955cd51fa1ed52a521c7fcebcc0fc279f53c8c1ee6006dfe/coverage-7.10.4-cp313-cp313-win_amd64.whl", hash = "sha256:f68835d31c421736be367d32f179e14ca932978293fe1b4c7a6a49b555dff5b2", size = 219969, upload-time = "2025-08-17T00:25:23.501Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/ac/96/c39d92d5aad8fec28d4606556bfc92b6fee0ab51e4a548d9b49fb15a777c/coverage-7.10.4-cp313-cp313-win_arm64.whl", hash = "sha256:6eaa61ff6724ca7ebc5326d1fae062d85e19b38dd922d50903702e6078370ae7", size = 218601, upload-time = "2025-08-17T00:25:25.295Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/79/13/34d549a6177bd80fa5db758cb6fd3057b7ad9296d8707d4ab7f480b0135f/coverage-7.10.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:702978108876bfb3d997604930b05fe769462cc3000150b0e607b7b444f2fd84", size = 217445, upload-time = "2025-08-17T00:25:27.129Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/6a/c0/433da866359bf39bf595f46d134ff2d6b4293aeea7f3328b6898733b0633/coverage-7.10.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e8f978e8c5521d9c8f2086ac60d931d583fab0a16f382f6eb89453fe998e2484", size = 217676, upload-time = "2025-08-17T00:25:28.641Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/7e/d7/2b99aa8737f7801fd95222c79a4ebc8c5dd4460d4bed7ef26b17a60c8d74/coverage-7.10.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:df0ac2ccfd19351411c45e43ab60932b74472e4648b0a9edf6a3b58846e246a9", size = 259002, upload-time = "2025-08-17T00:25:30.065Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/08/cf/86432b69d57debaef5abf19aae661ba8f4fcd2882fa762e14added4bd334/coverage-7.10.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73a0d1aaaa3796179f336448e1576a3de6fc95ff4f07c2d7251d4caf5d18cf8d", size = 261178, upload-time = "2025-08-17T00:25:31.517Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/23/78/85176593f4aa6e869cbed7a8098da3448a50e3fac5cb2ecba57729a5220d/coverage-7.10.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873da6d0ed6b3ffc0bc01f2c7e3ad7e2023751c0d8d86c26fe7322c314b031dc", size = 263402, upload-time = "2025-08-17T00:25:33.339Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/88/1d/57a27b6789b79abcac0cc5805b31320d7a97fa20f728a6a7c562db9a3733/coverage-7.10.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c6446c75b0e7dda5daa876a1c87b480b2b52affb972fedd6c22edf1aaf2e00ec", size = 260957, upload-time = "2025-08-17T00:25:34.795Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/fa/e5/3e5ddfd42835c6def6cd5b2bdb3348da2e34c08d9c1211e91a49e9fd709d/coverage-7.10.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6e73933e296634e520390c44758d553d3b573b321608118363e52113790633b9", size = 258718, upload-time = "2025-08-17T00:25:36.259Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/1a/0b/d364f0f7ef111615dc4e05a6ed02cac7b6f2ac169884aa57faeae9eb5fa0/coverage-7.10.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52073d4b08d2cb571234c8a71eb32af3c6923149cf644a51d5957ac128cf6aa4", size = 259848, upload-time = "2025-08-17T00:25:37.754Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/10/c6/bbea60a3b309621162e53faf7fac740daaf083048ea22077418e1ecaba3f/coverage-7.10.4-cp313-cp313t-win32.whl", hash = "sha256:e24afb178f21f9ceb1aefbc73eb524769aa9b504a42b26857243f881af56880c", size = 219833, upload-time = "2025-08-17T00:25:39.252Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/44/a5/f9f080d49cfb117ddffe672f21eab41bd23a46179a907820743afac7c021/coverage-7.10.4-cp313-cp313t-win_amd64.whl", hash = "sha256:be04507ff1ad206f4be3d156a674e3fb84bbb751ea1b23b142979ac9eebaa15f", size = 220897, upload-time = "2025-08-17T00:25:40.772Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/46/89/49a3fc784fa73d707f603e586d84a18c2e7796707044e9d73d13260930b7/coverage-7.10.4-cp313-cp313t-win_arm64.whl", hash = "sha256:f3e3ff3f69d02b5dad67a6eac68cc9c71ae343b6328aae96e914f9f2f23a22e2", size = 219160, upload-time = "2025-08-17T00:25:42.229Z" }, { 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" },
{ url = "https://files.pythonhosted.org/packages/b5/22/525f84b4cbcff66024d29f6909d7ecde97223f998116d3677cfba0d115b5/coverage-7.10.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a59fe0af7dd7211ba595cf7e2867458381f7e5d7b4cffe46274e0b2f5b9f4eb4", size = 216717, upload-time = "2025-08-17T00:25:43.875Z" }, { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" },
{ url = "https://files.pythonhosted.org/packages/a6/58/213577f77efe44333a416d4bcb251471e7f64b19b5886bb515561b5ce389/coverage-7.10.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a6c35c5b70f569ee38dc3350cd14fdd0347a8b389a18bb37538cc43e6f730e6", size = 216994, upload-time = "2025-08-17T00:25:45.405Z" }, { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" },
{ url = "https://files.pythonhosted.org/packages/17/85/34ac02d0985a09472f41b609a1d7babc32df87c726c7612dc93d30679b5a/coverage-7.10.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:acb7baf49f513554c4af6ef8e2bd6e8ac74e6ea0c7386df8b3eb586d82ccccc4", size = 248038, upload-time = "2025-08-17T00:25:46.981Z" }, { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" },
{ url = "https://files.pythonhosted.org/packages/47/4f/2140305ec93642fdaf988f139813629cbb6d8efa661b30a04b6f7c67c31e/coverage-7.10.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a89afecec1ed12ac13ed203238b560cbfad3522bae37d91c102e690b8b1dc46c", size = 250575, upload-time = "2025-08-17T00:25:48.613Z" }, { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" },
{ url = "https://files.pythonhosted.org/packages/f2/b5/41b5784180b82a083c76aeba8f2c72ea1cb789e5382157b7dc852832aea2/coverage-7.10.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:480442727f464407d8ade6e677b7f21f3b96a9838ab541b9a28ce9e44123c14e", size = 251927, upload-time = "2025-08-17T00:25:50.881Z" }, { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" },
{ url = "https://files.pythonhosted.org/packages/78/ca/c1dd063e50b71f5aea2ebb27a1c404e7b5ecf5714c8b5301f20e4e8831ac/coverage-7.10.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a89bf193707f4a17f1ed461504031074d87f035153239f16ce86dfb8f8c7ac76", size = 249930, upload-time = "2025-08-17T00:25:52.422Z" }, { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" },
{ url = "https://files.pythonhosted.org/packages/8d/66/d8907408612ffee100d731798e6090aedb3ba766ecf929df296c1a7ee4fb/coverage-7.10.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3ddd912c2fc440f0fb3229e764feec85669d5d80a988ff1b336a27d73f63c818", size = 247862, upload-time = "2025-08-17T00:25:54.316Z" }, { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" },
{ url = "https://files.pythonhosted.org/packages/29/db/53cd8ec8b1c9c52d8e22a25434785bfc2d1e70c0cfb4d278a1326c87f741/coverage-7.10.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a538944ee3a42265e61c7298aeba9ea43f31c01271cf028f437a7b4075592cf", size = 249360, upload-time = "2025-08-17T00:25:55.833Z" }, { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" },
{ url = "https://files.pythonhosted.org/packages/4f/75/5ec0a28ae4a0804124ea5a5becd2b0fa3adf30967ac656711fb5cdf67c60/coverage-7.10.4-cp314-cp314-win32.whl", hash = "sha256:fd2e6002be1c62476eb862b8514b1ba7e7684c50165f2a8d389e77da6c9a2ebd", size = 219449, upload-time = "2025-08-17T00:25:57.984Z" }, { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" },
{ url = "https://files.pythonhosted.org/packages/9d/ab/66e2ee085ec60672bf5250f11101ad8143b81f24989e8c0e575d16bb1e53/coverage-7.10.4-cp314-cp314-win_amd64.whl", hash = "sha256:ec113277f2b5cf188d95fb66a65c7431f2b9192ee7e6ec9b72b30bbfb53c244a", size = 220246, upload-time = "2025-08-17T00:25:59.868Z" }, { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" },
{ url = "https://files.pythonhosted.org/packages/37/3b/00b448d385f149143190846217797d730b973c3c0ec2045a7e0f5db3a7d0/coverage-7.10.4-cp314-cp314-win_arm64.whl", hash = "sha256:9744954bfd387796c6a091b50d55ca7cac3d08767795b5eec69ad0f7dbf12d38", size = 218825, upload-time = "2025-08-17T00:26:01.44Z" }, { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" },
{ url = "https://files.pythonhosted.org/packages/ee/2e/55e20d3d1ce00b513efb6fd35f13899e1c6d4f76c6cbcc9851c7227cd469/coverage-7.10.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5af4829904dda6aabb54a23879f0f4412094ba9ef153aaa464e3c1b1c9bc98e6", size = 217462, upload-time = "2025-08-17T00:26:03.014Z" }, { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" },
{ url = "https://files.pythonhosted.org/packages/47/b3/aab1260df5876f5921e2c57519e73a6f6eeacc0ae451e109d44ee747563e/coverage-7.10.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7bba5ed85e034831fac761ae506c0644d24fd5594727e174b5a73aff343a7508", size = 217675, upload-time = "2025-08-17T00:26:04.606Z" }, { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" },
{ url = "https://files.pythonhosted.org/packages/67/23/1cfe2aa50c7026180989f0bfc242168ac7c8399ccc66eb816b171e0ab05e/coverage-7.10.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d57d555b0719834b55ad35045de6cc80fc2b28e05adb6b03c98479f9553b387f", size = 259176, upload-time = "2025-08-17T00:26:06.159Z" }, { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" },
{ url = "https://files.pythonhosted.org/packages/9d/72/5882b6aeed3f9de7fc4049874fd7d24213bf1d06882f5c754c8a682606ec/coverage-7.10.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ba62c51a72048bb1ea72db265e6bd8beaabf9809cd2125bbb5306c6ce105f214", size = 261341, upload-time = "2025-08-17T00:26:08.137Z" }, { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" },
{ url = "https://files.pythonhosted.org/packages/1b/70/a0c76e3087596ae155f8e71a49c2c534c58b92aeacaf4d9d0cbbf2dde53b/coverage-7.10.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0acf0c62a6095f07e9db4ec365cc58c0ef5babb757e54745a1aa2ea2a2564af1", size = 263600, upload-time = "2025-08-17T00:26:11.045Z" }, { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" },
{ url = "https://files.pythonhosted.org/packages/cb/5f/27e4cd4505b9a3c05257fb7fc509acbc778c830c450cb4ace00bf2b7bda7/coverage-7.10.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1033bf0f763f5cf49ffe6594314b11027dcc1073ac590b415ea93463466deec", size = 261036, upload-time = "2025-08-17T00:26:12.693Z" }, { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" },
{ url = "https://files.pythonhosted.org/packages/02/d6/cf2ae3a7f90ab226ea765a104c4e76c5126f73c93a92eaea41e1dc6a1892/coverage-7.10.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:92c29eff894832b6a40da1789b1f252305af921750b03ee4535919db9179453d", size = 258794, upload-time = "2025-08-17T00:26:14.261Z" }, { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" },
{ url = "https://files.pythonhosted.org/packages/9e/b1/39f222eab0d78aa2001cdb7852aa1140bba632db23a5cfd832218b496d6c/coverage-7.10.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:822c4c830989c2093527e92acd97be4638a44eb042b1bdc0e7a278d84a070bd3", size = 259946, upload-time = "2025-08-17T00:26:15.899Z" }, { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" },
{ url = "https://files.pythonhosted.org/packages/74/b2/49d82acefe2fe7c777436a3097f928c7242a842538b190f66aac01f29321/coverage-7.10.4-cp314-cp314t-win32.whl", hash = "sha256:e694d855dac2e7cf194ba33653e4ba7aad7267a802a7b3fc4347d0517d5d65cd", size = 220226, upload-time = "2025-08-17T00:26:17.566Z" }, { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" },
{ url = "https://files.pythonhosted.org/packages/06/b0/afb942b6b2fc30bdbc7b05b087beae11c2b0daaa08e160586cf012b6ad70/coverage-7.10.4-cp314-cp314t-win_amd64.whl", hash = "sha256:efcc54b38ef7d5bfa98050f220b415bc5bb3d432bd6350a861cf6da0ede2cdcd", size = 221346, upload-time = "2025-08-17T00:26:19.311Z" }, { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" },
{ url = "https://files.pythonhosted.org/packages/d8/66/e0531c9d1525cb6eac5b5733c76f27f3053ee92665f83f8899516fea6e76/coverage-7.10.4-cp314-cp314t-win_arm64.whl", hash = "sha256:6f3a3496c0fa26bfac4ebc458747b778cff201c8ae94fa05e1391bab0dbc473c", size = 219368, upload-time = "2025-08-17T00:26:21.011Z" }, { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" },
{ url = "https://files.pythonhosted.org/packages/bb/78/983efd23200921d9edb6bd40512e1aa04af553d7d5a171e50f9b2b45d109/coverage-7.10.4-py3-none-any.whl", hash = "sha256:065d75447228d05121e5c938ca8f0e91eed60a1eb2d1258d42d5084fecfc3302", size = 208365, upload-time = "2025-08-17T00:26:41.479Z" }, { 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" },
] ]
[[package]] [[package]]
@@ -145,26 +145,46 @@ wheels = [
[[package]] [[package]]
name = "lxml" name = "lxml"
version = "6.0.0" version = "6.0.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } sdist = { url = "https://files.pythonhosted.org/packages/8f/bd/f9d01fd4132d81c6f43ab01983caea69ec9614b913c290a26738431a015d/lxml-6.0.1.tar.gz", hash = "sha256:2b3a882ebf27dd026df3801a87cf49ff791336e0f94b0fad195db77e01240690", size = 4070214, upload-time = "2025-08-22T10:37:53.525Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" }, { url = "https://files.pythonhosted.org/packages/43/c4/cd757eeec4548e6652eff50b944079d18ce5f8182d2b2cf514e125e8fbcb/lxml-6.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:485eda5d81bb7358db96a83546949c5fe7474bec6c68ef3fa1fb61a584b00eea", size = 8405139, upload-time = "2025-08-22T10:33:34.09Z" },
{ url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" }, { url = "https://files.pythonhosted.org/packages/ff/99/0290bb86a7403893f5e9658490c705fcea103b9191f2039752b071b4ef07/lxml-6.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d12160adea318ce3d118f0b4fbdff7d1225c75fb7749429541b4d217b85c3f76", size = 4585954, upload-time = "2025-08-22T10:33:36.294Z" },
{ url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" }, { url = "https://files.pythonhosted.org/packages/88/a7/4bb54dd1e626342a0f7df6ec6ca44fdd5d0e100ace53acc00e9a689ead04/lxml-6.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48c8d335d8ab72f9265e7ba598ae5105a8272437403f4032107dbcb96d3f0b29", size = 4944052, upload-time = "2025-08-22T10:33:38.19Z" },
{ url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" }, { url = "https://files.pythonhosted.org/packages/71/8d/20f51cd07a7cbef6214675a8a5c62b2559a36d9303fe511645108887c458/lxml-6.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:405e7cf9dbdbb52722c231e0f1257214202dfa192327fab3de45fd62e0554082", size = 5098885, upload-time = "2025-08-22T10:33:40.035Z" },
{ url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" }, { url = "https://files.pythonhosted.org/packages/5a/63/efceeee7245d45f97d548e48132258a36244d3c13c6e3ddbd04db95ff496/lxml-6.0.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:299a790d403335a6a057ade46f92612ebab87b223e4e8c5308059f2dc36f45ed", size = 5017542, upload-time = "2025-08-22T10:33:41.896Z" },
{ url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" }, { url = "https://files.pythonhosted.org/packages/57/5d/92cb3d3499f5caba17f7933e6be3b6c7de767b715081863337ced42eb5f2/lxml-6.0.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:48da704672f6f9c461e9a73250440c647638cc6ff9567ead4c3b1f189a604ee8", size = 5347303, upload-time = "2025-08-22T10:33:43.868Z" },
{ url = "https://files.pythonhosted.org/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" }, { url = "https://files.pythonhosted.org/packages/69/f8/606fa16a05d7ef5e916c6481c634f40870db605caffed9d08b1a4fb6b989/lxml-6.0.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21e364e1bb731489e3f4d51db416f991a5d5da5d88184728d80ecfb0904b1d68", size = 5641055, upload-time = "2025-08-22T10:33:45.784Z" },
{ url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" }, { url = "https://files.pythonhosted.org/packages/b3/01/15d5fc74ebb49eac4e5df031fbc50713dcc081f4e0068ed963a510b7d457/lxml-6.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bce45a2c32032afddbd84ed8ab092130649acb935536ef7a9559636ce7ffd4a", size = 5242719, upload-time = "2025-08-22T10:33:48.089Z" },
{ url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" }, { url = "https://files.pythonhosted.org/packages/42/a5/1b85e2aaaf8deaa67e04c33bddb41f8e73d07a077bf9db677cec7128bfb4/lxml-6.0.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:fa164387ff20ab0e575fa909b11b92ff1481e6876835014e70280769920c4433", size = 4717310, upload-time = "2025-08-22T10:33:49.852Z" },
{ url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" }, { url = "https://files.pythonhosted.org/packages/42/23/f3bb1292f55a725814317172eeb296615db3becac8f1a059b53c51fc1da8/lxml-6.0.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7587ac5e000e1594e62278422c5783b34a82b22f27688b1074d71376424b73e8", size = 5254024, upload-time = "2025-08-22T10:33:52.22Z" },
{ url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" }, { url = "https://files.pythonhosted.org/packages/b4/be/4d768f581ccd0386d424bac615d9002d805df7cc8482ae07d529f60a3c1e/lxml-6.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:57478424ac4c9170eabf540237125e8d30fad1940648924c058e7bc9fb9cf6dd", size = 5055335, upload-time = "2025-08-22T10:33:54.041Z" },
{ url = "https://files.pythonhosted.org/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" }, { url = "https://files.pythonhosted.org/packages/40/07/ed61d1a3e77d1a9f856c4fab15ee5c09a2853fb7af13b866bb469a3a6d42/lxml-6.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:09c74afc7786c10dd6afaa0be2e4805866beadc18f1d843cf517a7851151b499", size = 4784864, upload-time = "2025-08-22T10:33:56.382Z" },
{ url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" }, { url = "https://files.pythonhosted.org/packages/01/37/77e7971212e5c38a55431744f79dff27fd751771775165caea096d055ca4/lxml-6.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7fd70681aeed83b196482d42a9b0dc5b13bab55668d09ad75ed26dff3be5a2f5", size = 5657173, upload-time = "2025-08-22T10:33:58.698Z" },
{ url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" }, { url = "https://files.pythonhosted.org/packages/32/a3/e98806d483941cd9061cc838b1169626acef7b2807261fbe5e382fcef881/lxml-6.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:10a72e456319b030b3dd900df6b1f19d89adf06ebb688821636dc406788cf6ac", size = 5245896, upload-time = "2025-08-22T10:34:00.586Z" },
{ url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" }, { url = "https://files.pythonhosted.org/packages/07/de/9bb5a05e42e8623bf06b4638931ea8c8f5eb5a020fe31703abdbd2e83547/lxml-6.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0fa45fb5f55111ce75b56c703843b36baaf65908f8b8d2fbbc0e249dbc127ed", size = 5267417, upload-time = "2025-08-22T10:34:02.719Z" },
{ url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" }, { url = "https://files.pythonhosted.org/packages/f2/43/c1cb2a7c67226266c463ef8a53b82d42607228beb763b5fbf4867e88a21f/lxml-6.0.1-cp313-cp313-win32.whl", hash = "sha256:01dab65641201e00c69338c9c2b8a0f2f484b6b3a22d10779bb417599fae32b5", size = 3610051, upload-time = "2025-08-22T10:34:04.553Z" },
{ url = "https://files.pythonhosted.org/packages/34/96/6a6c3b8aa480639c1a0b9b6faf2a63fb73ab79ffcd2a91cf28745faa22de/lxml-6.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:bdf8f7c8502552d7bff9e4c98971910a0a59f60f88b5048f608d0a1a75e94d1c", size = 4009325, upload-time = "2025-08-22T10:34:06.24Z" },
{ url = "https://files.pythonhosted.org/packages/8c/66/622e8515121e1fd773e3738dae71b8df14b12006d9fb554ce90886689fd0/lxml-6.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6aeca75959426b9fd8d4782c28723ba224fe07cfa9f26a141004210528dcbe2", size = 3670443, upload-time = "2025-08-22T10:34:07.974Z" },
{ url = "https://files.pythonhosted.org/packages/38/e3/b7eb612ce07abe766918a7e581ec6a0e5212352194001fd287c3ace945f0/lxml-6.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:29b0e849ec7030e3ecb6112564c9f7ad6881e3b2375dd4a0c486c5c1f3a33859", size = 8426160, upload-time = "2025-08-22T10:34:10.154Z" },
{ url = "https://files.pythonhosted.org/packages/35/8f/ab3639a33595cf284fe733c6526da2ca3afbc5fd7f244ae67f3303cec654/lxml-6.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:02a0f7e629f73cc0be598c8b0611bf28ec3b948c549578a26111b01307fd4051", size = 4589288, upload-time = "2025-08-22T10:34:12.972Z" },
{ url = "https://files.pythonhosted.org/packages/2c/65/819d54f2e94d5c4458c1db8c1ccac9d05230b27c1038937d3d788eb406f9/lxml-6.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:beab5e54de016e730875f612ba51e54c331e2fa6dc78ecf9a5415fc90d619348", size = 4964523, upload-time = "2025-08-22T10:34:15.474Z" },
{ url = "https://files.pythonhosted.org/packages/5b/4a/d4a74ce942e60025cdaa883c5a4478921a99ce8607fc3130f1e349a83b28/lxml-6.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a08aefecd19ecc4ebf053c27789dd92c87821df2583a4337131cf181a1dffa", size = 5101108, upload-time = "2025-08-22T10:34:17.348Z" },
{ url = "https://files.pythonhosted.org/packages/cb/48/67f15461884074edd58af17b1827b983644d1fae83b3d909e9045a08b61e/lxml-6.0.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c8fa7e177649470bc3dcf7eae6bee1e4984aaee496b9ccbf30e97ac4127fa2", size = 5053498, upload-time = "2025-08-22T10:34:19.232Z" },
{ url = "https://files.pythonhosted.org/packages/b6/d4/ec1bf1614828a5492f4af0b6a9ee2eb3e92440aea3ac4fa158e5228b772b/lxml-6.0.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:5d08e0f1af6916267bb7eff21c09fa105620f07712424aaae09e8cb5dd4164d1", size = 5351057, upload-time = "2025-08-22T10:34:21.143Z" },
{ url = "https://files.pythonhosted.org/packages/65/2b/c85929dacac08821f2100cea3eb258ce5c8804a4e32b774f50ebd7592850/lxml-6.0.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9705cdfc05142f8c38c97a61bd3a29581ceceb973a014e302ee4a73cc6632476", size = 5671579, upload-time = "2025-08-22T10:34:23.528Z" },
{ url = "https://files.pythonhosted.org/packages/d0/36/cf544d75c269b9aad16752fd9f02d8e171c5a493ca225cb46bb7ba72868c/lxml-6.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74555e2da7c1636e30bff4e6e38d862a634cf020ffa591f1f63da96bf8b34772", size = 5250403, upload-time = "2025-08-22T10:34:25.642Z" },
{ url = "https://files.pythonhosted.org/packages/c2/e8/83dbc946ee598fd75fdeae6151a725ddeaab39bb321354a9468d4c9f44f3/lxml-6.0.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:e38b5f94c5a2a5dadaddd50084098dfd005e5a2a56cd200aaf5e0a20e8941782", size = 4696712, upload-time = "2025-08-22T10:34:27.753Z" },
{ url = "https://files.pythonhosted.org/packages/f4/72/889c633b47c06205743ba935f4d1f5aa4eb7f0325d701ed2b0540df1b004/lxml-6.0.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5ec101a92ddacb4791977acfc86c1afd624c032974bfb6a21269d1083c9bc49", size = 5268177, upload-time = "2025-08-22T10:34:29.804Z" },
{ url = "https://files.pythonhosted.org/packages/b0/b6/f42a21a1428479b66ea0da7bd13e370436aecaff0cfe93270c7e165bd2a4/lxml-6.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5c17e70c82fd777df586c12114bbe56e4e6f823a971814fd40dec9c0de518772", size = 5094648, upload-time = "2025-08-22T10:34:31.703Z" },
{ url = "https://files.pythonhosted.org/packages/51/b0/5f8c1e8890e2ee1c2053c2eadd1cb0e4b79e2304e2912385f6ca666f48b1/lxml-6.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:45fdd0415a0c3d91640b5d7a650a8f37410966a2e9afebb35979d06166fd010e", size = 4745220, upload-time = "2025-08-22T10:34:33.595Z" },
{ url = "https://files.pythonhosted.org/packages/eb/f9/820b5125660dae489ca3a21a36d9da2e75dd6b5ffe922088f94bbff3b8a0/lxml-6.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d417eba28981e720a14fcb98f95e44e7a772fe25982e584db38e5d3b6ee02e79", size = 5692913, upload-time = "2025-08-22T10:34:35.482Z" },
{ url = "https://files.pythonhosted.org/packages/23/8e/a557fae9eec236618aecf9ff35fec18df41b6556d825f3ad6017d9f6e878/lxml-6.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8e5d116b9e59be7934febb12c41cce2038491ec8fdb743aeacaaf36d6e7597e4", size = 5259816, upload-time = "2025-08-22T10:34:37.482Z" },
{ url = "https://files.pythonhosted.org/packages/fa/fd/b266cfaab81d93a539040be699b5854dd24c84e523a1711ee5f615aa7000/lxml-6.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c238f0d0d40fdcb695c439fe5787fa69d40f45789326b3bb6ef0d61c4b588d6e", size = 5276162, upload-time = "2025-08-22T10:34:39.507Z" },
{ url = "https://files.pythonhosted.org/packages/25/6c/6f9610fbf1de002048e80585ea4719591921a0316a8565968737d9f125ca/lxml-6.0.1-cp314-cp314-win32.whl", hash = "sha256:537b6cf1c5ab88cfd159195d412edb3e434fee880f206cbe68dff9c40e17a68a", size = 3669595, upload-time = "2025-08-22T10:34:41.783Z" },
{ url = "https://files.pythonhosted.org/packages/72/a5/506775e3988677db24dc75a7b03e04038e0b3d114ccd4bccea4ce0116c15/lxml-6.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:911d0a2bb3ef3df55b3d97ab325a9ca7e438d5112c102b8495321105d25a441b", size = 4079818, upload-time = "2025-08-22T10:34:44.04Z" },
{ url = "https://files.pythonhosted.org/packages/0a/44/9613f300201b8700215856e5edd056d4e58dd23368699196b58877d4408b/lxml-6.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:2834377b0145a471a654d699bdb3a2155312de492142ef5a1d426af2c60a0a31", size = 3753901, upload-time = "2025-08-22T10:34:45.799Z" },
] ]
[[package]] [[package]]
@@ -206,15 +226,15 @@ wheels = [
[[package]] [[package]]
name = "megasniff" name = "megasniff"
version = "0.2.3.post1" version = "0.2.5.post1"
source = { registry = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" } source = { registry = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }
dependencies = [ dependencies = [
{ name = "hatchling" }, { name = "hatchling" },
{ name = "jinja2" }, { name = "jinja2" },
] ]
sdist = { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/megasniff/0.2.3.post1/megasniff-0.2.3.post1.tar.gz", hash = "sha256:41dd6c235225df13b0d18ad417b93bbddfd70d4b9e5ca405ddef976a8f2e753c" } sdist = { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/megasniff/0.2.5.post1/megasniff-0.2.5.post1.tar.gz", hash = "sha256:d9ac04dd2b6df734e6582252cb60f46cefad667f519f5801ba43e8b2f56546a5" }
wheels = [ wheels = [
{ url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/megasniff/0.2.3.post1/megasniff-0.2.3.post1-py3-none-any.whl", hash = "sha256:414560ae1a2b1a00a7fcaf102e8faf45214748f230ad756ee34a71f2598d93e4" }, { url = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/files/megasniff/0.2.5.post1/megasniff-0.2.5.post1-py3-none-any.whl", hash = "sha256:69ce434c27286e4d559736bcb003df884f418b9405d04834e2560f1aa0e94e54" },
] ]
[[package]] [[package]]
@@ -237,28 +257,28 @@ wheels = [
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.17.1" version = "1.18.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "mypy-extensions" }, { name = "mypy-extensions" },
{ name = "pathspec" }, { name = "pathspec" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } sdist = { url = "https://files.pythonhosted.org/packages/14/a3/931e09fc02d7ba96da65266884da4e4a8806adcdb8a57faaacc6edf1d538/mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9", size = 3448447, upload-time = "2025-09-11T23:00:47.067Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, { url = "https://files.pythonhosted.org/packages/e4/ec/ef4a7260e1460a3071628a9277a7579e7da1b071bc134ebe909323f2fbc7/mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f", size = 12918671, upload-time = "2025-09-11T22:58:29.814Z" },
{ url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, { url = "https://files.pythonhosted.org/packages/a1/82/0ea6c3953f16223f0b8eda40c1aeac6bd266d15f4902556ae6e91f6fca4c/mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce", size = 11913023, upload-time = "2025-09-11T23:00:29.049Z" },
{ url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, { url = "https://files.pythonhosted.org/packages/ae/ef/5e2057e692c2690fc27b3ed0a4dbde4388330c32e2576a23f0302bc8358d/mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e", size = 12473355, upload-time = "2025-09-11T23:00:04.544Z" },
{ url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, { url = "https://files.pythonhosted.org/packages/98/43/b7e429fc4be10e390a167b0cd1810d41cb4e4add4ae50bab96faff695a3b/mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71", size = 13346944, upload-time = "2025-09-11T22:58:23.024Z" },
{ url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, { url = "https://files.pythonhosted.org/packages/89/4e/899dba0bfe36bbd5b7c52e597de4cf47b5053d337b6d201a30e3798e77a6/mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746", size = 13512574, upload-time = "2025-09-11T22:59:52.152Z" },
{ url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, { url = "https://files.pythonhosted.org/packages/f5/f8/7661021a5b0e501b76440454d786b0f01bb05d5c4b125fcbda02023d0250/mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d", size = 9837684, upload-time = "2025-09-11T22:58:44.454Z" },
{ url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, { url = "https://files.pythonhosted.org/packages/bf/87/7b173981466219eccc64c107cf8e5ab9eb39cc304b4c07df8e7881533e4f/mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61", size = 12900265, upload-time = "2025-09-11T22:59:03.4Z" },
{ url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, { url = "https://files.pythonhosted.org/packages/ae/cc/b10e65bae75b18a5ac8f81b1e8e5867677e418f0dd2c83b8e2de9ba96ebd/mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5", size = 11942890, upload-time = "2025-09-11T23:00:00.607Z" },
{ url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, { url = "https://files.pythonhosted.org/packages/39/d4/aeefa07c44d09f4c2102e525e2031bc066d12e5351f66b8a83719671004d/mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8", size = 12472291, upload-time = "2025-09-11T22:59:43.425Z" },
{ url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, { url = "https://files.pythonhosted.org/packages/c6/07/711e78668ff8e365f8c19735594ea95938bff3639a4c46a905e3ed8ff2d6/mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d", size = 13318610, upload-time = "2025-09-11T23:00:17.604Z" },
{ url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, { url = "https://files.pythonhosted.org/packages/ca/85/df3b2d39339c31d360ce299b418c55e8194ef3205284739b64962f6074e7/mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d", size = 13513697, upload-time = "2025-09-11T22:58:59.534Z" },
{ url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, { url = "https://files.pythonhosted.org/packages/b1/df/462866163c99ea73bb28f0eb4d415c087e30de5d36ee0f5429d42e28689b/mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce", size = 9985739, upload-time = "2025-09-11T22:58:51.644Z" },
{ url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, { url = "https://files.pythonhosted.org/packages/e0/1d/4b97d3089b48ef3d904c9ca69fab044475bd03245d878f5f0b3ea1daf7ce/mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e", size = 2352212, upload-time = "2025-09-11T22:59:26.576Z" },
] ]
[[package]] [[package]]
@@ -272,36 +292,36 @@ wheels = [
[[package]] [[package]]
name = "orjson" name = "orjson"
version = "3.11.2" version = "3.11.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/1d/5e0ae38788bdf0721326695e65fdf41405ed535f633eb0df0f06f57552fa/orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309", size = 5470739, upload-time = "2025-08-12T15:12:28.626Z" } sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/f3/0dd6b4750eb556ae4e2c6a9cb3e219ec642e9c6d95f8ebe5dc9020c67204/orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219", size = 226419, upload-time = "2025-08-12T15:11:25.517Z" }, { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" },
{ url = "https://files.pythonhosted.org/packages/44/d5/e67f36277f78f2af8a4690e0c54da6b34169812f807fd1b4bfc4dbcf9558/orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad", size = 115803, upload-time = "2025-08-12T15:11:27.357Z" }, { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" },
{ url = "https://files.pythonhosted.org/packages/24/37/ff8bc86e0dacc48f07c2b6e20852f230bf4435611bab65e3feae2b61f0ae/orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2", size = 111337, upload-time = "2025-08-12T15:11:28.805Z" }, { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" },
{ url = "https://files.pythonhosted.org/packages/b9/25/37d4d3e8079ea9784ea1625029988e7f4594ce50d4738b0c1e2bf4a9e201/orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe", size = 116222, upload-time = "2025-08-12T15:11:30.18Z" }, { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" },
{ url = "https://files.pythonhosted.org/packages/b7/32/a63fd9c07fce3b4193dcc1afced5dd4b0f3a24e27556604e9482b32189c9/orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae", size = 119020, upload-time = "2025-08-12T15:11:31.59Z" }, { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" },
{ url = "https://files.pythonhosted.org/packages/b4/b6/400792b8adc3079a6b5d649264a3224d6342436d9fac9a0ed4abc9dc4596/orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6", size = 120721, upload-time = "2025-08-12T15:11:33.035Z" }, { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" },
{ url = "https://files.pythonhosted.org/packages/40/f3/31ab8f8c699eb9e65af8907889a0b7fef74c1d2b23832719a35da7bb0c58/orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1", size = 123574, upload-time = "2025-08-12T15:11:34.433Z" }, { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" },
{ url = "https://files.pythonhosted.org/packages/bd/a6/ce4287c412dff81878f38d06d2c80845709c60012ca8daf861cb064b4574/orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa", size = 121225, upload-time = "2025-08-12T15:11:36.133Z" }, { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" },
{ url = "https://files.pythonhosted.org/packages/69/b0/7a881b2aef4fed0287d2a4fbb029d01ed84fa52b4a68da82bdee5e50598e/orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e", size = 119201, upload-time = "2025-08-12T15:11:37.642Z" }, { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" },
{ url = "https://files.pythonhosted.org/packages/cf/98/a325726b37f7512ed6338e5e65035c3c6505f4e628b09a5daf0419f054ea/orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15", size = 392193, upload-time = "2025-08-12T15:11:39.153Z" }, { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" },
{ url = "https://files.pythonhosted.org/packages/cb/4f/a7194f98b0ce1d28190e0c4caa6d091a3fc8d0107ad2209f75c8ba398984/orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac", size = 134548, upload-time = "2025-08-12T15:11:40.768Z" }, { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" },
{ url = "https://files.pythonhosted.org/packages/e8/5e/b84caa2986c3f472dc56343ddb0167797a708a8d5c3be043e1e2677b55df/orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8", size = 123798, upload-time = "2025-08-12T15:11:42.164Z" }, { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" },
{ url = "https://files.pythonhosted.org/packages/9c/5b/e398449080ce6b4c8fcadad57e51fa16f65768e1b142ba90b23ac5d10801/orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5", size = 124402, upload-time = "2025-08-12T15:11:44.036Z" }, { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" },
{ url = "https://files.pythonhosted.org/packages/b3/66/429e4608e124debfc4790bfc37131f6958e59510ba3b542d5fc163be8e5f/orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d", size = 119498, upload-time = "2025-08-12T15:11:45.864Z" }, { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" },
{ url = "https://files.pythonhosted.org/packages/7b/04/f8b5f317cce7ad3580a9ad12d7e2df0714dfa8a83328ecddd367af802f5b/orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535", size = 114051, upload-time = "2025-08-12T15:11:47.555Z" }, { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" },
{ url = "https://files.pythonhosted.org/packages/74/83/2c363022b26c3c25b3708051a19d12f3374739bb81323f05b284392080c0/orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7", size = 226406, upload-time = "2025-08-12T15:11:49.445Z" }, { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" },
{ url = "https://files.pythonhosted.org/packages/b0/a7/aa3c973de0b33fc93b4bd71691665ffdfeae589ea9d0625584ab10a7d0f5/orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81", size = 115788, upload-time = "2025-08-12T15:11:50.992Z" }, { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" },
{ url = "https://files.pythonhosted.org/packages/ef/f2/e45f233dfd09fdbb052ec46352363dca3906618e1a2b264959c18f809d0b/orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f", size = 111318, upload-time = "2025-08-12T15:11:52.495Z" }, { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" },
{ url = "https://files.pythonhosted.org/packages/3e/23/cf5a73c4da6987204cbbf93167f353ff0c5013f7c5e5ef845d4663a366da/orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7", size = 121231, upload-time = "2025-08-12T15:11:53.941Z" }, { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" },
{ url = "https://files.pythonhosted.org/packages/40/1d/47468a398ae68a60cc21e599144e786e035bb12829cb587299ecebc088f1/orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4", size = 119204, upload-time = "2025-08-12T15:11:55.409Z" }, { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" },
{ url = "https://files.pythonhosted.org/packages/4d/d9/f99433d89b288b5bc8836bffb32a643f805e673cf840ef8bab6e73ced0d1/orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f", size = 392237, upload-time = "2025-08-12T15:11:57.18Z" }, { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" },
{ url = "https://files.pythonhosted.org/packages/d4/dc/1b9d80d40cebef603325623405136a29fb7d08c877a728c0943dd066c29a/orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7", size = 134578, upload-time = "2025-08-12T15:11:58.844Z" }, { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" },
{ url = "https://files.pythonhosted.org/packages/45/b3/72e7a4c5b6485ef4e83ef6aba7f1dd041002bad3eb5d1d106ca5b0fc02c6/orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6", size = 123799, upload-time = "2025-08-12T15:12:00.352Z" }, { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" },
{ url = "https://files.pythonhosted.org/packages/c8/3e/a3d76b392e7acf9b34dc277171aad85efd6accc75089bb35b4c614990ea9/orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f", size = 124461, upload-time = "2025-08-12T15:12:01.854Z" }, { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" },
{ url = "https://files.pythonhosted.org/packages/fb/e3/75c6a596ff8df9e4a5894813ff56695f0a218e6ea99420b4a645c4f7795d/orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8", size = 119494, upload-time = "2025-08-12T15:12:03.337Z" }, { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" },
{ url = "https://files.pythonhosted.org/packages/5b/3d/9e74742fc261c5ca473c96bb3344d03995869e1dc6402772c60afb97736a/orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67", size = 114046, upload-time = "2025-08-12T15:12:04.87Z" }, { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" },
] ]
[[package]] [[package]]
@@ -342,7 +362,7 @@ wheels = [
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.4.1" version = "8.4.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" }, { name = "colorama", marker = "sys_platform == 'win32'" },
@@ -351,32 +371,41 @@ dependencies = [
{ name = "pluggy" }, { name = "pluggy" },
{ name = "pygments" }, { name = "pygments" },
] ]
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" } 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" }
wheels = [ wheels = [
{ 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" }, { 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" },
] ]
[[package]] [[package]]
name = "pytest-cov" name = "pytest-cov"
version = "6.2.1" version = "7.0.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "coverage" }, { name = "coverage" },
{ name = "pluggy" }, { name = "pluggy" },
{ name = "pytest" }, { name = "pytest" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
] ]
[[package]] [[package]]
name = "trove-classifiers" name = "trove-classifiers"
version = "2025.8.6.13" version = "2025.9.11.17"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/21/707af14daa638b0df15b5d5700349e0abdd3e5140069f9ab6e0ccb922806/trove_classifiers-2025.8.6.13.tar.gz", hash = "sha256:5a0abad839d2ed810f213ab133d555d267124ddea29f1d8a50d6eca12a50ae6e", size = 16932, upload-time = "2025-08-06T13:26:26.479Z" } sdist = { url = "https://files.pythonhosted.org/packages/ca/9a/778622bc06632529817c3c524c82749a112603ae2bbcf72ee3eb33a2c4f1/trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd", size = 16975, upload-time = "2025-09-11T17:07:50.97Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/44/323a87d78f04d5329092aada803af3612dd004a64b69ba8b13046601a8c9/trove_classifiers-2025.8.6.13-py3-none-any.whl", hash = "sha256:c4e7fc83012770d80b3ae95816111c32b085716374dccee0d3fbf5c235495f9f", size = 14121, upload-time = "2025-08-06T13:26:25.063Z" }, { url = "https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd", size = 14158, upload-time = "2025-09-11T17:07:49.886Z" },
] ]
[[package]] [[package]]
@@ -389,6 +418,7 @@ dependencies = [
{ name = "jinja2" }, { name = "jinja2" },
{ name = "megasniff" }, { name = "megasniff" },
{ name = "mypy" }, { name = "mypy" },
{ name = "python-multipart" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -423,8 +453,9 @@ requires-dist = [
{ name = "breakshaft", specifier = ">=0.1.6", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }, { name = "breakshaft", specifier = ">=0.1.6", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
{ name = "case-insensitive-dictionary", specifier = ">=0.2.1" }, { name = "case-insensitive-dictionary", specifier = ">=0.2.1" },
{ name = "jinja2", specifier = ">=3.1.6" }, { name = "jinja2", specifier = ">=3.1.6" },
{ name = "megasniff", specifier = ">=0.2.3", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }, { name = "megasniff", specifier = ">=0.2.4", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
{ name = "mypy", specifier = ">=1.17.0" }, { name = "mypy", specifier = ">=1.17.0" },
{ name = "python-multipart", specifier = ">=0.0.20" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
@@ -452,11 +483,11 @@ xml = [
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.14.1" version = "4.15.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
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" } 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" }
wheels = [ wheels = [
{ 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" }, { 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" },
] ]
[[package]] [[package]]