Fix most of types

This commit is contained in:
2025-07-16 17:48:39 +03:00
parent ee25d17683
commit ddfd6e8d78
21 changed files with 197 additions and 67 deletions

5
mypy.ini Normal file
View File

@@ -0,0 +1,5 @@
[mypy]
python_version = 3.13
strict = True
ignore_missing_imports = True
files = src/

View File

@@ -11,6 +11,7 @@ dependencies = [
"megasniff>=0.2.0", "megasniff>=0.2.0",
"breakshaft>=0.1.0", "breakshaft>=0.1.0",
"case-insensitive-dictionary>=0.2.1", "case-insensitive-dictionary>=0.2.1",
"mypy>=1.17.0",
] ]
[tool.uv.sources] [tool.uv.sources]
@@ -37,7 +38,7 @@ dev = [
{ "include-group" = "all_containers" }, { "include-group" = "all_containers" },
] ]
xml = ["lxml>=6.0.0", ] xml = ["lxml>=6.0.0", "lxml-stubs>=0.5.1"]
msgpack = ["msgpack>=1.1.1", ] msgpack = ["msgpack>=1.1.1", ]
json = ["orjson>=3.11.0", ] json = ["orjson>=3.11.0", ]

View File

@@ -1 +1,5 @@
import interfaces
import router
from .app import SlothApp from .app import SlothApp
__all__ = ['SlothApp', 'router', 'interfaces']

View File

@@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
from turbosloth import SlothApp from turbosloth import SlothApp
from turbosloth.exceptions import NotFoundException
from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest
from turbosloth.types import Scope, Receive, Send, BasicRequest, BasicResponse
app = SlothApp() app = SlothApp()
@@ -17,6 +17,5 @@ async def index(req: SerializedRequest) -> SerializedResponse:
@app.post("/user/") @app.post("/user/")
async def get_user(req: SerializedRequest) -> SerializedResponse: async def get_user(req: SerializedRequest) -> SerializedResponse:
print(req.basic.query) print(req.basic.query)
resp = {'message': f'Hello, User ы {req.basic.query["id"]}!', 'from': 'server'} resp: dict[str, Any] = {'message': f'Hello, User ы {req.basic.query["id"]}!', 'from': 'server', 'echo': req.body}
resp['echo'] = req.body
return SerializedResponse(200, {}, resp) return SerializedResponse(200, {}, resp)

View File

@@ -1,11 +1,12 @@
import json
from typing import Optional, Callable, Awaitable, Protocol from typing import Optional, Callable, Awaitable, Protocol
from .exceptions import HTTPException from .exceptions import HTTPException
from .interfaces.base import BasicRequest, BasicResponse
from .interfaces.serialize_selector import SerializeSelector from .interfaces.serialize_selector import SerializeSelector
from .interfaces.serialized import SerializedResponse, TextSerializedResponse from .interfaces.serialized import SerializedResponse
from .interfaces.serialized.text import TextSerializedResponse
from .router import Router from .router import Router
from .types import Scope, Receive, Send, MethodType, HandlerType, BasicRequest, BasicResponse from .types import Scope, Receive, Send, MethodType, HandlerType
class ASGIApp(Protocol): class ASGIApp(Protocol):
@@ -18,7 +19,7 @@ class ASGIApp(Protocol):
class HTTPApp(ASGIApp): class HTTPApp(ASGIApp):
serialize_selector: SerializeSelector serialize_selector: SerializeSelector
async def _do_http(self, scope: Scope, receive: Receive, send: Send): async def _do_http(self, scope: Scope, receive: Receive, send: Send) -> None:
method = scope['method'] method = scope['method']
path = scope['path'] path = scope['path']
@@ -31,9 +32,11 @@ class HTTPApp(ASGIApp):
scope['body'] = body scope['body'] = body
req = BasicRequest.from_scope(scope) req = BasicRequest.from_scope(scope)
sresponser = TextSerializedResponse sresponser: type[SerializedResponse] = TextSerializedResponse
charset = 'latin1' charset = 'latin1'
sresp: SerializedResponse
resp: BasicResponse
try: try:
handler = self.router.match(method, path) handler = self.router.match(method, path)
@@ -59,7 +62,7 @@ class HTTPApp(ASGIApp):
class WSApp(ASGIApp): class WSApp(ASGIApp):
async def _do_websocket(self, scope: Scope, receive: Receive, send: Send): async def _do_websocket(self, scope: Scope, receive: Receive, send: Send) -> None:
raise NotImplementedError() raise NotImplementedError()
@@ -67,7 +70,7 @@ class LifespanApp:
_on_startup: Optional[Callable[[], Awaitable[None]]] _on_startup: Optional[Callable[[], Awaitable[None]]]
_on_shutdown: Optional[Callable[[], Awaitable[None]]] _on_shutdown: Optional[Callable[[], Awaitable[None]]]
async def _do_startup(self, send: Send): async def _do_startup(self, send: Send) -> None:
if self._on_startup: if self._on_startup:
try: try:
await self._on_startup() await self._on_startup()
@@ -77,12 +80,12 @@ class LifespanApp:
else: else:
await send({'type': 'lifespan.startup.complete'}) await send({'type': 'lifespan.startup.complete'})
async def _do_shutdown(self, send: Send): async def _do_shutdown(self, send: Send) -> None:
if self._on_shutdown: if self._on_shutdown:
await self._on_shutdown() await self._on_shutdown()
await send({'type': 'lifespan.shutdown.complete'}) await send({'type': 'lifespan.shutdown.complete'})
async def _do_lifespan(self, receive: Receive, send: Send): async def _do_lifespan(self, receive: Receive, send: Send) -> None:
while True: while True:
event = await receive() event = await receive()
if event['type'] == 'lifespan.startup': if event['type'] == 'lifespan.startup':

View File

@@ -1,2 +1,47 @@
from http_base import HTTPException
from .client_errors import * from .client_errors import *
from .server_errors import * from .server_errors import *
__all__ = [
'HTTPException',
'BadRequestException',
'UnauthorizedException',
'PaymentRequiredException',
'ForbiddenException',
'NotFoundException',
'MethodNotAllowedException',
'NotAcceptableException',
'ProxyAuthenticationRequiredException',
'RequestTimeoutException',
'ConflictException',
'GoneException',
'LengthRequiredException',
'PreconditionFailedException',
'ContentTooLargeException',
'URITooLongException',
'UnsupportedMediaTypeException',
'RangeNotSatisfiableException',
'ExpectationFailedException',
'ImaTeapotException',
'MisdirectedRequestException',
'UnprocessableContentException',
'LockedException',
'FailedDependencyException',
'TooEarlyExperimentalException',
'UpgradeRequiredException',
'PreconditionRequiredException',
'TooManyRequestsException',
'RequestHeaderFieldsTooLargeException',
'UnavailableForLegalReasonsException',
'InternalServerError',
'NotImplementedException',
'BadGatewayException',
'ServiceUnavailableException',
'GatewayTimeoutException',
'HTTPVersionNotSupportedException',
'VariantAlsoNegotiatesException',
'InsufficientStorageException',
'LoopDetectedException',
'NotExtendedException',
'NetworkAuthenticationRequiredException',
]

View File

@@ -13,7 +13,7 @@ class HTTPException(Exception):
def AutoException(code: int, description: str) -> Callable[[Optional[str]], HTTPException]: def AutoException(code: int, description: str) -> Callable[[Optional[str]], HTTPException]:
def foo(message: str | None = None): def foo(message: str | None = None) -> HTTPException:
return HTTPException(code, description, message) return HTTPException(code, description, message)
return foo return foo

View File

@@ -1,4 +1,4 @@
from turbosloth.exceptions import HTTPException from turbosloth.exceptions.http_base import HTTPException
_server_error_codes = { _server_error_codes = {
500: 'Internal Server Error', 500: 'Internal Server Error',

View File

@@ -1,24 +1,28 @@
from __future__ import annotations from __future__ import annotations
import typing
import urllib.parse
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Mapping from typing import Any, Mapping
from case_insensitive_dict import CaseInsensitiveDict from case_insensitive_dict import CaseInsensitiveDict
import urllib.parse
from turbosloth.types import MethodType, Scope, ASGIMessage
@dataclass @dataclass
class BasicRequest: class BasicRequest:
method: str method: MethodType
path: str path: str
headers: CaseInsensitiveDict[str, str] headers: CaseInsensitiveDict[str, str]
query: dict[str, list[str] | str] query: dict[str, list[Any] | Any]
body: bytes body: bytes
def __init__(self, def __init__(self,
method: str, method: MethodType,
path: str, path: str,
headers: Mapping[str, str], headers: Mapping[str, str],
query: dict[str, list[str] | str], query: dict[str, list[Any] | Any],
body: bytes): body: bytes):
self.method = method self.method = method
self.path = path self.path = path
@@ -27,9 +31,9 @@ class BasicRequest:
self.body = body self.body = body
@classmethod @classmethod
def from_scope(cls, scope: dict) -> BasicRequest: def from_scope(cls, scope: Scope) -> BasicRequest:
path = scope['path'] path = scope['path']
method = scope['method'] method = typing.cast(MethodType, scope['method'])
headers = {} headers = {}
for key, value in scope.get('headers', []): for key, value in scope.get('headers', []):
headers[key.decode('latin1')] = value.decode('latin1') headers[key.decode('latin1')] = value.decode('latin1')
@@ -42,6 +46,7 @@ class BasicRequest:
if len(v) == 1: if len(v) == 1:
v = v[0] v = v[0]
query[k] = v query[k] = v
query = typing.cast(dict[str, list[Any] | Any], query)
body = scope['body'] body = scope['body']
@@ -59,7 +64,7 @@ class BasicResponse:
self.headers = CaseInsensitiveDict(headers) self.headers = CaseInsensitiveDict(headers)
self.body = body self.body = body
def into_start_message(self) -> dict: def into_start_message(self) -> ASGIMessage:
enc_headers = [] enc_headers = []
for k, v in self.headers.items(): for k, v in self.headers.items():
enc_headers.append((k.encode('latin1'), v.encode('latin1'))) enc_headers.append((k.encode('latin1'), v.encode('latin1')))

View File

@@ -1,10 +1,10 @@
import typing
from typing import NamedTuple, Optional 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_serializers
from turbosloth.util import parse_content_type from turbosloth.util import parse_content_type
@@ -12,11 +12,11 @@ class SerializeChoise(NamedTuple):
req: type[SerializedRequest] req: type[SerializedRequest]
resp: type[SerializedResponse] resp: type[SerializedResponse]
charset: str charset: str
content_properties: dict content_properties: dict[str, str]
class SerializeSelector: class SerializeSelector:
default_content_type: str default_content_type: Optional[str]
serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]] serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]]
def __init__(self, def __init__(self,
@@ -27,7 +27,7 @@ class SerializeSelector:
ser = {} ser = {}
if filter_content_types is None: if filter_content_types is None:
filter_content_types = default_serializers.keys() filter_content_types = list(default_serializers.keys())
for k, v in default_serializers.items(): for k, v in default_serializers.items():
if k in filter_content_types: if k in filter_content_types:
@@ -36,6 +36,7 @@ class SerializeSelector:
def select(self, headers: CaseInsensitiveDict[str, str]) -> SerializeChoise: def select(self, headers: CaseInsensitiveDict[str, str]) -> SerializeChoise:
contenttype_header = headers.get('Content-Type') contenttype_header = headers.get('Content-Type')
properties: dict[str, str]
if contenttype_header is None: if contenttype_header is None:
contenttype = self.default_content_type contenttype = self.default_content_type
properties = {} properties = {}
@@ -50,8 +51,8 @@ class SerializeSelector:
else: else:
charset = 'latin1' charset = 'latin1'
choise = self.serializers.get(contenttype) choise = self.serializers.get(typing.cast(str, contenttype))
if choise is None: if choise is None and self.default_content_type is not None:
choise = self.serializers.get(self.default_content_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.serializers.keys()))

View File

@@ -1,6 +1,6 @@
from .base import SerializedRequest, SerializedResponse from .base import SerializedRequest, SerializedResponse
default_serializers = {} default_serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]] = {}
try: try:
from .text import TextSerializedRequest, TextSerializedResponse from .text import TextSerializedRequest, TextSerializedResponse
@@ -28,3 +28,5 @@ try:
default_serializers['application/vnd.msgpack'] = (MessagePackSerializedRequest, MessagePackSerializedResponse) default_serializers['application/vnd.msgpack'] = (MessagePackSerializedRequest, MessagePackSerializedResponse)
except: except:
pass pass
__all__ = ['SerializedRequest', 'SerializedResponse']

View File

@@ -1,17 +1,36 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Mapping from typing import Any, Mapping
from case_insensitive_dict import CaseInsensitiveDict
from turbosloth.interfaces.base import BasicRequest, BasicResponse from turbosloth.interfaces.base import BasicRequest, BasicResponse
from turbosloth.types import MethodType
@dataclass @dataclass
class SerializedRequest: class SerializedRequest:
body: dict[str, Any] | list | str | None body: dict[str, Any] | list[Any] | str | None
basic: BasicRequest basic: BasicRequest
charset: str charset: str
@property
def query(self) -> dict[str, list[Any] | Any]:
return self.basic.query
@property
def path(self) -> str:
return self.basic.path
@property
def method(self) -> MethodType:
return self.basic.method
@property
def headers(self) -> CaseInsensitiveDict[str, str]:
return self.basic.headers
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str): def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
raise NotImplementedError() raise NotImplementedError()
@@ -19,7 +38,7 @@ class SerializedRequest:
class SerializedResponse: class SerializedResponse:
code: int code: int
headers: Mapping[str, str] headers: Mapping[str, str]
body: dict[str, Any] | list | str body: dict[str, Any] | list[Any] | str
def into_basic(self, charset: str) -> BasicResponse: def into_basic(self, charset: str) -> BasicResponse:
raise NotImplementedError() raise NotImplementedError()

View File

@@ -1,5 +1,4 @@
import orjson import orjson
from case_insensitive_dict import CaseInsensitiveDict from case_insensitive_dict import CaseInsensitiveDict
from turbosloth.interfaces.base import BasicRequest, BasicResponse from turbosloth.interfaces.base import BasicRequest, BasicResponse
@@ -8,7 +7,7 @@ from .base import SerializedRequest, SerializedResponse
class JsonSerializedRequest(SerializedRequest): class JsonSerializedRequest(SerializedRequest):
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str): def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
if len(basic.body) == 0: if len(basic.body) == 0:
b = None b = None
elif charset.lower() in {'utf-8', 'utf8'}: elif charset.lower() in {'utf-8', 'utf8'}:

View File

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

View File

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

View File

@@ -1,17 +1,15 @@
import json
from typing import Any from typing import Any
from case_insensitive_dict import CaseInsensitiveDict from case_insensitive_dict import CaseInsensitiveDict
from lxml import etree
from turbosloth.interfaces.base import BasicRequest, BasicResponse from turbosloth.interfaces.base import BasicRequest, BasicResponse
from .base import SerializedRequest, SerializedResponse from .base import SerializedRequest, SerializedResponse
from lxml import etree
class XMLSerializedRequest(SerializedRequest): class XMLSerializedRequest(SerializedRequest):
@classmethod @classmethod
def deserialize(cls, basic: BasicRequest, charset: str): def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest:
if len(basic.body) == 0: if len(basic.body) == 0:
b = {} b = {}
else: else:
@@ -45,19 +43,15 @@ def _into_xml(data: dict[str, Any] | list[Any] | str) -> str:
elif value is not None: elif value is not None:
element.text = str(value) element.text = str(value)
# Создаем корневой элемент
root = etree.Element('response') root = etree.Element('response')
if isinstance(data, dict): if isinstance(data, dict):
# Если входной тип — dict, используем его ключи как теги
for key, value in data.items(): for key, value in data.items():
_build_element(root, key, value) _build_element(root, key, value)
elif isinstance(data, list): elif isinstance(data, list):
# Если список, оборачиваем элементы в общий тег "item"
for item in data: for item in data:
_build_element(root, 'item', item) _build_element(root, 'item', item)
elif isinstance(data, str): elif isinstance(data, str):
# Если строка, добавляем как текст корневого элемента
root.text = data root.text = data
return etree.tostring(root, pretty_print=True, encoding="unicode") return etree.tostring(root, pretty_print=True, encoding="unicode")

View File

@@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
import typing import typing
from typing import Optional, Sequence, get_args from typing import Optional, Sequence
from .exceptions import MethodNotAllowedException, NotFoundException, HTTPException from .exceptions import MethodNotAllowedException, NotFoundException
from .types import HandlerType, MethodType from .types import HandlerType, MethodType
@@ -11,11 +11,11 @@ class Route:
static_subroutes: dict[str, Route] static_subroutes: dict[str, Route]
handler: dict[MethodType, HandlerType] handler: dict[MethodType, HandlerType]
def __init__(self): def __init__(self) -> None:
self.static_subroutes = {} self.static_subroutes = {}
self.handler = {} self.handler = {}
def add(self, method: MethodType, sequence: Sequence[str], handler: HandlerType): def add(self, method: MethodType, sequence: Sequence[str], handler: HandlerType) -> None:
if len(sequence) == 0: if len(sequence) == 0:
self.handler[method] = handler self.handler[method] = handler
return return
@@ -42,10 +42,10 @@ class Route:
class Router: class Router:
_root: Route _root: Route
def __init__(self): def __init__(self) -> None:
self._root = Route() self._root = Route()
def add(self, method: MethodType, path_pattern: str, handler: HandlerType): def add(self, method: MethodType, path_pattern: str, handler: HandlerType) -> None:
assert method.upper() == method assert method.upper() == method
segments = path_pattern.split('/') segments = path_pattern.split('/')
@@ -65,6 +65,8 @@ class Router:
raise NotFoundException(path or '/') from e raise NotFoundException(path or '/') from e
except MethodNotAllowedException as e: except MethodNotAllowedException as e:
raise MethodNotAllowedException( raise MethodNotAllowedException(
f'{method} /{path}' + ', allowed: ' + (e.message if len(e.message) > 0 else 'none')) \ f'{method} /{path}' + ', allowed: ' + (e.message or '' if len(e.message or '') > 0 else 'none')) \
from e from e
if h is None:
raise NotFoundException(path or '/')
return h return h

View File

@@ -1,19 +1,13 @@
from __future__ import annotations from __future__ import annotations
import collections from typing import Callable, Awaitable, Literal, Any
from dataclasses import dataclass
from typing import Callable, Awaitable, Dict, Literal, Any, TypeVar
import urllib.parse
from case_insensitive_dict.case_insensitive_dict import CaseInsensitiveDict
from turbosloth.interfaces.base import BasicRequest, BasicResponse
from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest
type Scope = Dict type Scope = dict[str, Any]
type Receive = Callable[[], Awaitable[Dict]] type ASGIMessage = dict[str, Any]
type Send = Callable[[Dict], Awaitable[None]] type Receive = Callable[[], Awaitable[ASGIMessage]]
type Send = Callable[[ASGIMessage], Awaitable[None]]
type HandlerType = Callable[[SerializedRequest], Awaitable[SerializedResponse]] type HandlerType = Callable[[SerializedRequest], Awaitable[SerializedResponse]]
type MethodType = ( type MethodType = (

View File

@@ -1,6 +1,6 @@
from email.policy import EmailPolicy from email.policy import EmailPolicy
def parse_content_type(content_type: str) -> tuple[str, dict]: def parse_content_type(content_type: str) -> tuple[str, dict[str, str]]:
header = EmailPolicy.header_factory('content-type', content_type) header = EmailPolicy.header_factory('content-type', content_type)
return header.content_type, dict(header.params) return header.content_type, dict(header.params)

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from src.turbosloth.router import Router
from src.turbosloth.exceptions import NotFoundException, MethodNotAllowedException from src.turbosloth.exceptions import NotFoundException, MethodNotAllowedException
from src.turbosloth.router import Router
def test_router_root_handler(): def test_router_root_handler():

59
uv.lock generated
View File

@@ -145,6 +145,15 @@ wheels = [
{ 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/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" },
] ]
[[package]]
name = "lxml-stubs"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/99/da/1a3a3e5d159b249fc2970d73437496b908de8e4716a089c69591b4ffa6fd/lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d", size = 14778, upload-time = "2024-01-10T09:37:46.521Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/c9/e0f8e4e6e8a69e5959b06499582dca6349db6769cc7fdfb8a02a7c75a9ae/lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272", size = 13584, upload-time = "2024-01-10T09:37:44.931Z" },
]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "3.0.2" version = "3.0.2"
@@ -204,6 +213,35 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" },
] ]
[[package]]
name = "mypy"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" },
{ url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" },
{ url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" },
{ url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" },
{ url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" },
{ url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]] [[package]]
name = "orjson" name = "orjson"
version = "3.11.0" version = "3.11.0"
@@ -310,16 +348,19 @@ dependencies = [
{ name = "breakshaft" }, { name = "breakshaft" },
{ name = "case-insensitive-dictionary" }, { name = "case-insensitive-dictionary" },
{ name = "megasniff" }, { name = "megasniff" },
{ name = "mypy" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
all-containers = [ all-containers = [
{ name = "lxml" }, { name = "lxml" },
{ name = "lxml-stubs" },
{ name = "msgpack" }, { name = "msgpack" },
{ name = "orjson" }, { name = "orjson" },
] ]
dev = [ dev = [
{ name = "lxml" }, { name = "lxml" },
{ name = "lxml-stubs" },
{ name = "msgpack" }, { name = "msgpack" },
{ name = "orjson" }, { name = "orjson" },
{ name = "pytest" }, { name = "pytest" },
@@ -334,6 +375,7 @@ msgpack = [
] ]
xml = [ xml = [
{ name = "lxml" }, { name = "lxml" },
{ name = "lxml-stubs" },
] ]
[package.metadata] [package.metadata]
@@ -341,16 +383,19 @@ requires-dist = [
{ name = "breakshaft", specifier = ">=0.1.0", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }, { name = "breakshaft", specifier = ">=0.1.0", 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 = "megasniff", specifier = ">=0.2.0", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" }, { name = "megasniff", specifier = ">=0.2.0", index = "https://git.nikto-b.ru/api/packages/nikto_b/pypi/simple" },
{ name = "mypy", specifier = ">=1.17.0" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
all-containers = [ all-containers = [
{ name = "lxml", specifier = ">=6.0.0" }, { name = "lxml", specifier = ">=6.0.0" },
{ name = "lxml-stubs", specifier = ">=0.5.1" },
{ name = "msgpack", specifier = ">=1.1.1" }, { name = "msgpack", specifier = ">=1.1.1" },
{ name = "orjson", specifier = ">=3.11.0" }, { name = "orjson", specifier = ">=3.11.0" },
] ]
dev = [ dev = [
{ name = "lxml", specifier = ">=6.0.0" }, { name = "lxml", specifier = ">=6.0.0" },
{ name = "lxml-stubs", specifier = ">=0.5.1" },
{ name = "msgpack", specifier = ">=1.1.1" }, { name = "msgpack", specifier = ">=1.1.1" },
{ name = "orjson", specifier = ">=3.11.0" }, { name = "orjson", specifier = ">=3.11.0" },
{ name = "pytest", specifier = ">=8.4.1" }, { name = "pytest", specifier = ">=8.4.1" },
@@ -359,7 +404,19 @@ dev = [
] ]
json = [{ name = "orjson", specifier = ">=3.11.0" }] json = [{ name = "orjson", specifier = ">=3.11.0" }]
msgpack = [{ name = "msgpack", specifier = ">=1.1.1" }] msgpack = [{ name = "msgpack", specifier = ">=1.1.1" }]
xml = [{ name = "lxml", specifier = ">=6.0.0" }] xml = [
{ name = "lxml", specifier = ">=6.0.0" },
{ name = "lxml-stubs", specifier = ">=0.5.1" },
]
[[package]]
name = "typing-extensions"
version = "4.14.1"
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" }
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" },
]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"