Fix most of types
This commit is contained in:
5
mypy.ini
Normal file
5
mypy.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[mypy]
|
||||
python_version = 3.13
|
||||
strict = True
|
||||
ignore_missing_imports = True
|
||||
files = src/
|
||||
@@ -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", ]
|
||||
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
import interfaces
|
||||
import router
|
||||
from .app import SlothApp
|
||||
|
||||
__all__ = ['SlothApp', 'router', 'interfaces']
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from turbosloth.exceptions import HTTPException
|
||||
from turbosloth.exceptions.http_base import HTTPException
|
||||
|
||||
_server_error_codes = {
|
||||
500: 'Internal Server Error',
|
||||
|
||||
@@ -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')))
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'}:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
59
uv.lock
generated
59
uv.lock
generated
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user