├── .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 | [](https://pypi.python.org/pypi/feedforbot)
5 | [](https://pypi.python.org/pypi/feedforbot)
6 | [](https://hub.docker.com/r/shpaker/feedforbot)
7 | [](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 |
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 |
--------------------------------------------------------------------------------