From ddfd6e8d789eca5676615c9723f034740b67c037 Mon Sep 17 00:00:00 2001 From: nikto_b Date: Wed, 16 Jul 2025 17:48:39 +0300 Subject: [PATCH] Fix most of types --- mypy.ini | 5 ++ pyproject.toml | 3 +- src/turbosloth/__init__.py | 4 ++ src/turbosloth/__main__.py | 7 +-- src/turbosloth/app.py | 21 ++++--- src/turbosloth/exceptions/__init__.py | 45 ++++++++++++++ src/turbosloth/exceptions/http_base.py | 2 +- src/turbosloth/exceptions/server_errors.py | 2 +- src/turbosloth/interfaces/base.py | 21 ++++--- .../interfaces/serialize_selector.py | 13 ++-- .../interfaces/serialized/__init__.py | 4 +- src/turbosloth/interfaces/serialized/base.py | 25 +++++++- src/turbosloth/interfaces/serialized/json.py | 3 +- .../interfaces/serialized/msgpack.py | 2 +- src/turbosloth/interfaces/serialized/text.py | 2 +- src/turbosloth/interfaces/serialized/xml.py | 10 +--- src/turbosloth/router.py | 16 ++--- src/turbosloth/types.py | 16 ++--- src/turbosloth/util.py | 2 +- tests/test_router.py | 2 +- uv.lock | 59 ++++++++++++++++++- 21 files changed, 197 insertions(+), 67 deletions(-) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..e5b11e2 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +python_version = 3.13 +strict = True +ignore_missing_imports = True +files = src/ diff --git a/pyproject.toml b/pyproject.toml index 78e0bb2..35e9a65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "megasniff>=0.2.0", "breakshaft>=0.1.0", "case-insensitive-dictionary>=0.2.1", + "mypy>=1.17.0", ] [tool.uv.sources] @@ -37,7 +38,7 @@ dev = [ { "include-group" = "all_containers" }, ] -xml = ["lxml>=6.0.0", ] +xml = ["lxml>=6.0.0", "lxml-stubs>=0.5.1"] msgpack = ["msgpack>=1.1.1", ] json = ["orjson>=3.11.0", ] diff --git a/src/turbosloth/__init__.py b/src/turbosloth/__init__.py index 75c5be3..c4e2295 100644 --- a/src/turbosloth/__init__.py +++ b/src/turbosloth/__init__.py @@ -1 +1,5 @@ +import interfaces +import router from .app import SlothApp + +__all__ = ['SlothApp', 'router', 'interfaces'] diff --git a/src/turbosloth/__main__.py b/src/turbosloth/__main__.py index 2c5840c..01a3520 100644 --- a/src/turbosloth/__main__.py +++ b/src/turbosloth/__main__.py @@ -1,9 +1,9 @@ from __future__ import annotations +from typing import Any + from turbosloth import SlothApp -from turbosloth.exceptions import NotFoundException from turbosloth.interfaces.serialized import SerializedResponse, SerializedRequest -from turbosloth.types import Scope, Receive, Send, BasicRequest, BasicResponse app = SlothApp() @@ -17,6 +17,5 @@ async def index(req: SerializedRequest) -> SerializedResponse: @app.post("/user/") async def get_user(req: SerializedRequest) -> SerializedResponse: print(req.basic.query) - resp = {'message': f'Hello, User ы {req.basic.query["id"]}!', 'from': 'server'} - resp['echo'] = req.body + resp: dict[str, Any] = {'message': f'Hello, User ы {req.basic.query["id"]}!', 'from': 'server', 'echo': req.body} return SerializedResponse(200, {}, resp) diff --git a/src/turbosloth/app.py b/src/turbosloth/app.py index 916f1b4..85c06bc 100644 --- a/src/turbosloth/app.py +++ b/src/turbosloth/app.py @@ -1,11 +1,12 @@ -import json from typing import Optional, Callable, Awaitable, Protocol from .exceptions import HTTPException +from .interfaces.base import BasicRequest, BasicResponse 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 .types import Scope, Receive, Send, MethodType, HandlerType, BasicRequest, BasicResponse +from .types import Scope, Receive, Send, MethodType, HandlerType class ASGIApp(Protocol): @@ -18,7 +19,7 @@ class ASGIApp(Protocol): class HTTPApp(ASGIApp): 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'] path = scope['path'] @@ -31,9 +32,11 @@ class HTTPApp(ASGIApp): scope['body'] = body req = BasicRequest.from_scope(scope) - sresponser = TextSerializedResponse + sresponser: type[SerializedResponse] = TextSerializedResponse charset = 'latin1' + sresp: SerializedResponse + resp: BasicResponse try: handler = self.router.match(method, path) @@ -59,7 +62,7 @@ class HTTPApp(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() @@ -67,7 +70,7 @@ class LifespanApp: _on_startup: 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: try: await self._on_startup() @@ -77,12 +80,12 @@ class LifespanApp: else: 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: await self._on_shutdown() 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: event = await receive() if event['type'] == 'lifespan.startup': diff --git a/src/turbosloth/exceptions/__init__.py b/src/turbosloth/exceptions/__init__.py index 159842c..8f952dc 100644 --- a/src/turbosloth/exceptions/__init__.py +++ b/src/turbosloth/exceptions/__init__.py @@ -1,2 +1,47 @@ +from http_base import HTTPException from .client_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', +] diff --git a/src/turbosloth/exceptions/http_base.py b/src/turbosloth/exceptions/http_base.py index 62cbc62..8c68c3f 100644 --- a/src/turbosloth/exceptions/http_base.py +++ b/src/turbosloth/exceptions/http_base.py @@ -13,7 +13,7 @@ class HTTPException(Exception): 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 foo diff --git a/src/turbosloth/exceptions/server_errors.py b/src/turbosloth/exceptions/server_errors.py index eb11552..180a3a7 100644 --- a/src/turbosloth/exceptions/server_errors.py +++ b/src/turbosloth/exceptions/server_errors.py @@ -1,4 +1,4 @@ -from turbosloth.exceptions import HTTPException +from turbosloth.exceptions.http_base import HTTPException _server_error_codes = { 500: 'Internal Server Error', diff --git a/src/turbosloth/interfaces/base.py b/src/turbosloth/interfaces/base.py index dd4c8b5..bffe614 100644 --- a/src/turbosloth/interfaces/base.py +++ b/src/turbosloth/interfaces/base.py @@ -1,24 +1,28 @@ from __future__ import annotations + +import typing +import urllib.parse from dataclasses import dataclass from typing import Any, Mapping from case_insensitive_dict import CaseInsensitiveDict -import urllib.parse + +from turbosloth.types import MethodType, Scope, ASGIMessage @dataclass class BasicRequest: - method: str + method: MethodType path: str headers: CaseInsensitiveDict[str, str] - query: dict[str, list[str] | str] + query: dict[str, list[Any] | Any] body: bytes def __init__(self, - method: str, + method: MethodType, path: str, headers: Mapping[str, str], - query: dict[str, list[str] | str], + query: dict[str, list[Any] | Any], body: bytes): self.method = method self.path = path @@ -27,9 +31,9 @@ class BasicRequest: self.body = body @classmethod - def from_scope(cls, scope: dict) -> BasicRequest: + def from_scope(cls, scope: Scope) -> BasicRequest: path = scope['path'] - method = scope['method'] + method = typing.cast(MethodType, scope['method']) headers = {} for key, value in scope.get('headers', []): headers[key.decode('latin1')] = value.decode('latin1') @@ -42,6 +46,7 @@ class BasicRequest: if len(v) == 1: v = v[0] query[k] = v + query = typing.cast(dict[str, list[Any] | Any], query) body = scope['body'] @@ -59,7 +64,7 @@ class BasicResponse: self.headers = CaseInsensitiveDict(headers) self.body = body - def into_start_message(self) -> dict: + def into_start_message(self) -> ASGIMessage: enc_headers = [] for k, v in self.headers.items(): enc_headers.append((k.encode('latin1'), v.encode('latin1'))) diff --git a/src/turbosloth/interfaces/serialize_selector.py b/src/turbosloth/interfaces/serialize_selector.py index 5268416..71767b5 100644 --- a/src/turbosloth/interfaces/serialize_selector.py +++ b/src/turbosloth/interfaces/serialize_selector.py @@ -1,10 +1,10 @@ +import typing from typing import NamedTuple, Optional from case_insensitive_dict import CaseInsensitiveDict from turbosloth.exceptions import NotAcceptableException from turbosloth.interfaces.serialized import SerializedRequest, SerializedResponse, default_serializers - from turbosloth.util import parse_content_type @@ -12,11 +12,11 @@ class SerializeChoise(NamedTuple): req: type[SerializedRequest] resp: type[SerializedResponse] charset: str - content_properties: dict + content_properties: dict[str, str] class SerializeSelector: - default_content_type: str + default_content_type: Optional[str] serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]] def __init__(self, @@ -27,7 +27,7 @@ class SerializeSelector: ser = {} 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(): if k in filter_content_types: @@ -36,6 +36,7 @@ class SerializeSelector: def select(self, headers: CaseInsensitiveDict[str, str]) -> SerializeChoise: contenttype_header = headers.get('Content-Type') + properties: dict[str, str] if contenttype_header is None: contenttype = self.default_content_type properties = {} @@ -50,8 +51,8 @@ class SerializeSelector: else: charset = 'latin1' - choise = self.serializers.get(contenttype) - if choise is None: + choise = self.serializers.get(typing.cast(str, contenttype)) + if choise is None and self.default_content_type is not None: choise = self.serializers.get(self.default_content_type) if choise is None: raise NotAcceptableException('acceptable content types: ' + ', '.join(self.serializers.keys())) diff --git a/src/turbosloth/interfaces/serialized/__init__.py b/src/turbosloth/interfaces/serialized/__init__.py index 083cd3a..995fe80 100644 --- a/src/turbosloth/interfaces/serialized/__init__.py +++ b/src/turbosloth/interfaces/serialized/__init__.py @@ -1,6 +1,6 @@ from .base import SerializedRequest, SerializedResponse -default_serializers = {} +default_serializers: dict[str, tuple[type[SerializedRequest], type[SerializedResponse]]] = {} try: from .text import TextSerializedRequest, TextSerializedResponse @@ -28,3 +28,5 @@ try: default_serializers['application/vnd.msgpack'] = (MessagePackSerializedRequest, MessagePackSerializedResponse) except: pass + +__all__ = ['SerializedRequest', 'SerializedResponse'] diff --git a/src/turbosloth/interfaces/serialized/base.py b/src/turbosloth/interfaces/serialized/base.py index a7ac00f..a30cb0a 100644 --- a/src/turbosloth/interfaces/serialized/base.py +++ b/src/turbosloth/interfaces/serialized/base.py @@ -1,17 +1,36 @@ from dataclasses import dataclass from typing import Any, Mapping +from case_insensitive_dict import CaseInsensitiveDict + from turbosloth.interfaces.base import BasicRequest, BasicResponse +from turbosloth.types import MethodType @dataclass class SerializedRequest: - body: dict[str, Any] | list | str | None + body: dict[str, Any] | list[Any] | str | None basic: BasicRequest 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 - def deserialize(cls, basic: BasicRequest, charset: str): + def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: raise NotImplementedError() @@ -19,7 +38,7 @@ class SerializedRequest: class SerializedResponse: code: int headers: Mapping[str, str] - body: dict[str, Any] | list | str + body: dict[str, Any] | list[Any] | str def into_basic(self, charset: str) -> BasicResponse: raise NotImplementedError() diff --git a/src/turbosloth/interfaces/serialized/json.py b/src/turbosloth/interfaces/serialized/json.py index 2b7c002..9e22a43 100644 --- a/src/turbosloth/interfaces/serialized/json.py +++ b/src/turbosloth/interfaces/serialized/json.py @@ -1,5 +1,4 @@ import orjson - from case_insensitive_dict import CaseInsensitiveDict from turbosloth.interfaces.base import BasicRequest, BasicResponse @@ -8,7 +7,7 @@ from .base import SerializedRequest, SerializedResponse class JsonSerializedRequest(SerializedRequest): @classmethod - def deserialize(cls, basic: BasicRequest, charset: str): + def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: if len(basic.body) == 0: b = None elif charset.lower() in {'utf-8', 'utf8'}: diff --git a/src/turbosloth/interfaces/serialized/msgpack.py b/src/turbosloth/interfaces/serialized/msgpack.py index 00fe86b..6978f0b 100644 --- a/src/turbosloth/interfaces/serialized/msgpack.py +++ b/src/turbosloth/interfaces/serialized/msgpack.py @@ -7,7 +7,7 @@ from .base import SerializedRequest, SerializedResponse class MessagePackSerializedRequest(SerializedRequest): @classmethod - def deserialize(cls, basic: BasicRequest, charset: str): + def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: if len(basic.body) == 0: b = None else: diff --git a/src/turbosloth/interfaces/serialized/text.py b/src/turbosloth/interfaces/serialized/text.py index 03f495b..9fabc4e 100644 --- a/src/turbosloth/interfaces/serialized/text.py +++ b/src/turbosloth/interfaces/serialized/text.py @@ -6,7 +6,7 @@ from .base import SerializedRequest, SerializedResponse class TextSerializedRequest(SerializedRequest): @classmethod - def deserialize(cls, basic: BasicRequest, charset: str): + def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: b = basic.body.decode(charset) return cls(b, basic, charset) diff --git a/src/turbosloth/interfaces/serialized/xml.py b/src/turbosloth/interfaces/serialized/xml.py index f2358a9..af73e9e 100644 --- a/src/turbosloth/interfaces/serialized/xml.py +++ b/src/turbosloth/interfaces/serialized/xml.py @@ -1,17 +1,15 @@ -import json from typing import Any from case_insensitive_dict import CaseInsensitiveDict +from lxml import etree from turbosloth.interfaces.base import BasicRequest, BasicResponse from .base import SerializedRequest, SerializedResponse -from lxml import etree - class XMLSerializedRequest(SerializedRequest): @classmethod - def deserialize(cls, basic: BasicRequest, charset: str): + def deserialize(cls, basic: BasicRequest, charset: str) -> SerializedRequest: if len(basic.body) == 0: b = {} else: @@ -45,19 +43,15 @@ def _into_xml(data: dict[str, Any] | list[Any] | str) -> str: elif value is not None: element.text = str(value) - # Создаем корневой элемент root = etree.Element('response') if isinstance(data, dict): - # Если входной тип — dict, используем его ключи как теги for key, value in data.items(): _build_element(root, key, value) elif isinstance(data, list): - # Если список, оборачиваем элементы в общий тег "item" for item in data: _build_element(root, 'item', item) elif isinstance(data, str): - # Если строка, добавляем как текст корневого элемента root.text = data return etree.tostring(root, pretty_print=True, encoding="unicode") diff --git a/src/turbosloth/router.py b/src/turbosloth/router.py index b7ab876..ed58bd9 100644 --- a/src/turbosloth/router.py +++ b/src/turbosloth/router.py @@ -1,9 +1,9 @@ from __future__ import annotations 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 @@ -11,11 +11,11 @@ class Route: static_subroutes: dict[str, Route] handler: dict[MethodType, HandlerType] - def __init__(self): + def __init__(self) -> None: self.static_subroutes = {} 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: self.handler[method] = handler return @@ -42,10 +42,10 @@ class Route: class Router: _root: Route - def __init__(self): + def __init__(self) -> None: 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 segments = path_pattern.split('/') @@ -65,6 +65,8 @@ class Router: raise NotFoundException(path or '/') from e except MethodNotAllowedException as e: 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 + if h is None: + raise NotFoundException(path or '/') return h diff --git a/src/turbosloth/types.py b/src/turbosloth/types.py index fec375e..63127af 100644 --- a/src/turbosloth/types.py +++ b/src/turbosloth/types.py @@ -1,19 +1,13 @@ from __future__ import annotations -import collections -from dataclasses import dataclass -from typing import Callable, Awaitable, Dict, Literal, Any, TypeVar +from typing import Callable, Awaitable, Literal, Any -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 -type Scope = Dict -type Receive = Callable[[], Awaitable[Dict]] -type Send = Callable[[Dict], Awaitable[None]] +type Scope = dict[str, Any] +type ASGIMessage = dict[str, Any] +type Receive = Callable[[], Awaitable[ASGIMessage]] +type Send = Callable[[ASGIMessage], Awaitable[None]] type HandlerType = Callable[[SerializedRequest], Awaitable[SerializedResponse]] type MethodType = ( diff --git a/src/turbosloth/util.py b/src/turbosloth/util.py index 81a0630..491f9da 100644 --- a/src/turbosloth/util.py +++ b/src/turbosloth/util.py @@ -1,6 +1,6 @@ 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) return header.content_type, dict(header.params) diff --git a/tests/test_router.py b/tests/test_router.py index 887d0e7..a5b225f 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -1,7 +1,7 @@ import pytest -from src.turbosloth.router import Router from src.turbosloth.exceptions import NotFoundException, MethodNotAllowedException +from src.turbosloth.router import Router def test_router_root_handler(): diff --git a/uv.lock b/uv.lock index a4834af..b576230 100644 --- a/uv.lock +++ b/uv.lock @@ -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" }, ] +[[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]] name = "markupsafe" 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" }, ] +[[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]] name = "orjson" version = "3.11.0" @@ -310,16 +348,19 @@ dependencies = [ { name = "breakshaft" }, { name = "case-insensitive-dictionary" }, { name = "megasniff" }, + { name = "mypy" }, ] [package.dev-dependencies] all-containers = [ { name = "lxml" }, + { name = "lxml-stubs" }, { name = "msgpack" }, { name = "orjson" }, ] dev = [ { name = "lxml" }, + { name = "lxml-stubs" }, { name = "msgpack" }, { name = "orjson" }, { name = "pytest" }, @@ -334,6 +375,7 @@ msgpack = [ ] xml = [ { name = "lxml" }, + { name = "lxml-stubs" }, ] [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 = "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 = "mypy", specifier = ">=1.17.0" }, ] [package.metadata.requires-dev] all-containers = [ { name = "lxml", specifier = ">=6.0.0" }, + { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "msgpack", specifier = ">=1.1.1" }, { name = "orjson", specifier = ">=3.11.0" }, ] dev = [ { name = "lxml", specifier = ">=6.0.0" }, + { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "msgpack", specifier = ">=1.1.1" }, { name = "orjson", specifier = ">=3.11.0" }, { name = "pytest", specifier = ">=8.4.1" }, @@ -359,7 +404,19 @@ dev = [ ] json = [{ name = "orjson", specifier = ">=3.11.0" }] 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]] name = "uvicorn"