mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Added base code and make code improvements
This commit is contained in:
parent
5bd1162f57
commit
bd2a348aa0
16 changed files with 522 additions and 424 deletions
2
Makefile
2
Makefile
|
|
@ -47,7 +47,7 @@ help:
|
|||
|
||||
.PHONY: install
|
||||
install:
|
||||
poetry install
|
||||
poetry install -E fast -E redis -E proxy -E i18n -E docs
|
||||
$(py) pre-commit install
|
||||
|
||||
.PHONY: clean
|
||||
|
|
|
|||
|
|
@ -231,6 +231,14 @@ class Bot(ContextInstanceMixin["Bot"]):
|
|||
async for chunk in stream:
|
||||
await f.write(chunk)
|
||||
|
||||
@classmethod
|
||||
async def __aiofiles_reader(
|
||||
cls, file: str, chunk_size: int = 65536
|
||||
) -> AsyncGenerator[bytes, None]:
|
||||
async with aiofiles.open(file, "rb") as f:
|
||||
while chunk := await f.read(chunk_size):
|
||||
yield chunk
|
||||
|
||||
async def download_file(
|
||||
self,
|
||||
file_path: str,
|
||||
|
|
@ -254,15 +262,26 @@ class Bot(ContextInstanceMixin["Bot"]):
|
|||
if destination is None:
|
||||
destination = io.BytesIO()
|
||||
|
||||
url = self.session.api.file_url(self.__token, file_path)
|
||||
stream = self.session.stream_content(url=url, timeout=timeout, chunk_size=chunk_size)
|
||||
|
||||
if isinstance(destination, (str, pathlib.Path)):
|
||||
return await self.__download_file(destination=destination, stream=stream)
|
||||
else:
|
||||
return await self.__download_file_binary_io(
|
||||
destination=destination, seek=seek, stream=stream
|
||||
close_stream = False
|
||||
if self.session.api.is_local:
|
||||
stream = self.__aiofiles_reader(
|
||||
self.session.api.wrap_local_file(file_path), chunk_size=chunk_size
|
||||
)
|
||||
close_stream = True
|
||||
else:
|
||||
url = self.session.api.file_url(self.__token, file_path)
|
||||
stream = self.session.stream_content(url=url, timeout=timeout, chunk_size=chunk_size)
|
||||
|
||||
try:
|
||||
if isinstance(destination, (str, pathlib.Path)):
|
||||
return await self.__download_file(destination=destination, stream=stream)
|
||||
else:
|
||||
return await self.__download_file_binary_io(
|
||||
destination=destination, seek=seek, stream=stream
|
||||
)
|
||||
finally:
|
||||
if close_stream:
|
||||
await stream.aclose()
|
||||
|
||||
async def download(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol
|
||||
|
||||
|
||||
class WrapLocalFileCallbackCallbackProtocol(Protocol):
|
||||
def __call__(self, value: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -8,8 +14,13 @@ class TelegramAPIServer:
|
|||
"""
|
||||
|
||||
base: str
|
||||
"""Base URL"""
|
||||
file: str
|
||||
"""Files URL"""
|
||||
is_local: bool = False
|
||||
"""Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
|
||||
wrap_local_file: WrapLocalFileCallbackCallbackProtocol = lambda v: v
|
||||
"""Callback to wrap files path in local mode"""
|
||||
|
||||
def api_url(self, token: str, method: str) -> str:
|
||||
"""
|
||||
|
|
@ -32,19 +43,18 @@ class TelegramAPIServer:
|
|||
return self.file.format(token=token, path=path)
|
||||
|
||||
@classmethod
|
||||
def from_base(cls, base: str, is_local: bool = False) -> "TelegramAPIServer":
|
||||
def from_base(cls, base: str, **kwargs: Any) -> "TelegramAPIServer":
|
||||
"""
|
||||
Use this method to auto-generate TelegramAPIServer instance from base URL
|
||||
|
||||
:param base: Base URL
|
||||
:param is_local: Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_.
|
||||
:return: instance of :class:`TelegramAPIServer`
|
||||
"""
|
||||
base = base.rstrip("/")
|
||||
return cls(
|
||||
base=f"{base}/bot{{token}}/{{method}}",
|
||||
file=f"{base}/file/bot{{token}}/{{path}}",
|
||||
is_local=is_local,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, Union
|
||||
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, TypeVar, Union
|
||||
from unittest.mock import sentinel
|
||||
|
||||
from ...types import TelegramObject
|
||||
from ..middlewares.base import BaseMiddleware
|
||||
|
||||
NextMiddlewareType = Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]]
|
||||
MiddlewareEventType = TypeVar("MiddlewareEventType", bound=TelegramObject)
|
||||
NextMiddlewareType = Callable[[MiddlewareEventType, Dict[str, Any]], Awaitable[Any]]
|
||||
MiddlewareType = Union[
|
||||
BaseMiddleware, Callable[[NextMiddlewareType, TelegramObject, Dict[str, Any]], Awaitable[Any]]
|
||||
BaseMiddleware,
|
||||
Callable[
|
||||
[NextMiddlewareType[MiddlewareEventType], MiddlewareEventType, Dict[str, Any]],
|
||||
Awaitable[Any],
|
||||
],
|
||||
]
|
||||
|
||||
UNHANDLED = sentinel.UNHANDLED
|
||||
|
|
|
|||
|
|
@ -8,7 +8,14 @@ from pydantic import ValidationError
|
|||
|
||||
from ...types import TelegramObject
|
||||
from ..filters.base import BaseFilter
|
||||
from .bases import REJECTED, UNHANDLED, MiddlewareType, NextMiddlewareType, SkipHandler
|
||||
from .bases import (
|
||||
REJECTED,
|
||||
UNHANDLED,
|
||||
MiddlewareEventType,
|
||||
MiddlewareType,
|
||||
NextMiddlewareType,
|
||||
SkipHandler,
|
||||
)
|
||||
from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
|
|
@ -29,8 +36,8 @@ class TelegramEventObserver:
|
|||
|
||||
self.handlers: List[HandlerObject] = []
|
||||
self.filters: List[Type[BaseFilter]] = []
|
||||
self.outer_middlewares: List[MiddlewareType] = []
|
||||
self.middlewares: List[MiddlewareType] = []
|
||||
self.outer_middlewares: List[MiddlewareType[TelegramObject]] = []
|
||||
self.middlewares: List[MiddlewareType[TelegramObject]] = []
|
||||
|
||||
# Re-used filters check method from already implemented handler object
|
||||
# with dummy callback which never will be used
|
||||
|
|
@ -78,7 +85,7 @@ class TelegramEventObserver:
|
|||
yield filter_
|
||||
registry.append(filter_)
|
||||
|
||||
def _resolve_middlewares(self, *, outer: bool = False) -> List[MiddlewareType]:
|
||||
def _resolve_middlewares(self, *, outer: bool = False) -> List[MiddlewareType[TelegramObject]]:
|
||||
"""
|
||||
Get all middlewares in a tree
|
||||
:param *:
|
||||
|
|
@ -137,8 +144,8 @@ class TelegramEventObserver:
|
|||
|
||||
@classmethod
|
||||
def _wrap_middleware(
|
||||
cls, middlewares: List[MiddlewareType], handler: HandlerType
|
||||
) -> NextMiddlewareType:
|
||||
cls, middlewares: List[MiddlewareType[MiddlewareEventType]], handler: HandlerType
|
||||
) -> NextMiddlewareType[MiddlewareEventType]:
|
||||
@functools.wraps(handler)
|
||||
def mapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any:
|
||||
return handler(event, **kwargs)
|
||||
|
|
@ -194,8 +201,11 @@ class TelegramEventObserver:
|
|||
|
||||
def middleware(
|
||||
self,
|
||||
middleware: Optional[MiddlewareType] = None,
|
||||
) -> Union[Callable[[MiddlewareType], MiddlewareType], MiddlewareType]:
|
||||
middleware: Optional[MiddlewareType[TelegramObject]] = None,
|
||||
) -> Union[
|
||||
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
|
||||
MiddlewareType[TelegramObject],
|
||||
]:
|
||||
"""
|
||||
Decorator for registering inner middlewares
|
||||
|
||||
|
|
@ -215,7 +225,7 @@ class TelegramEventObserver:
|
|||
<event>.middleware(my_middleware) # via method
|
||||
"""
|
||||
|
||||
def wrapper(m: MiddlewareType) -> MiddlewareType:
|
||||
def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]:
|
||||
self.middlewares.append(m)
|
||||
return m
|
||||
|
||||
|
|
@ -225,8 +235,11 @@ class TelegramEventObserver:
|
|||
|
||||
def outer_middleware(
|
||||
self,
|
||||
middleware: Optional[MiddlewareType] = None,
|
||||
) -> Union[Callable[[MiddlewareType], MiddlewareType], MiddlewareType]:
|
||||
middleware: Optional[MiddlewareType[TelegramObject]] = None,
|
||||
) -> Union[
|
||||
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
|
||||
MiddlewareType[TelegramObject],
|
||||
]:
|
||||
"""
|
||||
Decorator for registering outer middlewares
|
||||
|
||||
|
|
@ -246,7 +259,7 @@ class TelegramEventObserver:
|
|||
<event>.outer_middleware(my_middleware) # via method
|
||||
"""
|
||||
|
||||
def wrapper(m: MiddlewareType) -> MiddlewareType:
|
||||
def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]:
|
||||
self.outer_middlewares.append(m)
|
||||
return m
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from .base import BaseFilter
|
|||
from .command import Command, CommandObject
|
||||
from .content_types import ContentTypesFilter
|
||||
from .exception import ExceptionMessageFilter, ExceptionTypeFilter
|
||||
from .state import StateFilter
|
||||
from .text import Text
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -15,21 +16,46 @@ __all__ = (
|
|||
"ContentTypesFilter",
|
||||
"ExceptionMessageFilter",
|
||||
"ExceptionTypeFilter",
|
||||
"StateFilter",
|
||||
)
|
||||
|
||||
BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = {
|
||||
"message": (Text, Command, ContentTypesFilter),
|
||||
"edited_message": (Text, Command, ContentTypesFilter),
|
||||
"channel_post": (Text, ContentTypesFilter),
|
||||
"edited_channel_post": (Text, ContentTypesFilter),
|
||||
"inline_query": (Text,),
|
||||
"chosen_inline_result": (),
|
||||
"callback_query": (Text,),
|
||||
"shipping_query": (),
|
||||
"pre_checkout_query": (),
|
||||
"poll": (),
|
||||
"poll_answer": (),
|
||||
"my_chat_member": (),
|
||||
"chat_member": (),
|
||||
"message": (
|
||||
Text,
|
||||
Command,
|
||||
ContentTypesFilter,
|
||||
StateFilter,
|
||||
),
|
||||
"edited_message": (
|
||||
Text,
|
||||
Command,
|
||||
ContentTypesFilter,
|
||||
StateFilter,
|
||||
),
|
||||
"channel_post": (
|
||||
Text,
|
||||
ContentTypesFilter,
|
||||
StateFilter,
|
||||
),
|
||||
"edited_channel_post": (
|
||||
Text,
|
||||
ContentTypesFilter,
|
||||
StateFilter,
|
||||
),
|
||||
"inline_query": (
|
||||
Text,
|
||||
StateFilter,
|
||||
),
|
||||
"chosen_inline_result": (StateFilter,),
|
||||
"callback_query": (
|
||||
Text,
|
||||
StateFilter,
|
||||
),
|
||||
"shipping_query": (StateFilter,),
|
||||
"pre_checkout_query": (StateFilter,),
|
||||
"poll": (StateFilter,),
|
||||
"poll_answer": (StateFilter,),
|
||||
"my_chat_member": (StateFilter,),
|
||||
"chat_member": (StateFilter,),
|
||||
"error": (ExceptionMessageFilter, ExceptionTypeFilter),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
from typing import Any, Dict, Optional, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Union
|
||||
|
||||
from pydantic import root_validator
|
||||
|
||||
from aiogram.dispatcher.filters import BaseFilter
|
||||
from aiogram.types import CallbackQuery, InlineQuery, Message, Poll
|
||||
|
||||
TextType = str
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.utils.i18n.lazy_proxy import LazyProxy
|
||||
|
||||
TextType = Union[str, "LazyProxy"]
|
||||
|
||||
|
||||
class Text(BaseFilter):
|
||||
|
|
@ -35,6 +38,9 @@ class Text(BaseFilter):
|
|||
text_ignore_case: bool = False
|
||||
"""Ignore case when checks"""
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@root_validator
|
||||
def _validate_constraints(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# Validate that only one text filter type is presented
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from aiogram.dispatcher.fsm.context import FSMContext
|
|||
from aiogram.dispatcher.fsm.storage.base import BaseStorage
|
||||
from aiogram.dispatcher.fsm.strategy import FSMStrategy, apply_strategy
|
||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
from aiogram.types import Update
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class FSMContextMiddleware(BaseMiddleware[Update]):
|
||||
class FSMContextMiddleware(BaseMiddleware):
|
||||
def __init__(
|
||||
self,
|
||||
storage: BaseStorage,
|
||||
|
|
@ -21,8 +21,8 @@ class FSMContextMiddleware(BaseMiddleware[Update]):
|
|||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
|
||||
event: Update,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
bot: Bot = cast(Bot, data["bot"])
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@ class StatesGroup(metaclass=StatesGroupMeta):
|
|||
return cls
|
||||
return cls.__parent__.get_root()
|
||||
|
||||
def __call__(cls, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
|
||||
return raw_state in type(cls).__all_states_names__
|
||||
def __call__(self, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
|
||||
return raw_state in type(self).__all_states_names__
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"StatesGroup {type(self).__full_group_name__}"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Awaitable, Callable, Dict, Generic, TypeVar
|
||||
from typing import Any, Awaitable, Callable, Dict, TypeVar
|
||||
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class BaseMiddleware(ABC, Generic[T]):
|
||||
class BaseMiddleware(ABC):
|
||||
"""
|
||||
Generic middleware class
|
||||
"""
|
||||
|
|
@ -12,8 +14,8 @@ class BaseMiddleware(ABC, Generic[T]):
|
|||
@abstractmethod
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[T, Dict[str, Any]], Awaitable[Any]],
|
||||
event: T,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any: # pragma: no cover
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
|
||||
|
||||
from ...types import Update
|
||||
from ...types import TelegramObject
|
||||
from ..event.bases import UNHANDLED, CancelHandler, SkipHandler
|
||||
from .base import BaseMiddleware
|
||||
|
||||
|
|
@ -10,14 +10,14 @@ if TYPE_CHECKING: # pragma: no cover
|
|||
from ..router import Router
|
||||
|
||||
|
||||
class ErrorsMiddleware(BaseMiddleware[Update]):
|
||||
class ErrorsMiddleware(BaseMiddleware):
|
||||
def __init__(self, router: Router):
|
||||
self.router = router
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||
event: Any,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@ from contextlib import contextmanager
|
|||
from typing import Any, Awaitable, Callable, Dict, Iterator, Optional, Tuple
|
||||
|
||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
from aiogram.types import Chat, Update, User
|
||||
from aiogram.types import Chat, TelegramObject, Update, User
|
||||
|
||||
|
||||
class UserContextMiddleware(BaseMiddleware[Update]):
|
||||
class UserContextMiddleware(BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
|
||||
event: Update,
|
||||
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: Dict[str, Any],
|
||||
) -> Any:
|
||||
if not isinstance(event, Update):
|
||||
raise RuntimeError("UserContextMiddleware got an unexpected event type!")
|
||||
chat, user = self.resolve_event_context(event=event)
|
||||
with self.context(chat=chat, user=user):
|
||||
if user is not None:
|
||||
|
|
|
|||
|
|
@ -216,8 +216,7 @@ class OrderedHelperMeta(type):
|
|||
|
||||
setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys)
|
||||
|
||||
# ref: https://gitter.im/python/typing?at=5da98cc5fa637359fc9cbfe1
|
||||
return cast(OrderedHelperMeta, cls)
|
||||
return cls
|
||||
|
||||
|
||||
class OrderedHelper(Helper, metaclass=OrderedHelperMeta):
|
||||
|
|
|
|||
3
mypy.ini
3
mypy.ini
|
|
@ -32,3 +32,6 @@ ignore_missing_imports = True
|
|||
|
||||
[mypy-aioredis]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-babel.*]
|
||||
ignore_missing_imports = True
|
||||
|
|
|
|||
684
poetry.lock
generated
684
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -32,32 +32,39 @@ classifiers = [
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
magic-filter = "^1.0.0"
|
||||
magic-filter = "^1.0.2"
|
||||
aiohttp = "^3.7.4"
|
||||
pydantic = "^1.8.1"
|
||||
Babel = "^2.9.1"
|
||||
aiofiles = "^0.6.0"
|
||||
pydantic = "^1.8.2"
|
||||
aiofiles = "^0.7.0"
|
||||
async_lru = "^1.0.2"
|
||||
# Fast
|
||||
uvloop = { version = "^0.16.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'", optional = true }
|
||||
# i18n
|
||||
Babel = { version = "^2.9.1", optional = true }
|
||||
# Proxy
|
||||
aiohttp-socks = { version = "^0.5.5", optional = true }
|
||||
aioredis = { version = "^2.0.0", allow-prereleases = true, optional = true }
|
||||
sphinx = { version = "^3.1.0", optional = true }
|
||||
# Redis
|
||||
aioredis = { version = "^2.0.0", optional = true }
|
||||
# Docs
|
||||
Sphinx = { version = "^4.2.0", optional = true }
|
||||
sphinx-intl = { version = "^2.0.1", optional = true }
|
||||
sphinx-autobuild = { version = "^2020.9.1", optional = true }
|
||||
sphinx-copybutton = { version = "^0.3.1", optional = true }
|
||||
furo = { version = "^2021.6.18-beta.36", optional = true }
|
||||
sphinx-prompt = { version = "^1.3.0", optional = true }
|
||||
sphinx-autobuild = { version = "^2021.3.14", optional = true }
|
||||
sphinx-copybutton = { version = "^0.4.0", optional = true }
|
||||
furo = { version = "^2021.9.8", optional = true }
|
||||
sphinx-prompt = { version = "^1.5.0", optional = true }
|
||||
Sphinx-Substitution-Extensions = { version = "^2020.9.30", optional = true }
|
||||
towncrier = { version = "^21.3.0", optional = true }
|
||||
pygments = { version = "^2.4", optional = true }
|
||||
pymdown-extensions = { version = "^8.0", optional = true }
|
||||
markdown-include = { version = "^0.6", optional = true }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
aiohttp-socks = "^0.5"
|
||||
aioredis = { version = "^2.0.0a1", allow-prereleases = true }
|
||||
ipython = "^7.22.0"
|
||||
uvloop = { version = "^0.15.2", markers = "sys_platform == 'darwin' or sys_platform == 'linux'" }
|
||||
black = "^21.4b2"
|
||||
isort = "^5.8.0"
|
||||
flake8 = "^3.9.1"
|
||||
flake8-html = "^0.4.1"
|
||||
mypy = "^0.812"
|
||||
mypy = "^0.910"
|
||||
pytest = "^6.2.3"
|
||||
pytest-html = "^3.1.1"
|
||||
pytest-asyncio = "^0.15.1"
|
||||
|
|
@ -68,27 +75,17 @@ pytest-cov = "^2.11.1"
|
|||
aresponses = "^2.1.4"
|
||||
asynctest = "^0.13.0"
|
||||
toml = "^0.10.2"
|
||||
pygments = "^2.4"
|
||||
pymdown-extensions = "^8.0"
|
||||
markdown-include = "^0.6"
|
||||
|
||||
pre-commit = "^2.15.0"
|
||||
packaging = "^20.3"
|
||||
typing-extensions = "^3.7.4"
|
||||
sphinx = "^3.1.0"
|
||||
sphinx-intl = "^2.0.1"
|
||||
sphinx-autobuild = "^2020.9.1"
|
||||
sphinx-copybutton = "^0.3.1"
|
||||
furo = "^2021.6.18-beta.36"
|
||||
sphinx-prompt = "^1.3.0"
|
||||
Sphinx-Substitution-Extensions = "^2020.9.30"
|
||||
towncrier = "^21.3.0"
|
||||
diagrams = "^0.20.0"
|
||||
|
||||
|
||||
[tool.poetry.extras]
|
||||
fast = ["uvloop"]
|
||||
redis = ["aioredis"]
|
||||
proxy = ["aiohttp-socks"]
|
||||
i18n = ["Babel"]
|
||||
docs = [
|
||||
"sphinx",
|
||||
"sphinx-intl",
|
||||
|
|
@ -98,6 +95,10 @@ docs = [
|
|||
"black",
|
||||
"sphinx-prompt",
|
||||
"Sphinx-Substitution-Extensions",
|
||||
"towncrier",
|
||||
"pygments",
|
||||
"pymdown-extensions",
|
||||
"markdown-include",
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue