├── tests ├── __init__.py ├── core │ ├── __init__.py │ └── test_states.py ├── forms │ ├── __init__.py │ ├── fields │ │ ├── __init__.py │ │ └── test_base.py │ ├── test_base.py │ ├── test_manager.py │ ├── test_validators.py │ └── test_fields.py ├── conftest.py ├── test_utils.py ├── test_middleware.py ├── test_dispatcher.py ├── test_filters.py └── test_flow.py ├── aiogram_forms ├── core │ ├── __init__.py │ ├── manager.py │ ├── entities.py │ └── states.py ├── const.py ├── types.py ├── forms │ ├── __init__.py │ ├── validators.py │ ├── base.py │ ├── fields.py │ └── manager.py ├── __init__.py ├── errors.py ├── utils.py ├── enums.py ├── middleware.py ├── filters.py └── dispatcher.py ├── docs ├── en │ ├── 2.usage │ │ ├── _dir.yml │ │ ├── forms.md │ │ ├── dispatcher.md │ │ ├── manager.md │ │ └── fields.md │ ├── 1.installation.md │ ├── 3.changelog.md │ ├── 0.index.md │ └── 4.roadmap.md └── ru │ ├── 3.usage │ ├── _dir.yml │ ├── 1.forms.md │ ├── 0.dispatcher.md │ ├── 3.manager.md │ └── 2.fields.md │ ├── 2.installation.md │ ├── 4.changelog.md │ ├── 1.index.md │ └── 5.roadmap.md ├── CONTRIBUTING.md ├── .github └── workflows │ └── ci.yaml ├── LICENSE ├── pyproject.toml ├── CHANGELOG.md ├── README.md ├── .gitignore ├── CODE_OF_CONDUCT.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aiogram_forms/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/forms/fields/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/en/2.usage/_dir.yml: -------------------------------------------------------------------------------- 1 | title: 'Usage' 2 | -------------------------------------------------------------------------------- /docs/ru/3.usage/_dir.yml: -------------------------------------------------------------------------------- 1 | title: 'Использование' 2 | -------------------------------------------------------------------------------- /aiogram_forms/const.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package constants. 3 | """ 4 | 5 | STATES_GROUP_CLASS_SUFFIX = 'StatesGroup' 6 | 7 | FORMS_MANAGER_DI_KEY = 'forms' 8 | -------------------------------------------------------------------------------- /aiogram_forms/types.py: -------------------------------------------------------------------------------- 1 | """ 2 | General types. 3 | """ 4 | from typing import Union 5 | from aiogram.utils.i18n.lazy_proxy import LazyProxy # type: ignore[attr-defined] 6 | 7 | TranslatableString = Union[str, LazyProxy] 8 | -------------------------------------------------------------------------------- /aiogram_forms/forms/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Forms module. 3 | """ 4 | from .base import Form 5 | from .manager import FormsManager 6 | 7 | from . import fields, validators 8 | 9 | __all__ = [ 10 | 'Form', 'FormsManager', 'fields', 'validators' 11 | ] 12 | -------------------------------------------------------------------------------- /docs/en/1.installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | ordering: 0 4 | --- 5 | 6 | Library available on [PyPI](https://pypi.org/project/aiogram-forms/). You can install it via pip 7 | or other dependency managers: 8 | 9 | ```bash 10 | pip install aiogram-forms 11 | ``` 12 | -------------------------------------------------------------------------------- /aiogram_forms/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | aiogram-forms is an addition for aiogram 3 | which allows you to create different forms 4 | and process user input step by step easily. 5 | """ 6 | from .forms import * 7 | from .dispatcher import EntityDispatcher 8 | 9 | dispatcher = EntityDispatcher() 10 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def message(): 8 | return AsyncMock(text='Test message.') 9 | 10 | 11 | @pytest.fixture 12 | def contact_message(): 13 | return AsyncMock(text='Test message.', content_type='contact') 14 | -------------------------------------------------------------------------------- /docs/en/3.changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | --- 4 | 5 | All notable changes to this project will be documented in [CHANGELOG](https://github.com/13g10n/aiogram-forms/blob/master/CHANGELOG.md) file. 6 | 7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | -------------------------------------------------------------------------------- /docs/ru/2.installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Установка 3 | icon: iog-insert 4 | --- 5 | 6 | Библиотека доступна на [PyPI](https://pypi.org/project/aiogram-forms/). Вы можете установить её с помощью pip 7 | или другого менеджера зависимостей: 8 | 9 | ```bash 10 | pip install aiogram-forms 11 | 12 | # or 13 | 14 | pip install aiogram-forms==3.0.0 # for aiogram < 3 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/ru/4.changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Список изменений 3 | --- 4 | 5 | Все изменения в проекте будут документированы в [CHANGELOG](https://github.com/13g10n/aiogram-forms/blob/master/CHANGELOG.md) файле. 6 | 7 | Формат основан на [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 8 | и этот проект следует [семантическому версионированию](https://semver.org/spec/v2.0.0.html). 9 | -------------------------------------------------------------------------------- /aiogram_forms/errors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Forms and fields custom errors. 3 | """ 4 | 5 | 6 | class ValidationError(ValueError): 7 | """ 8 | Field validation error. 9 | """ 10 | message: str 11 | code: str 12 | 13 | def __init__(self, message: str, code: str) -> None: 14 | super().__init__() 15 | self.message = message 16 | self.code = code 17 | -------------------------------------------------------------------------------- /docs/en/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | --- 4 | 5 | **_aiogram-forms_** is an addition for **aiogram** which allows you to create different forms 6 | and process user input step by step easily. 7 | 8 | ::warning 9 | aiogram-forms uses aiogram 3, for earlier releases of aiogram please 10 | use [0.3.0](https://pypi.org/project/aiogram-forms/0.3.0/) version (not maintained). 11 | :: 12 | -------------------------------------------------------------------------------- /aiogram_forms/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities and helpers. 3 | """ 4 | from typing import Any, Type, Tuple 5 | 6 | 7 | def get_attrs_of_type(obj: Any, type_: Type) -> Tuple: # type: ignore[type-arg] 8 | """Get object attrs of given type.""" 9 | return tuple( 10 | (key, value) 11 | for key, value 12 | in vars(obj).items() 13 | if isinstance(value, type_) and not key.startswith('__') 14 | ) 15 | -------------------------------------------------------------------------------- /docs/ru/1.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Введение 3 | --- 4 | 5 | **_aiogram-forms_** это дополнение для **aiogram** которое позволяет создавать различные формы и легко 6 | обрабатывать пользовательский ввод шаг за шагом. 7 | 8 | ::warning 9 | aiogram-forms использует aiogram 3. Для более ранних релизов aiogram, пожалуйста, 10 | используйте [0.3.0](https://pypi.org/project/aiogram-forms/0.3.0/) версию (не поддерживается). 11 | :: 12 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from aiogram_forms.utils import get_attrs_of_type 4 | 5 | 6 | @pytest.fixture 7 | def example_class(): 8 | class Example: 9 | foo = 42 10 | bar = 'value' 11 | another = 'too' 12 | exp = object() 13 | return Example 14 | 15 | 16 | def test_get_attrs_of_type(example_class): 17 | assert get_attrs_of_type(example_class, str) == (('bar', 'value'), ('another', 'too')) 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `aiogram-forms` 2 | 3 | **Table Of Content** 4 | + [Code of Conduct](#code-of-conduct) 5 | + [History](#history) 6 | 7 | ## Code of Conduct 8 | This project and everyone participating in it is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [i.13g10n@icloud.com](mailto:i.13g10n@icloud.com). 9 | 10 | ## History 11 | All notable changes to this project will be documented in [CHANGELOG](CHANGELOG.md) file. 12 | -------------------------------------------------------------------------------- /docs/en/4.roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Roadmap 3 | --- 4 | 5 | This section reflects generic plans for future releases. However, this can be changed any time. 6 | 7 | If you need something special or urgent please refer to [repo issues](https://github.com/13g10n/aiogram-forms/issues). 8 | 9 | ### 1.1.x 10 | Extend **tests** and **docs**, increase **coverage** and add **more generic** forms, fields and validators. 11 | 12 | ### 1.2.x 13 | Add new submodule - **menus**. Link menu buttons to other menus, forms of even plain callable handlers. 14 | -------------------------------------------------------------------------------- /tests/test_middleware.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, AsyncMock 2 | 3 | import pytest 4 | 5 | from aiogram_forms.middleware import EntityMiddleware 6 | 7 | 8 | @pytest.fixture 9 | def dispatcher(): 10 | return Mock() 11 | 12 | 13 | @pytest.fixture 14 | def middleware(dispatcher): 15 | return EntityMiddleware(dispatcher) 16 | 17 | 18 | @pytest.mark.asyncio 19 | async def test_middleware_call(message, middleware): 20 | data = { 21 | 'state': Mock() 22 | } 23 | handler = AsyncMock(return_value=42) 24 | assert await middleware(handler, message, data) == 42 25 | -------------------------------------------------------------------------------- /docs/ru/5.roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Дорожная карта 3 | --- 4 | 5 | Эта секция отражает основные планы на следующие релизы. Однако они могут быть изменены в любое время. 6 | 7 | Если вам нужно что-то конкретное или срочное, пожалуйста, используйте [issues репозитория](https://github.com/13g10n/aiogram-forms/issues). 8 | 9 | ### 1.1.x 10 | Расширить **тесты** и **документацию**, увеличить **покрытие** и добавить **больше дженериков**: форм, полей и валидаторов. 11 | 12 | ### 1.2.x 13 | Добавить новый сабмодуль - **меню**. Связывать кнопки меня с другими меню, формами или даже простыми функциями-обработчиками. 14 | -------------------------------------------------------------------------------- /docs/en/2.usage/forms.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Forms 3 | ordering: 1 4 | --- 5 | 6 | **Forms** alongside with fields are the main building blocks in aiogram-forms. 7 | 8 | You will create forms to collect user data automatically step by step. Optionally, you can 9 | overwrite _Form.callback_ method to control behaviour after form was successfully submitted: 10 | 11 | ```python {4, 8-9} 12 | from aiogram_forms.forms import Form 13 | 14 | @dispatcher.register('example') 15 | class ExampleForm(Form): 16 | ... 17 | 18 | @classmethod 19 | async def callback(cls, message: types.Message, **data) -> None: 20 | await message.answer(text='Thank you!') 21 | ``` 22 | -------------------------------------------------------------------------------- /aiogram_forms/enums.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """ 3 | Enums. 4 | """ 5 | import enum 6 | 7 | 8 | class RouterHandlerType(enum.Enum): 9 | """Aiogram router handler types.""" 10 | Update = 'update' 11 | Message = 'message' 12 | EditedMessage = 'edited_message' 13 | ChannelPost = 'channel_post' 14 | EditedChannelPost = 'edited_channel_post' 15 | InlineQuery = 'inline_query' 16 | ChosenInlineResult = 'chosen_inline_result' 17 | CallbackQuery = 'callback_query' 18 | ShippingQuery = 'shipping_query' 19 | PreCheckoutQuery = 'pre_checkout_query' 20 | Poll = 'poll' 21 | PollAnswer = 'poll_answer' 22 | Errors = 'errors' 23 | -------------------------------------------------------------------------------- /docs/en/2.usage/dispatcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dispatcher 3 | ordering: 0 4 | --- 5 | 6 | **Dispatcher** is the core controller of all entities processing. 7 | 8 | Basically, you will register your forms via _dispatcher.register_ method, passing unique form name: 9 | 10 | ```python {3} 11 | from aiogram_forms import dispatcher 12 | 13 | @dispatcher.register('example') 14 | class ExampleForm(Form): 15 | ... 16 | ``` 17 | 18 | After all attach aiogram's dispatcher to this object by calling _dispatcher.attach_ method in your setup: 19 | 20 | ```python {4} 21 | bot = Bot(...) 22 | dp = Dispatcher(...) 23 | 24 | dispatcher.attach(dp) 25 | 26 | await dp.start_polling(bot) 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/ru/3.usage/1.forms.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Формы 3 | --- 4 | 5 | **Формы** вместе с полями это главные строительные блоки в aiogram-forms. 6 | 7 | Вы будете создавать формы, чтобы принимать данные от пользователей шаг за шагом. Опционально, вы можете 8 | переопределить _Form.callback_ метод, чтобы контролировать поведение после того как форма была успешно отправлена: 9 | 10 | ```python {4, 8-9} 11 | from aiogram_forms.forms import Form 12 | 13 | @dispatcher.register('example') 14 | class ExampleForm(Form): 15 | ... 16 | 17 | @classmethod 18 | async def callback(cls, message: types.Message, **data) -> None: 19 | await message.answer(text='Thank you!') 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/ru/3.usage/0.dispatcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Диспатчер 3 | --- 4 | 5 | **Диспатчер** это главный контроллер-обработчик для всех видов сущностей. 6 | 7 | В основном вы будете регистрировать ваши формы через _dispatcher.register_, передавая уникальный идентификатор: 8 | 9 | ```python {3} 10 | from aiogram_forms import dispatcher 11 | 12 | @dispatcher.register('example') 13 | class ExampleForm(Form): 14 | ... 15 | ``` 16 | 17 | После этого, прикрепите диспатчер aiogram к нашему диспатчеру, вызвав _dispatcher.attach_ метод при создании бота: 18 | 19 | ```python {4} 20 | bot = Bot(...) 21 | dp = Dispatcher(...) 22 | 23 | dispatcher.attach(dp) 24 | 25 | await dp.start_polling(bot) 26 | ``` 27 | -------------------------------------------------------------------------------- /aiogram_forms/core/manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Code entity manager. 3 | """ 4 | import abc 5 | from typing import TYPE_CHECKING, Dict, Any 6 | 7 | from aiogram import types 8 | 9 | if TYPE_CHECKING: 10 | from .. import EntityDispatcher # type: ignore[attr-defined] 11 | 12 | 13 | class EntityManager(abc.ABC): # pylint: disable=too-few-public-methods 14 | """Entity manager.""" 15 | 16 | def __init__( 17 | self, 18 | dispatcher: 'EntityDispatcher', 19 | event: types.Message, 20 | data: Dict[str, Any] 21 | ) -> None: 22 | self._dispatcher = dispatcher 23 | self.event = event 24 | self.data = data 25 | 26 | @abc.abstractmethod 27 | async def show(self, name: str) -> None: 28 | """Start entity propagation.""" 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: pull_request 3 | 4 | jobs: 5 | lint: 6 | name: Lint & Test 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: [ "3.8", "3.9", "3.10" ] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-python@v2 14 | with: 15 | python-version: ${{ matrix.python-version }} 16 | - name: Setup poetry 17 | run: pip install poetry==1.2.2 18 | - name: Install dependencies 19 | run: poetry install 20 | - name: Lint project files with pylint 21 | run: poetry run pylint aiogram_forms 22 | - name: Type check with mypy 23 | run: poetry run mypy -p aiogram_forms 24 | - name: Run tests with pytest 25 | run: poetry run pytest tests 26 | -------------------------------------------------------------------------------- /tests/forms/test_base.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | 3 | import pytest 4 | from aiogram.filters import Filter 5 | 6 | from aiogram_forms.enums import RouterHandlerType 7 | from aiogram_forms.forms.base import Field, Form 8 | 9 | 10 | @pytest.fixture 11 | def form_class(): 12 | class TestForm(Form): 13 | first = Field('First') 14 | return TestForm 15 | 16 | 17 | def test_form_creation(form_class): 18 | assert issubclass(form_class, Form) 19 | 20 | 21 | def test_form_default_filter(form_class): 22 | filters = form_class.filters() 23 | assert isinstance(filters, dict) 24 | assert RouterHandlerType.Message in filters 25 | assert all(isinstance(filters[filter_type], Filter) for filter_type in filters) 26 | 27 | 28 | @pytest.mark.asyncio 29 | async def test_form_callback_callable(form_class): 30 | await form_class.callback(Mock()) 31 | -------------------------------------------------------------------------------- /docs/en/2.usage/manager.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Manager 3 | ordering: 3 4 | --- 5 | 6 | **FormsManager** is used to start form processing and obtain form data. Instance with request data 7 | automatically injected in event context, so you can show any form by name from any place: 8 | 9 | ```python {1, 4-5} 10 | from aiogram_forms.forms import FormsManager 11 | 12 | @router.message(Command(commands=['start'])) 13 | async def command_start(message: Message, forms: FormsManager) -> None: 14 | await forms.show('example') 15 | ``` 16 | 17 | To fetch form data from store, you can use 18 | ```python {3, 8} 19 | @dispatcher.register('test-form') 20 | class ExampleForm(Form): 21 | name = fields.TextField('Name') 22 | ... 23 | 24 | @classmethod 25 | async def callback(cls, message: types.Message, forms: FormsManager, **data) -> None: 26 | data = await forms.get_data(ExampleForm) 27 | await message.answer(text=f'Thank you, {data["name"]}!') 28 | ``` 29 | -------------------------------------------------------------------------------- /aiogram_forms/core/entities.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core entity types. 3 | """ 4 | import abc 5 | from typing import TYPE_CHECKING, Type, Mapping, Tuple, Any, Dict 6 | 7 | if TYPE_CHECKING: 8 | from aiogram.filters import Filter 9 | 10 | from .states import EntityContainerStatesGroup, EntityState 11 | from ..enums import RouterHandlerType 12 | from ..types import TranslatableString 13 | 14 | 15 | class Entity: # pylint: disable=too-few-public-methods 16 | """Base class for containing item.""" 17 | state: 'EntityState' 18 | label: 'TranslatableString' 19 | 20 | 21 | class EntityContainer(abc.ABC): # pylint: disable=too-few-public-methods 22 | """Base class for Entity container implementation.""" 23 | state: Type['EntityContainerStatesGroup'] = None # type: ignore[assignment] 24 | 25 | @classmethod 26 | @abc.abstractmethod 27 | def filters(cls, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> Mapping['RouterHandlerType', 'Filter']: 28 | """Event filters.""" 29 | -------------------------------------------------------------------------------- /aiogram_forms/middleware.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dependency injector. 3 | """ 4 | from typing import TYPE_CHECKING, Callable, Dict, Any, Awaitable 5 | 6 | from aiogram import BaseMiddleware, types 7 | 8 | from .const import FORMS_MANAGER_DI_KEY 9 | from .forms.manager import FormsManager 10 | 11 | if TYPE_CHECKING: 12 | from aiogram_forms.dispatcher import EntityDispatcher 13 | 14 | 15 | class EntityMiddleware(BaseMiddleware): # pylint: disable=too-few-public-methods 16 | """Entity middleware.""" 17 | dispatcher: 'EntityDispatcher' 18 | 19 | def __init__(self, dispatcher: 'EntityDispatcher') -> None: 20 | self.dispatcher = dispatcher 21 | 22 | async def __call__( 23 | self, 24 | handler: Callable[[types.TelegramObject, Dict[str, Any]], Awaitable[Any]], 25 | event: types.TelegramObject, 26 | data: Dict[str, Any] 27 | ) -> Any: 28 | data[FORMS_MANAGER_DI_KEY] = FormsManager(self.dispatcher, event, data) 29 | return await handler(event, data) 30 | -------------------------------------------------------------------------------- /docs/ru/3.usage/3.manager.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Менеджер 3 | --- 4 | 5 | **Менеджер** используется чтобы начать обработку формы и получить данные после её окончания. Объект с данными запроса автоматически 6 | вставляется в контекст запроса, так что вы можете показать любую форму по её идентификатору из любого места: 7 | 8 | ```python {1, 4-5} 9 | from aiogram_forms.forms import FormsManager 10 | 11 | @router.message(Command(commands=['start'])) 12 | async def command_start(message: Message, forms: FormsManager) -> None: 13 | await forms.show('example') 14 | ``` 15 | 16 | Чтобы получить данные формы вы можете использовать _FormsManager.get_data_: 17 | ```python {3, 8} 18 | @dispatcher.register('test-form') 19 | class ExampleForm(Form): 20 | name = fields.TextField('Name') 21 | ... 22 | 23 | @classmethod 24 | async def callback(cls, message: types.Message, forms: FormsManager, **data) -> None: 25 | data = await forms.get_data(ExampleForm) 26 | await message.answer(text=f'Thank you, {data["name"]}!') 27 | ``` 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [Ivan Borisenko] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /aiogram_forms/filters.py: -------------------------------------------------------------------------------- 1 | """ 2 | Aiogram events filters. 3 | """ 4 | from typing import TYPE_CHECKING, Type, Dict, Any, List 5 | 6 | from aiogram import types 7 | from aiogram.filters import Filter 8 | from aiogram.fsm.context import FSMContext 9 | 10 | if TYPE_CHECKING: 11 | from aiogram_forms.core.states import EntityContainerStatesGroup 12 | 13 | 14 | class EntityStatesFilter(Filter): 15 | """Filter by entity states.""" 16 | def __init__(self, state: Type['EntityContainerStatesGroup']) -> None: 17 | self._state = state 18 | 19 | async def __call__(self, message: types.Message, state: FSMContext) -> bool: 20 | current_state = await state.get_state() 21 | return current_state in [x.state for x in self._state] 22 | 23 | 24 | class EntityCallbackFilter(Filter): 25 | """Filter by callback data.""" 26 | def __init__(self, state: Type['EntityContainerStatesGroup']) -> None: 27 | self._state = state 28 | 29 | async def __call__(self, *args: List[Any], **kwargs: Dict[str, Any]) -> bool: 30 | callback_query_data = kwargs['event_update'].callback_query.data # type: ignore[attr-defined] 31 | return callback_query_data in [s.state for s in self._state.get_states()] 32 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "aiogram-forms" 3 | version = "1.1.1" 4 | description = "Forms for aiogram" 5 | authors = ["Ivan Borisenko "] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://13g10n.com/docs/aiogram-forms" 9 | repository = "https://github.com/13g10n/aiogram-forms" 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Environment :: Plugins", 13 | "License :: OSI Approved :: MIT License", 14 | "Programming Language :: Python :: 3.8", 15 | "Programming Language :: Python :: 3.9", 16 | "Programming Language :: Python :: 3.10" 17 | ] 18 | keywords = ["aiogram", "telegram", "forms"] 19 | include = [ 20 | "LICENSE", 21 | ] 22 | 23 | [tool.poetry.dependencies] 24 | python = "^3.8" 25 | aiogram = "^3.0.0b5" 26 | 27 | [tool.poetry.group.dev.dependencies] 28 | coverage = "^7.0.1" 29 | pylint = "^2.15.9" 30 | pytest = "^7.2.0" 31 | pytest-asyncio = "^0.20.3" 32 | mypy = "^1.0.0" 33 | babel = "^2.11.0" 34 | 35 | [build-system] 36 | requires = ["poetry-core>=1.0.0"] 37 | build-backend = "poetry.core.masonry.api" 38 | 39 | [tool.pylint.'MESSAGES CONTROL'] 40 | max-line-length = 120 41 | 42 | [tool.coverage.run] 43 | omit = [ 44 | "tests/*" 45 | ] 46 | 47 | [tool.coverage.report] 48 | exclude_lines = [ 49 | "pragma: no cover", 50 | "if TYPE_CHECKING:" 51 | ] 52 | 53 | [tool.mypy] 54 | strict = true 55 | -------------------------------------------------------------------------------- /tests/forms/fields/test_base.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock 2 | 3 | import pytest 4 | 5 | from aiogram_forms.forms.fields import Field 6 | 7 | TEST_LABEL = 'Test Label' 8 | 9 | 10 | @pytest.fixture 11 | def field(): 12 | return Field(TEST_LABEL) 13 | 14 | 15 | def test_label_set(field) -> None: 16 | assert field.label == TEST_LABEL 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_extract_returns_event_text(field, message) -> None: 21 | assert await field.extract(message) == message.text 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_process_returns_untouched_value(field, message) -> None: 26 | assert await field.process(message.text) == message.text 27 | 28 | 29 | @pytest.mark.asyncio 30 | async def test_validate_calls_single_validator(message) -> None: 31 | validator_mock = AsyncMock() 32 | field = Field(TEST_LABEL, validators=[validator_mock]) 33 | assert await field.validate(message.text) is None 34 | validator_mock.assert_called_once_with(message.text) 35 | 36 | 37 | @pytest.mark.asyncio 38 | async def test_validate_calls_multiple_validators(message) -> None: 39 | validator_mocks = [AsyncMock() for _ in range(5)] 40 | field = Field(TEST_LABEL, validators=validator_mocks) 41 | assert await field.validate(message.text) is None 42 | for mock in validator_mocks: 43 | mock.assert_called_once_with(message.text) 44 | -------------------------------------------------------------------------------- /tests/test_dispatcher.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, AsyncMock 2 | 3 | import pytest 4 | 5 | from aiogram_forms import EntityDispatcher 6 | from aiogram_forms.core.entities import EntityContainer, Entity 7 | 8 | 9 | @pytest.fixture 10 | def entity_container(): 11 | class TestEntityContainer(EntityContainer): 12 | first = Entity() 13 | return TestEntityContainer 14 | 15 | 16 | @pytest.fixture 17 | def entity_dispatcher(): 18 | return EntityDispatcher() 19 | 20 | 21 | @pytest.mark.asyncio 22 | async def test_dispatcher_attach_middleware(entity_dispatcher): 23 | dp = Mock() 24 | 25 | entity_dispatcher.attach(dp) 26 | 27 | dp.message.middleware.assert_called_once() 28 | 29 | 30 | @pytest.mark.asyncio 31 | async def test_dispatcher_attach_router(entity_dispatcher): 32 | dp = Mock() 33 | 34 | entity_dispatcher.attach(dp) 35 | 36 | dp.include_router.assert_called_once_with(entity_dispatcher._router) 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_dispatcher_get_entity_container_missing(entity_dispatcher, entity_container): 41 | with pytest.raises(ValueError): 42 | entity_dispatcher.get_entity_container(entity_container, 'not existing') 43 | 44 | 45 | @pytest.mark.asyncio 46 | async def test_dispatcher__get_entity_container_handler_invalid_type(entity_dispatcher, entity_container): 47 | handler = entity_dispatcher._get_entity_container_handler(entity_container) 48 | with pytest.raises(RuntimeError): 49 | await handler(AsyncMock()) 50 | -------------------------------------------------------------------------------- /aiogram_forms/core/states.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core entity states. 3 | """ 4 | from typing import Type, cast, Tuple 5 | 6 | from aiogram.fsm.state import StatesGroup, State 7 | 8 | from .entities import EntityContainer, Entity 9 | from ..const import STATES_GROUP_CLASS_SUFFIX 10 | from .. import utils 11 | 12 | 13 | class EntityState(State): 14 | """Entity state.""" 15 | entity: 'Entity' 16 | 17 | def __init__(self, entity: Entity) -> None: 18 | super().__init__() 19 | self.entity = entity 20 | 21 | 22 | class EntityContainerStatesGroup(StatesGroup): 23 | """Entity container states group.""" 24 | container: Type['EntityContainer'] 25 | 26 | @classmethod 27 | def get_states(cls) -> Tuple[EntityState]: 28 | """Get all entity container states.""" 29 | return cast( 30 | Tuple[EntityState], 31 | cls.__states__ # pylint: disable=no-member 32 | ) 33 | 34 | @classmethod 35 | def bind(cls, container: Type['EntityContainer']) -> Type['EntityContainerStatesGroup']: 36 | """Create and bind entity container states group.""" 37 | form_fields = utils.get_attrs_of_type(container, Entity) 38 | 39 | state_class = cast( 40 | Type[EntityContainerStatesGroup], 41 | type( 42 | f'{container.__name__}{STATES_GROUP_CLASS_SUFFIX}', 43 | (EntityContainerStatesGroup,), 44 | { 45 | key: EntityState(value) 46 | for key, value in form_fields 47 | } 48 | ) 49 | ) 50 | 51 | for key, value in form_fields: 52 | value.state = getattr(state_class, key) 53 | 54 | container.state = state_class 55 | state_class.container = container 56 | 57 | return state_class 58 | -------------------------------------------------------------------------------- /tests/core/test_states.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, patch 2 | 3 | import pytest 4 | 5 | from aiogram_forms.core.states import EntityState, EntityContainerStatesGroup 6 | 7 | 8 | @pytest.fixture 9 | def container(): 10 | class TestContainer: 11 | first = Mock() 12 | second = Mock() 13 | third = Mock() 14 | 15 | @classmethod 16 | def filters(cls, *args, **kwargs): 17 | return {} 18 | return TestContainer 19 | 20 | 21 | @pytest.fixture 22 | def container_states_group(container): 23 | with patch( 24 | 'aiogram_forms.utils.get_attrs_of_type', 25 | return_value=( 26 | ('first', container.first), 27 | ('second', container.second), 28 | ('third', container.third) 29 | ), 30 | autospec=True 31 | ): 32 | yield EntityContainerStatesGroup.bind(container) 33 | 34 | 35 | def test_container_states_group_created(container_states_group): 36 | assert issubclass(container_states_group, EntityContainerStatesGroup) 37 | 38 | 39 | def test_container_states_group_get_states(container_states_group): 40 | assert container_states_group.get_states() == ( 41 | container_states_group.first, container_states_group.second, container_states_group.third 42 | ) 43 | 44 | 45 | def test_container_states_group_container_assigned(container_states_group, container): 46 | assert container_states_group.container == container 47 | 48 | 49 | def test_container_states_group_container_state_assigned(container_states_group, container): 50 | assert container.state == container_states_group 51 | 52 | 53 | def test_container_states_group_field_states_assigned(container_states_group, container): 54 | for key in ['first', 'second', 'third']: 55 | assert hasattr(container, key) 56 | assert hasattr(getattr(container, key), 'state') 57 | assert isinstance(getattr(container, key).state, EntityState) 58 | -------------------------------------------------------------------------------- /tests/test_filters.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock, AsyncMock 2 | 3 | import pytest 4 | 5 | from aiogram.fsm.state import State 6 | 7 | from aiogram_forms.core.states import EntityContainerStatesGroup 8 | from aiogram_forms.filters import EntityStatesFilter, EntityCallbackFilter 9 | 10 | 11 | @pytest.fixture 12 | def state_group(): 13 | class ExampleStatesGroup(EntityContainerStatesGroup): 14 | first = State() 15 | second = State() 16 | third = State() 17 | return ExampleStatesGroup 18 | 19 | 20 | @pytest.fixture 21 | def entity_states_filter(state_group): 22 | return EntityStatesFilter(state_group) 23 | 24 | 25 | @pytest.fixture 26 | def entity_callback_filter(state_group): 27 | return EntityCallbackFilter(state_group) 28 | 29 | 30 | @pytest.mark.asyncio 31 | async def test_entity_states_filter_valid_state(entity_states_filter, state_group): 32 | fsm_context = Mock() 33 | fsm_context.get_state = AsyncMock(return_value=state_group.first) 34 | assert await entity_states_filter(None, fsm_context) # noqa 35 | fsm_context.get_state.assert_called_once() 36 | 37 | 38 | @pytest.mark.asyncio 39 | async def test_entity_states_filter_invalid_state(entity_states_filter): 40 | fsm_context = Mock() 41 | fsm_context.get_state = AsyncMock(return_value=42) 42 | assert not await entity_states_filter(None, fsm_context) # noqa 43 | 44 | 45 | @pytest.mark.asyncio 46 | async def test_callback_data_filter_valid_data(entity_callback_filter, state_group): 47 | event_update = Mock() 48 | event_update.callback_query = Mock() 49 | event_update.callback_query.data = state_group.first 50 | 51 | assert await entity_callback_filter(event_update=event_update) # noqa 52 | 53 | 54 | @pytest.mark.asyncio 55 | async def test_callback_data_filter_invalid_data(entity_callback_filter): 56 | event_update = Mock() 57 | event_update.callback_query = Mock() 58 | event_update.callback_query.data = '42' 59 | 60 | assert not await entity_callback_filter(event_update=event_update) # noqa 61 | -------------------------------------------------------------------------------- /tests/test_flow.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock, patch, Mock 2 | 3 | import pytest 4 | 5 | from aiogram_forms import dispatcher 6 | from aiogram_forms.forms import Form, FormsManager, fields 7 | 8 | 9 | class ExampleForm(Form): 10 | first = fields.TextField('First') 11 | second = fields.TextField('Second') 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_flow(): 16 | """Test whole form flow.""" 17 | 18 | with patch.object(dispatcher, '_router', Mock(message=Mock())) as router_mock: 19 | dispatcher.register('example')(ExampleForm) 20 | 21 | handler = router_mock.message.return_value.call_args.args[0] 22 | fsm = AsyncMock() 23 | 24 | message = AsyncMock(text='Start form.') 25 | manager = FormsManager(dispatcher, message, data=dict(state=fsm)) 26 | await manager.show('example') 27 | 28 | fsm.set_state.assert_called_once_with(ExampleForm.first.state.state) 29 | message.answer.assert_called_once_with('First', reply_markup=ExampleForm.first.reply_keyboard) 30 | 31 | fsm.get_state = AsyncMock(return_value=ExampleForm.first.state.state) 32 | fsm.get_data = AsyncMock(return_value={}) 33 | message = AsyncMock(text='First value') 34 | await handler(message, state=fsm) 35 | 36 | fsm.set_state.assert_called_with(ExampleForm.second.state.state) 37 | fsm.update_data.assert_called_once_with({'ExampleForm': {'first': 'First value'}}) 38 | message.answer.assert_called_once_with('Second', reply_markup=ExampleForm.first.reply_keyboard) 39 | 40 | fsm = AsyncMock() 41 | fsm.get_state = AsyncMock(return_value=ExampleForm.second.state.state) 42 | fsm.get_data = AsyncMock(return_value={'ExampleForm': {'first': 'First value'}}) 43 | message = AsyncMock(text='Second value') 44 | ExampleForm.callback = AsyncMock() 45 | await handler(message, state=fsm) 46 | 47 | fsm.set_state.assert_called_with(None) 48 | fsm.update_data.assert_called_once_with({'ExampleForm': {'first': 'First value', 'second': 'Second value'}}) 49 | ExampleForm.callback.assert_called_once() 50 | -------------------------------------------------------------------------------- /docs/ru/3.usage/2.fields.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Поля 3 | --- 4 | 5 | **Поля** используются чтобы описать отдельные вопросы в рамках формы. 6 | 7 | ```python 8 | from aiogram_forms.forms import Form, fields 9 | 10 | @dispatcher.register('example') 11 | class ExampleForm(Form): 12 | name = fields.TextField('Name') 13 | email = fields.EmailField('Email', help_text='We will send confirmation code.') 14 | phone = fields.PhoneNumberField('Phone number', share_contact=True) 15 | ``` 16 | 17 | ## Field types 18 | 19 | ### Field 20 | This is the base field for all fields. 21 | 22 |
23 |
label: str
24 |
Question label. Can be a string or LazyProxy from aiogram.utils.i18n package.
25 |
help_text: Optional[str]
26 |
Help text displayed under the label.
27 |
error_messages: Optional[Mapping[str, str]]
28 |
Error key to custom error message mapping. Used to overwrite default messages.
29 |
validators: List[Callable]
30 |
List of callable objects. Used to validate user's input.
31 |
32 | 33 | ### TextField 34 | TextField is used for text questions and contains additional optional params to control answer length. 35 | 36 |
37 |
min_length: Optional[int]
38 |
Used to validate min characters in user's answer. If given MinLengthValidator will be added.
39 |
max_length: Optional[int]
40 |
Used to validate max characters in user's answer. MaxLengthValidator will be added.
41 |
42 | 43 | ### EmailField 44 | Special field to ask and validate email. Adds _EmailValidator_. 45 | 46 | ### PhoneNumberField 47 | Special field to ask and validate phone number. Adds _PhoneNumberValidator_. 48 | 49 |
50 |
share_contact: bool = False
51 |
If applied, will activate special keyboard, so user can send his contact with single click.
52 |
53 | -------------------------------------------------------------------------------- /docs/en/2.usage/fields.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fields 3 | ordering: 2 4 | --- 5 | 6 | **Fields** are used to describe separate questions inside form. 7 | 8 | ```python 9 | from aiogram_forms.forms import Form, fields 10 | 11 | @dispatcher.register('example') 12 | class ExampleForm(Form): 13 | name = fields.TextField('Name') 14 | email = fields.EmailField('Email', help_text='We will send confirmation code.') 15 | phone = fields.PhoneNumberField('Phone number', share_contact=True) 16 | ``` 17 | 18 | ## Field types 19 | 20 | ### Field 21 | This is the base field for all fields. 22 | 23 |
24 |
label: str
25 |
Question label. Can be a string or LazyProxy from aiogram.utils.i18n package.
26 |
help_text: Optional[str]
27 |
Help text displayed under the label.
28 |
error_messages: Optional[Mapping[str, str]]
29 |
Error key to custom error message mapping. Used to overwrite default messages.
30 |
validators: List[Callable]
31 |
List of callable objects. Used to validate user's input.
32 |
33 | 34 | ### TextField 35 | TextField is used for text questions and contains additional optional params to control answer length. 36 | 37 |
38 |
min_length: Optional[int]
39 |
Used to validate min characters in user's answer. If given MinLengthValidator will be added.
40 |
max_length: Optional[int]
41 |
Used to validate max characters in user's answer. MaxLengthValidator will be added.
42 |
43 | 44 | ### EmailField 45 | Special field to ask and validate email. Adds _EmailValidator_. 46 | 47 | ### PhoneNumberField 48 | Special field to ask and validate phone number. Adds _PhoneNumberValidator_. 49 | 50 |
51 |
share_contact: bool = False
52 |
If applied, will activate special keyboard, so user can send his contact with single click.
53 |
54 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.1.1] - 2023-04-23 8 | 9 | ### Fixed 10 | - Fixed multiple forms issue 11 | - Fixed mypy types to pass checks 12 | 13 | ## [1.1.0] - 2023-04-02 14 | 15 | ### Added 16 | - New `ChoiceField` field with `ChoiceValidator` validator 17 | 18 | ### Changed 19 | - `FormsManager.get_data` can now accept form ID as param. Form class param marked as deprecated and will be removed in next releases. 20 | - Completed 100% tests coverage and full types coverage (mypy) 21 | 22 | ### Fixed 23 | - Small fixes and updates for `README` example 24 | 25 | ## [1.0.1] - 2023-01-01 26 | 27 | ### Changed 28 | - Added link to documentation website to `README.md` 29 | - Move coverage config from `.coveragerc` to `pyproject.toml` 30 | - Removed python 3.7 from supported versions and CI 31 | - More tests added, to cover 100% of codebase 32 | 33 | ### Fixed 34 | - Fix `Development Status` PyPI classifier to be `Production/Stable` 35 | - Fixed unhandled error during `dispatcher.show` call without any form registered 36 | 37 | ## [1.0.0] - 2022-12-30 38 | ### Changed 39 | - Whole package was re-worked from scratch 40 | 41 | ## [0.3.0] - 2022-10-21 42 | ### Added 43 | - Added new `PhoneNumberField` field with ability to process contact sharing 44 | - Added utility `get_fields()` method to fetch list of form fields 45 | - Removed `__version__` from package `__init__.py` 46 | - Removed `EmailValidator` from validators. Please use `validators.RegexValidator(EMAIL_REGEXP)` instead 47 | - Updated example with more details and comments 48 | - Added some classifiers for PyPI 49 | 50 | ## [0.2.0] - 2021-08-12 51 | ### Added 52 | - New `validation_error_message` option for fields to set custom error message in case of failed validation 53 | 54 | ### Fixes 55 | - Fixed label bug for `ChoicesField` 56 | - Minor type hint fixes 57 | 58 | ## [0.1.1] - 2021-06-19 59 | ### Added 60 | - `choices` parameter in `StringField` reworked to `ChoicesField` 61 | 62 | ### Fixes 63 | - `CHANGELOG` fixed 64 | - `README` example fixed 65 | 66 | ## [0.1.0] - 2021-06-18 67 | ### Added 68 | - `StringField`, `EmailField` fields added 69 | - `ChoicesValidator`, `RegexValidator`, `EmailValidator` validators added 70 | - Basic form processing flow added 71 | -------------------------------------------------------------------------------- /aiogram_forms/forms/validators.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=too-few-public-methods 2 | """ 3 | Form fields validators. 4 | """ 5 | import re 6 | from typing import Tuple, Any 7 | 8 | from aiogram_forms.errors import ValidationError 9 | 10 | 11 | class MinLengthValidator: 12 | """Min length validator.""" 13 | 14 | def __init__(self, limit: int) -> None: 15 | self.limit = limit 16 | 17 | def __call__(self, value: str) -> None: 18 | if len(value) < self.limit: 19 | raise ValidationError(f'Value should be at least {self.limit} characters.', code='min_length') 20 | 21 | 22 | class MaxLengthValidator: 23 | """Max length validator.""" 24 | 25 | def __init__(self, limit: int) -> None: 26 | self.limit = limit 27 | 28 | def __call__(self, value: str) -> None: 29 | if len(value) > self.limit: 30 | raise ValidationError(f'Value should be at most {self.limit} characters.', code='max_length') 31 | 32 | 33 | class RegexValidator: 34 | """Regex validator.""" 35 | error: ValidationError = ValidationError('Value if in invalid format.', code='regex') 36 | 37 | def __init__(self, regex: str) -> None: 38 | self.regex = re.compile(regex) 39 | 40 | def __call__(self, value: str) -> None: 41 | match = self.regex.match(value) 42 | if not match: 43 | raise self.error 44 | 45 | 46 | class EmailValidator(RegexValidator): 47 | """Email validator. 48 | 49 | Exactly one "@" sign and at least one "." in the part after the @. 50 | """ 51 | error = ValidationError('Value should be valid email address.', code='email') 52 | 53 | def __init__(self, regex: str = r'[^@]+@[^@]+\.[^@]+') -> None: 54 | super().__init__(regex) 55 | 56 | 57 | class PhoneNumberValidator(RegexValidator): 58 | """Phone number validator. 59 | 60 | Reference: https://ihateregex.io/expr/phone/ 61 | """ 62 | error = ValidationError('Value should be a valid phone number.', code='phone') 63 | 64 | def __init__(self, regex: str = r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'): 65 | super().__init__(regex) 66 | 67 | 68 | class ChoiceValidator: 69 | """Choices validator. 70 | 71 | Validates only on given values. 72 | """ 73 | error = ValidationError('Value is not allowed.', code='invalid_choice') 74 | 75 | def __init__(self, choices: Tuple[Any, ...]): 76 | self.choices = choices 77 | 78 | def __call__(self, value: str) -> None: 79 | if value not in self.choices: 80 | raise self.error 81 | -------------------------------------------------------------------------------- /tests/forms/test_manager.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock, Mock, patch 2 | 3 | import pytest 4 | 5 | from aiogram_forms import dispatcher, FormsManager 6 | from aiogram_forms.errors import ValidationError 7 | from aiogram_forms.forms import Form, fields 8 | 9 | 10 | @pytest.fixture 11 | def event(): 12 | return AsyncMock() 13 | 14 | 15 | @pytest.fixture 16 | def validation_error(): 17 | return ValidationError(message='Failed!', code='error') 18 | 19 | 20 | @pytest.fixture 21 | def form(validation_error): 22 | def failing_validator(value: str): 23 | raise validation_error 24 | 25 | class TestForm(Form): 26 | test = fields.TextField('Test', validators=[failing_validator]) 27 | 28 | dispatcher.register('test')(TestForm) 29 | return TestForm 30 | 31 | 32 | @pytest.fixture 33 | def dp(form, event): 34 | dp = Mock() 35 | dp.get_entity_container = Mock(return_value=form) 36 | return dp 37 | 38 | 39 | @pytest.fixture 40 | def manager(form, event, dp): 41 | state = AsyncMock() 42 | state.get_state = AsyncMock(return_value=form.state.__all_states_names__[0]) 43 | 44 | return FormsManager(dp, event, data=dict(state=state)) 45 | 46 | 47 | @pytest.mark.asyncio 48 | async def test_manager_show_not_subclass_form(manager, dp): 49 | with patch.object(dp, 'get_entity_container', return_value=object) as dp_mock: 50 | with pytest.raises(ValueError): 51 | await manager.show('unregistered') 52 | dp_mock.assert_called_once_with(Form, 'unregistered') 53 | 54 | 55 | @pytest.mark.asyncio 56 | async def test_manager_validation_raises(manager, form, validation_error): 57 | await manager.handle(form) 58 | manager.event.answer.assert_called_with(validation_error.message, reply_markup=form.test.reply_keyboard) 59 | 60 | 61 | @pytest.mark.asyncio 62 | async def test_get_data_by_form(manager, form): 63 | form_data = {'foo': '42'} 64 | manager.state.get_data = AsyncMock(return_value={form.__name__: form_data}) 65 | assert await manager.get_data(form) == form_data 66 | 67 | 68 | @pytest.mark.asyncio 69 | async def test_get_data_by_form_deprecated(manager, form): 70 | manager.state.get_data = AsyncMock(return_value={form.__name__: {}}) 71 | with pytest.deprecated_call(): 72 | await manager.get_data(form) 73 | 74 | 75 | @pytest.mark.asyncio 76 | async def test_get_data_by_name(manager, form): 77 | form_data = {'foo': '42'} 78 | manager.state.get_data = AsyncMock(return_value={form.__name__: form_data}) 79 | assert await manager.get_data('test') == form_data 80 | 81 | 82 | @pytest.mark.asyncio 83 | async def test_get_corrupted_data(manager, form): 84 | form_data = 'CORRUPTED' 85 | manager.state.get_data = AsyncMock(return_value={form.__name__: form_data}) 86 | assert await manager.get_data('test') == {} 87 | -------------------------------------------------------------------------------- /aiogram_forms/dispatcher.py: -------------------------------------------------------------------------------- 1 | """ 2 | Entity dispatcher. 3 | """ 4 | from typing import Type, MutableMapping, Dict, Any, Callable, Awaitable 5 | 6 | from aiogram import Dispatcher, Router, types 7 | 8 | from .core.entities import EntityContainer 9 | from .core.states import EntityContainerStatesGroup 10 | from .forms import Form, FormsManager 11 | from .middleware import EntityMiddleware 12 | 13 | 14 | class EntityDispatcher: 15 | """Entity dispatcher.""" 16 | _registry: MutableMapping[ 17 | str, 18 | MutableMapping[str, Type['EntityContainer']] 19 | ] = {} 20 | 21 | _dp: Dispatcher 22 | _router: Router 23 | 24 | def __init__(self) -> None: 25 | self._router = Router() 26 | 27 | def attach(self, dp: Dispatcher) -> None: # pylint: disable=invalid-name 28 | """Attach aiogram dispatcher.""" 29 | self._dp = dp 30 | self._dp.message.middleware(EntityMiddleware(self)) 31 | 32 | dp.include_router(self._router) 33 | 34 | def register(self, name: str) -> Callable[[Type[EntityContainer]], Type[EntityContainer]]: 35 | """Register entity with given name.""" 36 | def wrapper(container: Type[EntityContainer]) -> Type[EntityContainer]: 37 | EntityContainerStatesGroup.bind(container) 38 | 39 | for filter_type, filter_ in container.filters().items(): 40 | getattr(self._router, str(filter_type.value))(filter_)(self._get_entity_container_handler(container)) 41 | 42 | if 'forms' not in self._registry: 43 | self._registry['forms'] = {} 44 | 45 | self._registry['forms'][name] = container 46 | return container 47 | return wrapper 48 | 49 | def get_entity_container(self, container_type: Type[EntityContainer], name: str) -> Type[EntityContainer]: 50 | """Het entity container by name and type.""" 51 | entity_container = self._registry.get('forms', {}).get(name) 52 | if entity_container: 53 | return entity_container 54 | raise ValueError(f'There are no entity container with name "{name}" of type "{container_type.__name__}"!') 55 | 56 | def _get_entity_container_handler( 57 | self, container: Type['EntityContainer'] 58 | ) -> Callable[..., Awaitable[None]]: 59 | """Get entity container event handler.""" 60 | async def message_handler(event: types.Message, **data: Dict[str, Any]) -> None: 61 | """Entity container event handler, redirect to manager.""" 62 | if issubclass(container, Form): 63 | manager = FormsManager(self, event, data) 64 | await manager.handle(container) 65 | else: 66 | raise RuntimeError(f'Container of type "{container.__class__.__name__}" is not supported!') 67 | 68 | return message_handler 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aiogram-forms 2 | ![PyPI](https://img.shields.io/pypi/v/aiogram-forms) 3 | ![GitHub](https://img.shields.io/github/license/13g10n/aiogram-forms) 4 | ![Project status](https://img.shields.io/pypi/status/aiogram-forms) 5 | ![Project code coverage](https://img.shields.io/badge/coverage-100%25-brightgreen) 6 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/aiogram-forms?label=installs) 7 | 8 | ## Introduction 9 | `aiogram-forms` is an addition for `aiogram` which allows you to create different forms and process user input step by step easily. 10 | 11 | ## Documentation 12 | Documentation can be found [here](https://13g10n.com/en/docs/aiogram-forms). 13 | 14 | ## Installation 15 | ```bash 16 | pip install aiogram-forms 17 | ``` 18 | 19 | ## Usage 20 | Create form you need by subclassing `aiogram_forms.forms.Form`. Fields can be added from `aiogram_forms.forms.fields` subpackage. 21 | ```python 22 | from aiogram_forms import dispatcher 23 | from aiogram_forms.forms import Form, fields, FormsManager 24 | from aiogram_forms.errors import ValidationError 25 | 26 | def validate_username_format(value: str): 27 | """Validate username starts with leading @.""" 28 | if not value.startswith('@'): 29 | raise ValidationError('Username should starts with "@".', code='username_prefix') 30 | 31 | @dispatcher.register('test-form') 32 | class TestForm(Form): 33 | username = fields.TextField( 34 | 'Username', min_length=4, validators=[validate_username_format], 35 | error_messages={'min_length': 'Username must contain at least 4 characters!'} 36 | ) 37 | email = fields.EmailField('Email', help_text='We will send confirmation code.') 38 | phone = fields.PhoneNumberField('Phone number', share_contact=True) 39 | language = fields.ChoiceField('Language', choices=( 40 | ('English', 'en'), 41 | ('Russian', 'ru') 42 | )) 43 | 44 | @classmethod 45 | async def callback(cls, message: types.Message, forms: FormsManager, **data) -> None: 46 | data = await forms.get_data('test-form') # Get form data from state 47 | await message.answer( 48 | text=f'Thank you, {data["username"]}!', 49 | reply_markup=types.ReplyKeyboardRemove() # Use this for reset if last field contains keyboard 50 | ) 51 | 52 | router = Router() 53 | 54 | @router.message(Command(commands=['start'])) 55 | async def command_start(message: types.Message, forms: FormsManager) -> None: 56 | await forms.show('test-form') # Start form processing 57 | 58 | async def main(): 59 | dp = Dispatcher() 60 | dp.include_router(router) 61 | 62 | dispatcher.attach(dp) # Attach aiogram to forms dispatcher 63 | 64 | bot = Bot(...) 65 | await dp.start_polling(bot) 66 | ``` 67 | 68 | ## History 69 | All notable changes to this project will be documented in [CHANGELOG](CHANGELOG.md) file. 70 | -------------------------------------------------------------------------------- /aiogram_forms/forms/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Forms base implementation. 3 | """ 4 | import inspect 5 | from typing import TYPE_CHECKING, Mapping, Optional, Any, List, Callable, Awaitable, Union, Tuple, Dict 6 | 7 | from aiogram import types 8 | from aiogram.filters import Filter 9 | 10 | from ..core.entities import Entity, EntityContainer 11 | from ..enums import RouterHandlerType 12 | from ..filters import EntityStatesFilter 13 | 14 | if TYPE_CHECKING: 15 | from ..types import TranslatableString 16 | 17 | 18 | class Field(Entity): 19 | """Simple form field implementation.""" 20 | help_text: Optional['TranslatableString'] 21 | error_messages: Mapping[str, 'TranslatableString'] 22 | validators: List[Union[Callable[..., None], Callable[..., Awaitable[None]]]] 23 | 24 | def __init__( 25 | self, 26 | label: 'TranslatableString', 27 | help_text: Optional['TranslatableString'] = None, 28 | error_messages: Optional[Mapping[str, 'TranslatableString']] = None, 29 | validators: Optional[List[Union[Callable[..., None], Callable[..., Awaitable[None]]]]] = None 30 | ) -> None: 31 | self.label = label 32 | self.help_text = help_text 33 | self.error_messages = error_messages or {} 34 | self.validators = validators or [] 35 | 36 | @property 37 | def reply_keyboard( 38 | self 39 | ) -> Union[ 40 | types.InlineKeyboardMarkup, 41 | types.ReplyKeyboardMarkup, 42 | types.ReplyKeyboardRemove, 43 | types.ForceReply, 44 | None 45 | ]: 46 | """Field keyboard.""" 47 | return types.ReplyKeyboardRemove() # type: ignore[call-arg] 48 | 49 | async def extract(self, message: types.Message) -> Optional[str]: 50 | """Extract field value from message object.""" 51 | return message.text 52 | 53 | async def process(self, value: Any) -> Any: 54 | """Process field value format.""" 55 | return value 56 | 57 | async def validate(self, value: Any) -> None: 58 | """Run validators against processed field value.""" 59 | for validator in self.validators: 60 | if inspect.iscoroutinefunction(validator): 61 | await validator(value) 62 | elif hasattr(validator, '__call__') and inspect.iscoroutinefunction(validator.__call__): 63 | await validator(value) # type: ignore[misc] 64 | else: 65 | validator(value) 66 | 67 | 68 | class Form(EntityContainer): 69 | """Simple form implementation.""" 70 | 71 | @classmethod 72 | def filters(cls, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> Mapping[RouterHandlerType, Filter]: 73 | """Form handler filters.""" 74 | return { 75 | RouterHandlerType.Message: EntityStatesFilter(cls.state) 76 | } 77 | 78 | @classmethod 79 | async def callback(cls, message: types.Message, **data: Dict[str, Any]) -> None: 80 | """Form completion callback.""" 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | pytestdebug.log 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | doc/_build/ 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | #poetry.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | # .env 114 | .env/ 115 | .venv/ 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | pythonenv* 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # operating system-related files 145 | # file properties cache/storage on macOS 146 | *.DS_Store 147 | # thumbnail cache on Windows 148 | Thumbs.db 149 | 150 | # profiling data 151 | .prof 152 | 153 | 154 | # End of https://www.toptal.com/developers/gitignore/api/python 155 | 156 | # IDE 157 | .idea 158 | 159 | # Custom data 160 | .examples 161 | -------------------------------------------------------------------------------- /tests/forms/test_validators.py: -------------------------------------------------------------------------------- 1 | from contextlib import nullcontext 2 | 3 | import pytest 4 | 5 | from aiogram_forms.errors import ValidationError 6 | from aiogram_forms.forms import validators 7 | 8 | 9 | @pytest.mark.parametrize( 10 | 'limit,value,exception', 11 | [ 12 | (-3, 'foo', nullcontext()), 13 | (3, '12', pytest.raises(ValidationError)), 14 | (3, '123', nullcontext()), 15 | (3, '1234', nullcontext()), 16 | ], 17 | ids=[ 18 | 'Passes when negative number', 19 | 'Fails when less characters', 20 | 'Passes when same length', 21 | 'Passes when greater length' 22 | ] 23 | ) 24 | def test_min_length_validator(limit: int, value: str, exception: bool): 25 | with exception: 26 | validators.MinLengthValidator(limit)(value) 27 | 28 | 29 | @pytest.mark.parametrize( 30 | 'limit,value,exception', 31 | [ 32 | (-3, 'foo', pytest.raises(ValidationError)), 33 | (3, '1234', pytest.raises(ValidationError)), 34 | (3, '123', nullcontext()), 35 | (3, '12', nullcontext()), 36 | ], 37 | ids=[ 38 | 'Fails when negative number', 39 | 'Fails when greater characters', 40 | 'Passes when same length', 41 | 'Passes when less length' 42 | ] 43 | ) 44 | def test_max_length_validator(limit: int, value: str, exception): 45 | with exception: 46 | validators.MaxLengthValidator(limit)(value) 47 | 48 | 49 | @pytest.mark.parametrize( 50 | 'regex,value,exception', 51 | [ 52 | (r'a{3}$', 'bbb', pytest.raises(ValidationError)), 53 | (r'a{3}$', 'aaa', nullcontext()), 54 | ], 55 | ids=[ 56 | 'Fails when not contains', 57 | 'Passes when contains' 58 | ] 59 | ) 60 | def test_regex_validator(regex: str, value: str, exception): 61 | with exception: 62 | validators.RegexValidator(regex)(value) 63 | 64 | 65 | @pytest.mark.parametrize( 66 | 'value,exception', 67 | [ 68 | ('test.email@example.com', nullcontext()), 69 | ('test.email@example', pytest.raises(ValidationError)), 70 | ('test.email', pytest.raises(ValidationError)), 71 | ], 72 | ids=[ 73 | 'Passes when valid email', 74 | 'Fails when invalid domain', 75 | 'Fails when contains no "@" sign' 76 | ] 77 | ) 78 | def test_email_validator(value: str, exception): 79 | with exception: 80 | validators.EmailValidator()(value) 81 | 82 | 83 | @pytest.mark.parametrize( 84 | 'value,exception', 85 | [ 86 | ('+123456789012', nullcontext()), 87 | ('123456789012', nullcontext()), 88 | ('not even a number', pytest.raises(ValidationError)) 89 | ], 90 | ids=[ 91 | 'Passes when valid phone', 92 | 'Passes when valid phone without "+" sign', 93 | 'Fails when invalid format' 94 | ] 95 | ) 96 | def test_phone_validator(value: str, exception): 97 | with exception: 98 | validators.PhoneNumberValidator()(value) 99 | 100 | 101 | @pytest.mark.parametrize( 102 | 'value,exception', 103 | [ 104 | (1, nullcontext()), 105 | ('1', pytest.raises(ValidationError)), 106 | (4, pytest.raises(ValidationError)) 107 | ], 108 | ids=[ 109 | 'Passes when value in list', 110 | 'Fails when invalid type', 111 | 'Fails when not in list' 112 | ] 113 | ) 114 | def test_choices_validator(value: str, exception): 115 | with exception: 116 | validators.ChoiceValidator(choices=(1, 2, 3))(value) 117 | -------------------------------------------------------------------------------- /aiogram_forms/forms/fields.py: -------------------------------------------------------------------------------- 1 | """ 2 | Form fields. 3 | """ 4 | from typing import Optional, Any, Dict, TYPE_CHECKING, Tuple, Union, Iterable 5 | 6 | from aiogram import types 7 | from aiogram.utils.i18n.lazy_proxy import LazyProxy # type: ignore[attr-defined] 8 | 9 | from .base import Field 10 | from . import validators 11 | 12 | if TYPE_CHECKING: 13 | from ..types import TranslatableString 14 | 15 | 16 | class TextField(Field): 17 | """Simple text field.""" 18 | 19 | def __init__( 20 | self, 21 | *args: Tuple[Any], 22 | min_length: Optional[int] = None, 23 | max_length: Optional[int] = None, 24 | **kwargs: Dict[str, Any] 25 | ) -> None: 26 | super().__init__(*args, **kwargs) # type: ignore[arg-type] 27 | if min_length is not None: 28 | self.validators.append(validators.MinLengthValidator(min_length)) 29 | if max_length is not None: 30 | self.validators.append(validators.MaxLengthValidator(max_length)) 31 | 32 | 33 | class EmailField(Field): 34 | """Email field.""" 35 | 36 | def __init__(self, label: 'TranslatableString', *args: Tuple[Any], **kwargs: Dict[str, Any]) -> None: 37 | super().__init__(label, *args, **kwargs) # type: ignore[arg-type] 38 | self.validators.append(validators.EmailValidator()) 39 | 40 | 41 | class PhoneNumberField(Field): 42 | """Phone number field.""" 43 | 44 | def __init__( 45 | self, 46 | label: 'TranslatableString', 47 | *args: Tuple[Any], 48 | share_contact: Optional[bool] = False, 49 | **kwargs: Dict[str, Any] 50 | ) -> None: 51 | super().__init__(label, *args, **kwargs) # type: ignore[arg-type] 52 | self.share_contact = share_contact 53 | self.validators.append(validators.PhoneNumberValidator()) 54 | 55 | @property 56 | def reply_keyboard(self) -> Union[ 57 | types.InlineKeyboardMarkup, 58 | types.ReplyKeyboardMarkup, 59 | types.ReplyKeyboardRemove, 60 | types.ForceReply, 61 | None 62 | ]: 63 | if self.share_contact: 64 | return types.ReplyKeyboardMarkup( 65 | keyboard=[ 66 | [types.KeyboardButton(text=str(self.label), request_contact=True)] 67 | ], 68 | resize_keyboard=True 69 | ) 70 | return super().reply_keyboard 71 | 72 | async def extract(self, message: types.Message) -> Optional[str]: 73 | if message.content_type == 'contact': 74 | return message.contact.phone_number # type: ignore[union-attr] 75 | return await super().extract(message) 76 | 77 | 78 | class ChoiceField(Field): 79 | """Choices field.""" 80 | 81 | def __init__( 82 | self, 83 | label: 'TranslatableString', 84 | *args: Tuple[Any], 85 | choices: Iterable[Tuple['TranslatableString', Any]], 86 | **kwargs: Dict[str, Any] 87 | ) -> None: 88 | super().__init__(label, *args, **kwargs) # type: ignore[arg-type] 89 | self.choices = choices 90 | self.validators.append( 91 | validators.ChoiceValidator(choices=tuple( 92 | map(lambda x: x[1], choices) 93 | )) 94 | ) 95 | 96 | async def process(self, value: str) -> Any: 97 | for label, key in self.choices: 98 | if value == label: 99 | return key 100 | return None 101 | 102 | @property 103 | def reply_keyboard(self) -> types.ReplyKeyboardMarkup: 104 | return types.ReplyKeyboardMarkup( 105 | keyboard=[ 106 | [types.KeyboardButton(text=label.value if isinstance(label, LazyProxy) else label)] 107 | for label, option in self.choices 108 | ], 109 | resize_keyboard=True 110 | ) 111 | -------------------------------------------------------------------------------- /tests/forms/test_fields.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch, Mock 2 | 3 | import pytest 4 | from aiogram.types import ReplyKeyboardMarkup, ReplyKeyboardRemove 5 | 6 | from aiogram_forms.forms import fields 7 | 8 | 9 | @patch('aiogram_forms.forms.validators.MinLengthValidator') 10 | @patch('aiogram_forms.forms.validators.MaxLengthValidator') 11 | def test_text_field_adds_validators(min_validator, max_validator): 12 | text_field = fields.TextField('Text', min_length=3, max_length=5) 13 | assert len(text_field.validators) == 2 14 | assert min_validator.called 15 | assert max_validator.called 16 | 17 | 18 | @patch('aiogram_forms.forms.validators.EmailValidator') 19 | def test_email_field_adds_validators(validator): 20 | text_field = fields.EmailField('Email') 21 | assert len(text_field.validators) == 1 22 | assert validator.called 23 | 24 | 25 | @patch('aiogram_forms.forms.validators.PhoneNumberValidator') 26 | def test_phone_field_adds_validators(validator): 27 | text_field = fields.PhoneNumberField('Phone') 28 | assert len(text_field.validators) == 1 29 | assert validator.called 30 | 31 | 32 | def test_phone_field_default_reply_keyboard(): 33 | field = fields.PhoneNumberField('Phone') 34 | assert isinstance(field.reply_keyboard, ReplyKeyboardRemove) 35 | 36 | 37 | def test_phone_field_adds_reply_keyboard(): 38 | field = fields.PhoneNumberField('Phone', share_contact=True) 39 | assert isinstance(field.reply_keyboard, ReplyKeyboardMarkup) 40 | 41 | 42 | @pytest.mark.asyncio 43 | async def test_phone_field_extract_default(message): 44 | field = fields.PhoneNumberField('Phone') 45 | value = await field.extract(message) 46 | assert value is message.text 47 | 48 | 49 | @pytest.mark.asyncio 50 | async def test_phone_field_extract_shared_contact(contact_message): 51 | field = fields.PhoneNumberField('Phone') 52 | value = await field.extract(contact_message) 53 | assert value is contact_message.contact.phone_number 54 | 55 | 56 | @pytest.mark.asyncio 57 | async def test_sync_function_validator(): 58 | def validator(value: str): 59 | ... 60 | 61 | field = fields.Field('Test', validators=[validator]) 62 | await field.validate('value') 63 | 64 | 65 | @pytest.mark.asyncio 66 | async def test_async_function_validator(): 67 | async def validator(value: str): 68 | ... 69 | 70 | field = fields.Field('Test', validators=[validator]) 71 | await field.validate('value') 72 | 73 | 74 | @pytest.mark.asyncio 75 | async def test_sync_class_validator(): 76 | class Validator: 77 | def __call__(self, *args, **kwargs): 78 | ... 79 | 80 | field = fields.Field('Test', validators=[Validator()]) 81 | await field.validate('value') 82 | 83 | 84 | @pytest.mark.asyncio 85 | async def test_async_class_validator(): 86 | class Validator: 87 | async def __call__(self, *args, **kwargs): 88 | ... 89 | 90 | field = fields.Field('Test', validators=[Validator()]) 91 | await field.validate('value') 92 | 93 | 94 | @pytest.mark.asyncio 95 | async def test_choice_field_process_exists(): 96 | field = fields.ChoiceField('Status', choices=[ 97 | ('Published', 1), 98 | ('Drafted', 0) 99 | ]) 100 | value = await field.process('Published') 101 | assert value == 1 102 | 103 | 104 | @pytest.mark.asyncio 105 | async def test_choice_field_process_not_exists(): 106 | field = fields.ChoiceField('Status', choices=[ 107 | ('Published', 1), 108 | ('Drafted', 0) 109 | ]) 110 | value = await field.process('Trashed') 111 | assert value is None 112 | 113 | 114 | def test_choice_field_reply_keyboard(): 115 | field = fields.ChoiceField('Status', choices=[ 116 | ('Published', 1), 117 | ('Drafted', 0) 118 | ]) 119 | assert isinstance(field.reply_keyboard, ReplyKeyboardMarkup) 120 | 121 | for option, keyboard_row in zip(field.choices, field.reply_keyboard.keyboard): 122 | assert option[0] == keyboard_row[0].text 123 | -------------------------------------------------------------------------------- /aiogram_forms/forms/manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Forms manager. 3 | """ 4 | import warnings 5 | from typing import Type, cast, Optional, Dict, Any, Union 6 | 7 | from aiogram.fsm.context import FSMContext 8 | 9 | from .base import Field, Form 10 | from ..errors import ValidationError 11 | from ..core.manager import EntityManager 12 | from ..core.states import EntityState 13 | 14 | 15 | class FormsManager(EntityManager): 16 | """Forms manager.""" 17 | state: FSMContext 18 | 19 | def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] 20 | super().__init__(*args, **kwargs) 21 | self.state = self.data['state'] 22 | 23 | async def show(self, name: str) -> None: 24 | entity_container: Type['Form'] = self._get_form_by_name(name) 25 | 26 | first_entity = cast(Field, entity_container.state.get_states()[0].entity) 27 | await self.state.set_state(first_entity.state) 28 | await self.event.answer(first_entity.label, reply_markup=first_entity.reply_keyboard) # type: ignore[arg-type] 29 | 30 | async def handle(self, form: Type['Form']) -> None: 31 | """Handle form field.""" 32 | state_label = await self.state.get_state() 33 | current_state: 'EntityState' = next(iter([ 34 | st for st in form.state.get_states() if st.state == state_label 35 | ])) 36 | 37 | field: Field = cast(Field, current_state.entity) 38 | try: 39 | value = await field.process( 40 | await field.extract(self.event) 41 | ) 42 | await field.validate(value) 43 | except ValidationError as error: 44 | error_message = field.error_messages.get(error.code) or error.message 45 | await self.event.answer(error_message, reply_markup=field.reply_keyboard) # type: ignore[arg-type] 46 | return 47 | 48 | data = await self.state.get_data() 49 | form_data = data.get(form.__name__, {}) 50 | form_data.update({field.state.state.split(':')[-1]: value}) # type: ignore[union-attr] 51 | await self.state.update_data({form.__name__: form_data}) 52 | 53 | next_state_index = cast( 54 | Dict['EntityState', Optional['EntityState']], 55 | dict(zip(current_state.group, list(current_state.group)[1:])) 56 | ) 57 | next_entity_state: Optional['EntityState'] = next_state_index.get(current_state) 58 | if next_entity_state: 59 | next_field: Field = cast(Field, next_entity_state.entity) 60 | await self.state.set_state(next_field.state) 61 | await self.event.answer( 62 | '\n'.join([ 63 | str(next_field.label), 64 | str(next_field.help_text) or "" 65 | ] if next_field.help_text else [str(next_field.label)]), 66 | reply_markup=next_field.reply_keyboard 67 | ) 68 | else: 69 | await self.state.set_state(None) 70 | await form.callback(self.event, **self.data) 71 | 72 | async def get_data(self, form: Union[Type['Form'], str]) -> Dict[str, Any]: 73 | """Get form data from store.""" 74 | container: Type['Form'] 75 | if isinstance(form, str): 76 | container = self._get_form_by_name(form) 77 | else: 78 | warnings.warn( 79 | message='`FormsManager.get_data(...)` should accept form ID, ' 80 | 'form class passing will be deprecated in next releases.', 81 | category=DeprecationWarning 82 | ) 83 | container = form 84 | 85 | data = await self.state.get_data() 86 | form_data = data.get(container.__name__) 87 | if not form_data or not isinstance(form_data, dict): 88 | return {} 89 | return form_data 90 | 91 | def _get_form_by_name(self, name: str) -> Type['Form']: 92 | """Get registered form by name.""" 93 | entity_container: Type['Form'] = cast( 94 | Type['Form'], 95 | self._dispatcher.get_entity_container(Form, name) 96 | ) 97 | 98 | if not issubclass(entity_container, Form): 99 | raise ValueError(f'Entity registered with name {name} is not a valid form!') 100 | return entity_container 101 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | i.13g10n@icloud.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "aiofiles" 5 | version = "23.1.0" 6 | description = "File support for asyncio." 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.7,<4.0" 10 | files = [ 11 | {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"}, 12 | {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"}, 13 | ] 14 | 15 | [[package]] 16 | name = "aiogram" 17 | version = "3.0.0b7" 18 | description = "Modern and fully asynchronous framework for Telegram Bot API" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.8" 22 | files = [ 23 | {file = "aiogram-3.0.0b7-py3-none-any.whl", hash = "sha256:9c2256bfd0a3f06dfee9cd077ac510eb967da62a529fbfb443c680539cdd1870"}, 24 | {file = "aiogram-3.0.0b7.tar.gz", hash = "sha256:683c9aafa477e42f54df427089db3d18e4db15a2520abb9da5c4305a02e7fd82"}, 25 | ] 26 | 27 | [package.dependencies] 28 | aiofiles = ">=23.1.0,<23.2.0" 29 | aiohttp = ">=3.8.4,<3.9.0" 30 | certifi = ">=2022.9.24" 31 | magic-filter = ">=1.0.9,<1.1.0" 32 | pydantic = ">=1.10.4,<1.11.0" 33 | 34 | [package.extras] 35 | dev = ["black (>=23.1,<24.0)", "isort (>=5.11,<6.0)", "mypy (>=1.0.0,<1.1.0)", "packaging (>=23.0,<24.0)", "pre-commit (>=3.0.4,<3.1.0)", "ruff (>=0.0.246,<0.1.0)", "toml (>=0.10.2,<0.11.0)", "towncrier (>=22.12.0,<22.13.0)", "typing-extensions (>=4.4.0,<4.5.0)"] 36 | docs = ["furo (>=2022.9.29,<2022.10.0)", "markdown-include (>=0.7.0,<0.8.0)", "pygments (>=2.13.0,<2.14.0)", "pygments (>=2.4,<3.0)", "pymdown-extensions (>=9.6,<10.0)", "sphinx (>=5.2.3,<5.3.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.0,<0.6.0)", "sphinx-intl (>=2.0.1,<2.1.0)", "sphinx-prompt (>=1.5.0,<1.6.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.3.1a3,<0.4.0)", "towncrier (>=22.8.0,<22.9.0)"] 37 | fast = ["uvloop (>=0.17.0)"] 38 | i18n = ["babel (>=2.11.0,<2.12.0)"] 39 | proxy = ["aiohttp-socks (>=0.7.1,<0.8.0)"] 40 | redis = ["redis (>=4.5.1,<4.6.0)"] 41 | test = ["aresponses (>=2.1.6,<2.2.0)", "pytest (>=7.2.1,<7.3.0)", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-asyncio (>=0.20.3,<0.21.0)", "pytest-cov (>=4.0.0,<4.1.0)", "pytest-html (>=3.2.0,<3.3.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.10.0,<3.11.0)", "pytest-mypy (>=0.10.0,<0.11.0)"] 42 | 43 | [[package]] 44 | name = "aiohttp" 45 | version = "3.8.4" 46 | description = "Async http client/server framework (asyncio)" 47 | category = "main" 48 | optional = false 49 | python-versions = ">=3.6" 50 | files = [ 51 | {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"}, 52 | {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"}, 53 | {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"}, 54 | {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"}, 55 | {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"}, 56 | {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"}, 57 | {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"}, 58 | {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"}, 59 | {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"}, 60 | {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"}, 61 | {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"}, 62 | {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"}, 63 | {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"}, 64 | {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"}, 65 | {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"}, 66 | {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"}, 67 | {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"}, 68 | {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"}, 69 | {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"}, 70 | {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"}, 71 | {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"}, 72 | {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"}, 73 | {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"}, 74 | {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"}, 75 | {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"}, 76 | {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"}, 77 | {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"}, 78 | {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"}, 79 | {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"}, 80 | {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"}, 81 | {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"}, 82 | {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"}, 83 | {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"}, 84 | {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"}, 85 | {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"}, 86 | {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"}, 87 | {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"}, 88 | {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"}, 89 | {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"}, 90 | {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"}, 91 | {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"}, 92 | {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"}, 93 | {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"}, 94 | {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"}, 95 | {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"}, 96 | {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"}, 97 | {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"}, 98 | {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"}, 99 | {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"}, 100 | {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"}, 101 | {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"}, 102 | {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"}, 103 | {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"}, 104 | {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"}, 105 | {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"}, 106 | {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"}, 107 | {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"}, 108 | {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"}, 109 | {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"}, 110 | {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"}, 111 | {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"}, 112 | {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"}, 113 | {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"}, 114 | {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"}, 115 | {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"}, 116 | {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"}, 117 | {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"}, 118 | {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"}, 119 | {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"}, 120 | {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"}, 121 | {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"}, 122 | {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"}, 123 | {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"}, 124 | {file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"}, 125 | {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"}, 126 | {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"}, 127 | {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"}, 128 | {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"}, 129 | {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"}, 130 | {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"}, 131 | {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"}, 132 | {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"}, 133 | {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"}, 134 | {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"}, 135 | {file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"}, 136 | {file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"}, 137 | {file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"}, 138 | ] 139 | 140 | [package.dependencies] 141 | aiosignal = ">=1.1.2" 142 | async-timeout = ">=4.0.0a3,<5.0" 143 | attrs = ">=17.3.0" 144 | charset-normalizer = ">=2.0,<4.0" 145 | frozenlist = ">=1.1.1" 146 | multidict = ">=4.5,<7.0" 147 | yarl = ">=1.0,<2.0" 148 | 149 | [package.extras] 150 | speedups = ["Brotli", "aiodns", "cchardet"] 151 | 152 | [[package]] 153 | name = "aiosignal" 154 | version = "1.3.1" 155 | description = "aiosignal: a list of registered asynchronous callbacks" 156 | category = "main" 157 | optional = false 158 | python-versions = ">=3.7" 159 | files = [ 160 | {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, 161 | {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, 162 | ] 163 | 164 | [package.dependencies] 165 | frozenlist = ">=1.1.0" 166 | 167 | [[package]] 168 | name = "astroid" 169 | version = "2.15.3" 170 | description = "An abstract syntax tree for Python with inference support." 171 | category = "dev" 172 | optional = false 173 | python-versions = ">=3.7.2" 174 | files = [ 175 | {file = "astroid-2.15.3-py3-none-any.whl", hash = "sha256:f11e74658da0f2a14a8d19776a8647900870a63de71db83713a8e77a6af52662"}, 176 | {file = "astroid-2.15.3.tar.gz", hash = "sha256:44224ad27c54d770233751315fa7f74c46fa3ee0fab7beef1065f99f09897efe"}, 177 | ] 178 | 179 | [package.dependencies] 180 | lazy-object-proxy = ">=1.4.0" 181 | typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} 182 | wrapt = [ 183 | {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, 184 | {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, 185 | ] 186 | 187 | [[package]] 188 | name = "async-timeout" 189 | version = "4.0.2" 190 | description = "Timeout context manager for asyncio programs" 191 | category = "main" 192 | optional = false 193 | python-versions = ">=3.6" 194 | files = [ 195 | {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, 196 | {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, 197 | ] 198 | 199 | [[package]] 200 | name = "attrs" 201 | version = "23.1.0" 202 | description = "Classes Without Boilerplate" 203 | category = "main" 204 | optional = false 205 | python-versions = ">=3.7" 206 | files = [ 207 | {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, 208 | {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, 209 | ] 210 | 211 | [package.extras] 212 | cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] 213 | dev = ["attrs[docs,tests]", "pre-commit"] 214 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] 215 | tests = ["attrs[tests-no-zope]", "zope-interface"] 216 | tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 217 | 218 | [[package]] 219 | name = "babel" 220 | version = "2.12.1" 221 | description = "Internationalization utilities" 222 | category = "dev" 223 | optional = false 224 | python-versions = ">=3.7" 225 | files = [ 226 | {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, 227 | {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, 228 | ] 229 | 230 | [package.dependencies] 231 | pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} 232 | 233 | [[package]] 234 | name = "certifi" 235 | version = "2022.12.7" 236 | description = "Python package for providing Mozilla's CA Bundle." 237 | category = "main" 238 | optional = false 239 | python-versions = ">=3.6" 240 | files = [ 241 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 242 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 243 | ] 244 | 245 | [[package]] 246 | name = "charset-normalizer" 247 | version = "3.1.0" 248 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 249 | category = "main" 250 | optional = false 251 | python-versions = ">=3.7.0" 252 | files = [ 253 | {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, 254 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, 255 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, 256 | {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, 257 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, 258 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, 259 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, 260 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, 261 | {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, 262 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, 263 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, 264 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, 265 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, 266 | {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, 267 | {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, 268 | {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, 269 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, 270 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, 271 | {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, 272 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, 273 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, 274 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, 275 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, 276 | {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, 277 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, 278 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, 279 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, 280 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, 281 | {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, 282 | {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, 283 | {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, 284 | {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, 285 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, 286 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, 287 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, 288 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, 289 | {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, 290 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, 291 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, 292 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, 293 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, 294 | {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, 295 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, 296 | {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, 297 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, 298 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, 299 | {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, 300 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, 301 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, 302 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, 303 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, 304 | {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, 305 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, 306 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, 307 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, 308 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, 309 | {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, 310 | {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, 311 | {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, 312 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, 313 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, 314 | {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, 315 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, 316 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, 317 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, 318 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, 319 | {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, 320 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, 321 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, 322 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, 323 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, 324 | {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, 325 | {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, 326 | {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, 327 | {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, 328 | ] 329 | 330 | [[package]] 331 | name = "colorama" 332 | version = "0.4.6" 333 | description = "Cross-platform colored terminal text." 334 | category = "dev" 335 | optional = false 336 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 337 | files = [ 338 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 339 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 340 | ] 341 | 342 | [[package]] 343 | name = "coverage" 344 | version = "7.2.3" 345 | description = "Code coverage measurement for Python" 346 | category = "dev" 347 | optional = false 348 | python-versions = ">=3.7" 349 | files = [ 350 | {file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"}, 351 | {file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"}, 352 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"}, 353 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"}, 354 | {file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"}, 355 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"}, 356 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"}, 357 | {file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"}, 358 | {file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"}, 359 | {file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"}, 360 | {file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"}, 361 | {file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"}, 362 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"}, 363 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"}, 364 | {file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"}, 365 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"}, 366 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"}, 367 | {file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"}, 368 | {file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"}, 369 | {file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"}, 370 | {file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"}, 371 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"}, 372 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"}, 373 | {file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"}, 374 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"}, 375 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"}, 376 | {file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"}, 377 | {file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"}, 378 | {file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"}, 379 | {file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"}, 380 | {file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"}, 381 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"}, 382 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"}, 383 | {file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"}, 384 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"}, 385 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"}, 386 | {file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"}, 387 | {file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"}, 388 | {file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"}, 389 | {file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"}, 390 | {file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"}, 391 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"}, 392 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"}, 393 | {file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"}, 394 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"}, 395 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"}, 396 | {file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"}, 397 | {file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"}, 398 | {file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"}, 399 | {file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"}, 400 | {file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"}, 401 | ] 402 | 403 | [package.extras] 404 | toml = ["tomli"] 405 | 406 | [[package]] 407 | name = "dill" 408 | version = "0.3.6" 409 | description = "serialize all of python" 410 | category = "dev" 411 | optional = false 412 | python-versions = ">=3.7" 413 | files = [ 414 | {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, 415 | {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, 416 | ] 417 | 418 | [package.extras] 419 | graph = ["objgraph (>=1.7.2)"] 420 | 421 | [[package]] 422 | name = "exceptiongroup" 423 | version = "1.1.1" 424 | description = "Backport of PEP 654 (exception groups)" 425 | category = "dev" 426 | optional = false 427 | python-versions = ">=3.7" 428 | files = [ 429 | {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, 430 | {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, 431 | ] 432 | 433 | [package.extras] 434 | test = ["pytest (>=6)"] 435 | 436 | [[package]] 437 | name = "frozenlist" 438 | version = "1.3.3" 439 | description = "A list-like structure which implements collections.abc.MutableSequence" 440 | category = "main" 441 | optional = false 442 | python-versions = ">=3.7" 443 | files = [ 444 | {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, 445 | {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, 446 | {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, 447 | {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, 448 | {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, 449 | {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, 450 | {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, 451 | {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, 452 | {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, 453 | {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, 454 | {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, 455 | {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, 456 | {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, 457 | {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, 458 | {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, 459 | {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, 460 | {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, 461 | {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, 462 | {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, 463 | {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, 464 | {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, 465 | {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, 466 | {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, 467 | {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, 468 | {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, 469 | {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, 470 | {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, 471 | {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, 472 | {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, 473 | {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, 474 | {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, 475 | {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, 476 | {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, 477 | {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, 478 | {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, 479 | {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, 480 | {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, 481 | {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, 482 | {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, 483 | {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, 484 | {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, 485 | {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, 486 | {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, 487 | {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, 488 | {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, 489 | {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, 490 | {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, 491 | {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, 492 | {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, 493 | {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, 494 | {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, 495 | {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, 496 | {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, 497 | {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, 498 | {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, 499 | {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, 500 | {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, 501 | {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, 502 | {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, 503 | {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, 504 | {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, 505 | {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, 506 | {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, 507 | {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, 508 | {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, 509 | {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, 510 | {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, 511 | {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, 512 | {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, 513 | {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, 514 | {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, 515 | {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, 516 | {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, 517 | {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, 518 | ] 519 | 520 | [[package]] 521 | name = "idna" 522 | version = "3.4" 523 | description = "Internationalized Domain Names in Applications (IDNA)" 524 | category = "main" 525 | optional = false 526 | python-versions = ">=3.5" 527 | files = [ 528 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 529 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 530 | ] 531 | 532 | [[package]] 533 | name = "iniconfig" 534 | version = "2.0.0" 535 | description = "brain-dead simple config-ini parsing" 536 | category = "dev" 537 | optional = false 538 | python-versions = ">=3.7" 539 | files = [ 540 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 541 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 542 | ] 543 | 544 | [[package]] 545 | name = "isort" 546 | version = "5.12.0" 547 | description = "A Python utility / library to sort Python imports." 548 | category = "dev" 549 | optional = false 550 | python-versions = ">=3.8.0" 551 | files = [ 552 | {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, 553 | {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, 554 | ] 555 | 556 | [package.extras] 557 | colors = ["colorama (>=0.4.3)"] 558 | pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] 559 | plugins = ["setuptools"] 560 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 561 | 562 | [[package]] 563 | name = "lazy-object-proxy" 564 | version = "1.9.0" 565 | description = "A fast and thorough lazy object proxy." 566 | category = "dev" 567 | optional = false 568 | python-versions = ">=3.7" 569 | files = [ 570 | {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, 571 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, 572 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, 573 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, 574 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, 575 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, 576 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, 577 | {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, 578 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, 579 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, 580 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, 581 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, 582 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, 583 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, 584 | {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, 585 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, 586 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, 587 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, 588 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, 589 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, 590 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, 591 | {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, 592 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, 593 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, 594 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, 595 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, 596 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, 597 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, 598 | {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, 599 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, 600 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, 601 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, 602 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, 603 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, 604 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, 605 | {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, 606 | ] 607 | 608 | [[package]] 609 | name = "magic-filter" 610 | version = "1.0.9" 611 | description = "This package provides magic filter based on dynamic attribute getter" 612 | category = "main" 613 | optional = false 614 | python-versions = ">=3.7,<4.0" 615 | files = [ 616 | {file = "magic-filter-1.0.9.tar.gz", hash = "sha256:d0f1ffa5ff1fbe5105fd5f293c79b5d3795f336ea0f6129c636959a687bf422a"}, 617 | {file = "magic_filter-1.0.9-py3-none-any.whl", hash = "sha256:51002312a8972fa514b998b7ff89340c98be3fc499967c1f5f2af98d13baf8d5"}, 618 | ] 619 | 620 | [[package]] 621 | name = "mccabe" 622 | version = "0.7.0" 623 | description = "McCabe checker, plugin for flake8" 624 | category = "dev" 625 | optional = false 626 | python-versions = ">=3.6" 627 | files = [ 628 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 629 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 630 | ] 631 | 632 | [[package]] 633 | name = "multidict" 634 | version = "6.0.4" 635 | description = "multidict implementation" 636 | category = "main" 637 | optional = false 638 | python-versions = ">=3.7" 639 | files = [ 640 | {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, 641 | {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, 642 | {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, 643 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, 644 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, 645 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, 646 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, 647 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, 648 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, 649 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, 650 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, 651 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, 652 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, 653 | {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, 654 | {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, 655 | {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, 656 | {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, 657 | {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, 658 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, 659 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, 660 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, 661 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, 662 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, 663 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, 664 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, 665 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, 666 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, 667 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, 668 | {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, 669 | {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, 670 | {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, 671 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, 672 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, 673 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, 674 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, 675 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, 676 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, 677 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, 678 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, 679 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, 680 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, 681 | {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, 682 | {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, 683 | {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, 684 | {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, 685 | {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, 686 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, 687 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, 688 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, 689 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, 690 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, 691 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, 692 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, 693 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, 694 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, 695 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, 696 | {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, 697 | {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, 698 | {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, 699 | {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, 700 | {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, 701 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, 702 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, 703 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, 704 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, 705 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, 706 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, 707 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, 708 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, 709 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, 710 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, 711 | {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, 712 | {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, 713 | {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, 714 | ] 715 | 716 | [[package]] 717 | name = "mypy" 718 | version = "1.2.0" 719 | description = "Optional static typing for Python" 720 | category = "dev" 721 | optional = false 722 | python-versions = ">=3.7" 723 | files = [ 724 | {file = "mypy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d"}, 725 | {file = "mypy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"}, 726 | {file = "mypy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e"}, 727 | {file = "mypy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a"}, 728 | {file = "mypy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128"}, 729 | {file = "mypy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41"}, 730 | {file = "mypy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb"}, 731 | {file = "mypy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937"}, 732 | {file = "mypy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9"}, 733 | {file = "mypy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602"}, 734 | {file = "mypy-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140"}, 735 | {file = "mypy-1.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336"}, 736 | {file = "mypy-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e"}, 737 | {file = "mypy-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119"}, 738 | {file = "mypy-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a"}, 739 | {file = "mypy-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950"}, 740 | {file = "mypy-1.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6"}, 741 | {file = "mypy-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5"}, 742 | {file = "mypy-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8"}, 743 | {file = "mypy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed"}, 744 | {file = "mypy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f"}, 745 | {file = "mypy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521"}, 746 | {file = "mypy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238"}, 747 | {file = "mypy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48"}, 748 | {file = "mypy-1.2.0-py3-none-any.whl", hash = "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394"}, 749 | {file = "mypy-1.2.0.tar.gz", hash = "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1"}, 750 | ] 751 | 752 | [package.dependencies] 753 | mypy-extensions = ">=1.0.0" 754 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 755 | typing-extensions = ">=3.10" 756 | 757 | [package.extras] 758 | dmypy = ["psutil (>=4.0)"] 759 | install-types = ["pip"] 760 | python2 = ["typed-ast (>=1.4.0,<2)"] 761 | reports = ["lxml"] 762 | 763 | [[package]] 764 | name = "mypy-extensions" 765 | version = "1.0.0" 766 | description = "Type system extensions for programs checked with the mypy type checker." 767 | category = "dev" 768 | optional = false 769 | python-versions = ">=3.5" 770 | files = [ 771 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 772 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 773 | ] 774 | 775 | [[package]] 776 | name = "packaging" 777 | version = "23.1" 778 | description = "Core utilities for Python packages" 779 | category = "dev" 780 | optional = false 781 | python-versions = ">=3.7" 782 | files = [ 783 | {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, 784 | {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, 785 | ] 786 | 787 | [[package]] 788 | name = "platformdirs" 789 | version = "3.2.0" 790 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 791 | category = "dev" 792 | optional = false 793 | python-versions = ">=3.7" 794 | files = [ 795 | {file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"}, 796 | {file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"}, 797 | ] 798 | 799 | [package.extras] 800 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] 801 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 802 | 803 | [[package]] 804 | name = "pluggy" 805 | version = "1.0.0" 806 | description = "plugin and hook calling mechanisms for python" 807 | category = "dev" 808 | optional = false 809 | python-versions = ">=3.6" 810 | files = [ 811 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 812 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 813 | ] 814 | 815 | [package.extras] 816 | dev = ["pre-commit", "tox"] 817 | testing = ["pytest", "pytest-benchmark"] 818 | 819 | [[package]] 820 | name = "pydantic" 821 | version = "1.10.7" 822 | description = "Data validation and settings management using python type hints" 823 | category = "main" 824 | optional = false 825 | python-versions = ">=3.7" 826 | files = [ 827 | {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, 828 | {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, 829 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, 830 | {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, 831 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, 832 | {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, 833 | {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, 834 | {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, 835 | {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, 836 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, 837 | {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, 838 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, 839 | {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, 840 | {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, 841 | {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, 842 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, 843 | {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, 844 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, 845 | {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, 846 | {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, 847 | {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, 848 | {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, 849 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, 850 | {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, 851 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, 852 | {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, 853 | {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, 854 | {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, 855 | {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, 856 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, 857 | {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, 858 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, 859 | {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, 860 | {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, 861 | {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, 862 | {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, 863 | ] 864 | 865 | [package.dependencies] 866 | typing-extensions = ">=4.2.0" 867 | 868 | [package.extras] 869 | dotenv = ["python-dotenv (>=0.10.4)"] 870 | email = ["email-validator (>=1.0.3)"] 871 | 872 | [[package]] 873 | name = "pylint" 874 | version = "2.17.2" 875 | description = "python code static checker" 876 | category = "dev" 877 | optional = false 878 | python-versions = ">=3.7.2" 879 | files = [ 880 | {file = "pylint-2.17.2-py3-none-any.whl", hash = "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8"}, 881 | {file = "pylint-2.17.2.tar.gz", hash = "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb"}, 882 | ] 883 | 884 | [package.dependencies] 885 | astroid = ">=2.15.2,<=2.17.0-dev0" 886 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} 887 | dill = [ 888 | {version = ">=0.2", markers = "python_version < \"3.11\""}, 889 | {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, 890 | ] 891 | isort = ">=4.2.5,<6" 892 | mccabe = ">=0.6,<0.8" 893 | platformdirs = ">=2.2.0" 894 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 895 | tomlkit = ">=0.10.1" 896 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 897 | 898 | [package.extras] 899 | spelling = ["pyenchant (>=3.2,<4.0)"] 900 | testutils = ["gitpython (>3)"] 901 | 902 | [[package]] 903 | name = "pytest" 904 | version = "7.3.1" 905 | description = "pytest: simple powerful testing with Python" 906 | category = "dev" 907 | optional = false 908 | python-versions = ">=3.7" 909 | files = [ 910 | {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, 911 | {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, 912 | ] 913 | 914 | [package.dependencies] 915 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 916 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 917 | iniconfig = "*" 918 | packaging = "*" 919 | pluggy = ">=0.12,<2.0" 920 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 921 | 922 | [package.extras] 923 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 924 | 925 | [[package]] 926 | name = "pytest-asyncio" 927 | version = "0.20.3" 928 | description = "Pytest support for asyncio" 929 | category = "dev" 930 | optional = false 931 | python-versions = ">=3.7" 932 | files = [ 933 | {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, 934 | {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, 935 | ] 936 | 937 | [package.dependencies] 938 | pytest = ">=6.1.0" 939 | 940 | [package.extras] 941 | docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] 942 | testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] 943 | 944 | [[package]] 945 | name = "pytz" 946 | version = "2023.3" 947 | description = "World timezone definitions, modern and historical" 948 | category = "dev" 949 | optional = false 950 | python-versions = "*" 951 | files = [ 952 | {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, 953 | {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, 954 | ] 955 | 956 | [[package]] 957 | name = "tomli" 958 | version = "2.0.1" 959 | description = "A lil' TOML parser" 960 | category = "dev" 961 | optional = false 962 | python-versions = ">=3.7" 963 | files = [ 964 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 965 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 966 | ] 967 | 968 | [[package]] 969 | name = "tomlkit" 970 | version = "0.11.7" 971 | description = "Style preserving TOML library" 972 | category = "dev" 973 | optional = false 974 | python-versions = ">=3.7" 975 | files = [ 976 | {file = "tomlkit-0.11.7-py3-none-any.whl", hash = "sha256:5325463a7da2ef0c6bbfefb62a3dc883aebe679984709aee32a317907d0a8d3c"}, 977 | {file = "tomlkit-0.11.7.tar.gz", hash = "sha256:f392ef70ad87a672f02519f99967d28a4d3047133e2d1df936511465fbb3791d"}, 978 | ] 979 | 980 | [[package]] 981 | name = "typing-extensions" 982 | version = "4.5.0" 983 | description = "Backported and Experimental Type Hints for Python 3.7+" 984 | category = "main" 985 | optional = false 986 | python-versions = ">=3.7" 987 | files = [ 988 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, 989 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, 990 | ] 991 | 992 | [[package]] 993 | name = "wrapt" 994 | version = "1.15.0" 995 | description = "Module for decorators, wrappers and monkey patching." 996 | category = "dev" 997 | optional = false 998 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 999 | files = [ 1000 | {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, 1001 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, 1002 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, 1003 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, 1004 | {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, 1005 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, 1006 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, 1007 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, 1008 | {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, 1009 | {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, 1010 | {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, 1011 | {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, 1012 | {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, 1013 | {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, 1014 | {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, 1015 | {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, 1016 | {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, 1017 | {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, 1018 | {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, 1019 | {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, 1020 | {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, 1021 | {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, 1022 | {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, 1023 | {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, 1024 | {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, 1025 | {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, 1026 | {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, 1027 | {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, 1028 | {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, 1029 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, 1030 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, 1031 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, 1032 | {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, 1033 | {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, 1034 | {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, 1035 | {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, 1036 | {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, 1037 | {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, 1038 | {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, 1039 | {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, 1040 | {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, 1041 | {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, 1042 | {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, 1043 | {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, 1044 | {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, 1045 | {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, 1046 | {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, 1047 | {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, 1048 | {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, 1049 | {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, 1050 | {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, 1051 | {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, 1052 | {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, 1053 | {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, 1054 | {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, 1055 | {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, 1056 | {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, 1057 | {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, 1058 | {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, 1059 | {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, 1060 | {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, 1061 | {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, 1062 | {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, 1063 | {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, 1064 | {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, 1065 | {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, 1066 | {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, 1067 | {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, 1068 | {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, 1069 | {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, 1070 | {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, 1071 | {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, 1072 | {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, 1073 | {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, 1074 | {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "yarl" 1079 | version = "1.9.1" 1080 | description = "Yet another URL library" 1081 | category = "main" 1082 | optional = false 1083 | python-versions = ">=3.7" 1084 | files = [ 1085 | {file = "yarl-1.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e124b283a04cc06d22443cae536f93d86cd55108fa369f22b8fe1f2288b2fe1c"}, 1086 | {file = "yarl-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56956b13ec275de31fe4fb991510b735c4fb3e1b01600528c952b9ac90464430"}, 1087 | {file = "yarl-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecaa5755a39f6f26079bf13f336c67af589c222d76b53cd3824d3b684b84d1f1"}, 1088 | {file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92a101f6d5a9464e86092adc36cd40ef23d18a25bfb1eb32eaeb62edc22776bb"}, 1089 | {file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92e37999e36f9f3ded78e9d839face6baa2abdf9344ea8ed2735f495736159de"}, 1090 | {file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef7e2f6c47c41e234600a02e1356b799761485834fe35d4706b0094cb3a587ee"}, 1091 | {file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7a0075a55380b19aa43b9e8056e128b058460d71d75018a4f9d60ace01e78c"}, 1092 | {file = "yarl-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f01351b7809182822b21061d2a4728b7b9e08f4585ba90ee4c5c4d3faa0812"}, 1093 | {file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6cf47fe9df9b1ededc77e492581cdb6890a975ad96b4172e1834f1b8ba0fc3ba"}, 1094 | {file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:098bdc06ffb4db39c73883325b8c738610199f5f12e85339afedf07e912a39af"}, 1095 | {file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:6cdb47cbbacae8e1d7941b0d504d0235d686090eef5212ca2450525905e9cf02"}, 1096 | {file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:73a4b46689f2d59c8ec6b71c9a0cdced4e7863dd6eb98a8c30ea610e191f9e1c"}, 1097 | {file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65d952e464df950eed32bb5dcbc1b4443c7c2de4d7abd7265b45b1b3b27f5fa2"}, 1098 | {file = "yarl-1.9.1-cp310-cp310-win32.whl", hash = "sha256:39a7a9108e9fc633ae381562f8f0355bb4ba00355218b5fb19cf5263fcdbfa68"}, 1099 | {file = "yarl-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b63d41e0eecf3e3070d44f97456cf351fff7cb960e97ecb60a936b877ff0b4f6"}, 1100 | {file = "yarl-1.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4295790981630c4dab9d6de7b0f555a4c8defe3ed7704a8e9e595a321e59a0f5"}, 1101 | {file = "yarl-1.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b2b2382d59dec0f1fdca18ea429c4c4cee280d5e0dbc841180abb82e188cf6e9"}, 1102 | {file = "yarl-1.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:575975d28795a61e82c85f114c02333ca54cbd325fd4e4b27598c9832aa732e7"}, 1103 | {file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bb794882818fae20ff65348985fdf143ea6dfaf6413814db1848120db8be33e"}, 1104 | {file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89da1fd6068553e3a333011cc17ad91c414b2100c32579ddb51517edc768b49c"}, 1105 | {file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d817593d345fefda2fae877accc8a0d9f47ada57086da6125fa02a62f6d1a94"}, 1106 | {file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85aa6fd779e194901386709e0eedd45710b68af2709f82a84839c44314b68c10"}, 1107 | {file = "yarl-1.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eed9827033b7f67ad12cb70bd0cb59d36029144a7906694317c2dbf5c9eb5ddd"}, 1108 | {file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:df747104ef27ab1aa9a1145064fa9ea26ad8cf24bfcbdba7db7abf0f8b3676b9"}, 1109 | {file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:efec77851231410125cb5be04ec96fa4a075ca637f415a1f2d2c900b09032a8a"}, 1110 | {file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d5c407e530cf2979ea383885516ae79cc4f3c3530623acf5e42daf521f5c2564"}, 1111 | {file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f76edb386178a54ea7ceffa798cb830c3c22ab50ea10dfb25dc952b04848295f"}, 1112 | {file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:75676110bce59944dd48fd18d0449bd37eaeb311b38a0c768f7670864b5f8b68"}, 1113 | {file = "yarl-1.9.1-cp311-cp311-win32.whl", hash = "sha256:9ba5a18c4fbd408fe49dc5da85478a76bc75c1ce912d7fd7b43ed5297c4403e1"}, 1114 | {file = "yarl-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:b20a5ddc4e243cbaa54886bfe9af6ffc4ba4ef58f17f1bb691e973eb65bba84d"}, 1115 | {file = "yarl-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:791357d537a09a194f92b834f28c98d074e7297bac0a8f1d5b458a906cafa17c"}, 1116 | {file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89099c887338608da935ba8bee027564a94f852ac40e472de15d8309517ad5fe"}, 1117 | {file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:395ea180257a3742d09dcc5071739682a95f7874270ebe3982d6696caec75be0"}, 1118 | {file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90ebaf448b5f048352ec7c76cb8d452df30c27cb6b8627dfaa9cf742a14f141a"}, 1119 | {file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f878a78ed2ccfbd973cab46dd0933ecd704787724db23979e5731674d76eb36f"}, 1120 | {file = "yarl-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390c2318d066962500045aa145f5412169bce842e734b8c3e6e3750ad5b817"}, 1121 | {file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f8e73f526140c1c32f5fca4cd0bc3b511a1abcd948f45b2a38a95e4edb76ca72"}, 1122 | {file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ac8e593df1fbea820da7676929f821a0c7c2cecb8477d010254ce8ed54328ea8"}, 1123 | {file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:01cf88cb80411978a14aa49980968c1aeb7c18a90ac978c778250dd234d8e0ba"}, 1124 | {file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:97d76a3128f48fa1c721ef8a50e2c2f549296b2402dc8a8cde12ff60ed922f53"}, 1125 | {file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:01a073c9175481dfed6b40704a1b67af5a9435fc4a58a27d35fd6b303469b0c7"}, 1126 | {file = "yarl-1.9.1-cp37-cp37m-win32.whl", hash = "sha256:ecad20c3ef57c513dce22f58256361d10550a89e8eaa81d5082f36f8af305375"}, 1127 | {file = "yarl-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f5bcb80006efe9bf9f49ae89711253dd06df8053ff814622112a9219346566a7"}, 1128 | {file = "yarl-1.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7ddebeabf384099814353a2956ed3ab5dbaa6830cc7005f985fcb03b5338f05"}, 1129 | {file = "yarl-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:13a1ad1f35839b3bb5226f59816b71e243d95d623f5b392efaf8820ddb2b3cd5"}, 1130 | {file = "yarl-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0cd87949d619157a0482c6c14e5011f8bf2bc0b91cb5087414d9331f4ef02dd"}, 1131 | {file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d21887cbcf6a3cc5951662d8222bc9c04e1b1d98eebe3bb659c3a04ed49b0eec"}, 1132 | {file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4764114e261fe49d5df9b316b3221493d177247825c735b2aae77bc2e340d800"}, 1133 | {file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abe37fd89a93ebe0010417ca671f422fa6fcffec54698f623b09f46b4d4a512"}, 1134 | {file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fe3a1c073ab80a28a06f41d2b623723046709ed29faf2c56bea41848597d86"}, 1135 | {file = "yarl-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3b5f8da07a21f2e57551f88a6709c2d340866146cf7351e5207623cfe8aad16"}, 1136 | {file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f6413ff5edfb9609e2769e32ce87a62353e66e75d264bf0eaad26fb9daa8f2"}, 1137 | {file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b5d5fb6c94b620a7066a3adb7c246c87970f453813979818e4707ac32ce4d7bd"}, 1138 | {file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f206adb89424dca4a4d0b31981869700e44cd62742527e26d6b15a510dd410a2"}, 1139 | {file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44fa6158e6b4b8ccfa2872c3900a226b29e8ce543ce3e48aadc99816afa8874d"}, 1140 | {file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:08c8599d6aa8a24425f8635f6c06fa8726afe3be01c8e53e236f519bcfa5db5b"}, 1141 | {file = "yarl-1.9.1-cp38-cp38-win32.whl", hash = "sha256:6b09cce412386ea9b4dda965d8e78d04ac5b5792b2fa9cced3258ec69c7d1c16"}, 1142 | {file = "yarl-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:09c56a32c26e24ef98d5757c5064e252836f621f9a8b42737773aa92936b8e08"}, 1143 | {file = "yarl-1.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b86e98c3021b7e2740d8719bf074301361bf2f51221ca2765b7a58afbfbd9042"}, 1144 | {file = "yarl-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5faf3ec98747318cb980aaf9addf769da68a66431fc203a373d95d7ee9c1fbb4"}, 1145 | {file = "yarl-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a21789bdf28549d4eb1de6910cabc762c9f6ae3eef85efc1958197c1c6ef853b"}, 1146 | {file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8b8d4b478a9862447daef4cafc89d87ea4ed958672f1d11db7732b77ead49cc"}, 1147 | {file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:307a782736ebf994e7600dcaeea3b3113083584da567272f2075f1540919d6b3"}, 1148 | {file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46c4010de941e2e1365c07fb4418ddca10fcff56305a6067f5ae857f8c98f3a7"}, 1149 | {file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab67d041c78e305ff3eef5e549304d843bd9b603c8855b68484ee663374ce15"}, 1150 | {file = "yarl-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1baf8cdaaab65d9ccedbf8748d626ad648b74b0a4d033e356a2f3024709fb82f"}, 1151 | {file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:27efc2e324f72df02818cd72d7674b1f28b80ab49f33a94f37c6473c8166ce49"}, 1152 | {file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca14b84091700ae7c1fcd3a6000bd4ec1a3035009b8bcb94f246741ca840bb22"}, 1153 | {file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c3ca8d71b23bdf164b36d06df2298ec8a5bd3de42b17bf3e0e8e6a7489195f2c"}, 1154 | {file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:8c72a1dc7e2ea882cd3df0417c808ad3b69e559acdc43f3b096d67f2fb801ada"}, 1155 | {file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d966cd59df9a4b218480562e8daab39e87e746b78a96add51a3ab01636fc4291"}, 1156 | {file = "yarl-1.9.1-cp39-cp39-win32.whl", hash = "sha256:518a92a34c741836a315150460b5c1c71ae782d569eabd7acf53372e437709f7"}, 1157 | {file = "yarl-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:78755ce43b6e827e65ec0c68be832f86d059fcf05d4b33562745ebcfa91b26b1"}, 1158 | {file = "yarl-1.9.1.tar.gz", hash = "sha256:5ce0bcab7ec759062c818d73837644cde567ab8aa1e0d6c45db38dfb7c284441"}, 1159 | ] 1160 | 1161 | [package.dependencies] 1162 | idna = ">=2.0" 1163 | multidict = ">=4.0" 1164 | 1165 | [metadata] 1166 | lock-version = "2.0" 1167 | python-versions = "^3.8" 1168 | content-hash = "4e83f6dc1682f98af7e508a32e04d799f76c8c06c73cdf0f19389b587b98394c" 1169 | --------------------------------------------------------------------------------