├── .editorconfig ├── .github └── workflows │ ├── build.yml │ ├── lint.yml │ └── pypi.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── Justfile ├── LICENSE ├── README.md ├── feedforbot ├── __init__.py ├── __main__.py ├── cli.py ├── config.py ├── constants.py ├── core │ ├── __init__.py │ ├── article.py │ ├── cache.py │ ├── listeners.py │ ├── scheduler.py │ ├── transports.py │ ├── types.py │ └── utils.py ├── exceptions.py ├── logger.py ├── utils.py └── version.py ├── poetry.lock ├── pyproject.toml └── tests ├── __init__.py ├── conftest.py ├── core ├── __init__.py ├── listeners │ ├── __init__.py │ └── test_rss.py └── test_adapters.py └── mocks └── rss_short.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.py] 12 | indent_size = 4 13 | 14 | [{Makefile,**.mk}] 15 | indent_style = tab 16 | 17 | [*.{md,txt,rst}] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | on: 3 | release: 4 | types: [created] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | env: 10 | REGISTRY_HOST: ghcr.io 11 | steps: 12 | - uses: actions/checkout@v2 13 | - shell: bash 14 | run: | 15 | echo "##[set-output name=name;]$(echo ${GITHUB_REPOSITORY#*/})" 16 | echo "##[set-output name=ver;]$(echo ${GITHUB_REF#refs/*/})" 17 | echo "##[set-output name=minor_ver;]$(TMP_VAR=${GITHUB_REF#refs/*/}; echo ${TMP_VAR%.*})" 18 | echo "##[set-output name=major_ver;]$(TMP_VAR=${GITHUB_REF#refs/*/}; echo ${TMP_VAR%.*.*})" 19 | echo "##[set-output name=sha;]$(git rev-parse --short "$GITHUB_SHA")" 20 | id: extract_name_and_version 21 | - uses: docker/setup-qemu-action@v2 22 | - uses: docker/setup-buildx-action@v2 23 | - uses: docker/login-action@v2 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USER }} 26 | password: ${{ secrets.DOCKERHUB_PASS }} 27 | - uses: docker/login-action@v2 28 | with: 29 | registry: ${{ env.REGISTRY_HOST }} 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | - run: sed -i 's/0.1.0/'"${{ steps.extract_name_and_version.outputs.ver }}"'/' pyproject.toml 33 | - run: sed -i 's/0.1.0/'"${{ steps.extract_name_and_version.outputs.ver }}"'/' feedforbot/version.py 34 | - uses: docker/build-push-action@v3 35 | with: 36 | context: . 37 | file: ./Dockerfile 38 | push: true 39 | tags: | 40 | "${{ env.REGISTRY_HOST }}/${{ github.repository }}:latest" 41 | "${{ env.REGISTRY_HOST }}/${{ github.repository }}:${{ steps.extract_name_and_version.outputs.sha }}" 42 | "${{ env.REGISTRY_HOST }}/${{ github.repository }}:${{ steps.extract_name_and_version.outputs.major_ver }}" 43 | "${{ env.REGISTRY_HOST }}/${{ github.repository }}:${{ steps.extract_name_and_version.outputs.minor_ver }}" 44 | "${{ env.REGISTRY_HOST }}/${{ github.repository }}:${{ steps.extract_name_and_version.outputs.ver }}" 45 | "${{ github.repository }}:latest" 46 | "${{ github.repository }}:${{ steps.extract_name_and_version.outputs.sha }}" 47 | "${{ github.repository }}:${{ steps.extract_name_and_version.outputs.major_ver }}" 48 | "${{ github.repository }}:${{ steps.extract_name_and_version.outputs.minor_ver }}" 49 | "${{ github.repository }}:${{ steps.extract_name_and_version.outputs.ver }}" 50 | labels: 51 | "org.opencontainers.image.source=https://github.com/${{ github.repository }}" 52 | build-args: | 53 | SHORTENER_VERSION=${{ steps.extract_name_and_version.outputs.ver }} 54 | cache-from: type=registry,ref=${{ env.REGISTRY_HOST }}/${{ github.repository }}:${{ steps.extract_name_and_version.outputs.minor_ver }} 55 | cache-to: type=inline 56 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | PYTHON_VERSION: 13 | - "3.10" 14 | - "3.11" 15 | - "3.12" 16 | JUST_JOB: 17 | - "linters" 18 | - "tests" 19 | steps: 20 | - uses: extractions/setup-just@v1 21 | - uses: actions/setup-python@v4 22 | with: 23 | python-version: ${{ matrix.PYTHON_VERSION }} 24 | - uses: actions/checkout@v3 25 | - run: | 26 | python -m pip install poetry~=1.7.0 27 | poetry install --no-root --no-ansi --with ${{ matrix.JUST_JOB }} 28 | - run: just ${{ matrix.JUST_JOB }} 29 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | on: 3 | release: 4 | types: [created] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/setup-python@v4 11 | with: 12 | python-version: "3.10" 13 | - uses: actions/checkout@v3 14 | - name: Extract branch name 15 | shell: bash 16 | run: | 17 | echo "##[set-output name=name;]$(echo ${GITHUB_REPOSITORY#*/})" 18 | echo "##[set-output name=ver;]$(echo ${GITHUB_REF#refs/*/})" 19 | echo "##[set-output name=minor_ver;]$(TMP_VAR=${GITHUB_REF#refs/*/}; echo ${TMP_VAR%.*})" 20 | echo "##[set-output name=major_ver;]$(TMP_VAR=${GITHUB_REF#refs/*/}; echo ${TMP_VAR%.*.*})" 21 | echo "##[set-output name=sha;]$(git rev-parse --short "$GITHUB_SHA")" 22 | id: extract_name_and_version 23 | - run: sed -i 's/0.1.0/'"${{ steps.extract_name_and_version.outputs.ver }}"'/' pyproject.toml 24 | - run: head -n 10 pyproject.toml 25 | - run: sed -i 's/0.1.0/'"${{ steps.extract_name_and_version.outputs.ver }}"'/' feedforbot/version.py 26 | - run: python -m pip install poetry==1.2.2 27 | - run: poetry build 28 | - run: poetry config http-basic.pypi ${{ secrets.PYPI_LOGIN }} ${{ secrets.PYPI_PASS }} 29 | - run: poetry publish 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .env 3 | .DS_Store 4 | 5 | *.yml 6 | *.yaml 7 | *.json 8 | redis_data/ 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | env/ 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *,cover 55 | .hypothesis/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # IPython Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # dotenv 88 | .env 89 | 90 | # virtualenv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - 3 | repo: local 4 | hooks: 5 | 6 | - id: black 7 | name: black 8 | entry: python -m black 9 | language: system 10 | files: ^(src|tests)/.+\.py$ 11 | 12 | - id: isort 13 | name: isort 14 | entry: python -m isort 15 | language: system 16 | files: ^(src|tests)/.+\.py$ 17 | 18 | - id: ruff 19 | name: ruff 20 | entry: python -m ruff check --fix 21 | language: system 22 | files: ^(src|tests)/.+\.py$ 23 | 24 | - id: mypy 25 | name: mypy 26 | entry: python -m mypy 27 | language: system 28 | files: ^(src|test)/.+\.py$ 29 | args: [--show-error-codes] 30 | 31 | - id: bandit 32 | name: bandit 33 | entry: just bandir 34 | language: system 35 | pass_filenames: false 36 | files: ^(src)/.+\.py$ 37 | 38 | - id: safety 39 | name: safety 40 | entry: just safety 41 | language: system 42 | pass_filenames: false 43 | files: ^(poetry.lock)$ 44 | 45 | - repo: https://github.com/pre-commit/pre-commit-hooks 46 | rev: "v4.5.0" 47 | hooks: 48 | - id: check-added-large-files 49 | - id: check-json 50 | - id: check-merge-conflict 51 | - id: check-yaml 52 | - id: detect-private-key 53 | - id: end-of-file-fixer 54 | - id: requirements-txt-fixer 55 | - id: forbid-new-submodules 56 | - id: trailing-whitespace 57 | exclude: ^.+(\.md)$ 58 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim as base-image 2 | ARG POETRY_VERSION=1.8.4 3 | WORKDIR /service 4 | RUN pip install "poetry==$POETRY_VERSION" 5 | ADD pyproject.toml poetry.lock README.md ./ 6 | ADD feedforbot feedforbot 7 | RUN poetry build 8 | RUN python -m venv .venv 9 | RUN .venv/bin/pip install dist/*.whl 10 | 11 | FROM python:3.11-slim as runtime-image 12 | WORKDIR /service 13 | COPY --from=base-image /service/.venv ./.venv 14 | ENTRYPOINT ["/service/.venv/bin/python3", "-m", "feedforbot"] 15 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | SOURCE_DIR := "feedforbot" 2 | 3 | linters: mypy ruff bandit safety 4 | tests: pytest 5 | format: isort black 6 | 7 | isort: 8 | poetry run isort {{ SOURCE_DIR }} --diff --color 9 | 10 | black: 11 | poetry run isort {{ SOURCE_DIR }} 12 | 13 | ruff: 14 | poetry run ruff check --fix --unsafe-fixes {{ SOURCE_DIR }} 15 | 16 | mypy: 17 | poetry run mypy --pretty -p {{ SOURCE_DIR }} 18 | 19 | bandit: 20 | poetry run bandit -r {{ SOURCE_DIR }} 21 | 22 | safety: 23 | poetry run safety --disable-optional-telemetry-data check --file poetry.lock 24 | 25 | pytest: 26 | poetry run pytest -vv 27 | 28 | help: 29 | python -m {{ SOURCE_DIR }} --help 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 shpaker 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FeedForBot 2 | ========== 3 | 4 | [![PyPI](https://img.shields.io/pypi/v/feedforbot.svg)](https://pypi.python.org/pypi/feedforbot) 5 | [![PyPI](https://img.shields.io/pypi/dm/feedforbot.svg)](https://pypi.python.org/pypi/feedforbot) 6 | [![Docker Pulls](https://img.shields.io/docker/pulls/shpaker/feedforbot)](https://hub.docker.com/r/shpaker/feedforbot) 7 | [![PyPI](https://img.shields.io/badge/code%20style-black-000000.svg)](href="https://github.com/psf/black) 8 | 9 | Forward links from RSS/Atom feeds to messengers 10 | 11 | Installation 12 | ------------ 13 | 14 | ```commandline 15 | pip install feedforbot -U 16 | ``` 17 | 18 | Usage 19 | ----- 20 | 21 | ### From code 22 | 23 | ```python 24 | import asyncio 25 | 26 | from feedforbot import Scheduler, TelegramBotTransport, RSSListener 27 | 28 | 29 | def main(): 30 | loop = asyncio.new_event_loop() 31 | asyncio.set_event_loop(loop) 32 | scheduler = Scheduler( 33 | '* * * * *', 34 | listener=RSSListener('https://www.debian.org/News/news'), 35 | transport=TelegramBotTransport( 36 | token='123456789:AAAAAAAAAA-BBBB-CCCCCCCCCCCC-DDDDDD', 37 | to='@channel', 38 | ) 39 | ) 40 | scheduler.run() 41 | loop.run_forever() 42 | 43 | if __name__ == '__main__': 44 | main() 45 | ``` 46 | 47 | ### CLI 48 | 49 | #### Save to file `config.yml` data 50 | 51 | ```yaml 52 | --- 53 | cache: 54 | type: 'files' 55 | schedulers: 56 | - listener: 57 | type: 'rss' 58 | params: 59 | url: 'https://habr.com/ru/rss/all/all/?fl=ru' 60 | transport: 61 | type: 'telegram_bot' 62 | params: 63 | token: '123456789:AAAAAAAAAA-BBBB-CCCCCCCCCCCC-DDDDDD' 64 | to: '@tmfeed' 65 | template: |- 66 | {{ TITLE }} #habr 67 | {{ ID }} 68 | Tags: {% for category in CATEGORIES %}{{ category }}{{ ", " if not loop.last else "" }}{% endfor %} 69 | Author: {{ AUTHORS[0] }} 70 | - listener: 71 | type: 'rss' 72 | params: 73 | url: 'https://habr.com/ru/rss/news/?fl=ru' 74 | transport: 75 | type: 'telegram_bot' 76 | params: 77 | token: '123456789:AAAAAAAAAA-BBBB-CCCCCCCCCCCC-DDDDDD' 78 | to: '@tmfeed' 79 | template: |- 80 | {{ TITLE }} #habr 81 | {{ ID }} 82 | Tags: {% for category in CATEGORIES %}{{ category }}{{ ", " if not loop.last else "" }}{% endfor %} 83 | - listener: 84 | type: 'rss' 85 | params: 86 | url: 'http://www.opennet.ru/opennews/opennews_all.rss' 87 | transport: 88 | type: 'telegram_bot' 89 | params: 90 | token: '123456789:AAAAAAAAAA-BBBB-CCCCCCCCCCCC-DDDDDD' 91 | to: '@tmfeed' 92 | disable_web_page_preview: yes 93 | template: |- 94 | {{ TITLE }} #opennet 95 | {{ URL }} 96 | 97 | {{ TEXT }} 98 | ``` 99 | 100 | #### Start script 101 | 102 | ```commandline 103 | feedforbot --verbose config.yml 104 | ``` 105 | 106 | ### Docker 107 | 108 | #### Docker Hub 109 | 110 | ```commandline 111 | docker run shpaker/feedforbot --help 112 | ``` 113 | 114 | #### GHCR 115 | 116 | ```commandline 117 | docker run ghcr.io/shpaker/feedforbot --help 118 | ``` 119 | -------------------------------------------------------------------------------- /feedforbot/__init__.py: -------------------------------------------------------------------------------- 1 | from feedforbot.core.cache import CacheBase, FilesCache, InMemoryCache 2 | from feedforbot.core.listeners import ListenerBase, RSSListener 3 | from feedforbot.core.scheduler import Scheduler 4 | from feedforbot.core.transports import TelegramBotTransport, TransportBase 5 | from feedforbot.core.types import ( 6 | CacheProtocol, 7 | ListenerProtocol, 8 | TransportProtocol, 9 | ) 10 | from feedforbot.version import VERSION 11 | 12 | __version__ = VERSION 13 | __all__ = ( 14 | "Scheduler", 15 | "InMemoryCache", 16 | "FilesCache", 17 | "RSSListener", 18 | "TelegramBotTransport", 19 | "CacheProtocol", 20 | "ListenerProtocol", 21 | "TransportProtocol", 22 | "CacheBase", 23 | "ListenerBase", 24 | "TransportBase", 25 | "VERSION", 26 | ) 27 | -------------------------------------------------------------------------------- /feedforbot/__main__.py: -------------------------------------------------------------------------------- 1 | from feedforbot.cli import main 2 | 3 | if __name__ == "__main__": 4 | main() # pylint: disable=no-value-for-parameter 5 | -------------------------------------------------------------------------------- /feedforbot/cli.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from logging import basicConfig 3 | from pathlib import Path 4 | 5 | import click 6 | import sentry_sdk 7 | from click import Context, argument, command, option, pass_context, types 8 | 9 | from feedforbot import VERSION 10 | from feedforbot.config import read_config 11 | from feedforbot.constants import APP_NAME, VERBOSITY_LEVELS 12 | from feedforbot.utils import echo_version 13 | 14 | 15 | @command() 16 | @argument( 17 | "configuration", 18 | required=True, 19 | type=types.Path( 20 | exists=True, 21 | resolve_path=True, 22 | dir_okay=False, 23 | path_type=Path, 24 | ), 25 | ) 26 | @option( 27 | "--verbose", 28 | "-v", 29 | count=True, 30 | ) 31 | @option( 32 | "--version", 33 | "-V", 34 | is_flag=True, 35 | default=False, 36 | expose_value=False, 37 | is_eager=True, 38 | callback=echo_version, 39 | help="Show script version and exit.", 40 | ) 41 | @option( 42 | "--sentry", 43 | type=click.STRING, 44 | default=None, 45 | show_default=True, 46 | help="Sentry DSN.", 47 | ) 48 | @pass_context 49 | def main( 50 | ctx: Context, 51 | configuration: Path, 52 | verbose: int, 53 | sentry: str | None, 54 | ) -> None: 55 | """ 56 | Bot for forwarding updates from RSS/Atom feeds to Telegram messenger 57 | https://github.com/shpaker/feedforbot 58 | """ 59 | ctx.obj = {"verbose": verbose} 60 | if sentry is not None: 61 | sentry_sdk.init( 62 | dsn=sentry, 63 | release=f"{APP_NAME}-{VERSION}", 64 | attach_stacktrace=True, 65 | ) 66 | if verbose >= len(VERBOSITY_LEVELS): 67 | verbose = len(VERBOSITY_LEVELS) - 1 68 | log_level = VERBOSITY_LEVELS[verbose] 69 | basicConfig(level=log_level) 70 | loop = asyncio.new_event_loop() 71 | asyncio.set_event_loop(loop) 72 | schedulers = read_config(configuration) 73 | for scheduler in schedulers: 74 | scheduler.run() 75 | loop.run_forever() 76 | -------------------------------------------------------------------------------- /feedforbot/config.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pathlib import Path 3 | from typing import Any 4 | 5 | from pydantic import BaseModel 6 | from yaml import safe_load 7 | 8 | from feedforbot import ( 9 | CacheProtocol, 10 | FilesCache, 11 | InMemoryCache, 12 | ListenerProtocol, 13 | RSSListener, 14 | Scheduler, 15 | TelegramBotTransport, 16 | TransportProtocol, 17 | ) 18 | 19 | 20 | class _CacheTypes(str, Enum): 21 | IN_MEMORY = "in_memory" 22 | FILES = "files" 23 | 24 | 25 | class _CacheConfigMapping(Enum): 26 | IN_MEMORY = InMemoryCache 27 | FILES = FilesCache 28 | 29 | 30 | class _ListenerTypes(str, Enum): 31 | RSS = "rss" 32 | 33 | 34 | class _ListenerConfigMapping(Enum): 35 | RSS = RSSListener 36 | 37 | 38 | class _TransportTypes(str, Enum): 39 | TELEGRAM_BOT = "telegram_bot" 40 | 41 | 42 | class _TransportConfigMapping(Enum): 43 | TELEGRAM_BOT = TelegramBotTransport 44 | 45 | 46 | class _ConfigEntryModel(BaseModel): 47 | params: dict[str, Any] = {} 48 | 49 | 50 | class _ListenerConfigModel(_ConfigEntryModel): 51 | type: _ListenerTypes 52 | 53 | 54 | class _TransportConfigModel(_ConfigEntryModel): 55 | type: _TransportTypes 56 | 57 | 58 | class _CacheConfigModel(_ConfigEntryModel): 59 | type: _CacheTypes 60 | 61 | 62 | class _SchedulerConfigModel(BaseModel): 63 | rule: str = "* * * * *" 64 | listener: _ListenerConfigModel 65 | transport: _TransportConfigModel 66 | 67 | 68 | class _ConfigModel(BaseModel): 69 | cache: _CacheConfigModel 70 | schedulers: tuple[_SchedulerConfigModel, ...] 71 | 72 | 73 | def _cache_from_config( 74 | config: _ConfigModel, 75 | ) -> type[CacheProtocol]: 76 | name = config.cache.type.name 77 | return _CacheConfigMapping[name].value 78 | 79 | 80 | def _listener_from_config( 81 | scheduler_config: _SchedulerConfigModel, 82 | ) -> type[ListenerProtocol]: 83 | name = scheduler_config.listener.type.name 84 | return _ListenerConfigMapping[name].value 85 | 86 | 87 | def _transport_from_config( 88 | scheduler_config: _SchedulerConfigModel, 89 | ) -> type[TransportProtocol]: 90 | name = scheduler_config.transport.type.name 91 | return _TransportConfigMapping[name].value 92 | 93 | 94 | def _make_scheduler_from_config( 95 | config: _SchedulerConfigModel, 96 | *, 97 | cache_cls: Any, 98 | ) -> Scheduler: 99 | transport_cls = _transport_from_config(config) 100 | listener_cls = _listener_from_config(config) 101 | listener = listener_cls(**config.listener.params) 102 | transport = transport_cls(**config.transport.params) 103 | return Scheduler( 104 | config.rule, 105 | listener=listener, 106 | transport=transport, 107 | cache=cache_cls( 108 | id=f"{transport}-{listener}", 109 | ), 110 | ) 111 | 112 | 113 | def read_config( 114 | path: Path, 115 | ) -> tuple[Scheduler, ...]: 116 | with path.open(encoding="utf-8", mode="r") as fh: 117 | data = safe_load(fh.read()) 118 | config = _ConfigModel(**data) 119 | cache_cls = _cache_from_config(config) 120 | return tuple( 121 | _make_scheduler_from_config( 122 | scheduler_config, 123 | cache_cls=cache_cls, 124 | ) 125 | for scheduler_config in config.schedulers 126 | ) 127 | -------------------------------------------------------------------------------- /feedforbot/constants.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from jinja2 import Template 4 | 5 | APP_NAME = "FeedForBot" 6 | DEFAULT_MESSAGE_TEMPLATE = Template("{{ TITLE }}\n\n{{ TEXT }}\n\n{{ URL }}") 7 | DEFAULT_FILES_CACHE_DIR = Path.home() / f".{APP_NAME.lower()}" 8 | VERBOSITY_LEVELS = ( 9 | "WARNING", 10 | "INFO", 11 | "DEBUG", 12 | "NOTSET", 13 | ) 14 | -------------------------------------------------------------------------------- /feedforbot/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpaker/feedforbot/43fca6b931ebcd823ba95730a6c49bd590a73cf6/feedforbot/core/__init__.py -------------------------------------------------------------------------------- /feedforbot/core/article.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | from typing import Any 3 | 4 | from pydantic import ( 5 | BaseModel, 6 | ConfigDict, 7 | Field, 8 | HttpUrl, 9 | field_serializer, 10 | field_validator, 11 | ) 12 | from pydantic_core.core_schema import SerializationInfo 13 | 14 | from feedforbot.core.utils import now 15 | 16 | 17 | def _to_upper( 18 | string: str, 19 | ) -> str: 20 | return string.upper() 21 | 22 | 23 | class ArticleModel( 24 | BaseModel, 25 | ): 26 | model_config = ConfigDict( 27 | alias_generator=_to_upper, 28 | populate_by_name=True, 29 | ) 30 | 31 | id: str 32 | published_at: datetime | None = None 33 | grabbed_at: datetime = Field(default_factory=now) 34 | title: str 35 | url: HttpUrl 36 | text: str 37 | images: tuple[HttpUrl, ...] = () 38 | authors: tuple[str, ...] = () 39 | categories: tuple[str, ...] = () 40 | 41 | def __eq__( 42 | self, 43 | other: Any, 44 | ) -> Any: 45 | return self.id == other.id 46 | 47 | @field_serializer("url") 48 | def serialize_url( 49 | self, 50 | value: HttpUrl, 51 | _info: SerializationInfo, 52 | ) -> str: 53 | return str(value) 54 | 55 | @field_serializer("images") 56 | def serialize_images( 57 | self, 58 | value: tuple[HttpUrl, ...], 59 | _info: SerializationInfo, 60 | ) -> tuple[str, ...]: 61 | return tuple(str(entry) for entry in value) 62 | 63 | @field_validator("published_at") 64 | @classmethod 65 | def _published_at( 66 | cls, 67 | value: datetime | None, 68 | ) -> datetime | None: 69 | if value is None: 70 | return None 71 | if value.tzinfo is None: 72 | return value.replace(tzinfo=timezone.utc) 73 | return value 74 | -------------------------------------------------------------------------------- /feedforbot/core/cache.py: -------------------------------------------------------------------------------- 1 | import json 2 | from abc import ABC, abstractmethod 3 | from collections.abc import Iterable 4 | from pathlib import Path 5 | from typing import Any 6 | 7 | import aiofiles 8 | import aiofiles.os 9 | import orjson 10 | 11 | from feedforbot.constants import APP_NAME, DEFAULT_FILES_CACHE_DIR 12 | from feedforbot.core.article import ArticleModel 13 | from feedforbot.core.utils import make_sha2 14 | 15 | 16 | class CacheBase(ABC): 17 | def __init__( 18 | self, 19 | id: str, 20 | ) -> None: 21 | self.id = id 22 | 23 | def __repr__(self) -> str: 24 | return f"<{APP_NAME}.{self.__class__.__name__}>" 25 | 26 | @abstractmethod 27 | async def write( 28 | self, 29 | *articles: ArticleModel, 30 | ) -> None: 31 | raise NotImplementedError 32 | 33 | @abstractmethod 34 | async def read( 35 | self, 36 | ) -> Iterable[ArticleModel] | None: 37 | raise NotImplementedError 38 | 39 | 40 | class InMemoryCache( 41 | CacheBase, 42 | ): 43 | def __init__( 44 | self, 45 | **kwargs: Any, 46 | ): 47 | self._cache: Iterable[ArticleModel] | None = None 48 | super().__init__(**kwargs) 49 | 50 | async def write( 51 | self, 52 | *articles: ArticleModel, 53 | ) -> None: 54 | self._cache = articles 55 | 56 | async def read( 57 | self, 58 | ) -> Iterable[ArticleModel] | None: 59 | return self._cache 60 | 61 | 62 | class FilesCache( 63 | CacheBase, 64 | ): 65 | def __init__( 66 | self, 67 | id: str, 68 | data_dir: Path = DEFAULT_FILES_CACHE_DIR, 69 | ) -> None: 70 | self.data_dir = data_dir.resolve() 71 | self.cache_path = self.data_dir / f"{make_sha2(id)}.json" 72 | super().__init__(id) 73 | 74 | def __repr__(self) -> str: 75 | return f"<{APP_NAME}.{self.__class__.__name__}: {self.cache_path}>" 76 | 77 | async def write( 78 | self, 79 | *articles: ArticleModel, 80 | ) -> None: 81 | await self._ensure_data_dir() 82 | async with aiofiles.open( 83 | self.cache_path, 84 | mode="wb", 85 | ) as fh: 86 | await fh.write( 87 | orjson.dumps( 88 | [article.model_dump() for article in articles], 89 | option=orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS, 90 | ), 91 | ) 92 | 93 | async def read( 94 | self, 95 | ) -> Iterable[ArticleModel] | None: 96 | if not await aiofiles.os.path.exists(self.cache_path): 97 | return None 98 | async with aiofiles.open( 99 | self.cache_path, 100 | mode="r", 101 | ) as fh: 102 | contents = await fh.read() 103 | if not contents: 104 | return None 105 | return tuple(ArticleModel(**data) for data in json.loads(contents)) 106 | 107 | async def _ensure_data_dir( 108 | self, 109 | ) -> None: 110 | if await aiofiles.os.path.exists(self.data_dir): 111 | return 112 | try: 113 | await aiofiles.os.mkdir(self.data_dir) 114 | except FileExistsError: 115 | return 116 | -------------------------------------------------------------------------------- /feedforbot/core/listeners.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from collections.abc import Iterable 3 | from email.utils import parsedate_to_datetime 4 | from typing import TYPE_CHECKING 5 | 6 | from bs4 import BeautifulSoup 7 | from feedparser import FeedParserDict, parse 8 | from httpx import HTTPError, RequestError 9 | 10 | from feedforbot.constants import APP_NAME 11 | from feedforbot.core.article import ArticleModel 12 | from feedforbot.core.utils import make_get_request 13 | from feedforbot.exceptions import ListenerReceiveError 14 | 15 | if TYPE_CHECKING: 16 | from datetime import datetime 17 | 18 | 19 | class ListenerBase( 20 | ABC, 21 | ): 22 | def __repr__(self) -> str: 23 | return f"<{APP_NAME}.{self.__class__.__name__}>" 24 | 25 | @abstractmethod 26 | async def receive( 27 | self, 28 | ) -> Iterable[ArticleModel]: 29 | raise NotImplementedError 30 | 31 | 32 | class RSSListener( 33 | ListenerBase, 34 | ): 35 | def __init__( 36 | self, 37 | url: str, 38 | ) -> None: 39 | self.url = url 40 | 41 | def __repr__(self) -> str: 42 | return f"<{APP_NAME}.{self.__class__.__name__}: {self.url}>" 43 | 44 | def _parse_entry( 45 | self, 46 | entry: FeedParserDict, 47 | ) -> ArticleModel: 48 | soup = BeautifulSoup(entry.summary, "html.parser") 49 | authors: tuple[str, ...] = () 50 | if "authors" in entry and entry.authors != [{}]: 51 | authors = tuple(author.name for author in entry.authors) 52 | text = soup.text 53 | _id = entry.id if "id" in entry else entry.link 54 | published_at: datetime | None = None 55 | if "published" in entry: 56 | published_at = parsedate_to_datetime(entry.published) 57 | if published_at is None and "updated" in entry: 58 | published_at = parsedate_to_datetime(entry.updated) 59 | return ArticleModel( 60 | id=_id, 61 | published_at=published_at, 62 | title=entry.title, 63 | url=entry.link if "link" in entry else _id, 64 | text=text.strip(), 65 | images=tuple(img["src"] for img in soup.find_all("img")), 66 | authors=authors, 67 | categories=tuple(tag.term for tag in entry.tags) 68 | if "tags" in entry 69 | else (), 70 | ) 71 | 72 | async def receive( 73 | self, 74 | ) -> Iterable[ArticleModel]: 75 | try: 76 | response = await make_get_request(self.url) 77 | except (HTTPError, RequestError) as exc: 78 | raise ListenerReceiveError from exc 79 | parsed = parse(response) 80 | return tuple(self._parse_entry(entry) for entry in parsed.entries) 81 | -------------------------------------------------------------------------------- /feedforbot/core/scheduler.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiocron import Cron 4 | import sentry_sdk 5 | 6 | from feedforbot.core.cache import InMemoryCache 7 | from feedforbot.core.types import ( 8 | CacheProtocol, 9 | ListenerProtocol, 10 | TransportProtocol, 11 | ) 12 | from feedforbot.exceptions import ListenerReceiveError 13 | from feedforbot.logger import logger 14 | 15 | 16 | class Scheduler: 17 | def __init__( 18 | self, 19 | cron_rule: str, 20 | *, 21 | listener: ListenerProtocol, 22 | transport: TransportProtocol, 23 | cache: CacheProtocol | None = None, 24 | ) -> None: 25 | self.cron_rule = cron_rule 26 | self.listener = listener 27 | self.transport = transport 28 | self.cache = cache or InMemoryCache(id=listener.source_id) 29 | 30 | async def action( 31 | self, 32 | ) -> None: 33 | try: 34 | articles = await self.listener.receive() 35 | except ListenerReceiveError as exc: 36 | with sentry_sdk.new_scope() as scope: 37 | scope.set_tag('cron', self.cron_rule) 38 | scope.set_tag('listener', self.listener.__repr__()) 39 | scope.set_tag('transport', self.transport.__repr__()) 40 | sentry_sdk.capture_exception(exc) 41 | logger.warning(f"ListenerReceiveError {self.listener}") 42 | return 43 | if (cached := await self.cache.read()) is None: 44 | await self.cache.write(*articles) 45 | return 46 | to_send = tuple( 47 | article for article in articles if article not in cached 48 | ) 49 | if to_send: 50 | ids = "\n ".join([article.id for article in to_send]) 51 | logger.info( 52 | f"SEND\n" 53 | f" from : {self.listener}\n" 54 | f" ids :\n" 55 | f" {ids}", 56 | ) 57 | failed = await self.transport.send(*to_send) 58 | if failed: 59 | ids = "\n ".join([article.id for article in to_send]) 60 | logger.warning( 61 | f"FAILED\n" 62 | f" from : {self.listener}\n" 63 | f" ids :\n" 64 | f" {ids}", 65 | ) 66 | to_cache = tuple( 67 | article for article in articles if article not in failed 68 | ) 69 | await self.cache.write(*to_cache) 70 | 71 | def run( 72 | self, 73 | ) -> None: 74 | logger.info( 75 | f"SCHEDULER\n" 76 | f" rule : {self.cron_rule}\n" 77 | f" listen : {self.listener}\n" 78 | f" transport : {self.transport}\n" 79 | f" cache : {self.cache}", 80 | ) 81 | crontab = Cron( 82 | spec=self.cron_rule, 83 | func=self.action, 84 | loop=asyncio.get_event_loop(), 85 | ) 86 | crontab.start() 87 | -------------------------------------------------------------------------------- /feedforbot/core/transports.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | import sentry_sdk 3 | from httpx import HTTPError, RequestError 4 | from jinja2 import Template 5 | 6 | from feedforbot.constants import APP_NAME, DEFAULT_MESSAGE_TEMPLATE 7 | from feedforbot.core.article import ArticleModel 8 | from feedforbot.core.utils import make_post_request 9 | from feedforbot.exceptions import TransportSendError 10 | 11 | 12 | class TransportBase( 13 | ABC, 14 | ): 15 | def __repr__(self) -> str: 16 | return f"<{APP_NAME}.{self.__class__.__name__}>" 17 | 18 | @abstractmethod 19 | async def send( 20 | self, 21 | *articles: ArticleModel, 22 | ) -> list[ArticleModel]: 23 | raise NotImplementedError 24 | 25 | 26 | class TelegramBotTransport( 27 | TransportBase, 28 | ): 29 | def __init__( 30 | self, 31 | token: str, 32 | to: str, 33 | template: Template = DEFAULT_MESSAGE_TEMPLATE, 34 | disable_notification: bool = False, 35 | disable_web_page_preview: bool = False, 36 | ) -> None: 37 | if isinstance(template, str): 38 | template = Template(template) 39 | self._to = to 40 | self._api_url = f"https://api.telegram.org/bot{token}/sendMessage" 41 | self._message_template = template 42 | self._disable_web_page_preview = disable_web_page_preview 43 | self._disable_notification = disable_notification 44 | 45 | def __repr__(self) -> str: 46 | return f"<{APP_NAME}.{self.__class__.__name__}: {self._to}>" 47 | 48 | async def _send_article( 49 | self, 50 | article: ArticleModel, 51 | ) -> bool: 52 | try: 53 | response = await make_post_request( 54 | self._api_url, 55 | data={ 56 | "chat_id": self._to, 57 | "text": self._message_template.render( 58 | **article.model_dump( 59 | by_alias=True, 60 | ), 61 | ), 62 | "parse_mode": "HTML", 63 | "disable_notification": self._disable_notification, 64 | "disable_web_page_preview": self._disable_web_page_preview, 65 | }, 66 | ) 67 | except (HTTPError, RequestError) as exc: 68 | raise TransportSendError from exc 69 | is_ok: bool = response["ok"] 70 | return is_ok 71 | 72 | async def send( 73 | self, 74 | *articles: ArticleModel, 75 | ) -> list[ArticleModel]: 76 | failed = [] 77 | for article in articles: 78 | try: 79 | is_success = await self._send_article(article) 80 | except TransportSendError as exc: 81 | with sentry_sdk.new_scope() as scope: 82 | scope.set_tag('transport', self.__repr__()) 83 | scope.set_extra('article', article.model_dump()) 84 | sentry_sdk.capture_exception(exc) 85 | failed.append(article) 86 | continue 87 | if not is_success: 88 | failed.append(article) 89 | return failed 90 | -------------------------------------------------------------------------------- /feedforbot/core/types.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | from typing import Protocol 3 | 4 | from feedforbot.core.article import ArticleModel 5 | 6 | 7 | class CacheProtocol( 8 | Protocol, 9 | ): 10 | async def write( 11 | self, 12 | *articles: ArticleModel, 13 | ) -> None: 14 | ... 15 | 16 | async def read( 17 | self, 18 | ) -> Iterable[ArticleModel] | None: 19 | ... 20 | 21 | 22 | class ListenerProtocol( 23 | Protocol, 24 | ): 25 | source_id: str 26 | 27 | async def receive( 28 | self, 29 | ) -> tuple[ArticleModel, ...]: 30 | ... 31 | 32 | 33 | class TransportProtocol( 34 | Protocol, 35 | ): 36 | async def send( 37 | self, 38 | *articles: ArticleModel, 39 | ) -> list[ArticleModel]: 40 | ... 41 | -------------------------------------------------------------------------------- /feedforbot/core/utils.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from datetime import datetime, timezone 3 | from typing import Any 4 | 5 | from httpx import AsyncClient 6 | 7 | 8 | def now() -> datetime: 9 | return datetime.now(tz=timezone.utc) 10 | 11 | 12 | async def make_get_request( 13 | url: str, 14 | ) -> bytes: 15 | async with AsyncClient() as client: 16 | response = await client.get(url) 17 | response.raise_for_status() 18 | return response.read() 19 | 20 | 21 | async def make_post_request( 22 | url: str, 23 | *, 24 | data: dict[str, Any], 25 | ) -> Any: 26 | async with AsyncClient() as client: 27 | response = await client.post(url, json=data) 28 | response.raise_for_status() 29 | return response.json() 30 | 31 | 32 | def make_sha2( 33 | data: str, 34 | ) -> str: 35 | return hashlib.sha256(data.encode("utf-8")).hexdigest() 36 | -------------------------------------------------------------------------------- /feedforbot/exceptions.py: -------------------------------------------------------------------------------- 1 | class FeedForBotError( 2 | Exception, 3 | ): 4 | ... 5 | 6 | 7 | class ListenerReceiveError( 8 | FeedForBotError, 9 | ): 10 | ... 11 | 12 | 13 | class TransportSendError( 14 | FeedForBotError, 15 | ): 16 | ... 17 | -------------------------------------------------------------------------------- /feedforbot/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger("feedforbot") 4 | -------------------------------------------------------------------------------- /feedforbot/utils.py: -------------------------------------------------------------------------------- 1 | from click import Context, echo 2 | 3 | from feedforbot import VERSION 4 | 5 | 6 | def echo_version( 7 | ctx: Context, 8 | param: bool, # noqa: ARG001 9 | value: str, 10 | ) -> None: 11 | if not value or ctx.resilient_parsing: 12 | return 13 | echo(VERSION) 14 | ctx.exit() 15 | -------------------------------------------------------------------------------- /feedforbot/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.1.0" 2 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "aiocron" 5 | version = "1.8" 6 | description = "Crontabs for asyncio" 7 | optional = false 8 | python-versions = "*" 9 | files = [ 10 | {file = "aiocron-1.8-py3-none-any.whl", hash = "sha256:b6313214c311b62aa2220e872b94139b648631b3103d062ef29e5d3230ddce6d"}, 11 | {file = "aiocron-1.8.tar.gz", hash = "sha256:48546513faf2eb7901e65a64eba7b653c80106ed00ed9ca3419c3d10b6555a01"}, 12 | ] 13 | 14 | [package.dependencies] 15 | croniter = "*" 16 | tzlocal = "*" 17 | 18 | [package.extras] 19 | test = ["coverage"] 20 | 21 | [[package]] 22 | name = "aiofiles" 23 | version = "24.1.0" 24 | description = "File support for asyncio." 25 | optional = false 26 | python-versions = ">=3.8" 27 | files = [ 28 | {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, 29 | {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, 30 | ] 31 | 32 | [[package]] 33 | name = "annotated-types" 34 | version = "0.7.0" 35 | description = "Reusable constraint types to use with typing.Annotated" 36 | optional = false 37 | python-versions = ">=3.8" 38 | files = [ 39 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 40 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 41 | ] 42 | 43 | [[package]] 44 | name = "anyio" 45 | version = "4.7.0" 46 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 47 | optional = false 48 | python-versions = ">=3.9" 49 | files = [ 50 | {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, 51 | {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, 52 | ] 53 | 54 | [package.dependencies] 55 | exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} 56 | idna = ">=2.8" 57 | sniffio = ">=1.1" 58 | typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} 59 | 60 | [package.extras] 61 | doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] 62 | test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] 63 | trio = ["trio (>=0.26.1)"] 64 | 65 | [[package]] 66 | name = "bandit" 67 | version = "1.8.0" 68 | description = "Security oriented static analyser for python code." 69 | optional = false 70 | python-versions = ">=3.9" 71 | files = [ 72 | {file = "bandit-1.8.0-py3-none-any.whl", hash = "sha256:b1a61d829c0968aed625381e426aa378904b996529d048f8d908fa28f6b13e38"}, 73 | {file = "bandit-1.8.0.tar.gz", hash = "sha256:b5bfe55a095abd9fe20099178a7c6c060f844bfd4fe4c76d28e35e4c52b9d31e"}, 74 | ] 75 | 76 | [package.dependencies] 77 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} 78 | PyYAML = ">=5.3.1" 79 | rich = "*" 80 | stevedore = ">=1.20.0" 81 | 82 | [package.extras] 83 | baseline = ["GitPython (>=3.1.30)"] 84 | sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] 85 | test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] 86 | toml = ["tomli (>=1.1.0)"] 87 | yaml = ["PyYAML"] 88 | 89 | [[package]] 90 | name = "beautifulsoup4" 91 | version = "4.12.3" 92 | description = "Screen-scraping library" 93 | optional = false 94 | python-versions = ">=3.6.0" 95 | files = [ 96 | {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, 97 | {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, 98 | ] 99 | 100 | [package.dependencies] 101 | soupsieve = ">1.2" 102 | 103 | [package.extras] 104 | cchardet = ["cchardet"] 105 | chardet = ["chardet"] 106 | charset-normalizer = ["charset-normalizer"] 107 | html5lib = ["html5lib"] 108 | lxml = ["lxml"] 109 | 110 | [[package]] 111 | name = "black" 112 | version = "24.10.0" 113 | description = "The uncompromising code formatter." 114 | optional = false 115 | python-versions = ">=3.9" 116 | files = [ 117 | {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, 118 | {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, 119 | {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, 120 | {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, 121 | {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, 122 | {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, 123 | {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, 124 | {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, 125 | {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, 126 | {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, 127 | {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, 128 | {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, 129 | {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, 130 | {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, 131 | {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, 132 | {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, 133 | {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, 134 | {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, 135 | {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, 136 | {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, 137 | {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, 138 | {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, 139 | ] 140 | 141 | [package.dependencies] 142 | click = ">=8.0.0" 143 | mypy-extensions = ">=0.4.3" 144 | packaging = ">=22.0" 145 | pathspec = ">=0.9.0" 146 | platformdirs = ">=2" 147 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 148 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 149 | 150 | [package.extras] 151 | colorama = ["colorama (>=0.4.3)"] 152 | d = ["aiohttp (>=3.10)"] 153 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 154 | uvloop = ["uvloop (>=0.15.2)"] 155 | 156 | [[package]] 157 | name = "certifi" 158 | version = "2024.12.14" 159 | description = "Python package for providing Mozilla's CA Bundle." 160 | optional = false 161 | python-versions = ">=3.6" 162 | files = [ 163 | {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, 164 | {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, 165 | ] 166 | 167 | [[package]] 168 | name = "cfgv" 169 | version = "3.4.0" 170 | description = "Validate configuration and produce human readable error messages." 171 | optional = false 172 | python-versions = ">=3.8" 173 | files = [ 174 | {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, 175 | {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, 176 | ] 177 | 178 | [[package]] 179 | name = "charset-normalizer" 180 | version = "3.4.1" 181 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 182 | optional = false 183 | python-versions = ">=3.7" 184 | files = [ 185 | {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, 186 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, 187 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, 188 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, 189 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, 190 | {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, 191 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, 192 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, 193 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, 194 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, 195 | {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, 196 | {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, 197 | {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, 198 | {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, 199 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, 200 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, 201 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, 202 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, 203 | {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, 204 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, 205 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, 206 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, 207 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, 208 | {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, 209 | {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, 210 | {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, 211 | {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, 212 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, 213 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, 214 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, 215 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, 216 | {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, 217 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, 218 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, 219 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, 220 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, 221 | {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, 222 | {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, 223 | {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, 224 | {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, 225 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, 226 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, 227 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, 228 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, 229 | {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, 230 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, 231 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, 232 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, 233 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, 234 | {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, 235 | {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, 236 | {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, 237 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, 238 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, 239 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, 240 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, 241 | {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, 242 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, 243 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, 244 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, 245 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, 246 | {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, 247 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, 248 | {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, 249 | {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, 250 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, 251 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, 252 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, 253 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, 254 | {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, 255 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, 256 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, 257 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, 258 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, 259 | {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, 260 | {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, 261 | {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, 262 | {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, 263 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, 264 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, 265 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, 266 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, 267 | {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, 268 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, 269 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, 270 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, 271 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, 272 | {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, 273 | {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, 274 | {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, 275 | {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, 276 | {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, 277 | ] 278 | 279 | [[package]] 280 | name = "click" 281 | version = "8.1.8" 282 | description = "Composable command line interface toolkit" 283 | optional = false 284 | python-versions = ">=3.7" 285 | files = [ 286 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 287 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 288 | ] 289 | 290 | [package.dependencies] 291 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 292 | 293 | [[package]] 294 | name = "colorama" 295 | version = "0.4.6" 296 | description = "Cross-platform colored terminal text." 297 | optional = false 298 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 299 | files = [ 300 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 301 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 302 | ] 303 | 304 | [[package]] 305 | name = "croniter" 306 | version = "6.0.0" 307 | description = "croniter provides iteration for datetime object with cron like format" 308 | optional = false 309 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.6" 310 | files = [ 311 | {file = "croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368"}, 312 | {file = "croniter-6.0.0.tar.gz", hash = "sha256:37c504b313956114a983ece2c2b07790b1f1094fe9d81cc94739214748255577"}, 313 | ] 314 | 315 | [package.dependencies] 316 | python-dateutil = "*" 317 | pytz = ">2021.1" 318 | 319 | [[package]] 320 | name = "distlib" 321 | version = "0.3.9" 322 | description = "Distribution utilities" 323 | optional = false 324 | python-versions = "*" 325 | files = [ 326 | {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, 327 | {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, 328 | ] 329 | 330 | [[package]] 331 | name = "dparse" 332 | version = "0.6.4" 333 | description = "A parser for Python dependency files" 334 | optional = false 335 | python-versions = ">=3.7" 336 | files = [ 337 | {file = "dparse-0.6.4-py3-none-any.whl", hash = "sha256:fbab4d50d54d0e739fbb4dedfc3d92771003a5b9aa8545ca7a7045e3b174af57"}, 338 | {file = "dparse-0.6.4.tar.gz", hash = "sha256:90b29c39e3edc36c6284c82c4132648eaf28a01863eb3c231c2512196132201a"}, 339 | ] 340 | 341 | [package.dependencies] 342 | packaging = "*" 343 | tomli = {version = "*", markers = "python_version < \"3.11\""} 344 | 345 | [package.extras] 346 | all = ["pipenv", "poetry", "pyyaml"] 347 | conda = ["pyyaml"] 348 | pipenv = ["pipenv"] 349 | poetry = ["poetry"] 350 | 351 | [[package]] 352 | name = "exceptiongroup" 353 | version = "1.2.2" 354 | description = "Backport of PEP 654 (exception groups)" 355 | optional = false 356 | python-versions = ">=3.7" 357 | files = [ 358 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 359 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 360 | ] 361 | 362 | [package.extras] 363 | test = ["pytest (>=6)"] 364 | 365 | [[package]] 366 | name = "feedparser" 367 | version = "6.0.11" 368 | description = "Universal feed parser, handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds" 369 | optional = false 370 | python-versions = ">=3.6" 371 | files = [ 372 | {file = "feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45"}, 373 | {file = "feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5"}, 374 | ] 375 | 376 | [package.dependencies] 377 | sgmllib3k = "*" 378 | 379 | [[package]] 380 | name = "filelock" 381 | version = "3.16.1" 382 | description = "A platform independent file lock." 383 | optional = false 384 | python-versions = ">=3.8" 385 | files = [ 386 | {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, 387 | {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, 388 | ] 389 | 390 | [package.extras] 391 | docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] 392 | testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] 393 | typing = ["typing-extensions (>=4.12.2)"] 394 | 395 | [[package]] 396 | name = "freezegun" 397 | version = "1.5.1" 398 | description = "Let your Python tests travel through time" 399 | optional = false 400 | python-versions = ">=3.7" 401 | files = [ 402 | {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, 403 | {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, 404 | ] 405 | 406 | [package.dependencies] 407 | python-dateutil = ">=2.7" 408 | 409 | [[package]] 410 | name = "h11" 411 | version = "0.14.0" 412 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 413 | optional = false 414 | python-versions = ">=3.7" 415 | files = [ 416 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 417 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 418 | ] 419 | 420 | [[package]] 421 | name = "httpcore" 422 | version = "1.0.7" 423 | description = "A minimal low-level HTTP client." 424 | optional = false 425 | python-versions = ">=3.8" 426 | files = [ 427 | {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, 428 | {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, 429 | ] 430 | 431 | [package.dependencies] 432 | certifi = "*" 433 | h11 = ">=0.13,<0.15" 434 | 435 | [package.extras] 436 | asyncio = ["anyio (>=4.0,<5.0)"] 437 | http2 = ["h2 (>=3,<5)"] 438 | socks = ["socksio (==1.*)"] 439 | trio = ["trio (>=0.22.0,<1.0)"] 440 | 441 | [[package]] 442 | name = "httpx" 443 | version = "0.28.1" 444 | description = "The next generation HTTP client." 445 | optional = false 446 | python-versions = ">=3.8" 447 | files = [ 448 | {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, 449 | {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, 450 | ] 451 | 452 | [package.dependencies] 453 | anyio = "*" 454 | certifi = "*" 455 | httpcore = "==1.*" 456 | idna = "*" 457 | 458 | [package.extras] 459 | brotli = ["brotli", "brotlicffi"] 460 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] 461 | http2 = ["h2 (>=3,<5)"] 462 | socks = ["socksio (==1.*)"] 463 | zstd = ["zstandard (>=0.18.0)"] 464 | 465 | [[package]] 466 | name = "identify" 467 | version = "2.6.3" 468 | description = "File identification library for Python" 469 | optional = false 470 | python-versions = ">=3.9" 471 | files = [ 472 | {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, 473 | {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, 474 | ] 475 | 476 | [package.extras] 477 | license = ["ukkonen"] 478 | 479 | [[package]] 480 | name = "idna" 481 | version = "3.10" 482 | description = "Internationalized Domain Names in Applications (IDNA)" 483 | optional = false 484 | python-versions = ">=3.6" 485 | files = [ 486 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 487 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 488 | ] 489 | 490 | [package.extras] 491 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 492 | 493 | [[package]] 494 | name = "iniconfig" 495 | version = "2.0.0" 496 | description = "brain-dead simple config-ini parsing" 497 | optional = false 498 | python-versions = ">=3.7" 499 | files = [ 500 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 501 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 502 | ] 503 | 504 | [[package]] 505 | name = "isort" 506 | version = "5.13.2" 507 | description = "A Python utility / library to sort Python imports." 508 | optional = false 509 | python-versions = ">=3.8.0" 510 | files = [ 511 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 512 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 513 | ] 514 | 515 | [package.dependencies] 516 | colorama = {version = ">=0.4.6", optional = true, markers = "extra == \"colors\""} 517 | 518 | [package.extras] 519 | colors = ["colorama (>=0.4.6)"] 520 | 521 | [[package]] 522 | name = "jinja2" 523 | version = "3.1.5" 524 | description = "A very fast and expressive template engine." 525 | optional = false 526 | python-versions = ">=3.7" 527 | files = [ 528 | {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, 529 | {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, 530 | ] 531 | 532 | [package.dependencies] 533 | MarkupSafe = ">=2.0" 534 | 535 | [package.extras] 536 | i18n = ["Babel (>=2.7)"] 537 | 538 | [[package]] 539 | name = "markdown-it-py" 540 | version = "3.0.0" 541 | description = "Python port of markdown-it. Markdown parsing, done right!" 542 | optional = false 543 | python-versions = ">=3.8" 544 | files = [ 545 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 546 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 547 | ] 548 | 549 | [package.dependencies] 550 | mdurl = ">=0.1,<1.0" 551 | 552 | [package.extras] 553 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 554 | code-style = ["pre-commit (>=3.0,<4.0)"] 555 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 556 | linkify = ["linkify-it-py (>=1,<3)"] 557 | plugins = ["mdit-py-plugins"] 558 | profiling = ["gprof2dot"] 559 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 560 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 561 | 562 | [[package]] 563 | name = "markupsafe" 564 | version = "3.0.2" 565 | description = "Safely add untrusted strings to HTML/XML markup." 566 | optional = false 567 | python-versions = ">=3.9" 568 | files = [ 569 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, 570 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, 571 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, 572 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, 573 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, 574 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, 575 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, 576 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, 577 | {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, 578 | {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, 579 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, 580 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, 581 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, 582 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, 583 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, 584 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, 585 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, 586 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, 587 | {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, 588 | {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, 589 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, 590 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, 591 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, 592 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, 593 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, 594 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, 595 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, 596 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, 597 | {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, 598 | {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, 599 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, 600 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, 601 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, 602 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, 603 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, 604 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, 605 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, 606 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, 607 | {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, 608 | {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, 609 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, 610 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, 611 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, 612 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, 613 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, 614 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, 615 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, 616 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, 617 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, 618 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, 619 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, 620 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, 621 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, 622 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, 623 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, 624 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, 625 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, 626 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, 627 | {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, 628 | {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, 629 | {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, 630 | ] 631 | 632 | [[package]] 633 | name = "mdurl" 634 | version = "0.1.2" 635 | description = "Markdown URL utilities" 636 | optional = false 637 | python-versions = ">=3.7" 638 | files = [ 639 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 640 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 641 | ] 642 | 643 | [[package]] 644 | name = "mypy" 645 | version = "1.14.0" 646 | description = "Optional static typing for Python" 647 | optional = false 648 | python-versions = ">=3.8" 649 | files = [ 650 | {file = "mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87"}, 651 | {file = "mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179"}, 652 | {file = "mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e"}, 653 | {file = "mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3"}, 654 | {file = "mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44"}, 655 | {file = "mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a"}, 656 | {file = "mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc"}, 657 | {file = "mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015"}, 658 | {file = "mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb"}, 659 | {file = "mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc"}, 660 | {file = "mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd"}, 661 | {file = "mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1"}, 662 | {file = "mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63"}, 663 | {file = "mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d"}, 664 | {file = "mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba"}, 665 | {file = "mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741"}, 666 | {file = "mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7"}, 667 | {file = "mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8"}, 668 | {file = "mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc"}, 669 | {file = "mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f"}, 670 | {file = "mypy-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b16738b1d80ec4334654e89e798eb705ac0c36c8a5c4798496cd3623aa02286"}, 671 | {file = "mypy-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10065fcebb7c66df04b05fc799a854b1ae24d9963c8bb27e9064a9bdb43aa8ad"}, 672 | {file = "mypy-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbb7d683fa6bdecaa106e8368aa973ecc0ddb79a9eaeb4b821591ecd07e9e03c"}, 673 | {file = "mypy-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3498cb55448dc5533e438cd13d6ddd28654559c8c4d1fd4b5ca57a31b81bac01"}, 674 | {file = "mypy-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c7b243408ea43755f3a21a0a08e5c5ae30eddb4c58a80f415ca6b118816e60aa"}, 675 | {file = "mypy-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14117b9da3305b39860d0aa34b8f1ff74d209a368829a584eb77524389a9c13e"}, 676 | {file = "mypy-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af98c5a958f9c37404bd4eef2f920b94874507e146ed6ee559f185b8809c44cc"}, 677 | {file = "mypy-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b343a1d3989547024377c2ba0dca9c74a2428ad6ed24283c213af8dbb0710b"}, 678 | {file = "mypy-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cdb5563c1726c85fb201be383168f8c866032db95e1095600806625b3a648cb7"}, 679 | {file = "mypy-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:74e925649c1ee0a79aa7448baf2668d81cc287dc5782cff6a04ee93f40fb8d3f"}, 680 | {file = "mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab"}, 681 | {file = "mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6"}, 682 | ] 683 | 684 | [package.dependencies] 685 | mypy_extensions = ">=1.0.0" 686 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 687 | typing_extensions = ">=4.6.0" 688 | 689 | [package.extras] 690 | dmypy = ["psutil (>=4.0)"] 691 | faster-cache = ["orjson"] 692 | install-types = ["pip"] 693 | mypyc = ["setuptools (>=50)"] 694 | reports = ["lxml"] 695 | 696 | [[package]] 697 | name = "mypy-extensions" 698 | version = "1.0.0" 699 | description = "Type system extensions for programs checked with the mypy type checker." 700 | optional = false 701 | python-versions = ">=3.5" 702 | files = [ 703 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 704 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 705 | ] 706 | 707 | [[package]] 708 | name = "nodeenv" 709 | version = "1.9.1" 710 | description = "Node.js virtual environment builder" 711 | optional = false 712 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 713 | files = [ 714 | {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, 715 | {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, 716 | ] 717 | 718 | [[package]] 719 | name = "orjson" 720 | version = "3.10.12" 721 | description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" 722 | optional = false 723 | python-versions = ">=3.8" 724 | files = [ 725 | {file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"}, 726 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"}, 727 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"}, 728 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"}, 729 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"}, 730 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"}, 731 | {file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"}, 732 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"}, 733 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"}, 734 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"}, 735 | {file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"}, 736 | {file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"}, 737 | {file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"}, 738 | {file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"}, 739 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"}, 740 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"}, 741 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"}, 742 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"}, 743 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"}, 744 | {file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"}, 745 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"}, 746 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"}, 747 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"}, 748 | {file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"}, 749 | {file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"}, 750 | {file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"}, 751 | {file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"}, 752 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"}, 753 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"}, 754 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"}, 755 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"}, 756 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"}, 757 | {file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"}, 758 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"}, 759 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"}, 760 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"}, 761 | {file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"}, 762 | {file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"}, 763 | {file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"}, 764 | {file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"}, 765 | {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"}, 766 | {file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"}, 767 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"}, 768 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"}, 769 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"}, 770 | {file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"}, 771 | {file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"}, 772 | {file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"}, 773 | {file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"}, 774 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"}, 775 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"}, 776 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"}, 777 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"}, 778 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"}, 779 | {file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"}, 780 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"}, 781 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"}, 782 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"}, 783 | {file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"}, 784 | {file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"}, 785 | {file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"}, 786 | {file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"}, 787 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"}, 788 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"}, 789 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"}, 790 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"}, 791 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"}, 792 | {file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"}, 793 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"}, 794 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"}, 795 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"}, 796 | {file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"}, 797 | {file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"}, 798 | {file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"}, 799 | {file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"}, 800 | ] 801 | 802 | [[package]] 803 | name = "packaging" 804 | version = "24.2" 805 | description = "Core utilities for Python packages" 806 | optional = false 807 | python-versions = ">=3.8" 808 | files = [ 809 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 810 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 811 | ] 812 | 813 | [[package]] 814 | name = "pathspec" 815 | version = "0.12.1" 816 | description = "Utility library for gitignore style pattern matching of file paths." 817 | optional = false 818 | python-versions = ">=3.8" 819 | files = [ 820 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 821 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 822 | ] 823 | 824 | [[package]] 825 | name = "pbr" 826 | version = "6.1.0" 827 | description = "Python Build Reasonableness" 828 | optional = false 829 | python-versions = ">=2.6" 830 | files = [ 831 | {file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"}, 832 | {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, 833 | ] 834 | 835 | [[package]] 836 | name = "platformdirs" 837 | version = "4.3.6" 838 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 839 | optional = false 840 | python-versions = ">=3.8" 841 | files = [ 842 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 843 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 844 | ] 845 | 846 | [package.extras] 847 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 848 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 849 | type = ["mypy (>=1.11.2)"] 850 | 851 | [[package]] 852 | name = "pluggy" 853 | version = "1.5.0" 854 | description = "plugin and hook calling mechanisms for python" 855 | optional = false 856 | python-versions = ">=3.8" 857 | files = [ 858 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 859 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 860 | ] 861 | 862 | [package.extras] 863 | dev = ["pre-commit", "tox"] 864 | testing = ["pytest", "pytest-benchmark"] 865 | 866 | [[package]] 867 | name = "pre-commit" 868 | version = "4.0.1" 869 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 870 | optional = false 871 | python-versions = ">=3.9" 872 | files = [ 873 | {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, 874 | {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, 875 | ] 876 | 877 | [package.dependencies] 878 | cfgv = ">=2.0.0" 879 | identify = ">=1.0.0" 880 | nodeenv = ">=0.11.1" 881 | pyyaml = ">=5.1" 882 | virtualenv = ">=20.10.0" 883 | 884 | [[package]] 885 | name = "pydantic" 886 | version = "2.10.4" 887 | description = "Data validation using Python type hints" 888 | optional = false 889 | python-versions = ">=3.8" 890 | files = [ 891 | {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, 892 | {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, 893 | ] 894 | 895 | [package.dependencies] 896 | annotated-types = ">=0.6.0" 897 | pydantic-core = "2.27.2" 898 | typing-extensions = ">=4.12.2" 899 | 900 | [package.extras] 901 | email = ["email-validator (>=2.0.0)"] 902 | timezone = ["tzdata"] 903 | 904 | [[package]] 905 | name = "pydantic-core" 906 | version = "2.27.2" 907 | description = "Core functionality for Pydantic validation and serialization" 908 | optional = false 909 | python-versions = ">=3.8" 910 | files = [ 911 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, 912 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, 913 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, 914 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, 915 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, 916 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, 917 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, 918 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, 919 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, 920 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, 921 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, 922 | {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, 923 | {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, 924 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, 925 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, 926 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, 927 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, 928 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, 929 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, 930 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, 931 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, 932 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, 933 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, 934 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, 935 | {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, 936 | {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, 937 | {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, 938 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, 939 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, 940 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, 941 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, 942 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, 943 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, 944 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, 945 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, 946 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, 947 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, 948 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, 949 | {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, 950 | {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, 951 | {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, 952 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, 953 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, 954 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, 955 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, 956 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, 957 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, 958 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, 959 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, 960 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, 961 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, 962 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, 963 | {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, 964 | {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, 965 | {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, 966 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, 967 | {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, 968 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, 969 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, 970 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, 971 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, 972 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, 973 | {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, 974 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, 975 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, 976 | {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, 977 | {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, 978 | {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, 979 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, 980 | {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, 981 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, 982 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, 983 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, 984 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, 985 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, 986 | {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, 987 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, 988 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, 989 | {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, 990 | {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, 991 | {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, 992 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, 993 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, 994 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, 995 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, 996 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, 997 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, 998 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, 999 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, 1000 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, 1001 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, 1002 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, 1003 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, 1004 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, 1005 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, 1006 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, 1007 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, 1008 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, 1009 | {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, 1010 | {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, 1011 | ] 1012 | 1013 | [package.dependencies] 1014 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 1015 | 1016 | [[package]] 1017 | name = "pygments" 1018 | version = "2.18.0" 1019 | description = "Pygments is a syntax highlighting package written in Python." 1020 | optional = false 1021 | python-versions = ">=3.8" 1022 | files = [ 1023 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 1024 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 1025 | ] 1026 | 1027 | [package.extras] 1028 | windows-terminal = ["colorama (>=0.4.6)"] 1029 | 1030 | [[package]] 1031 | name = "pytest" 1032 | version = "8.3.4" 1033 | description = "pytest: simple powerful testing with Python" 1034 | optional = false 1035 | python-versions = ">=3.8" 1036 | files = [ 1037 | {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, 1038 | {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, 1039 | ] 1040 | 1041 | [package.dependencies] 1042 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 1043 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 1044 | iniconfig = "*" 1045 | packaging = "*" 1046 | pluggy = ">=1.5,<2" 1047 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 1048 | 1049 | [package.extras] 1050 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 1051 | 1052 | [[package]] 1053 | name = "pytest-asyncio" 1054 | version = "0.25.0" 1055 | description = "Pytest support for asyncio" 1056 | optional = false 1057 | python-versions = ">=3.9" 1058 | files = [ 1059 | {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, 1060 | {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, 1061 | ] 1062 | 1063 | [package.dependencies] 1064 | pytest = ">=8.2,<9" 1065 | 1066 | [package.extras] 1067 | docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] 1068 | testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] 1069 | 1070 | [[package]] 1071 | name = "python-dateutil" 1072 | version = "2.9.0.post0" 1073 | description = "Extensions to the standard Python datetime module" 1074 | optional = false 1075 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1076 | files = [ 1077 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, 1078 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, 1079 | ] 1080 | 1081 | [package.dependencies] 1082 | six = ">=1.5" 1083 | 1084 | [[package]] 1085 | name = "pytz" 1086 | version = "2024.2" 1087 | description = "World timezone definitions, modern and historical" 1088 | optional = false 1089 | python-versions = "*" 1090 | files = [ 1091 | {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, 1092 | {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "pyyaml" 1097 | version = "6.0.2" 1098 | description = "YAML parser and emitter for Python" 1099 | optional = false 1100 | python-versions = ">=3.8" 1101 | files = [ 1102 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 1103 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 1104 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 1105 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 1106 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 1107 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 1108 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 1109 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 1110 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 1111 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 1112 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 1113 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 1114 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 1115 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 1116 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 1117 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 1118 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 1119 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 1120 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 1121 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 1122 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 1123 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 1124 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 1125 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 1126 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 1127 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 1128 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 1129 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 1130 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 1131 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 1132 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 1133 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 1134 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 1135 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 1136 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 1137 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 1138 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 1139 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 1140 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 1141 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 1142 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 1143 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 1144 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 1145 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 1146 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 1147 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 1148 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 1149 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 1150 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 1151 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 1152 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 1153 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 1154 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "requests" 1159 | version = "2.32.3" 1160 | description = "Python HTTP for Humans." 1161 | optional = false 1162 | python-versions = ">=3.8" 1163 | files = [ 1164 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 1165 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 1166 | ] 1167 | 1168 | [package.dependencies] 1169 | certifi = ">=2017.4.17" 1170 | charset-normalizer = ">=2,<4" 1171 | idna = ">=2.5,<4" 1172 | urllib3 = ">=1.21.1,<3" 1173 | 1174 | [package.extras] 1175 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1176 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1177 | 1178 | [[package]] 1179 | name = "respx" 1180 | version = "0.22.0" 1181 | description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." 1182 | optional = false 1183 | python-versions = ">=3.8" 1184 | files = [ 1185 | {file = "respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0"}, 1186 | {file = "respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91"}, 1187 | ] 1188 | 1189 | [package.dependencies] 1190 | httpx = ">=0.25.0" 1191 | 1192 | [[package]] 1193 | name = "rich" 1194 | version = "13.9.4" 1195 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 1196 | optional = false 1197 | python-versions = ">=3.8.0" 1198 | files = [ 1199 | {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, 1200 | {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, 1201 | ] 1202 | 1203 | [package.dependencies] 1204 | markdown-it-py = ">=2.2.0" 1205 | pygments = ">=2.13.0,<3.0.0" 1206 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} 1207 | 1208 | [package.extras] 1209 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 1210 | 1211 | [[package]] 1212 | name = "ruamel-yaml" 1213 | version = "0.18.6" 1214 | description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" 1215 | optional = false 1216 | python-versions = ">=3.7" 1217 | files = [ 1218 | {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, 1219 | {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, 1220 | ] 1221 | 1222 | [package.dependencies] 1223 | "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} 1224 | 1225 | [package.extras] 1226 | docs = ["mercurial (>5.7)", "ryd"] 1227 | jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] 1228 | 1229 | [[package]] 1230 | name = "ruamel-yaml-clib" 1231 | version = "0.2.12" 1232 | description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" 1233 | optional = false 1234 | python-versions = ">=3.9" 1235 | files = [ 1236 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, 1237 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, 1238 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, 1239 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, 1240 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, 1241 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, 1242 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, 1243 | {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, 1244 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, 1245 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, 1246 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, 1247 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, 1248 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, 1249 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, 1250 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, 1251 | {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, 1252 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, 1253 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, 1254 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, 1255 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, 1256 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, 1257 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, 1258 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, 1259 | {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, 1260 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, 1261 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, 1262 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, 1263 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, 1264 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, 1265 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, 1266 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, 1267 | {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, 1268 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, 1269 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, 1270 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, 1271 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, 1272 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, 1273 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, 1274 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, 1275 | {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, 1276 | {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "ruff" 1281 | version = "0.4.10" 1282 | description = "An extremely fast Python linter and code formatter, written in Rust." 1283 | optional = false 1284 | python-versions = ">=3.7" 1285 | files = [ 1286 | {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, 1287 | {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, 1288 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, 1289 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, 1290 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, 1291 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, 1292 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, 1293 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, 1294 | {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, 1295 | {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, 1296 | {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, 1297 | {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, 1298 | {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, 1299 | {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, 1300 | {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, 1301 | {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, 1302 | {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "safety" 1307 | version = "2.3.4" 1308 | description = "Checks installed dependencies for known vulnerabilities and licenses." 1309 | optional = false 1310 | python-versions = "*" 1311 | files = [ 1312 | {file = "safety-2.3.4-py3-none-any.whl", hash = "sha256:6224dcd9b20986a2b2c5e7acfdfba6bca42bb11b2783b24ed04f32317e5167ea"}, 1313 | {file = "safety-2.3.4.tar.gz", hash = "sha256:b9e74e794e82f54d11f4091c5d820c4d2d81de9f953bf0b4f33ac8bc402ae72c"}, 1314 | ] 1315 | 1316 | [package.dependencies] 1317 | Click = ">=8.0.2" 1318 | dparse = ">=0.6.2" 1319 | packaging = ">=21.0" 1320 | requests = "*" 1321 | "ruamel.yaml" = ">=0.17.21" 1322 | setuptools = ">=19.3" 1323 | 1324 | [package.extras] 1325 | github = ["jinja2 (>=3.1.0)", "pygithub (>=1.43.3)"] 1326 | gitlab = ["python-gitlab (>=1.3.0)"] 1327 | 1328 | [[package]] 1329 | name = "sentry-sdk" 1330 | version = "2.19.2" 1331 | description = "Python client for Sentry (https://sentry.io)" 1332 | optional = false 1333 | python-versions = ">=3.6" 1334 | files = [ 1335 | {file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"}, 1336 | {file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"}, 1337 | ] 1338 | 1339 | [package.dependencies] 1340 | certifi = "*" 1341 | urllib3 = ">=1.26.11" 1342 | 1343 | [package.extras] 1344 | aiohttp = ["aiohttp (>=3.5)"] 1345 | anthropic = ["anthropic (>=0.16)"] 1346 | arq = ["arq (>=0.23)"] 1347 | asyncpg = ["asyncpg (>=0.23)"] 1348 | beam = ["apache-beam (>=2.12)"] 1349 | bottle = ["bottle (>=0.12.13)"] 1350 | celery = ["celery (>=3)"] 1351 | celery-redbeat = ["celery-redbeat (>=2)"] 1352 | chalice = ["chalice (>=1.16.0)"] 1353 | clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] 1354 | django = ["django (>=1.8)"] 1355 | falcon = ["falcon (>=1.4)"] 1356 | fastapi = ["fastapi (>=0.79.0)"] 1357 | flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] 1358 | grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] 1359 | http2 = ["httpcore[http2] (==1.*)"] 1360 | httpx = ["httpx (>=0.16.0)"] 1361 | huey = ["huey (>=2)"] 1362 | huggingface-hub = ["huggingface_hub (>=0.22)"] 1363 | langchain = ["langchain (>=0.0.210)"] 1364 | launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"] 1365 | litestar = ["litestar (>=2.0.0)"] 1366 | loguru = ["loguru (>=0.5)"] 1367 | openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] 1368 | openfeature = ["openfeature-sdk (>=0.7.1)"] 1369 | opentelemetry = ["opentelemetry-distro (>=0.35b0)"] 1370 | opentelemetry-experimental = ["opentelemetry-distro"] 1371 | pure-eval = ["asttokens", "executing", "pure_eval"] 1372 | pymongo = ["pymongo (>=3.1)"] 1373 | pyspark = ["pyspark (>=2.4.4)"] 1374 | quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] 1375 | rq = ["rq (>=0.6)"] 1376 | sanic = ["sanic (>=0.8)"] 1377 | sqlalchemy = ["sqlalchemy (>=1.2)"] 1378 | starlette = ["starlette (>=0.19.1)"] 1379 | starlite = ["starlite (>=1.48)"] 1380 | tornado = ["tornado (>=6)"] 1381 | 1382 | [[package]] 1383 | name = "setuptools" 1384 | version = "75.6.0" 1385 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 1386 | optional = false 1387 | python-versions = ">=3.9" 1388 | files = [ 1389 | {file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"}, 1390 | {file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"}, 1391 | ] 1392 | 1393 | [package.extras] 1394 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"] 1395 | core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] 1396 | cover = ["pytest-cov"] 1397 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] 1398 | enabler = ["pytest-enabler (>=2.2)"] 1399 | test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] 1400 | type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"] 1401 | 1402 | [[package]] 1403 | name = "sgmllib3k" 1404 | version = "1.0.0" 1405 | description = "Py3k port of sgmllib." 1406 | optional = false 1407 | python-versions = "*" 1408 | files = [ 1409 | {file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"}, 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "six" 1414 | version = "1.17.0" 1415 | description = "Python 2 and 3 compatibility utilities" 1416 | optional = false 1417 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1418 | files = [ 1419 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, 1420 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "sniffio" 1425 | version = "1.3.1" 1426 | description = "Sniff out which async library your code is running under" 1427 | optional = false 1428 | python-versions = ">=3.7" 1429 | files = [ 1430 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 1431 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "soupsieve" 1436 | version = "2.6" 1437 | description = "A modern CSS selector implementation for Beautiful Soup." 1438 | optional = false 1439 | python-versions = ">=3.8" 1440 | files = [ 1441 | {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, 1442 | {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "stevedore" 1447 | version = "5.4.0" 1448 | description = "Manage dynamic plugins for Python applications" 1449 | optional = false 1450 | python-versions = ">=3.9" 1451 | files = [ 1452 | {file = "stevedore-5.4.0-py3-none-any.whl", hash = "sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857"}, 1453 | {file = "stevedore-5.4.0.tar.gz", hash = "sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d"}, 1454 | ] 1455 | 1456 | [package.dependencies] 1457 | pbr = ">=2.0.0" 1458 | 1459 | [[package]] 1460 | name = "tomli" 1461 | version = "2.2.1" 1462 | description = "A lil' TOML parser" 1463 | optional = false 1464 | python-versions = ">=3.8" 1465 | files = [ 1466 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 1467 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 1468 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 1469 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 1470 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 1471 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 1472 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 1473 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 1474 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 1475 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 1476 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 1477 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 1478 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 1479 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 1480 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 1481 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 1482 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 1483 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 1484 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 1485 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 1486 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 1487 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 1488 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 1489 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 1490 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 1491 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 1492 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 1493 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 1494 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 1495 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 1496 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 1497 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 1498 | ] 1499 | 1500 | [[package]] 1501 | name = "types-aiofiles" 1502 | version = "23.2.0.20240623" 1503 | description = "Typing stubs for aiofiles" 1504 | optional = false 1505 | python-versions = ">=3.8" 1506 | files = [ 1507 | {file = "types-aiofiles-23.2.0.20240623.tar.gz", hash = "sha256:d515b2fa46bf894aff45a364a704f050de3898344fd6c5994d58dc8b59ab71e6"}, 1508 | {file = "types_aiofiles-23.2.0.20240623-py3-none-any.whl", hash = "sha256:70597b29fc40c8583b6d755814b2cd5fcdb6785622e82d74ef499f9066316e08"}, 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "types-beautifulsoup4" 1513 | version = "4.12.0.20241020" 1514 | description = "Typing stubs for beautifulsoup4" 1515 | optional = false 1516 | python-versions = ">=3.8" 1517 | files = [ 1518 | {file = "types-beautifulsoup4-4.12.0.20241020.tar.gz", hash = "sha256:158370d08d0cd448bd11b132a50ff5279237a5d4b5837beba074de152a513059"}, 1519 | {file = "types_beautifulsoup4-4.12.0.20241020-py3-none-any.whl", hash = "sha256:c95e66ce15a4f5f0835f7fbc5cd886321ae8294f977c495424eaf4225307fd30"}, 1520 | ] 1521 | 1522 | [package.dependencies] 1523 | types-html5lib = "*" 1524 | 1525 | [[package]] 1526 | name = "types-html5lib" 1527 | version = "1.1.11.20241018" 1528 | description = "Typing stubs for html5lib" 1529 | optional = false 1530 | python-versions = ">=3.8" 1531 | files = [ 1532 | {file = "types-html5lib-1.1.11.20241018.tar.gz", hash = "sha256:98042555ff78d9e3a51c77c918b1041acbb7eb6c405408d8a9e150ff5beccafa"}, 1533 | {file = "types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403"}, 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "types-pyyaml" 1538 | version = "6.0.12.20241221" 1539 | description = "Typing stubs for PyYAML" 1540 | optional = false 1541 | python-versions = ">=3.8" 1542 | files = [ 1543 | {file = "types_PyYAML-6.0.12.20241221-py3-none-any.whl", hash = "sha256:0657a4ff8411a030a2116a196e8e008ea679696b5b1a8e1a6aa8ebb737b34688"}, 1544 | {file = "types_pyyaml-6.0.12.20241221.tar.gz", hash = "sha256:4f149aa893ff6a46889a30af4c794b23833014c469cc57cbc3ad77498a58996f"}, 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "typing-extensions" 1549 | version = "4.12.2" 1550 | description = "Backported and Experimental Type Hints for Python 3.8+" 1551 | optional = false 1552 | python-versions = ">=3.8" 1553 | files = [ 1554 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 1555 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 1556 | ] 1557 | 1558 | [[package]] 1559 | name = "tzdata" 1560 | version = "2024.2" 1561 | description = "Provider of IANA time zone data" 1562 | optional = false 1563 | python-versions = ">=2" 1564 | files = [ 1565 | {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, 1566 | {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, 1567 | ] 1568 | 1569 | [[package]] 1570 | name = "tzlocal" 1571 | version = "5.2" 1572 | description = "tzinfo object for the local timezone" 1573 | optional = false 1574 | python-versions = ">=3.8" 1575 | files = [ 1576 | {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, 1577 | {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, 1578 | ] 1579 | 1580 | [package.dependencies] 1581 | tzdata = {version = "*", markers = "platform_system == \"Windows\""} 1582 | 1583 | [package.extras] 1584 | devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] 1585 | 1586 | [[package]] 1587 | name = "urllib3" 1588 | version = "2.3.0" 1589 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1590 | optional = false 1591 | python-versions = ">=3.9" 1592 | files = [ 1593 | {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, 1594 | {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, 1595 | ] 1596 | 1597 | [package.extras] 1598 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1599 | h2 = ["h2 (>=4,<5)"] 1600 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1601 | zstd = ["zstandard (>=0.18.0)"] 1602 | 1603 | [[package]] 1604 | name = "virtualenv" 1605 | version = "20.28.0" 1606 | description = "Virtual Python Environment builder" 1607 | optional = false 1608 | python-versions = ">=3.8" 1609 | files = [ 1610 | {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, 1611 | {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, 1612 | ] 1613 | 1614 | [package.dependencies] 1615 | distlib = ">=0.3.7,<1" 1616 | filelock = ">=3.12.2,<4" 1617 | platformdirs = ">=3.9.1,<5" 1618 | 1619 | [package.extras] 1620 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] 1621 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] 1622 | 1623 | [metadata] 1624 | lock-version = "2.0" 1625 | python-versions = ">=3.10" 1626 | content-hash = "4ee07811a849f113649987377bf7c30a4a76c5e0a5b3a292bc92630188976b85" 1627 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "FeedForBot" 3 | version = "0.1.0" 4 | description = "Forward links from RSS/Atom feeds to messengers" 5 | authors = ["Aleksandr Shpak "] 6 | readme = "README.md" 7 | homepage = "https://github.com/shpaker/feedforbot" 8 | repository = "https://github.com/shpaker/feedforbot" 9 | 10 | [tool.poetry.scripts] 11 | feedforbot = 'feedforbot.cli:main' 12 | 13 | [tool.poetry.dependencies] 14 | python = ">=3.10" 15 | aiocron = "^1.8" 16 | aiofiles = ">=23.2" 17 | beautifulsoup4 = "^4.12" 18 | click = "^8.1" 19 | feedparser = "^6.0" 20 | httpx = ">=0.26" 21 | jinja2 = "^3.1" 22 | orjson = "^3.9" 23 | pydantic = ">=2.0,<3.0" 24 | pyyaml = "^6.0" 25 | sentry-sdk = ">=2.0,<3.0" 26 | 27 | [tool.poetry.group.dev.dependencies] 28 | pre-commit = ">=3.5.0" 29 | 30 | [tool.poetry.group.linters.dependencies] 31 | bandit = ">=1.7" 32 | black = ">=24.2" 33 | isort = {extras = ["colors"], version = "^5.13"} 34 | mypy = ">=1.8,<2.0" 35 | ruff = "^0.4" 36 | safety = "^2.0" 37 | types-aiofiles = "^23.2.0" 38 | types-beautifulsoup4 = "^4.12.0.7" 39 | types-pyyaml = "^6.0.12" 40 | 41 | [tool.poetry.group.tests.dependencies] 42 | freezegun = ">=1.2.2" 43 | pytest = ">=7.4.3" 44 | pytest-asyncio = ">=0.21.1" 45 | respx = ">=0.20.2" 46 | 47 | [build-system] 48 | requires = ["poetry-core"] 49 | build-backend = "poetry.core.masonry.api" 50 | 51 | [tool.isort] 52 | multi_line_output = 3 53 | include_trailing_comma = true 54 | force_grid_wrap = 0 55 | use_parentheses = true 56 | ensure_newline_before_comments = true 57 | line_length = 79 58 | src_paths = ["feedforbot", "tests"] 59 | skip = [".mypy_cache", ".pytest_cache", "venv"] 60 | 61 | [tool.mypy] 62 | python_version = "3.10" 63 | plugins = [ 64 | "pydantic.mypy" 65 | ] 66 | follow_imports = "silent" 67 | warn_redundant_casts = true 68 | warn_unused_ignores = true 69 | disallow_any_generics = true 70 | check_untyped_defs = true 71 | no_implicit_reexport = true 72 | disallow_untyped_defs = true 73 | 74 | [tool.pydantic-mypy] 75 | init_forbid_extra = true 76 | init_typed = true 77 | warn_required_dynamic_aliases = true 78 | 79 | [[tool.mypy.overrides]] 80 | module = ["feedparser.*", "aiocron"] 81 | ignore_missing_imports = true 82 | 83 | [tool.black] 84 | line-length = 79 85 | verbose = 1 86 | color = true 87 | exclude = ''' 88 | ( 89 | /( 90 | \.eggs # exclude a few common directories in the 91 | | \.git # root of the project 92 | | \.mypy_cache 93 | | \.pytest_cache 94 | | \.venv 95 | )/ 96 | ) 97 | ''' 98 | 99 | [tool.ruff] 100 | target-version = "py310" 101 | line-length = 120 102 | exclude = [ 103 | ".venv", 104 | ] 105 | lint.ignore = [ 106 | "PLR0913", # Too many arguments to function call 107 | "PTH123", # PTH123 `open("foo")` should be replaced by `Path("foo").open()` 108 | "RUF001", # Docstring contains ambiguous chars 109 | "RUF002", # Docstring contains ambiguous chars 110 | "RUF003", # Docstring contains ambiguous chars 111 | ] 112 | lint.flake8-tidy-imports.ban-relative-imports = "all" 113 | lint.mccabe.max-complexity = 20 114 | lint.select = [ 115 | "F", # Pyflakes 116 | "E", # pycodestyle 117 | "C90", # mccabe 118 | # "I", # isort 119 | # "N", # pep8-naming 120 | # "D", # pydocstyle 121 | "UP", # pyupgrade 122 | "YTT", # flake8-2020 123 | # "ANN", # flake8-annotations 124 | # "S", # flake8-bandit 125 | "BLE", # flake8-blind-except 126 | # "FBT", # flake8-boolean-trap 127 | "B", # flake8-bugbear 128 | # "A", # flake8-builtins 129 | "COM", # flake8-commas 130 | "C4", # flake8-comprehensions 131 | "DTZ", # flake8-datetimez 132 | "T10", # flake8-debugger 133 | # "EM", # flake8-errmsg 134 | "EXE", # flake8-executable 135 | "ISC", # flake8-implicit-str-concat 136 | # "ICN", # flake8-import-conventions 137 | "G", # flake8-logging-format 138 | "INP", # flake8-no-pep420 139 | # "PIE", # flake8-pie 140 | "T20", # flake8-print 141 | # "PT", # flake8-pytest-style 142 | # "Q", # flake8-quotes 143 | "RET", # flake8-return 144 | "SIM", # flake8-simplify 145 | "TID", # flake8-tidy-imports 146 | "TCH", # flake8-type-checking 147 | "ARG", # flake8-unused-arguments 148 | "PTH", # flake8-use-pathlib 149 | "ERA", # eradicate 150 | # "PD", # pandas-vet 151 | # "PGH", # pygrep-hooks 152 | "PL", # Pylint 153 | "TRY", # tryceratops 154 | "RSE", # flake8-raise 155 | "SLF", # flake8-self 156 | "RUF", # Ruff-specific rules 157 | ] 158 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpaker/feedforbot/43fca6b931ebcd823ba95730a6c49bd590a73cf6/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from pytest import fixture 4 | 5 | _MOCK_DIR = Path(__file__).parent / "mocks" 6 | _DEFAULT_EXTENSION = ".xml" 7 | 8 | 9 | @fixture(name="read_mock") 10 | def _read_mock(): 11 | def _func( 12 | name: str, 13 | strip: bool = True, 14 | ) -> str: 15 | if not name.lower().endswith(_DEFAULT_EXTENSION): 16 | name = name + _DEFAULT_EXTENSION 17 | filepath = _MOCK_DIR / name 18 | with open(filepath, "r", encoding="utf-8") as fh: 19 | data = fh.read() 20 | if strip: 21 | data = data.strip() 22 | return data 23 | 24 | return _func 25 | -------------------------------------------------------------------------------- /tests/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpaker/feedforbot/43fca6b931ebcd823ba95730a6c49bd590a73cf6/tests/core/__init__.py -------------------------------------------------------------------------------- /tests/core/listeners/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpaker/feedforbot/43fca6b931ebcd823ba95730a6c49bd590a73cf6/tests/core/listeners/__init__.py -------------------------------------------------------------------------------- /tests/core/listeners/test_rss.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | from typing import Callable 3 | from unittest.mock import AsyncMock 4 | 5 | from freezegun import freeze_time 6 | from pytest import MonkeyPatch, mark 7 | from respx import MockRouter 8 | 9 | from feedforbot.core import listeners 10 | from feedforbot.core.article import ArticleModel 11 | from feedforbot.core.listeners import RSSListener 12 | 13 | 14 | def _http_get_mock( 15 | monkeypatch: MonkeyPatch, 16 | return_value: str, 17 | ) -> None: 18 | monkeypatch.setattr( 19 | listeners, 20 | "make_get_request", 21 | AsyncMock(return_value=return_value), 22 | ) 23 | 24 | 25 | _TESTING_URL = "http://q" 26 | 27 | 28 | @freeze_time("2012-01-14 12:00:01+00:00") 29 | @mark.asyncio 30 | async def test_receive_feed( 31 | read_mock: Callable[[str], str], 32 | respx_mock: MockRouter, 33 | ) -> None: 34 | respx_mock.get(_TESTING_URL).respond(text=read_mock("rss_short")) 35 | listener = RSSListener(url=_TESTING_URL) 36 | feed = await listener.receive() 37 | assert ( 38 | feed[0].model_dump() 39 | == ArticleModel( 40 | id="https://aaa.ccc", 41 | published_at=datetime( 42 | 2022, 11, 23, 19, 22, 24, tzinfo=timezone.utc 43 | ), 44 | grabbed_at=datetime(2012, 1, 14, 12, 0, 1, tzinfo=timezone.utc), 45 | title="FOO", 46 | url="https://aaa.ccc", 47 | text="BAR", 48 | categories=("a", "b", "c"), 49 | ).model_dump() 50 | ) 51 | -------------------------------------------------------------------------------- /tests/core/test_adapters.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpaker/feedforbot/43fca6b931ebcd823ba95730a6c49bd590a73cf6/tests/core/test_adapters.py -------------------------------------------------------------------------------- /tests/mocks/rss_short.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Все публикации подряд 5 | https://asd.asd/s/ 6 | 7 | ru 8 | editor@aaa.ccc 9 | Wed, 23 Nov 2022 19:26:19 GMT 10 | 11 | <![CDATA[FOO]]> 12 | https://aaa.ccc 13 | 14 | Wed, 23 Nov 2022 19:22:24 GMT 15 | 16 | a 17 | b 18 | c 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------