Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -11,39 +11,27 @@ import platform
import re
import sys
import warnings
from functools import wraps
from pathlib import Path
from types import MappingProxyType
from typing import (
TYPE_CHECKING,
Any,
from collections.abc import (
Awaitable,
Callable,
Container,
Dict,
Final,
Generator,
Iterable,
Iterator,
List,
Mapping,
NoReturn,
Optional,
Pattern,
Set,
Sized,
Tuple,
Type,
TypedDict,
Union,
cast,
)
from functools import wraps
from pathlib import Path
from re import Pattern
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, TypedDict, cast
from yarl import URL, __version__ as yarl_version
from . import hdrs
from .abc import AbstractMatchInfo, AbstractRouter, AbstractView
from .helpers import DEBUG
from .helpers import DEBUG, DEFAULT_CHUNK_SIZE
from .http import HttpVersion11
from .typedefs import Handler, PathLike
from .web_exceptions import (
@ -75,17 +63,13 @@ __all__ = (
if TYPE_CHECKING:
from .web_app import Application
BaseDict = Dict[str, str]
BaseDict = dict[str, str]
else:
BaseDict = dict
CIRCULAR_SYMLINK_ERROR = (
(OSError,)
if sys.version_info < (3, 10) and sys.platform.startswith("win32")
else (RuntimeError,) if sys.version_info < (3, 13) else ()
)
CIRCULAR_SYMLINK_ERROR = (RuntimeError,) if sys.version_info < (3, 13) else ()
YARL_VERSION: Final[Tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2]))
YARL_VERSION: Final[tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2]))
HTTP_METHOD_RE: Final[Pattern[str]] = re.compile(
r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$"
@ -97,8 +81,8 @@ PATH_SEP: Final[str] = re.escape("/")
IS_WINDOWS: Final[bool] = platform.system() == "Windows"
_ExpectHandler = Callable[[Request], Awaitable[Optional[StreamResponse]]]
_Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]]
_ExpectHandler = Callable[[Request], Awaitable[StreamResponse | None]]
_Resolve = tuple[Optional["UrlMappingMatchInfo"], set[str]]
html_escape = functools.partial(html.escape, quote=True)
@ -123,11 +107,11 @@ class _InfoDict(TypedDict, total=False):
class AbstractResource(Sized, Iterable["AbstractRoute"]):
def __init__(self, *, name: Optional[str] = None) -> None:
def __init__(self, *, name: str | None = None) -> None:
self._name = name
@property
def name(self) -> Optional[str]:
def name(self) -> str | None:
return self._name
@property
@ -173,10 +157,10 @@ class AbstractRoute(abc.ABC):
def __init__(
self,
method: str,
handler: Union[Handler, Type[AbstractView]],
handler: Handler | type[AbstractView],
*,
expect_handler: Optional[_ExpectHandler] = None,
resource: Optional[AbstractResource] = None,
expect_handler: _ExpectHandler | None = None,
resource: AbstractResource | None = None,
) -> None:
if expect_handler is None:
@ -235,11 +219,11 @@ class AbstractRoute(abc.ABC):
@property
@abc.abstractmethod
def name(self) -> Optional[str]:
def name(self) -> str | None:
"""Optional route's name, always equals to resource's name."""
@property
def resource(self) -> Optional[AbstractResource]:
def resource(self) -> AbstractResource | None:
return self._resource
@abc.abstractmethod
@ -250,7 +234,7 @@ class AbstractRoute(abc.ABC):
def url_for(self, *args: str, **kwargs: str) -> URL:
"""Construct url for route with additional params."""
async def handle_expect_header(self, request: Request) -> Optional[StreamResponse]:
async def handle_expect_header(self, request: Request) -> StreamResponse | None:
return await self._expect_handler(request)
@ -258,11 +242,11 @@ class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo):
__slots__ = ("_route", "_apps", "_current_app", "_frozen")
def __init__(self, match_dict: Dict[str, str], route: AbstractRoute) -> None:
def __init__(self, match_dict: dict[str, str], route: AbstractRoute) -> None:
super().__init__(match_dict)
self._route = route
self._apps: List[Application] = []
self._current_app: Optional[Application] = None
self._apps: list[Application] = []
self._current_app: Application | None = None
self._frozen = False
@property
@ -278,14 +262,14 @@ class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo):
return self._route.handle_expect_header
@property
def http_exception(self) -> Optional[HTTPException]:
def http_exception(self) -> HTTPException | None:
return None
def get_info(self) -> _InfoDict: # type: ignore[override]
return self._route.get_info()
@property
def apps(self) -> Tuple["Application", ...]:
def apps(self) -> tuple["Application", ...]:
return tuple(self._apps)
def add_app(self, app: "Application") -> None:
@ -306,9 +290,7 @@ class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo):
if DEBUG: # pragma: no cover
if app not in self._apps:
raise RuntimeError(
"Expected one of the following apps {!r}, got {!r}".format(
self._apps, app
)
f"Expected one of the following apps {self._apps!r}, got {app!r}"
)
self._current_app = app
@ -332,9 +314,7 @@ class MatchInfoError(UrlMappingMatchInfo):
return self._exception
def __repr__(self) -> str:
return "<MatchInfoError {}: {}>".format(
self._exception.status, self._exception.reason
)
return f"<MatchInfoError {self._exception.status}: {self._exception.reason}>"
async def _default_expect_handler(request: Request) -> None:
@ -354,18 +334,18 @@ async def _default_expect_handler(request: Request) -> None:
class Resource(AbstractResource):
def __init__(self, *, name: Optional[str] = None) -> None:
def __init__(self, *, name: str | None = None) -> None:
super().__init__(name=name)
self._routes: Dict[str, ResourceRoute] = {}
self._any_route: Optional[ResourceRoute] = None
self._allowed_methods: Set[str] = set()
self._routes: dict[str, ResourceRoute] = {}
self._any_route: ResourceRoute | None = None
self._allowed_methods: set[str] = set()
def add_route(
self,
method: str,
handler: Union[Type[AbstractView], Handler],
handler: type[AbstractView] | Handler,
*,
expect_handler: Optional[_ExpectHandler] = None,
expect_handler: _ExpectHandler | None = None,
) -> "ResourceRoute":
if route := self._routes.get(method, self._any_route):
raise RuntimeError(
@ -395,7 +375,7 @@ class Resource(AbstractResource):
return None, self._allowed_methods
@abc.abstractmethod
def _match(self, path: str) -> Optional[Dict[str, str]]:
def _match(self, path: str) -> dict[str, str] | None:
pass # pragma: no cover
def __len__(self) -> int:
@ -408,7 +388,7 @@ class Resource(AbstractResource):
class PlainResource(Resource):
def __init__(self, path: str, *, name: Optional[str] = None) -> None:
def __init__(self, path: str, *, name: str | None = None) -> None:
super().__init__(name=name)
assert not path or path.startswith("/")
self._path = path
@ -427,7 +407,7 @@ class PlainResource(Resource):
assert len(prefix) > 1
self._path = prefix + self._path
def _match(self, path: str) -> Optional[Dict[str, str]]:
def _match(self, path: str) -> dict[str, str] | None:
# string comparison is about 10 times faster than regexp matching
if self._path == path:
return {}
@ -453,7 +433,7 @@ class DynamicResource(Resource):
DYN_WITH_RE = re.compile(r"\{(?P<var>[_a-zA-Z][_a-zA-Z0-9]*):(?P<re>.+)\}")
GOOD = r"[^{}/]+"
def __init__(self, path: str, *, name: Optional[str] = None) -> None:
def __init__(self, path: str, *, name: str | None = None) -> None:
super().__init__(name=name)
self._orig_path = path
pattern = ""
@ -498,7 +478,7 @@ class DynamicResource(Resource):
self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern)
self._formatter = prefix + self._formatter
def _match(self, path: str) -> Optional[Dict[str, str]]:
def _match(self, path: str) -> dict[str, str] | None:
match = self._pattern.fullmatch(path)
if match is None:
return None
@ -518,13 +498,11 @@ class DynamicResource(Resource):
def __repr__(self) -> str:
name = "'" + self.name + "' " if self.name is not None else ""
return "<DynamicResource {name} {formatter}>".format(
name=name, formatter=self._formatter
)
return f"<DynamicResource {name} {self._formatter}>"
class PrefixResource(AbstractResource):
def __init__(self, prefix: str, *, name: Optional[str] = None) -> None:
def __init__(self, prefix: str, *, name: str | None = None) -> None:
assert not prefix or prefix.startswith("/"), prefix
assert prefix in ("", "/") or not prefix.endswith("/"), prefix
super().__init__(name=name)
@ -556,9 +534,9 @@ class StaticResource(PrefixResource):
prefix: str,
directory: PathLike,
*,
name: Optional[str] = None,
expect_handler: Optional[_ExpectHandler] = None,
chunk_size: int = 256 * 1024,
name: str | None = None,
expect_handler: _ExpectHandler | None = None,
chunk_size: int = DEFAULT_CHUNK_SIZE,
show_index: bool = False,
follow_symlinks: bool = False,
append_version: bool = False,
@ -591,7 +569,7 @@ class StaticResource(PrefixResource):
self,
*,
filename: PathLike,
append_version: Optional[bool] = None,
append_version: bool | None = None,
) -> URL:
if append_version is None:
append_version = self._append_version
@ -676,6 +654,10 @@ class StaticResource(PrefixResource):
async def _handle(self, request: Request) -> StreamResponse:
filename = request.match_info["filename"]
if Path(filename).is_absolute():
# filename is an absolute path e.g. //network/share or D:\path
# which could be a UNC path leading to NTLM credential theft
raise HTTPNotFound()
unresolved_path = self._directory.joinpath(filename)
loop = asyncio.get_running_loop()
return await loop.run_in_executor(
@ -751,9 +733,7 @@ class StaticResource(PrefixResource):
def __repr__(self) -> str:
name = "'" + self.name + "'" if self.name is not None else ""
return "<StaticResource {name} {path} -> {directory!r}>".format(
name=name, path=self._prefix, directory=self._directory
)
return f"<StaticResource {name} {self._prefix} -> {self._directory!r}>"
class PrefixedSubAppResource(PrefixResource):
@ -797,9 +777,7 @@ class PrefixedSubAppResource(PrefixResource):
return iter(self._app.router.routes())
def __repr__(self) -> str:
return "<PrefixedSubAppResource {prefix} -> {app!r}>".format(
prefix=self._prefix, app=self._app
)
return f"<PrefixedSubAppResource {self._prefix} -> {self._app!r}>"
class AbstractRuleMatching(abc.ABC):
@ -908,22 +886,20 @@ class ResourceRoute(AbstractRoute):
def __init__(
self,
method: str,
handler: Union[Handler, Type[AbstractView]],
handler: Handler | type[AbstractView],
resource: AbstractResource,
*,
expect_handler: Optional[_ExpectHandler] = None,
expect_handler: _ExpectHandler | None = None,
) -> None:
super().__init__(
method, handler, expect_handler=expect_handler, resource=resource
)
def __repr__(self) -> str:
return "<ResourceRoute [{method}] {resource} -> {handler!r}".format(
method=self.method, resource=self._resource, handler=self.handler
)
return f"<ResourceRoute [{self.method}] {self._resource} -> {self.handler!r}"
@property
def name(self) -> Optional[str]:
def name(self) -> str | None:
if self._resource is None:
return None
return self._resource.name
@ -947,7 +923,7 @@ class SystemRoute(AbstractRoute):
raise RuntimeError(".url_for() is not allowed for SystemRoute")
@property
def name(self) -> Optional[str]:
def name(self) -> str | None:
return None
def get_info(self) -> _InfoDict:
@ -965,14 +941,14 @@ class SystemRoute(AbstractRoute):
return self._http_exception.reason
def __repr__(self) -> str:
return "<SystemRoute {self.status}: {self.reason}>".format(self=self)
return f"<SystemRoute {self.status}: {self.reason}>"
class View(AbstractView):
async def _iter(self) -> StreamResponse:
if self.request.method not in hdrs.METH_ALL:
self._raise_allowed_methods()
method: Optional[Callable[[], Awaitable[StreamResponse]]]
method: Callable[[], Awaitable[StreamResponse]] | None
method = getattr(self, self.request.method.lower(), None)
if method is None:
self._raise_allowed_methods()
@ -989,7 +965,7 @@ class View(AbstractView):
class ResourcesView(Sized, Iterable[AbstractResource], Container[AbstractResource]):
def __init__(self, resources: List[AbstractResource]) -> None:
def __init__(self, resources: list[AbstractResource]) -> None:
self._resources = resources
def __len__(self) -> int:
@ -1003,8 +979,8 @@ class ResourcesView(Sized, Iterable[AbstractResource], Container[AbstractResourc
class RoutesView(Sized, Iterable[AbstractRoute], Container[AbstractRoute]):
def __init__(self, resources: List[AbstractResource]):
self._routes: List[AbstractRoute] = []
def __init__(self, resources: list[AbstractResource]):
self._routes: list[AbstractRoute] = []
for resource in resources:
for route in resource:
self._routes.append(route)
@ -1025,14 +1001,14 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
def __init__(self) -> None:
super().__init__()
self._resources: List[AbstractResource] = []
self._named_resources: Dict[str, AbstractResource] = {}
self._resources: list[AbstractResource] = []
self._named_resources: dict[str, AbstractResource] = {}
self._resource_index: dict[str, list[AbstractResource]] = {}
self._matched_sub_app_resources: List[MatchedSubAppResource] = []
self._matched_sub_app_resources: list[MatchedSubAppResource] = []
async def resolve(self, request: Request) -> UrlMappingMatchInfo:
resource_index = self._resource_index
allowed_methods: Set[str] = set()
allowed_methods: set[str] = set()
# MatchedSubAppResource is primarily used to match on domain names
# (though custom rules could match on other things). This means that
@ -1112,15 +1088,15 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
)
if not part.isidentifier():
raise ValueError(
"Incorrect route name {!r}, "
f"Incorrect route name {name!r}, "
"the name should be a sequence of "
"python identifiers separated "
"by dash, dot or column".format(name)
"by dash, dot or column"
)
if name in self._named_resources:
raise ValueError(
"Duplicate {!r}, "
"already handled by {!r}".format(name, self._named_resources[name])
f"Duplicate {name!r}, "
f"already handled by {self._named_resources[name]!r}"
)
self._named_resources[name] = resource
self._resources.append(resource)
@ -1155,7 +1131,7 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
resource_key = self._get_resource_index_key(resource)
self._resource_index[resource_key].remove(resource)
def add_resource(self, path: str, *, name: Optional[str] = None) -> Resource:
def add_resource(self, path: str, *, name: str | None = None) -> Resource:
if path and not path.startswith("/"):
raise ValueError("path should be started with / or be empty")
# Reuse last added resource if path and name are the same
@ -1175,10 +1151,10 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
self,
method: str,
path: str,
handler: Union[Handler, Type[AbstractView]],
handler: Handler | type[AbstractView],
*,
name: Optional[str] = None,
expect_handler: Optional[_ExpectHandler] = None,
name: str | None = None,
expect_handler: _ExpectHandler | None = None,
) -> AbstractRoute:
resource = self.add_resource(path, name=name)
return resource.add_route(method, handler, expect_handler=expect_handler)
@ -1188,9 +1164,9 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
prefix: str,
path: PathLike,
*,
name: Optional[str] = None,
expect_handler: Optional[_ExpectHandler] = None,
chunk_size: int = 256 * 1024,
name: str | None = None,
expect_handler: _ExpectHandler | None = None,
chunk_size: int = DEFAULT_CHUNK_SIZE,
show_index: bool = False,
follow_symlinks: bool = False,
append_version: bool = False,
@ -1230,7 +1206,7 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
path: str,
handler: Handler,
*,
name: Optional[str] = None,
name: str | None = None,
allow_head: bool = True,
**kwargs: Any,
) -> AbstractRoute:
@ -1261,7 +1237,7 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
return self.add_route(hdrs.METH_DELETE, path, handler, **kwargs)
def add_view(
self, path: str, handler: Type[AbstractView], **kwargs: Any
self, path: str, handler: type[AbstractView], **kwargs: Any
) -> AbstractRoute:
"""Shortcut for add_route with ANY methods for a class-based view."""
return self.add_route(hdrs.METH_ANY, path, handler, **kwargs)
@ -1271,7 +1247,7 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
for resource in self._resources:
resource.freeze()
def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]:
def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:
"""Append routes to route table.
Parameter should be a sequence of RouteDef objects.