diff --git a/.flake8 b/.flake8 index 68847e2..3bcf684 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,4 @@ [flake8] max-line-length = 119 -exclude = .git, __pycache__, venv \ No newline at end of file +exclude = .git,__pycache__,venv,.venv +ignore = E203,W503 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 71c7e50..e0b3257 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,216 @@ +# Byte-compiled / optimized / DLL files __pycache__/ -*.pyc +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments .env +.envrc .venv -poetry.lock -*.xlsx -*.json -.DS_Store +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/PythonProject9.iml b/.idea/PythonProject9.iml deleted file mode 100644 index 0b63526..0000000 --- a/.idea/PythonProject9.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index c110604..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - Markdown - - - Python - - - - - UvPackageVersionsInspection - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 1570b0d..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80e16ba --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +# Transaction Analyzer + +Приложение для анализа банковских транзакций с поддержкой веб-интерфейса, отчетов и аналитики. + +## Функциональность + +- 📊 Анализ транзакций из Excel-файлов +- 🌐 Генерация JSON данных для веб-страниц +- 📈 Отчеты и аналитика +- 💰 Расчет кешбэка и инвесткопилки +- 🔍 Поиск и фильтрация транзакций +- 💹 Интеграция с API курсов валют и акций + +# Установка +## 1. Клонирование и настройка +### Клонируйте репозиторий: +```bash +git clone +cd transaction-analyzer +``` + +### Установите зависимости: +```bash +poetry install +``` + +### Активируйте виртуальное окружение +```bash +poetry shell +``` + +## 2. Подготовка данных + +### Вариант A: Используйте тестовые данные + +Сгенерируйте тестовые данные +```bash +python data/sample_data.py +``` +### Вариант B: Загрузите свои данные +1. Поместите ваш файл с транзакциями в папку data/ + +2. Переименуйте его в operations.xlsx + +## 3. Запуск приложения + +Запуск в режиме веб-страниц (генерирует JSON для фронтенда) +```bash +python -m src.main --command web +``` + +Запуск в режиме отчетов (генерирует отчеты в папку reports/) +```bash +python -m src.main --command report +``` + +Запуск в режиме анализа (кешбэк и инвесткопилка) +```bash +python -m src.main --command analyze +``` + +Простой тест функциональности +```bash +python -m src.main --command test +``` + +## Настройки приложения + +### Файл user_settings.json содержит пользовательские настройки: + +Файл user_settings.json содержит пользовательские настройки: + +```json +{ + "user_currencies": ["USD", "EUR", "GBP"], + "user_stocks": ["AAPL", "AMZN", "GOOGL", "MSFT", "TSLA"], + "cashback_rules": { + "Супермаркеты": 0.05, + "Фастфуд": 0.03, + "Транспорт": 0.02, + "default": 0.01 + } +} +``` + +### Параметры настроек: + +* user_currencies - список валют для отслеживания курсов + +* user_stocks - список акций для отслеживания цен + +* cashback_rules - правила расчета кешбэка по категориям + +# Веб-страницы API +## Главная страница +```python +# Генерирует JSON для главной страницы +from src.views import main_page + +data = main_page("2023-12-20 15:30:00") +``` + +### Формат ответа: +```json +{ + "greeting": "Добрый день", + "cards": [ + { + "last_digits": "5814", + "total_spent": 1262.00, + "cashback": 12.62 + } + ], + "top_transactions": [...], + "currency_rates": [...], + "stock_prices": [...] +} +``` +## Страница событий +```python +# Генерирует JSON для страницы событий +from src.views import events_page + +data = events_page("2023-12-20", "M") # M - месяц, W - неделя, Y - год, ALL - все данные +``` + +# Сервисы поиска + +## Простой поиск +```python +from src.services import simple_search + +# Поиск по описанию и категории +results = simple_search(transactions, "магазин") +``` + +## Поиск по телефонным номерам +```python +from src.services import search_by_phone + +# Поиск транзакций с номерами телефонов +results = search_by_phone(transactions) +``` +## Поиск переводов физлицам +```python +from src.services import search_by_person_transfers + +# Поиск переводов физическим лицам +results = search_by_person_transfers(transactions) +``` + +# Генерация отчетов + +## Траты по категории +```python +from src.reports import spending_by_category + +# Траты по категории за последние 3 месяца +report = spending_by_category(transactions, "Супермаркеты", "2023-12-20") +``` +## Траты по дням недели +```python +from src.reports import spending_by_weekday + +# Средние траты по дням недели +report = spending_by_weekday(transactions) +``` +## Все отчеты через главный модуль +```bash +python -m src.main --command report +``` +#### Отчеты сохраняются в папку reports/ в формате JSON. + +# Тестирование + +```bash +# Запуск всех тестов +pytest tests/ -v +``` +```bash +# Запуск конкретного модуля тестов +pytest tests/test_views.py -v +``` +```bash +# Запуск с покрытием кода +pytest tests/ --cov=src --cov-report=html +``` diff --git a/data/operations.xlsx b/data/operations.xlsx new file mode 100644 index 0000000..b65d76a Binary files /dev/null and b/data/operations.xlsx differ diff --git a/data/sample_data.py b/data/sample_data.py new file mode 100644 index 0000000..5eee45a --- /dev/null +++ b/data/sample_data.py @@ -0,0 +1,72 @@ +""" +Скрипт для генерации тестовых данных +""" + +import random +from datetime import datetime, timedelta + +import pandas as pd + + +def generate_sample_data(num_records: int = 1000) -> pd.DataFrame: + """Генерация тестовых данных транзакций""" + + # Категории транзакций + categories = [ + 'Супермаркеты', 'Фастфуд', 'Транспорт', 'Развлечения', + 'Одежда', 'Электроника', 'Здоровье', 'Образование', + 'Переводы', 'Наличные', 'Зарплата', 'Инвестиции' + ] + + # Статусы операций + statuses = ['OK', 'FAILED', 'PENDING'] + + # Номера карт + cards = ['1234567812345814', '1234567812347512', '1234567812340923'] + + # Генерация дат + start_date = datetime(2023, 1, 1) + end_date = datetime(2023, 12, 31) + date_range = (end_date - start_date).days + + data = [] + for i in range(num_records): + # Случайная дата в 2023 году + random_days = random.randint(0, date_range) + operation_date = start_date + timedelta(days=random_days) + payment_date = operation_date + timedelta(days=random.randint(0, 30)) + + # Случайная сумма (большинство - расходы, некоторые - доходы) + is_income = random.random() < 0.2 # 20% доходы + amount = round(random.uniform(100, 50000), 2) + if is_income: + amount = -amount + + transaction = { + 'Дата операции': operation_date.strftime('%d.%m.%Y'), + 'Дата платежа': payment_date.strftime('%d.%m.%Y'), + 'Номер карты': random.choice(cards), + 'Статус': random.choices(statuses, weights=[0.85, 0.1, 0.05])[0], + 'Сумма операции': amount, + 'Валюта операции': 'RUB', + 'Сумма платежа': amount, + 'Валюта платежа': 'RUB', + 'Кешбэк': round(abs(amount) * 0.01, 2) if amount > 0 and random.random() < 0.7 else 0, + 'Категория': random.choice(categories), + 'MCC': random.randint(1000, 9999), + 'Описание': f'Транзакция #{i + 1}', + 'Бонусы (включая кешбэк)': round(abs(amount) * 0.005, 2) if amount > 0 else 0, + 'Округление на «Инвесткопилку»': random.randint(0, 50), + 'Сумма операции с округлением': round(amount / 50) * 50 if amount > 0 else amount + } + + data.append(transaction) + + return pd.DataFrame(data) + + +if __name__ == "__main__": + # Генерация тестовых данных + df = generate_sample_data(1000) + df.to_excel('data/operations.xlsx', index=False) + print("Тестовые данные сохранены в data/operations.xlsx") diff --git a/env.example b/env.example new file mode 100644 index 0000000..11f8414 --- /dev/null +++ b/env.example @@ -0,0 +1,3 @@ +ALPHA_VANTAGE_API_KEY=your_alpha_vantage_key_here +EXCHANGERATE_API_KEY=your_exchangerate_api_key_here +CURRENCY_API_KEY=your_currency_api_key_here diff --git a/logs/transaction_analyzer.log b/logs/transaction_analyzer.log new file mode 100644 index 0000000..d75d15f --- /dev/null +++ b/logs/transaction_analyzer.log @@ -0,0 +1,2130 @@ +2025-09-27 23:14:32,365 - __main__ - INFO - ... +2025-09-27 23:14:32,366 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:14:32,622 - src.utils - INFO - 1000 +2025-09-27 23:14:32,623 - __main__ - INFO - +2025-09-27 23:14:32,623 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:14:32,747 - src.utils - INFO - 1000 +2025-09-27 23:14:32,748 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:14:34,849 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:14:34,978 - src.utils - INFO - 1000 +2025-09-27 23:14:34,979 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:14:34,980 - src.api_client - INFO - Using cached currency rates +2025-09-27 23:14:34,981 - src.api_client - INFO - Using cached stock prices +2025-09-27 23:23:25,440 - __main__ - INFO - ... +2025-09-27 23:23:25,440 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:23:25,684 - src.utils - INFO - 1000 +2025-09-27 23:23:25,685 - __main__ - INFO - +2025-09-27 23:23:25,685 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:23:25,812 - src.utils - INFO - 1000 +2025-09-27 23:23:25,813 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:23:28,055 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:23:28,057 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:23:28,182 - src.utils - INFO - 1000 +2025-09-27 23:23:28,183 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:23:28,184 - src.api_client - INFO - Using cached currency rates +2025-09-27 23:23:29,450 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:30:07,421 - __main__ - INFO - ... +2025-09-27 23:30:07,422 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:30:07,670 - src.utils - INFO - 1000 +2025-09-27 23:30:07,671 - __main__ - INFO - +2025-09-27 23:30:07,671 - __main__ - ERROR - : name 'ReportGenerator' is not defined +2025-09-27 23:38:33,025 - __main__ - INFO - ... +2025-09-27 23:38:33,026 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:38:33,269 - src.utils - INFO - 1000 +2025-09-27 23:38:33,270 - __main__ - INFO - +2025-09-27 23:38:33,493 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:38:33,620 - src.utils - INFO - 1000 +2025-09-27 23:38:33,622 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:38:37,571 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:38:37,573 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:38:37,700 - src.utils - INFO - 1000 +2025-09-27 23:38:37,701 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:38:37,703 - src.api_client - INFO - Using cached currency rates +2025-09-27 23:38:41,146 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:39:37,249 - __main__ - INFO - ... +2025-09-27 23:39:37,249 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:39:37,518 - src.utils - INFO - 1000 +2025-09-27 23:39:37,518 - __main__ - INFO - +2025-09-27 23:39:37,741 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:39:37,869 - src.utils - INFO - 1000 +2025-09-27 23:39:37,871 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:39:42,148 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:39:42,150 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:39:42,279 - src.utils - INFO - 1000 +2025-09-27 23:39:42,280 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:39:42,282 - src.api_client - INFO - Using cached currency rates +2025-09-27 23:39:46,372 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:39:57,888 - __main__ - INFO - ... +2025-09-27 23:39:57,889 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:39:58,139 - src.utils - INFO - 1000 +2025-09-27 23:39:58,140 - __main__ - INFO - +2025-09-27 23:39:58,145 - src.reports - INFO - reports/spending_by_category_20250927_233958.json +2025-09-27 23:39:58,148 - src.reports - INFO - reports/spending_by_weekday_20250927_233958.json +2025-09-27 23:39:58,150 - src.reports - INFO - reports/spending_by_workday_20250927_233958.json +2025-09-27 23:39:58,151 - src.reports - INFO - reports/monthly_summary_20250927_233958.json +2025-09-27 23:40:18,779 - __main__ - INFO - ... +2025-09-27 23:40:18,779 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:40:19,028 - src.utils - INFO - 1000 +2025-09-27 23:40:19,028 - __main__ - INFO - +2025-09-27 23:40:19,029 - src.services - INFO - profitable_cashback_categories +2025-09-27 23:40:19,031 - src.services - WARNING - 9/2025 +2025-09-27 23:40:19,031 - src.services - INFO - profitable_cashback_categories +2025-09-27 23:40:19,038 - src.services - INFO - investment_bank +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,038 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,039 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,040 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,041 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,042 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,043 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,044 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,045 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,046 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,047 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,048 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,049 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,050 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,051 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,052 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,053 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,054 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,055 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,056 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,057 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,058 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,059 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,060 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,061 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,062 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,063 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,064 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,065 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,066 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,067 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,068 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,069 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,070 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,071 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,072 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,073 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,074 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,075 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,076 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,077 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,078 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,079 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,080 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,081 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,082 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,083 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,084 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,085 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,086 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,087 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,088 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,089 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,090 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,091 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,092 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,093 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,094 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,095 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,096 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,097 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,098 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,099 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,100 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,101 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,102 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,103 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,104 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,105 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - WARNING - +2025-09-27 23:40:19,106 - src.services - INFO - investment_bank +2025-09-27 23:45:29,262 - __main__ - INFO - ... +2025-09-27 23:45:29,262 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:45:29,513 - src.utils - INFO - 1000 +2025-09-27 23:45:29,514 - __main__ - INFO - +2025-09-27 23:45:29,741 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:45:29,871 - src.utils - INFO - 1000 +2025-09-27 23:45:29,873 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:45:31,924 - src.api_client - INFO - Using fallback stock prices +2025-09-27 23:45:31,926 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:45:32,054 - src.utils - INFO - 1000 +2025-09-27 23:45:32,055 - src.utils - INFO - 0 2025-09-01 - 2025-09-27 +2025-09-27 23:45:32,056 - src.api_client - INFO - Using cached currency rates +2025-09-27 23:45:33,406 - src.api_client - INFO - Using fallback stock prices +2025-09-28 00:43:06,698 - __main__ - INFO - ... +2025-09-28 00:43:06,699 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:43:06,949 - src.utils - INFO - 1000 +2025-09-28 00:43:06,949 - __main__ - INFO - +2025-09-28 00:43:06,952 - src.reports - INFO - reports/spending_by_category_20250928_004306.json +2025-09-28 00:43:06,954 - src.reports - INFO - reports/spending_by_weekday_20250928_004306.json +2025-09-28 00:43:06,956 - src.reports - INFO - reports/spending_by_workday_20250928_004306.json +2025-09-28 00:43:06,957 - src.reports - INFO - reports/monthly_summary_20250928_004306.json +2025-09-28 00:48:04,163 - __main__ - INFO - ... +2025-09-28 00:48:04,163 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:48:04,422 - src.utils - INFO - 1000 +2025-09-28 00:48:04,423 - __main__ - INFO - +2025-09-28 00:48:04,427 - src.reports - INFO - reports/spending_by_category_20250928_004804.json +2025-09-28 00:48:04,429 - src.reports - INFO - reports/spending_by_weekday_20250928_004804.json +2025-09-28 00:48:04,431 - src.reports - INFO - reports/spending_by_workday_20250928_004804.json +2025-09-28 00:48:04,432 - src.reports - INFO - reports/monthly_summary_20250928_004804.json +2025-09-28 00:49:29,974 - __main__ - INFO - ... +2025-09-28 00:49:29,975 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:49:30,227 - src.utils - INFO - 1000 +2025-09-28 00:49:30,228 - __main__ - INFO - +2025-09-28 00:49:30,470 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:49:30,596 - src.utils - INFO - 1000 +2025-09-28 00:49:30,598 - src.utils - INFO - 0 2025-09-01 - 2025-09-28 +2025-09-28 00:49:32,451 - src.api_client - INFO - Using fallback stock prices +2025-09-28 00:49:32,452 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:49:32,585 - src.utils - INFO - 1000 +2025-09-28 00:49:32,586 - src.utils - INFO - 0 2025-09-01 - 2025-09-28 +2025-09-28 00:49:32,587 - src.api_client - INFO - Using cached currency rates +2025-09-28 00:49:33,863 - src.api_client - INFO - Using fallback stock prices +2025-09-28 00:51:13,698 - __main__ - INFO - ... +2025-09-28 00:51:13,699 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:51:13,953 - src.utils - INFO - 1000 +2025-09-28 00:51:13,953 - __main__ - INFO - +2025-09-28 00:51:13,960 - src.services - INFO - simple_search +2025-09-28 00:51:13,961 - src.services - INFO - simple_search +2025-09-28 00:51:31,514 - __main__ - INFO - ... +2025-09-28 00:51:31,514 - src.utils - INFO - data/operations.xlsx +2025-09-28 00:51:31,759 - src.utils - INFO - 1000 +2025-09-28 00:51:31,759 - __main__ - INFO - +2025-09-28 00:51:31,760 - src.services - INFO - profitable_cashback_categories +2025-09-28 00:51:31,762 - src.services - WARNING - 9/2025 +2025-09-28 00:51:31,762 - src.services - INFO - profitable_cashback_categories +2025-09-28 00:51:31,767 - src.services - INFO - investment_bank +2025-09-28 00:51:31,768 - src.services - WARNING - +2025-09-28 00:51:31,768 - src.services - WARNING - +2025-09-28 00:51:31,768 - src.services - WARNING - +2025-09-28 00:51:31,768 - src.services - WARNING - +2025-09-28 00:51:31,768 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,769 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,770 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,771 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,772 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,773 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,774 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,775 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,776 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,777 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,778 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,779 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,780 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,781 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,782 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,783 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,784 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,785 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,786 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,787 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,788 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,789 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,790 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,791 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,792 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,793 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,794 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,795 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,796 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,797 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,798 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,799 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,800 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,801 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,802 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,803 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,804 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,805 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,806 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,807 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,808 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,809 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,810 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,811 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,812 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,813 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,814 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,815 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,816 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,817 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,818 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,819 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,820 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,821 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,822 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,823 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,824 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,825 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,826 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,827 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,828 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,829 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,830 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,831 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,832 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,833 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,834 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,835 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,836 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,837 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - WARNING - +2025-09-28 00:51:31,838 - src.services - INFO - investment_bank diff --git a/poetry.lock b/poetry.lock index 56365a4..bc5a275 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,235 @@ # This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, + {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, + {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, + {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, + {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, + {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, + {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, + {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, + {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, + {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, + {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, + {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "black" +version = "25.9.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, + {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, + {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, + {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, + {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, + {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, + {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, + {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, + {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, + {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, + {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, + {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, + {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, + {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, + {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, + {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, + {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, + {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, + {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, + {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, + {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, + {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +pytokens = ">=0.1.10" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cachetools" +version = "6.2.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "cachetools-6.2.0-py3-none-any.whl", hash = "sha256:1c76a8960c0041fcc21097e357f882197c79da0dbff766e7317890a65d7d8ba6"}, + {file = "cachetools-6.2.0.tar.gz", hash = "sha256:38b328c0889450f05f5e120f56ab68c8abaf424e1275522b138ffc93253f7e32"}, +] + [[package]] name = "certifi" version = "2025.8.3" @@ -101,6 +331,171 @@ files = [ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "click" +version = "8.3.0" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main", "dev"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.10.7" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, + {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, + {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, + {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, + {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, + {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, + {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, + {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, + {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, + {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, + {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, + {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, + {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, + {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, + {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, + {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, + {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, + {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, + {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, + {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, + {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, + {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, + {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, + {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, + {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + [[package]] name = "et-xmlfile" version = "2.0.0" @@ -113,6 +508,139 @@ files = [ {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, ] +[[package]] +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "frozenlist" +version = "1.7.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, + {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, +] + [[package]] name = "idna" version = "3.10" @@ -129,108 +657,283 @@ files = [ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] -name = "numpy" -version = "1.24.4" -description = "Fundamental package for array computing in Python" +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "isort" +version = "6.0.1" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.9.0" +groups = ["dev"] +files = [ + {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, + {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, +] + +[package.extras] +colors = ["colorama"] +plugins = ["setuptools"] + +[[package]] +name = "multidict" +version = "6.6.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" groups = ["main"] -markers = "python_version < \"3.10\"" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +files = [ + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, + {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, + {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, + {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, + {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, + {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, + {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, + {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, + {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, + {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, + {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, + {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, + {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, + {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, + {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, + {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, + {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, + {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, + {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, + {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, + {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.18.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "numpy" -version = "2.2.6" +version = "2.0.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.10\"" -files = [ - {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, - {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, - {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, - {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, - {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, - {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, - {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, - {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, - {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, - {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, - {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, - {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, +markers = "python_version < \"3.11\"" +files = [ + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, ] [[package]] @@ -334,121 +1037,80 @@ files = [ et-xmlfile = "*" [[package]] -name = "pandas" -version = "2.0.3" -description = "Powerful data structures for data analysis, time series, and statistics" +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.10\"" -files = [ - {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, - {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, - {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, - {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, - {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, - {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, - {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, - {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, - {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, - {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, - {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, - {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, - {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, - {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, - {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, - {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, - {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, - {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, +groups = ["main", "dev"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] -[package.dependencies] -numpy = {version = ">=1.20.3", markers = "python_version < \"3.10\""} -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.1" - -[package.extras] -all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] -aws = ["s3fs (>=2021.08.0)"] -clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] -compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] -computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2021.07.0)"] -gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] -hdf5 = ["tables (>=3.6.1)"] -html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] -mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] -spss = ["pyreadstat (>=1.1.2)"] -sql-other = ["SQLAlchemy (>=1.4.16)"] -test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.6.3)"] - [[package]] name = "pandas" -version = "2.3.2" +version = "2.3.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version >= \"3.10\"" -files = [ - {file = "pandas-2.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52bc29a946304c360561974c6542d1dd628ddafa69134a7131fdfd6a5d7a1a35"}, - {file = "pandas-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:220cc5c35ffaa764dd5bb17cf42df283b5cb7fdf49e10a7b053a06c9cb48ee2b"}, - {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c05e15111221384019897df20c6fe893b2f697d03c811ee67ec9e0bb5a3424"}, - {file = "pandas-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc03acc273c5515ab69f898df99d9d4f12c4d70dbfc24c3acc6203751d0804cf"}, - {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d25c20a03e8870f6339bcf67281b946bd20b86f1a544ebbebb87e66a8d642cba"}, - {file = "pandas-2.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21bb612d148bb5860b7eb2c10faacf1a810799245afd342cf297d7551513fbb6"}, - {file = "pandas-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:b62d586eb25cb8cb70a5746a378fc3194cb7f11ea77170d59f889f5dfe3cec7a"}, - {file = "pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743"}, - {file = "pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4"}, - {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2"}, - {file = "pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e"}, - {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea"}, - {file = "pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372"}, - {file = "pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f"}, - {file = "pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9"}, - {file = "pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b"}, - {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175"}, - {file = "pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9"}, - {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4"}, - {file = "pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811"}, - {file = "pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae"}, - {file = "pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e"}, - {file = "pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9"}, - {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a"}, - {file = "pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b"}, - {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6"}, - {file = "pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a"}, - {file = "pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b"}, - {file = "pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57"}, - {file = "pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2"}, - {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9"}, - {file = "pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2"}, - {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012"}, - {file = "pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370"}, - {file = "pandas-2.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88080a0ff8a55eac9c84e3ff3c7665b3b5476c6fbc484775ca1910ce1c3e0b87"}, - {file = "pandas-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d4a558c7620340a0931828d8065688b3cc5b4c8eb674bcaf33d18ff4a6870b4a"}, - {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45178cf09d1858a1509dc73ec261bf5b25a625a389b65be2e47b559905f0ab6a"}, - {file = "pandas-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77cefe00e1b210f9c76c697fedd8fdb8d3dd86563e9c8adc9fa72b90f5e9e4c2"}, - {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:13bd629c653856f00c53dc495191baa59bcafbbf54860a46ecc50d3a88421a96"}, - {file = "pandas-2.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:36d627906fd44b5fd63c943264e11e96e923f8de77d6016dc2f667b9ad193438"}, - {file = "pandas-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a9d7ec92d71a420185dec44909c32e9a362248c4ae2238234b76d5be37f208cc"}, - {file = "pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb"}, +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, ] [package.dependencies] @@ -486,6 +1148,305 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "propcache" +version = "0.3.2" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, +] + +[[package]] +name = "pydantic" +version = "1.10.24" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pydantic-1.10.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c"}, + {file = "pydantic-1.10.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683"}, + {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d"}, + {file = "pydantic-1.10.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b"}, + {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36"}, + {file = "pydantic-1.10.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37"}, + {file = "pydantic-1.10.24-cp310-cp310-win_amd64.whl", hash = "sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666"}, + {file = "pydantic-1.10.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702"}, + {file = "pydantic-1.10.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b"}, + {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6"}, + {file = "pydantic-1.10.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3"}, + {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d"}, + {file = "pydantic-1.10.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993"}, + {file = "pydantic-1.10.24-cp311-cp311-win_amd64.whl", hash = "sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a"}, + {file = "pydantic-1.10.24-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624"}, + {file = "pydantic-1.10.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6"}, + {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813"}, + {file = "pydantic-1.10.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de"}, + {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a"}, + {file = "pydantic-1.10.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1"}, + {file = "pydantic-1.10.24-cp312-cp312-win_amd64.whl", hash = "sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c"}, + {file = "pydantic-1.10.24-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c"}, + {file = "pydantic-1.10.24-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def"}, + {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574"}, + {file = "pydantic-1.10.24-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69"}, + {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8"}, + {file = "pydantic-1.10.24-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00"}, + {file = "pydantic-1.10.24-cp313-cp313-win_amd64.whl", hash = "sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6"}, + {file = "pydantic-1.10.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c8bbad6037a87effe9f3739bdf39851add6e0f7e101d103a601c504892ffa70"}, + {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f154a8a46a0d950c055254f8f010ba07e742ac4404a3b6e281a31913ac45ccd0"}, + {file = "pydantic-1.10.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f25d2f792afcd874cc8339c1da1cc52739f4f3d52993ed1f6c263ef2afadc47"}, + {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:49a6f0178063f15eaea6cbcb2dba04db0b73db9834bc7b1e1c4dbea28c7cd22f"}, + {file = "pydantic-1.10.24-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:bb3df10be3c7d264947180615819aeec0916f19650f2ba7309ed1fe546ead0d2"}, + {file = "pydantic-1.10.24-cp37-cp37m-win_amd64.whl", hash = "sha256:fa0ebefc169439267e4b4147c7d458908788367640509ed32c90a91a63ebb579"}, + {file = "pydantic-1.10.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1a5ef77efeb54def2695f2b8f4301aae8c7aa2b334bd15f61c18ef54317621"}, + {file = "pydantic-1.10.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02f7a25e8949d8ca568e4bcef2ffed7881d7843286e7c3488bdd3b67f092059c"}, + {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da2775712dda8b89e701ed2a72d5d81d23dbc6af84089da8a0f61a0be439c8c"}, + {file = "pydantic-1.10.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75259be0558ca3af09192ad7b18557f2e9033ad4cbd48c252131f5292f6374fd"}, + {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1a1ae996daa3d43c530b8d0bacc7e2d9cb55e3991f0e6b7cc2cb61a0fb9f6667"}, + {file = "pydantic-1.10.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:34109b0afa63b36eec2f2b115694e48ae5ee52f7d3c1baa0be36f80e586bda52"}, + {file = "pydantic-1.10.24-cp38-cp38-win_amd64.whl", hash = "sha256:4d7336bfcdb8cb58411e6b498772ba2cff84a2ce92f389bae3a8f1bb2c840c49"}, + {file = "pydantic-1.10.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2"}, + {file = "pydantic-1.10.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f"}, + {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94"}, + {file = "pydantic-1.10.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4"}, + {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb"}, + {file = "pydantic-1.10.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816"}, + {file = "pydantic-1.10.24-cp39-cp39-win_amd64.whl", hash = "sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01"}, + {file = "pydantic-1.10.24-py3-none-any.whl", hash = "sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193"}, + {file = "pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d"}, + {file = "pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -503,35 +1464,33 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] -markers = "python_version < \"3.10\"" files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, ] [package.extras] cli = ["click (>=5.0)"] [[package]] -name = "python-dotenv" -version = "1.1.1" -description = "Read key-value pairs from a .env file and set them as environment variables" +name = "pytokens" +version = "0.1.10" +description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version >= \"3.10\"" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, - {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, + {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, + {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, ] [package.extras] -cli = ["click (>=5.0)"] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] [[package]] name = "pytz" @@ -545,29 +1504,6 @@ files = [ {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, ] -[[package]] -name = "requests" -version = "2.32.4" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "python_version < \"3.10\"" -files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - [[package]] name = "requests" version = "2.32.5" @@ -575,7 +1511,6 @@ description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version >= \"3.10\"" files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, @@ -603,6 +1538,61 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + [[package]] name = "tzdata" version = "2025.2" @@ -617,15 +1607,14 @@ files = [ [[package]] name = "urllib3" -version = "2.2.3" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] -markers = "python_version < \"3.10\"" files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -635,25 +1624,125 @@ socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "yarl" +version = "1.20.1" +description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version >= \"3.10\"" files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, ] -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" [metadata] lock-version = "2.1" -python-versions = "^3.8" -content-hash = "9badf4e23decfc843ed04ca2f4d28d84d39dcf2982b95c01a89b78c668c706df" +python-versions = "^3.9" +content-hash = "f4ffc54e3bc3ec1c6356a31f19e3051f6c80267f33996611da2fdc875190131a" diff --git a/pyproject.toml b/pyproject.toml index 3b9b19b..5f247f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,38 +1,76 @@ [tool.poetry] name = "transaction-analyzer" -version = "0.1.0" -description = "Анализатор банковских транзакций" +version = "1.0.0" +description = "Bank transaction analysis application" authors = ["Your Name "] +readme = "README.md" [tool.poetry.dependencies] -python = "^3.8" -pandas = ">=2.0.0,<3.0.0" -openpyxl = ">=3.1.0,<4.0.0" -requests = ">=2.31.0,<3.0.0" -python-dotenv = ">=1.0.0,<2.0.0" -python-dateutil = ">=2.8.2,<3.0.0" +python = "^3.9" +pandas = "^2.0.0" +openpyxl = "^3.1.0" +requests = "^2.31.0" +python-dateutil = "^2.8.2" +pydantic = "^1.10.12" +python-dotenv = "^1.0.0" +aiohttp = "^3.8.0" +cachetools = ">=5.3.0" +pytest-asyncio = ">=0.21.0" +black = ">=23.12.1" -[poetry.group.dev.dependencies] -pytest = "^7.0.0" -pytest-cov = "^4.0.0" -flake8 = "^5.0.0" -black = "^22.0.0" -isort = "^5.10.0" -mypy = "^0.991.0" +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.0" +pytest-mock = "^3.11.0" +pytest-cov = "^4.1.0" +black = "^25.9.0" +isort = "^6.0.1" +mypy = "^1.18.2" [tool.black] -line-length = 119 -exclude = ''' +line-length = 120 +target-version = ['py39'] +include = '\.pyi?$' +extend-exclude = ''' /( - \.git - | __pycache__ + # directories + | \.eggs + | \.git + | \.venv + | build + | dist + # files + | poetry\.lock )/ ''' [tool.isort] -line_length = 119 +profile = "black" +multi_line_output = 3 +line_length = 120 +known_first_party = ["your_package_name"] # Замените на имя вашего пакета +skip_glob = ["**/migrations/*"] # Опционально: исключить миграции [tool.mypy] -disallow_untyped_defs = true +python_version = "3.9" warn_return_any = true -exclude = ['venv/'] +warn_unused_configs = true +disallow_untyped_defs = true +check_untyped_defs = true +no_implicit_optional = true +strict_equality = true +show_error_codes = true +enable_error_code = ["ignore-without-code"] + +[[tool.mypy.overrides]] +module = [ + "django.*", # Если используете Django + "celery.*", # Если используете Celery +] +ignore_missing_imports = true + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index c99e92b..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Для разработки -pytest>=7.4.0,<8.0.0 -pytest-cov>=4.1.0,<5.0.0 -pytest-mock>=3.11.1,<4.0.0 -flake8>=6.0.0,<7.0.0 -black>=23.7.0,<24.0.0 -isort>=5.12.0,<6.0.0 -mypy>=1.5.0,<2.0.0 -pre-commit>=3.3.0,<4.0.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9eaa878..61b4b19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,12 @@ -pandas>=2.0.0,<3.0.0 -openpyxl>=3.1.0,<4.0.0 -requests>=2.31.0,<3.0.0 -python-dotenv>=1.0.0,<2.0.0 -python-dateutil>=2.8.2,<3.0.0 +pandas>=2.0.0 +openpyxl>=3.1.0 +requests>=2.31.0 +python-dateutil>=2.8.2 +pydantic>=2.0.0 +python-dotenv>=1.0.0 +aiohttp>=3.8.0 +cachetools>=5.3.0 +pytest>=7.4.0 +pytest-asyncio>=0.21.0 +pytest-mock>=3.11.0 +black>=23.12.1 diff --git a/run_app.py b/run_app.py new file mode 100644 index 0000000..8de1f9b --- /dev/null +++ b/run_app.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +Упрощенный запуск приложения +""" + +import sys +from pathlib import Path + +# Добавляем src в путь Python +sys.path.insert(0, str(Path(__file__).parent / 'src')) + +# Создаем необходимые директории +Path('logs').mkdir(exist_ok=True) +Path('reports').mkdir(exist_ok=True) +Path('data').mkdir(exist_ok=True) + + +def main(): + """Основная функция""" + try: + from src.utils import load_transactions, load_user_settings + from src.views import events_page, main_page + + print("🚀 Запуск Transaction Analyzer...") + + # Загрузка данных + load_transactions() + load_user_settings() + + # Генерация данных для веб-страниц + current_time = "2023-12-20 15:30:00" + + print("\n=== ГЛАВНАЯ СТРАНИЦА ===") + main_data = main_page(current_time) + print(f"Приветствие: {main_data['greeting']}") + print(f"Карты: {len(main_data['cards'])}") + print(f"Топ транзакций: {len(main_data['top_transactions'])}") + + print("\n=== СТРАНИЦА СОБЫТИЙ ===") + events_data = events_page("2023-12-20", "M") + print(f"Расходы: {events_data['expenses']['total_amount']}") + print(f"Поступления: {events_data['income']['total_amount']}") + + print("\n✅ Приложение успешно запущено!") + + except Exception as e: + print(f"❌ Ошибка: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api_client.py b/src/api_client.py new file mode 100644 index 0000000..db2f0b6 --- /dev/null +++ b/src/api_client.py @@ -0,0 +1,222 @@ +import asyncio +import logging +from typing import Dict, List, Optional + +import aiohttp +import cachetools + +from .config import settings + +logger = logging.getLogger(__name__) + +# Кэш для API запросов +cache = cachetools.TTLCache(maxsize=100, ttl=settings.cache_ttl) + + +class APIClient: + """Клиент для работы с внешними API""" + + def __init__(self): + self.session: Optional[aiohttp.ClientSession] = None + + async def __aenter__(self): + import aiohttp + self.session = aiohttp.ClientSession() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self.session: + await self.session.close() + + async def get_currency_rates(self, currencies: List[str]) -> List[Dict[str, float]]: + """Получение актуальных курсов валют""" + cache_key = f"currency_rates_{'_'.join(sorted(currencies))}" + + if cache_key in cache: + logger.info("Using cached currency rates") + return cache[cache_key] + + try: + # Пробуем разные API по очереди + rates = await self._get_currency_rates_exchangerate(currencies) + if not rates: + rates = await self._get_currency_rates_currencyapi(currencies) + + if rates: + cache[cache_key] = rates + return rates + else: + # Fallback к статическим данным + return self._get_fallback_currency_rates(currencies) + + except Exception as e: + logger.error(f"Error fetching currency rates: {e}") + return self._get_fallback_currency_rates(currencies) + + async def _get_currency_rates_exchangerate(self, currencies: List[str]) -> List[Dict[str, float]]: + """Получение курсов валют через ExchangeRate API""" + try: + base_currency = "RUB" + target_currencies = [c for c in currencies if c != "RUB"] + + if not target_currencies: + return [{"currency": "RUB", "rate": 1.0}] + + async with self.session.get( + f"{settings.exchangerate_url}?base={base_currency}" + f"&symbols={','.join(target_currencies)}" + ) as response: + if response.status == 200: + data = await response.json() + rates = [{"currency": "RUB", "rate": 1.0}] + + for currency in target_currencies: + if currency in data.get("rates", {}): + rate = 1 / data["rates"][currency] # Конвертируем из RUB в валюту + rates.append({"currency": currency, "rate": round(rate, 4)}) + + return rates + except Exception as e: + logger.warning(f"ExchangeRate API failed: {e}") + return [] + + async def _get_currency_rates_currencyapi(self, currencies: List[str]) -> List[Dict[str, float]]: + """Получение курсов валют через CurrencyAPI""" + try: + if not settings.currency_api_key: + return [] + + base_currency = "RUB" + target_currencies = [c for c in currencies if c != "RUB"] + + async with self.session.get( + f"{settings.currency_api_url}?apikey={settings.currency_api_key}" + f"&base_currency={base_currency}" + ) as response: + if response.status == 200: + data = await response.json() + rates = [{"currency": "RUB", "rate": 1.0}] + + for currency in target_currencies: + if currency in data.get("data", {}): + rate = 1 / data["data"][currency]["value"] + rates.append({"currency": currency, "rate": round(rate, 4)}) + + return rates + except Exception as e: + logger.warning(f"CurrencyAPI failed: {e}") + return [] + + async def get_stock_prices(self, stocks: List[str]) -> List[Dict[str, float]]: + """Получение актуальных цен акций""" + cache_key = f"stock_prices_{'_'.join(sorted(stocks))}" + + if cache_key in cache: + logger.info("Using cached stock prices") + return cache[cache_key] + + try: + prices = await self._get_stock_prices_alphavantage(stocks) + if prices: + cache[cache_key] = prices + return prices + else: + return self._get_fallback_stock_prices(stocks) + + except Exception as e: + logger.error(f"Error fetching stock prices: {e}") + return self._get_fallback_stock_prices(stocks) + + async def _get_stock_prices_alphavantage(self, stocks: List[str]) -> List[Dict[str, float]]: + """Получение цен акций через Alpha Vantage""" + try: + prices = [] + + for stock in stocks: + # Для демо-режима используем лимитированные запросы + if settings.alpha_vantage_api_key == "demo": + # Используем fallback данные для демо + continue + + async with self.session.get( + f"{settings.alpha_vantage_url}?function=GLOBAL_QUOTE" + f"&symbol={stock}&apikey={settings.alpha_vantage_api_key}" + ) as response: + if response.status == 200: + data = await response.json() + quote = data.get("Global Quote", {}) + if quote: + price = float(quote.get("05. price", 0)) + prices.append({"stock": stock, "price": round(price, 2)}) + + return prices if prices else [] + + except Exception as e: + logger.warning(f"Alpha Vantage API failed: {e}") + return [] + + def _get_fallback_currency_rates(self, currencies: List[str]) -> List[Dict[str, float]]: + """Резервные данные о курсах валют""" + fallback_rates = { + "USD": 93.45, + "EUR": 101.23, + "GBP": 117.89, + "CNY": 12.87, + "JPY": 0.63, + "RUB": 1.0 + } + + rates = [] + for currency in currencies: + rate = fallback_rates.get(currency, 1.0) + rates.append({"currency": currency, "rate": rate}) + + logger.info("Using fallback currency rates") + return rates + + def _get_fallback_stock_prices(self, stocks: List[str]) -> List[Dict[str, float]]: + """Резервные данные о ценах акций""" + fallback_prices = { + "AAPL": 178.72, + "AMZN": 145.63, + "GOOGL": 138.21, + "MSFT": 374.51, + "TSLA": 235.49, + "META": 351.95, + "NVDA": 477.76, + "NFLX": 485.13 + } + + prices = [] + for stock in stocks: + price = fallback_prices.get(stock, 0.0) + if price > 0: + prices.append({"stock": stock, "price": price}) + + logger.info("Using fallback stock prices") + return prices + + +# Синхронная обертка для удобства использования +class SyncAPIClient: + """Синхронная обертка для APIClient""" + + @staticmethod + def get_currency_rates(currencies: List[str]) -> List[Dict[str, float]]: + """Получение курсов валют""" + + async def _fetch(): + async with APIClient() as client: + return await client.get_currency_rates(currencies) + + return asyncio.run(_fetch()) + + @staticmethod + def get_stock_prices(stocks: List[str]) -> List[Dict[str, float]]: + """Получение цен акций""" + + async def _fetch(): + async with APIClient() as client: + return await client.get_stock_prices(stocks) + + return asyncio.run(_fetch()) diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..cb8271e --- /dev/null +++ b/src/config.py @@ -0,0 +1,43 @@ +import os +from typing import List + +from dotenv import load_dotenv +from pydantic import BaseSettings + +load_dotenv() + + +class Settings(BaseSettings): + """Настройки приложения""" + + # API Keys + alpha_vantage_api_key: str = os.getenv("ALPHA_VANTAGE_API_KEY", "demo") + exchangerate_api_key: str = os.getenv("EXCHANGERATE_API_KEY", "") + currency_api_key: str = os.getenv("CURRENCY_API_KEY", "") + + # Paths + data_file_path: str = "data/operations.xlsx" + user_settings_path: str = "user_settings.json" + + # API endpoints + alpha_vantage_url: str = "https://www.alphavantage.co/query" + exchangerate_url: str = "https://api.exchangerate.host/latest" + currency_api_url: str = "https://api.currencyapi.com/v3/latest" + + # Cache settings + cache_ttl: int = 3600 # 1 hour + + # Date formats to try + date_formats: List[str] = [ + "%d.%m.%Y", + "%Y-%m-%d", + "%d/%m/%Y", + "%m/%d/%Y", + "%Y.%m.%d" + ] + + class Config: + env_file = ".env" + + +settings = Settings() diff --git a/src/logs/transaction_analyzer.log b/src/logs/transaction_analyzer.log new file mode 100644 index 0000000..d827da6 --- /dev/null +++ b/src/logs/transaction_analyzer.log @@ -0,0 +1,60 @@ +2025-09-27 22:32:18,219 - __main__ - INFO - ... +2025-09-27 22:32:18,219 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:32:18,220 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:32:18,220 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:32:20,607 - __main__ - INFO - ... +2025-09-27 22:32:20,608 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:32:20,608 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:32:20,609 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:36:18,943 - __main__ - INFO - ... +2025-09-27 22:36:18,943 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:36:18,943 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:36:18,943 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:39:34,228 - __main__ - INFO - ... +2025-09-27 22:39:34,229 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:39:34,229 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:39:34,229 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:39:38,088 - __main__ - INFO - ... +2025-09-27 22:39:38,088 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:39:38,088 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:39:38,088 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:40:17,643 - __main__ - INFO - ... +2025-09-27 22:40:17,644 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:40:17,645 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:40:17,645 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:40:50,211 - __main__ - INFO - ... +2025-09-27 22:40:50,212 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:40:50,213 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:40:50,213 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:41:04,925 - __main__ - INFO - ... +2025-09-27 22:41:04,926 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:41:04,927 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:41:04,927 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:42:25,867 - __main__ - INFO - ... +2025-09-27 22:42:25,867 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:42:25,867 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:42:25,867 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:42:43,338 - __main__ - INFO - ... +2025-09-27 22:42:43,338 - src.utils - INFO - data/operations.xlsx +2025-09-27 22:42:43,338 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 22:42:43,338 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:12:13,905 - __main__ - INFO - ... +2025-09-27 23:12:13,905 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:12:13,906 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:12:13,906 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:12:36,049 - __main__ - INFO - ... +2025-09-27 23:12:36,049 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:12:36,049 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:12:36,049 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:21:59,058 - __main__ - INFO - ... +2025-09-27 23:21:59,058 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:21:59,058 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:21:59,058 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:22:19,758 - __main__ - INFO - ... +2025-09-27 23:22:19,759 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:22:19,759 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:22:19,759 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:34:17,097 - __main__ - INFO - ... +2025-09-27 23:34:17,097 - src.utils - INFO - data/operations.xlsx +2025-09-27 23:34:17,097 - src.utils - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' +2025-09-27 23:34:17,097 - __main__ - ERROR - : [Errno 2] No such file or directory: 'data/operations.xlsx' diff --git a/src/main.py b/src/main.py index e42784b..4483084 100644 --- a/src/main.py +++ b/src/main.py @@ -1,52 +1,174 @@ +#!/usr/bin/env python3 +""" +Основной модуль приложения для анализа транзакций +""" +import argparse import json -import pandas as pd -from .utils import load_transactions -from .views import home_page -from .services import investment_bank, simple_search, find_phone_transactions -from .reports import spending_by_category, spending_by_weekday +import logging +from datetime import datetime +from pathlib import Path + +# Создаем необходимые директории +Path('logs').mkdir(exist_ok=True) +Path('reports').mkdir(exist_ok=True) +Path('data').mkdir(exist_ok=True) + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('logs/transaction_analyzer.log'), + logging.StreamHandler() + ] +) + +logger = logging.getLogger(__name__) + + +class TransactionAnalyzer: + """Основной класс приложения""" + + def __init__(self, data_file: str = None): + from src.config import settings + self.data_file = data_file or settings.data_file_path + self.transactions_df = None + self.settings = None + + def load_data(self): + """Загрузка данных""" + from src.utils import load_transactions, load_user_settings + logger.info("Загрузка данных...") + self.transactions_df = load_transactions(self.data_file) + self.settings = load_user_settings() + logger.info("Данные успешно загружены") + + def generate_main_page(self, date_time: str) -> dict: + """Генерация данных для главной страницы""" + from src.views import main_page + return main_page(date_time, self.data_file) + + def generate_events_page(self, date: str, period: str = 'M') -> dict: + """Генерация данных для страницы событий""" + from src.views import events_page + return events_page(date, period, self.data_file) + + def analyze_cashback_categories(self, year: int, month: int) -> dict: + """Анализ выгодных категорий кешбэка""" + from src.services import profitable_cashback_categories + cashback_rules = self.settings.get('cashback_rules', {'default': 0.01}) + return profitable_cashback_categories( + self.transactions_df, year, month, cashback_rules + ) + + def calculate_investment(self, month: str, limit: int) -> float: + """Расчет инвесткопилки""" + from src.services import investment_bank + transactions_list = self.transactions_df.to_dict('records') + return investment_bank(month, transactions_list, limit) + + def search_transactions(self, search_string: str) -> list: + """Поиск транзакций""" + from src.services import simple_search + transactions_list = self.transactions_df.to_dict('records') + return simple_search(transactions_list, search_string) + + def generate_reports(self): + """Генерация всех отчетов""" + from src.reports import ReportGenerator # Добавлен импорт + + reports = {'spending_by_category': ReportGenerator.spending_by_category( + self.transactions_df, 'Супермаркеты' + ), 'spending_by_weekday': ReportGenerator.spending_by_weekday( + self.transactions_df + ), 'spending_by_workday': ReportGenerator.spending_by_workday( + self.transactions_df + ), 'monthly_summary': ReportGenerator.monthly_summary( + self.transactions_df + )} + + # Отчет по категориям + + # Отчет по дням недели + + # Отчет по рабочим/выходным дням + + # Сводный отчет + + return reports def main(): - """Основная функция приложения.""" + """Основная функция приложения""" + parser = argparse.ArgumentParser(description='Анализатор банковских транзакций') + parser.add_argument('--data-file', help='Путь к файлу с транзакциями') + parser.add_argument('--command', choices=['web', 'report', 'analyze', 'test'], + default='web', help='Режим работы') + + args = parser.parse_args() + try: - # Загрузка данных - transactions_df = load_transactions('data/operations.xlsx') + # Инициализация анализатора + analyzer = TransactionAnalyzer(args.data_file) + analyzer.load_data() + + if args.command == 'web': + # Пример генерации данных для веб-страниц + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + main_data = analyzer.generate_main_page(current_time) + events_data = analyzer.generate_events_page(current_time.split()[0]) - # Загрузка настроек пользователя - with open('user_settings.json', 'r') as f: - user_settings = json.load(f) + print("=== ДАННЫЕ ДЛЯ ГЛАВНОЙ СТРАНИЦЫ ===") + print(json.dumps(main_data, ensure_ascii=False, indent=2)) - # Демонстрация функциональности - print("=== Анализатор транзакций ===") + print("\n=== ДАННЫЕ ДЛЯ СТРАНИЦЫ СОБЫТИЙ ===") + print(json.dumps(events_data, ensure_ascii=False, indent=2)) - # Главная страница - home_data = home_page("2023-12-20 15:30:00", transactions_df, user_settings) - print("Главная страница сгенерирована") + elif args.command == 'report': + # Генерация отчетов + reports = analyzer.generate_reports() + print("=== ОТЧЕТЫ СГЕНЕРИРОВАНЫ ===") - # Конвертация DataFrame в список словарей для сервисов - transactions_list = transactions_df.to_dict('records') + for report_name, report_data in reports.items(): + print(f"\n--- {report_name} ---") + if isinstance(report_data, dict): + print(json.dumps(report_data, ensure_ascii=False, indent=2)) + elif hasattr(report_data, 'head'): # DataFrame + print(report_data.head()) + else: + print(f"Тип данных: {type(report_data)}") + print(report_data) - # Сервисы - savings = investment_bank("2023-12", transactions_list, 50) - print(f"Инвесткопилка: {savings} руб.") + print("\n Отчеты сохранены в папке 'reports/'") - search_results = simple_search("кафе", transactions_list) - print(f"Найдено транзакций по запросу 'кафе': {len(search_results)}") + elif args.command == 'analyze': + # Анализ данных + current_year = datetime.now().year + current_month = datetime.now().month - phone_transactions = find_phone_transactions(transactions_list) - print(f"Найдено транзакций с телефонами: {len(phone_transactions)}") + cashback_analysis = analyzer.analyze_cashback_categories(current_year, current_month) + investment = analyzer.calculate_investment( + f"{current_year}-{current_month:02d}", 50 + ) - # Отчеты - category_spending = spending_by_category(transactions_df, "Супермаркеты") - print("Отчет по тратам по категории создан") + print("=== АНАЛИЗ ДАННЫХ ===") + print(f"Выгодные категории кешбэка: {cashback_analysis}") + print(f"Инвесткопилка за месяц: {investment} руб.") - weekday_spending = spending_by_weekday(transactions_df) - print("Отчет по тратам по дням недели создан") + elif args.command == 'test': + # Простой тест функциональности + print("=== ТЕСТ ФУНКЦИОНАЛЬНОСТИ ===") + print(f"Загружено транзакций: {len(analyzer.transactions_df)}") + print(f"Настройки: {list(analyzer.settings.keys())}") - print("\nВсе функции выполнены успешно!") + # Тест поиска + search_results = analyzer.search_transactions("магазин") + print(f"Результатов поиска 'магазин': {len(search_results)}") except Exception as e: - print(f"Ошибка: {e}") + logger.error(f"Ошибка приложения: {e}") + raise if __name__ == "__main__": diff --git a/src/reports.py b/src/reports.py index 4e47cbf..40bc114 100644 --- a/src/reports.py +++ b/src/reports.py @@ -1,33 +1,42 @@ import json import logging -import pandas as pd +import os from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional, Callable from functools import wraps +from typing import Any, Callable, Dict, Optional + +import pandas as pd logger = logging.getLogger(__name__) -def report(func: Optional[Callable] = None, filename: Optional[str] = None): - """Декоратор для записи результатов отчетов в файл.""" +def report_decorator(filename: Optional[str] = None): + """Декоратор для сохранения отчетов в файл""" - def decorator(report_func): - @wraps(report_func) + def decorator(func: Callable) -> Callable: + @wraps(func) def wrapper(*args, **kwargs): - result = report_func(*args, **kwargs) + result = func(*args, **kwargs) - # Генерация имени файла - if filename: - file_path = filename + # Генерация имени файла если не указано + if filename is None: + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + func_name = func.__name__ + report_filename = f"reports/{func_name}_{timestamp}.json" else: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - file_path = f"{report_func.__name__}_{timestamp}.json" + report_filename = filename - # Запись в файл + # Создаем директорию если не существует + os.makedirs(os.path.dirname(report_filename), exist_ok=True) + + # Сохранение результата try: - with open(file_path, 'w', encoding='utf-8') as f: - json.dump(result, f, ensure_ascii=False, indent=2) - logger.info(f"Отчет сохранен в файл: {file_path}") + with open(report_filename, 'w', encoding='utf-8') as f: + if isinstance(result, pd.DataFrame): + json.dump(result.to_dict('records'), f, ensure_ascii=False, indent=2) + else: + json.dump(result, f, ensure_ascii=False, indent=2) + logger.info(f"Отчет сохранен в {report_filename}") except Exception as e: logger.error(f"Ошибка сохранения отчета: {e}") @@ -35,70 +44,203 @@ def wrapper(*args, **kwargs): return wrapper - if func is None: - return decorator - else: - return decorator(func) - - -@report -def spending_by_category(transactions: pd.DataFrame, category: str, - date: Optional[str] = None) -> Dict[str, float]: - """Анализ трат по категории за последние 3 месяца.""" - try: - if date is None: - date = datetime.now().strftime("%Y-%m-%d") - - target_date = datetime.strptime(date, "%Y-%m-%d") - three_months_ago = target_date - timedelta(days=90) - - # Фильтрация транзакций - transactions['Дата операции'] = pd.to_datetime(transactions['Дата операции']) - filtered_df = transactions[ - (transactions['Дата операции'] >= three_months_ago) & - (transactions['Дата операции'] <= target_date) & - (transactions['Категория'] == category) - ] - - # Группировка по месяцам - filtered_df['Месяц'] = filtered_df['Дата операции'].dt.to_period('M') - monthly_spending = filtered_df.groupby('Месяц')['Сумма операции'].sum().abs() - - result = {str(month): round(amount, 2) for month, amount in monthly_spending.items()} - logger.info(f"Проанализированы траты по категории '{category}'") - - return result - except Exception as e: - logger.error(f"Ошибка анализа трат по категории: {e}") - return {} - - -@report -def spending_by_weekday(transactions: pd.DataFrame, date: Optional[str] = None) -> Dict[str, float]: - """Средние траты по дням недели за последние 3 месяца.""" - try: - if date is None: - date = datetime.now().strftime("%Y-%m-%d") - - target_date = datetime.strptime(date, "%Y-%m-%d") - three_months_ago = target_date - timedelta(days=90) - - # Фильтрация транзакций - transactions['Дата операции'] = pd.to_datetime(transactions['Дата операции']) - filtered_df = transactions[ - (transactions['Дата операции'] >= three_months_ago) & - (transactions['Дата операции'] <= target_date) - ] - - # Анализ по дням недели - filtered_df['День недели'] = filtered_df['Дата операции'].dt.day_name() - weekday_spending = filtered_df.groupby('День недели')['Сумма операции'].mean().abs() - - days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] - result = {day: round(weekday_spending.get(day, 0), 2) for day in days_order} - - logger.info("Проанализированы траты по дням недели") - return result - except Exception as e: - logger.error(f"Ошибка анализа трат по дням недели: {e}") - return {} \ No newline at end of file + return decorator + + +class ReportGenerator: + """Генератор отчетов""" + + @staticmethod + @report_decorator() + def spending_by_category(transactions: pd.DataFrame, + category: str, + date: Optional[str] = None) -> pd.DataFrame: + """Траты по категории за последние 3 месяца""" + try: + if date is None: + target_date = datetime.now() + else: + target_date = datetime.strptime(date, '%Y-%m-%d') + + # Расчет даты начала периода (3 месяца назад) + start_date = target_date - timedelta(days=90) + + # Фильтрация данных + mask = (transactions['Дата операции'] >= start_date) & \ + (transactions['Дата операции'] <= target_date) & \ + (transactions['Категория'] == category) & \ + (transactions['Статус'] == 'OK') & \ + (transactions['Сумма операции'] > 0) + + filtered_data = transactions.loc[mask].copy() + + if filtered_data.empty: + return pd.DataFrame(columns=['Месяц', 'Сумма']) + + # Группировка по месяцам + filtered_data['Месяц'] = filtered_data['Дата операции'].dt.to_period('M') + monthly_spending = filtered_data.groupby('Месяц')['Сумма операции'].sum().reset_index() + monthly_spending['Месяц'] = monthly_spending['Месяц'].astype(str) + monthly_spending['Сумма'] = monthly_spending['Сумма операции'].round(2) + + return monthly_spending[['Месяц', 'Сумма']] + + except Exception as e: + logger.error(f"Ошибка в spending_by_category: {e}") + return pd.DataFrame() + + @staticmethod + @report_decorator() + def spending_by_weekday(transactions: pd.DataFrame, + date: Optional[str] = None) -> pd.DataFrame: + """Средние траты по дням недели""" + try: + if date is None: + target_date = datetime.now() + else: + target_date = datetime.strptime(date, '%Y-%m-%d') + + start_date = target_date - timedelta(days=90) + + mask = (transactions['Дата операции'] >= start_date) & \ + (transactions['Дата операции'] <= target_date) & \ + (transactions['Статус'] == 'OK') & \ + (transactions['Сумма операции'] > 0) + + filtered_data = transactions.loc[mask].copy() + + if filtered_data.empty: + return pd.DataFrame(columns=['День недели', 'Средняя сумма']) + + # Маппинг дней недели на русский + day_mapping = { + 0: 'Понедельник', + 1: 'Вторник', + 2: 'Среда', + 3: 'Четверг', + 4: 'Пятница', + 5: 'Суббота', + 6: 'Воскресенье' + } + + filtered_data['День недели'] = filtered_data['Дата операции'].dt.weekday.map(day_mapping) + avg_spending = filtered_data.groupby('День недели')['Сумма операции'].mean().reset_index() + avg_spending['Средняя сумма'] = avg_spending['Сумма операции'].round(2) + + # Сортировка по порядку дней недели + day_order = list(day_mapping.values()) + avg_spending['День недели'] = pd.Categorical(avg_spending['День недели'], categories=day_order, + ordered=True) + avg_spending = avg_spending.sort_values('День недели') + + return avg_spending[['День недели', 'Средняя сумма']] + + except Exception as e: + logger.error(f"Ошибка в spending_by_weekday: {e}") + return pd.DataFrame() + + @staticmethod + @report_decorator() + def spending_by_workday(transactions: pd.DataFrame, + date: Optional[str] = None) -> pd.DataFrame: + """Средние траты в рабочие/выходные дни""" + try: + if date is None: + target_date = datetime.now() + else: + target_date = datetime.strptime(date, '%Y-%m-%d') + + start_date = target_date - timedelta(days=90) + + mask = (transactions['Дата операции'] >= start_date) & \ + (transactions['Дата операции'] <= target_date) & \ + (transactions['Статус'] == 'OK') & \ + (transactions['Сумма операции'] > 0) + + filtered_data = transactions.loc[mask].copy() + + if filtered_data.empty: + return pd.DataFrame(columns=['Тип дня', 'Средняя сумма']) + + filtered_data['День недели'] = filtered_data['Дата операции'].dt.weekday + filtered_data['Тип дня'] = filtered_data['День недели'].apply( + lambda x: 'Выходной' if x >= 5 else 'Рабочий' + ) + + avg_spending = filtered_data.groupby('Тип дня')['Сумма операции'].mean().reset_index() + avg_spending['Средняя сумма'] = avg_spending['Сумма операции'].round(2) + + return avg_spending[['Тип дня', 'Средняя сумма']] + + except Exception as e: + logger.error(f"Ошибка в spending_by_workday: {e}") + return pd.DataFrame() + + @staticmethod + @report_decorator() + def monthly_summary(transactions: pd.DataFrame, + months: int = 6) -> Dict[str, Any]: + """Сводный отчет за несколько месяцев""" + try: + end_date = datetime.now() + start_date = end_date - timedelta(days=30 * months) + + mask = (transactions['Дата операции'] >= start_date) & \ + (transactions['Дата операции'] <= end_date) + + filtered_data = transactions.loc[mask].copy() + + if filtered_data.empty: + return {"error": "Нет данных за указанный период"} + + # Расходы по месяцам + filtered_data['Месяц'] = filtered_data['Дата операции'].dt.to_period('M') + expenses = filtered_data[filtered_data['Сумма операции'] > 0] + income = filtered_data[filtered_data['Сумма операции'] < 0] + + monthly_expenses = expenses.groupby('Месяц')['Сумма операции'].sum() + monthly_income = income.groupby('Месяц')['Сумма операции'].sum().abs() + + # Топ категории расходов + top_categories = expenses.groupby('Категория')['Сумма операции'].sum().nlargest(5) + + report = { + "period": f"{start_date.strftime('%Y-%m')} - {end_date.strftime('%Y-%m')}", + "total_expenses": round(expenses['Сумма операции'].sum(), 2), + "total_income": round(income['Сумма операции'].sum().abs(), 2), + "monthly_expenses": { + month.strftime('%Y-%m'): round(amount, 2) + for month, amount in monthly_expenses.items() + }, + "monthly_income": { + month.strftime('%Y-%m'): round(amount, 2) + for month, amount in monthly_income.items() + }, + "top_categories": { + category: round(amount, 2) + for category, amount in top_categories.items() + } + } + + return report + + except Exception as e: + logger.error(f"Ошибка в monthly_summary: {e}") + return {"error": str(e)} + + +# Функции-обертки для совместимости +def spending_by_category(transactions: pd.DataFrame, + category: str, + date: Optional[str] = None) -> pd.DataFrame: + return ReportGenerator.spending_by_category(transactions, category, date) + + +def spending_by_weekday(transactions: pd.DataFrame, + date: Optional[str] = None) -> pd.DataFrame: + return ReportGenerator.spending_by_weekday(transactions, date) + + +def spending_by_workday(transactions: pd.DataFrame, + date: Optional[str] = None) -> pd.DataFrame: + return ReportGenerator.spending_by_workday(transactions, date) diff --git a/src/services.py b/src/services.py index 0225262..b2ae28c 100644 --- a/src/services.py +++ b/src/services.py @@ -1,74 +1,232 @@ -import json import logging import re from datetime import datetime -from typing import Dict, List, Any, Optional +from functools import reduce, wraps +from typing import Any, Callable, Dict, List + +import pandas as pd logger = logging.getLogger(__name__) -def investment_bank(month: str, transactions: List[Dict[str, Any]], limit: int) -> float: - """Расчет суммы для инвесткопилки через округление трат.""" - try: - total_savings = 0.0 +class CashbackAnalyzer: + """Анализатор выгодности категорий кешбэка""" + + def __init__(self, cashback_rules: Dict[str, float]): + self.cashback_rules = cashback_rules + + def analyze_profitable_categories(self, data: pd.DataFrame, year: int, + month: int) -> Dict[str, float]: + """Анализ выгодности категорий повышенного кешбэка""" + try: + # Фильтрация данных по году и месяцу + mask = (data['Дата операции'].dt.year == year) & \ + (data['Дата операции'].dt.month == month) & \ + (data['Статус'] == 'OK') & \ + (data['Сумма операции'] > 0) # Только расходы + + filtered_data = data.loc[mask] + + if filtered_data.empty: + logger.warning(f"Нет данных за {month}/{year}") + return {} + + # Группировка по категориям и расчет потенциального кешбэка + cashback_analysis = {} + + for category in filtered_data['Категория'].unique(): + if pd.isna(category) or category == '': + continue + + category_data = filtered_data[filtered_data['Категория'] == category] + + # Расчет кешбэка по сложной логике + from .utils import calculate_cashback + potential_cashback = sum( + calculate_cashback(row['Сумма операции'], category, self.cashback_rules) + for _, row in category_data.iterrows() + ) + + cashback_analysis[category] = round(potential_cashback, 2) + + # Сортировка по убыванию кешбэка + return dict(sorted(cashback_analysis.items(), + key=lambda x: x[1], reverse=True)) + + except Exception as e: + logger.error(f"Ошибка анализа категорий кешбэка: {e}") + return {} + + +class InvestmentCalculator: + """Калькулятор инвесткопилки""" + + @staticmethod + def investment_bank(month: str, transactions: List[Dict[str, Any]], + limit: int) -> float: + """Расчет суммы для инвесткопилки""" + try: + if limit not in [10, 50, 100]: + raise ValueError("Лимит округления должен быть 10, 50 или 100") + + target_month = datetime.strptime(month, '%Y-%m') + total_savings = 0.0 + + for transaction in transactions: + # Валидация транзакции + if not InvestmentCalculator._validate_transaction(transaction): + continue + + trans_date = datetime.strptime(transaction['Дата операции'], '%Y-%m-%d') + + if (trans_date.year == target_month.year + and trans_date.month == target_month.month): + + amount = float(transaction['Сумма операции']) + if amount > 0: # Только расходы + # Округление вверх до ближайшего кратного limit + rounded_amount = ((amount + limit - 1) // limit) * limit + savings = rounded_amount - amount + total_savings += savings + + return round(total_savings, 2) + + except Exception as e: + logger.error(f"Ошибка расчета инвесткопилки: {e}") + return 0.0 + + @staticmethod + def _validate_transaction(transaction: Dict[str, Any]) -> bool: + """Валидация транзакции""" + required_fields = ['Дата операции', 'Сумма операции'] + + for field in required_fields: + if field not in transaction: + logger.warning(f"Отсутствует поле {field} в транзакции") + return False + + try: + datetime.strptime(transaction['Дата операции'], '%Y-%m-%d') + float(transaction['Сумма операции']) + return True + except (ValueError, TypeError): + logger.warning("Некорректные данные в транзакции") + return False + + +class TransactionSearcher: + """Поисковик транзакций""" + + @staticmethod + def simple_search(transactions: List[Dict[str, Any]], + search_string: str) -> List[Dict[str, Any]]: + """Простой поиск по описанию и категории""" + if not search_string or len(search_string.strip()) < 2: + raise ValueError("Строка поиска должна содержать минимум 2 символа") + + def search_filter(transaction: Dict[str, Any]) -> bool: + description = str(transaction.get('Описание', '')).lower() + category = str(transaction.get('Категория', '')).lower() + search_lower = search_string.lower() + + return (search_lower in description + or search_lower in category) + + return list(filter(search_filter, transactions)) + + @staticmethod + def search_by_phone(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Поиск транзакций с телефонными номерами""" + # Паттерны для российских мобильных номеров + phone_patterns = [ + r'\+7\s?\d{3}\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}', # +7 XXX XXX-XX-XX + r'8\s?\d{3}\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}', # 8 XXX XXX-XX-XX + r'\d{3}[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}' # XXX XXX-XX-XX + ] + + def phone_filter(transaction: Dict[str, Any]) -> bool: + description = str(transaction.get('Описание', '')) + + for pattern in phone_patterns: + if re.search(pattern, description): + return True + return False + + return list(filter(phone_filter, transactions)) + + @staticmethod + def search_by_person_transfers(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Поиск переводов физическим лицам""" + # Паттерн для имени и фамилии с инициалом: "Имя Ф." + name_pattern = r'[А-Я][а-я]+\s[А-Я]\.' + + def transfer_filter(transaction: Dict[str, Any]) -> bool: + category = transaction.get('Категория', '') + description = str(transaction.get('Описание', '')) + + return (category == 'Переводы' + and bool(re.search(name_pattern, description))) + + return list(filter(transfer_filter, transactions)) + + +# Функциональные утилиты +def compose(*functions: Callable) -> Callable: + """Композиция функций""" + return reduce(lambda f, g: lambda x: f(g(x)), functions) + + +def pipe(value: Any, *functions: Callable) -> Any: + """Конвейерная обработка значения через функции""" + return compose(*functions)(value) + - for transaction in transactions: - # Проверяем, что транзакция в нужном месяце - trans_date = datetime.strptime(transaction['Дата операции'], '%Y-%m-%d') - if trans_date.strftime('%Y-%m') != month: - continue +# Декораторы для логирования +def log_service_call(service_name: str): + """Декоратор для логирования вызовов сервисов""" - amount = abs(transaction['Сумма операции']) - if amount > 0: # Только расходы - rounded_amount = _round_up_to_nearest(amount, limit) - savings = rounded_amount - amount - total_savings += savings + def decorator(func: Callable) -> Callable: + @wraps(func) + def wrapper(*args, **kwargs): + logger.info(f"Вызов сервиса {service_name}") + try: + result = func(*args, **kwargs) + logger.info(f"Сервис {service_name} выполнен успешно") + return result + except Exception as e: + logger.error(f"Ошибка в сервисе {service_name}: {e}") + raise - logger.info(f"Сумма для инвесткопилки за {month}: {total_savings}") - return round(total_savings, 2) - except Exception as e: - logger.error(f"Ошибка расчета инвесткопилки: {e}") - return 0.0 + return wrapper + return decorator -def _round_up_to_nearest(amount: float, limit: int) -> float: - """Округление до ближайшего кратного limit.""" - return ((amount + limit - 1) // limit) * limit +# Экспорт основных функций с декораторами +@log_service_call("profitable_cashback_categories") +def profitable_cashback_categories(data: pd.DataFrame, year: int, + month: int, cashback_rules: Dict[str, float]) -> Dict[str, float]: + analyzer = CashbackAnalyzer(cashback_rules) + return analyzer.analyze_profitable_categories(data, year, month) -def simple_search(query: str, transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """Простой поиск транзакций по описанию или категории.""" - try: - results = [] - query_lower = query.lower() - for transaction in transactions: - description = transaction.get('Описание', '').lower() - category = transaction.get('Категория', '').lower() +@log_service_call("investment_bank") +def investment_bank(month: str, transactions: List[Dict[str, Any]], + limit: int) -> float: + return InvestmentCalculator.investment_bank(month, transactions, limit) - if query_lower in description or query_lower in category: - results.append(transaction) - logger.info(f"Найдено {len(results)} транзакций по запросу '{query}'") - return results - except Exception as e: - logger.error(f"Ошибка поиска: {e}") - return [] +@log_service_call("simple_search") +def simple_search(transactions: List[Dict[str, Any]], + search_string: str) -> List[Dict[str, Any]]: + return TransactionSearcher.simple_search(transactions, search_string) -def find_phone_transactions(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """Поиск транзакций с телефонными номерами в описании.""" - try: - phone_pattern = r'\+7\s?\(?\d{3}\)?\s?\d{3}[\s-]?\d{2}[\s-]?\d{2}' - results = [] +@log_service_call("search_by_phone") +def search_by_phone(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return TransactionSearcher.search_by_phone(transactions) - for transaction in transactions: - description = transaction.get('Описание', '') - if re.search(phone_pattern, description): - results.append(transaction) - logger.info(f"Найдено {len(results)} транзакций с телефонными номерами") - return results - except Exception as e: - logger.error(f"Ошибка поиска телефонных номеров: {e}") - return [] +@log_service_call("search_by_person_transfers") +def search_by_person_transfers(transactions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return TransactionSearcher.search_by_person_transfers(transactions) diff --git a/src/utils.py b/src/utils.py index 8a0a74d..9af4160 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,103 +1,296 @@ import json import logging -import pandas as pd -import requests from datetime import datetime, timedelta -from typing import Dict, List, Any, Optional -import os -from dotenv import load_dotenv +from typing import Any, Dict, List, Tuple + +import pandas as pd -load_dotenv() +from src.config import settings -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -def load_transactions(file_path: str) -> pd.DataFrame: - """Загрузка транзакций из Excel файла.""" - try: +class DataValidator : + """Валидатор данных транзакций""" + + @staticmethod + def validate_transaction_data(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] : + """Проверка и очистка данных транзакций""" + errors = [] + + # Проверка обязательных колонок + required_columns = ['Дата операции', 'Сумма операции', 'Статус'] + missing_columns = [col for col in required_columns if col not in df.columns] + if missing_columns : + raise ValueError(f"Отсутствуют обязательные колонки: {missing_columns}") + + # Копируем данные для очистки + clean_df = df.copy() + + # Обработка дат + clean_df, date_errors = DataValidator._process_dates(clean_df) + errors.extend(date_errors) + + # Обработка числовых полей + clean_df, numeric_errors = DataValidator._process_numeric_fields(clean_df) + errors.extend(numeric_errors) + + # Обработка текстовых полей + clean_df, text_errors = DataValidator._process_text_fields(clean_df) + errors.extend(text_errors) + + # Удаление дубликатов + initial_count = len(clean_df) + clean_df = clean_df.drop_duplicates() + if len(clean_df) < initial_count : + errors.append(f"Удалено {initial_count - len(clean_df)} дубликатов") + + # Удаление строк с критическими ошибками + initial_count = len(clean_df) + clean_df = clean_df.dropna(subset=['Дата операции', 'Сумма операции', 'Статус']) + if len(clean_df) < initial_count : + errors.append(f"Удалено {initial_count - len(clean_df)} строк с некорректными данными") + + return clean_df, errors + + @staticmethod + def _process_dates(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] : + """Обработка и валидация дат""" + errors = [] + clean_df = df.copy() + + date_columns = ['Дата операции', 'Дата платежа'] + + for col in date_columns : + if col not in clean_df.columns : + continue + + original_non_null = clean_df[col].notna().sum() + + # Пробуем разные форматы дат + for date_format in settings.date_formats : + try : + clean_df[col] = pd.to_datetime( + clean_df[col], + format=date_format, + errors='coerce' + ) + # Если удалось преобразовать большинство дат, используем этот формат + if clean_df[col].notna().sum() > original_non_null * 0.8 : + break + except Exception: + continue + + # Убираем устаревший параметр infer_datetime_format + # Просто используем errors='coerce' для оставшихся проблемных значений + if clean_df[col].isna().any() : + clean_df[col] = pd.to_datetime(clean_df[col], errors='coerce') + + # Проверяем разумность дат (не в будущем и не слишком в прошлом) + if col in clean_df.columns and clean_df[col].notna().any() : + max_date = datetime.now() + timedelta(days=1) # Завтра + min_date = datetime(2000, 1, 1) # 2000 год + + invalid_dates = clean_df[ + (clean_df[col] > max_date) | (clean_df[col] < min_date)] + + if len(invalid_dates) > 0 : + errors.append(f"Найдено {len(invalid_dates)} некорректных дат в колонке {col}") + clean_df.loc[invalid_dates.index, col] = pd.NaT + + return clean_df, errors + + @staticmethod + def _process_numeric_fields(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] : + """Обработка числовых полей""" + errors = [] + clean_df = df.copy() + + numeric_columns = ['Сумма операции', 'Сумма платежа', 'Кешбэк', 'Бонусы (включая кешбэк)', + 'Округление на «Инвесткопилку»', 'Сумма операции с округлением'] + + for col in numeric_columns : + if col not in clean_df.columns : + continue + + # Заменяем запятые на точки и преобразуем в числа + clean_df[col] = pd.to_numeric( + clean_df[col].astype(str).str.replace(',', '.'), + errors='coerce' + ) + + # Проверяем на выбросы (суммы больше 10 млн) + if clean_df[col].notna().any() : + outliers = clean_df[clean_df[col].abs() > 10000000] + if len(outliers) > 0 : + errors.append(f"Найдено {len(outliers)} выбросов в колонке {col}") + + return clean_df, errors + + @staticmethod + def _process_text_fields(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[str]] : + """Обработка текстовых полей""" + errors = [] + clean_df = df.copy() + + text_columns = ['Статус', 'Категория', 'Описание', 'Номер карты'] + + for col in text_columns : + if col not in clean_df.columns : + continue + + clean_df[col] = clean_df[col].astype(str).str.strip() + + # Замена NaN строк + clean_df[col] = clean_df[col].replace('nan', '').replace('None', '') + + # Проверка на слишком длинные тексты + if col == 'Описание' : + too_long = clean_df[clean_df[col].str.len() > 500] + if len(too_long) > 0 : + errors.append(f"Найдено {len(too_long)} очень длинных описаний") + clean_df.loc[too_long.index, col] = clean_df.loc[too_long.index, col].str[:500] + + return clean_df, errors + + +def load_transactions(file_path: str = settings.data_file_path) -> pd.DataFrame : + """Загрузка и валидация транзакций из Excel файла""" + try : + logger.info(f"Загрузка данных из {file_path}") + + # Чтение файла df = pd.read_excel(file_path) - logger.info(f"Успешно загружено {len(df)} транзакций") - return df - except Exception as e: - logger.error(f"Ошибка загрузки файла: {e}") + + if df.empty : + raise ValueError("Файл не содержит данных") + + # Валидация и очистка данных + clean_df, errors = DataValidator.validate_transaction_data(df) + + if errors : + logger.warning(f"Обнаружены проблемы при загрузке данных: {errors}") + + logger.info(f"Успешно загружено {len(clean_df)} транзакций") + return clean_df + + except Exception as e : + logger.error(f"Ошибка загрузки транзакций: {e}") raise -def filter_transactions_by_date(df: pd.DataFrame, date_str: str) -> pd.DataFrame: - """Фильтрация транзакций по дате.""" - try: - target_date = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") - start_of_month = target_date.replace(day=1, hour=0, minute=0, second=0) +def get_date_range(date_str: str, period: str = 'M') -> Tuple[datetime, datetime] : + """Получение диапазона дат для анализа""" + try : + # Парсим дату с учетом времени + if ' ' in date_str : + date = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S') + else : + date = datetime.strptime(date_str, '%Y-%m-%d') + + if period == 'W' : # Неделя + start_date = date - timedelta(days=date.weekday()) + start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0) + end_date = start_date + timedelta(days=6) + elif period == 'M' : # Месяц + start_date = date.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + next_month = date.replace(day=28) + timedelta(days=4) + end_date = min(next_month.replace(day=1) - timedelta(days=1), date) + elif period == 'Y' : # Год + start_date = date.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) + end_date = date + elif period == 'ALL' : # Все данные + start_date = datetime(2000, 1, 1) + end_date = date + else : + raise ValueError(f"Неизвестный период: {period}") - df['Дата операции'] = pd.to_datetime(df['Дата операции']) - filtered_df = df[(df['Дата операции'] >= start_of_month) & - (df['Дата операции'] <= target_date)] + return start_date, end_date - logger.info(f"Отфильтровано {len(filtered_df)} транзакций за период") - return filtered_df - except Exception as e: - logger.error(f"Ошибка фильтрации по дате: {e}") + except ValueError as e : + logger.error(f"Ошибка парсинга даты {date_str}: {e}") raise -def get_greeting(time_str: str) -> str: - """Получение приветствия в зависимости от времени суток.""" - try: - time_obj = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S").time() +def filter_transactions_by_date(df: pd.DataFrame, start_date: datetime, + end_date: datetime) -> pd.DataFrame : + """Фильтрация транзакций по диапазону дат""" + mask = (df['Дата операции'] >= start_date) & (df['Дата операции'] <= end_date) + filtered_df = df.loc[mask].copy() - if time_obj.hour < 6: - return "Доброй ночи" - elif time_obj.hour < 12: + logger.info(f"Отфильтровано {len(filtered_df)} транзакций за период {start_date.date()} - {end_date.date()}") + return filtered_df + + +def get_greeting(time_str: str) -> str : + """Получение приветствия в зависимости от времени""" + try : + if ' ' in time_str : + hour = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S').hour + else : + hour = datetime.strptime(time_str, '%Y-%m-%d').hour + + if 5 <= hour < 12 : return "Доброе утро" - elif time_obj.hour < 18: + elif 12 <= hour < 17 : return "Добрый день" - else: + elif 17 <= hour < 23 : return "Добрый вечер" - except Exception as e: - logger.error(f"Ошибка определения приветствия: {e}") - return "Добрый день" - - -def get_currency_rates(currencies: List[str]) -> List[Dict[str, Any]]: - """Получение курсов валют через API.""" - try: - # Заглушка для демонстрации - в реальном проекте используйте реальное API - rates = [] - for currency in currencies: - if currency == "USD": - rates.append({"currency": currency, "rate": 73.21}) - elif currency == "EUR": - rates.append({"currency": currency, "rate": 87.08}) - else: - rates.append({"currency": currency, "rate": 1.0}) - - logger.info("Курсы валют успешно получены") - return rates - except Exception as e: - logger.error(f"Ошибка получения курсов валют: {e}") - return [] - - -def get_stock_prices(stocks: List[str]) -> List[Dict[str, Any]]: - """Получение цен акций через API.""" - try: - # Заглушка для демонстрации - prices = [] - stock_prices = { - "AAPL": 150.12, - "AMZN": 3173.18, - "GOOGL": 2742.39, - "MSFT": 296.71, - "TSLA": 1007.08 - } - - for stock in stocks: - price = stock_prices.get(stock, 0.0) - prices.append({"stock": stock, "price": price}) - - logger.info("Цены акций успешно получены") - return prices - except Exception as e: - logger.error(f"Ошибка получения цен акций: {e}") - return [] + else : + return "Доброй ночи" + + except ValueError : + return "Добрый день" # По умолчанию + + +def load_user_settings() -> Dict[str, Any] : + """Загрузка пользовательских настроек""" + try : + with open(settings.user_settings_path, 'r', encoding='utf-8') as f : + settings_data = json.load(f) + + # Валидация настроек + required_sections = ['user_currencies', 'user_stocks'] + for section in required_sections : + if section not in settings_data : + raise ValueError(f"Отсутствует обязательный раздел {section} в настройках") + + return settings_data + + except FileNotFoundError : + logger.error(f"Файл настроек {settings.user_settings_path} не найден") + raise + except json.JSONDecodeError as e : + logger.error(f"Ошибка парсинга JSON в настройках: {e}") + raise + + +def calculate_cashback(amount: float, category: str, cashback_rules: Dict[str, float]) -> float : + """Расчет кешбэка по сложной логике""" + try : + # Базовая ставка + base_rate = cashback_rules.get('default', 0.01) + + # Повышенный кешбэк для категорий + category_rate = cashback_rules.get(category, base_rate) + + # Дополнительный бонус для больших покупок (ИСПРАВЛЕНО) + bonus_rate = 0.0 + if amount > 10000 : + bonus_rate = 0.02 # +2% для покупок > 10,000 + elif amount > 5000 : + bonus_rate = 0.01 # +1% для покупок > 5,000 + + total_rate = category_rate + bonus_rate + + # Ограничение максимального кешбэка 15% + total_rate = min(total_rate, 0.15) + + cashback = amount * total_rate + + # Округление до 2 знаков + return round(cashback, 2) + + except Exception as e : + logger.warning(f"Ошибка расчета кешбэка: {e}") + return round(amount * 0.01, 2) # Fallback 1% diff --git a/src/views.py b/src/views.py index e856e82..e3a7ce1 100644 --- a/src/views.py +++ b/src/views.py @@ -1,80 +1,213 @@ -import json import logging +from typing import Any, Dict, List, Optional + import pandas as pd -from datetime import datetime -from typing import Dict, List, Any -from .utils import get_greeting, get_currency_rates, get_stock_prices, filter_transactions_by_date + +from .api_client import SyncAPIClient +from .utils import ( + calculate_cashback, + filter_transactions_by_date, + get_date_range, + get_greeting, + load_transactions, + load_user_settings, +) logger = logging.getLogger(__name__) -def home_page(date_str: str, transactions_df: pd.DataFrame, user_settings: Dict[str, Any]) -> Dict[str, Any]: - """Главная страница с анализом транзакций.""" - try: - # Фильтрация транзакций - filtered_df = filter_transactions_by_date(transactions_df, date_str) +class DataProcessor: + """Процессор данных для веб-страниц""" + + @staticmethod + def process_main_page_data(df: pd.DataFrame, date_time: str, + settings: Dict[str, Any]) -> Dict[str, Any]: + """Обработка данных для главной страницы""" + try: + start_date, end_date = get_date_range(date_time, 'M') + filtered_df = filter_transactions_by_date(df, start_date, end_date) + + cashback_rules = settings.get('cashback_rules', {'default': 0.01}) + + return { + 'greeting': get_greeting(date_time), + 'cards': DataProcessor._get_cards_data(filtered_df, cashback_rules), + 'top_transactions': DataProcessor._get_top_transactions(filtered_df, 5), + 'currency_rates': SyncAPIClient.get_currency_rates(settings['user_currencies']), + 'stock_prices': SyncAPIClient.get_stock_prices(settings['user_stocks']) + } + except Exception as e: + logger.error(f"Ошибка обработки данных главной страницы: {e}") + raise + + @staticmethod + def process_events_page_data(df: pd.DataFrame, date: str, period: str, + settings: Dict[str, Any]) -> Dict[str, Any]: + """Обработка данных для страницы событий""" + try: + start_date, end_date = get_date_range(date, period) + filtered_df = filter_transactions_by_date(df, start_date, end_date) + + return { + 'expenses': DataProcessor._get_expenses_data(filtered_df), + 'income': DataProcessor._get_income_data(filtered_df), + 'currency_rates': SyncAPIClient.get_currency_rates(settings['user_currencies']), + 'stock_prices': SyncAPIClient.get_stock_prices(settings['user_stocks']) + } + except Exception as e: + logger.error(f"Ошибка обработки данных страницы событий: {e}") + raise + + @staticmethod + def _get_cards_data(df: pd.DataFrame, cashback_rules: Dict[str, float]) -> List[Dict[str, Any]]: + """Данные по картам""" + cards_data = [] + + # Получаем уникальные номера карт + card_numbers = [card for card in df['Номер карты'].unique() + if not pd.isna(card) and str(card).strip() != ''] + + for card in card_numbers: + card_df = df[df['Номер карты'] == card] + # Только успешные операции расходов + expenses_df = card_df[(card_df['Статус'] == 'OK') + & (card_df['Сумма операции'] > 0)] + + if expenses_df.empty: + continue + + total_spent = expenses_df['Сумма операции'].sum() + + # Расчет общего кешбэка по сложной логике + total_cashback = 0 + for _, transaction in expenses_df.iterrows(): + category = transaction.get('Категория', '') + amount = transaction['Сумма операции'] + total_cashback += calculate_cashback(amount, category, cashback_rules) + + cards_data.append({ + 'last_digits': str(card)[-4:], + 'total_spent': round(total_spent, 2), + 'cashback': round(total_cashback, 2) + }) + + # Сортировка по убыванию общей суммы расходов + return sorted(cards_data, key=lambda x: x['total_spent'], reverse=True) + + @staticmethod + def _get_top_transactions(df: pd.DataFrame, limit: int) -> List[Dict[str, Any]]: + """Топ транзакций по сумме платежа""" + # Берем абсолютное значение для сравнения (учитываем и доходы и расходы) + df['Абсолютная сумма'] = df['Сумма платежа'].abs() + top_df = df.nlargest(limit, 'Абсолютная сумма') + + transactions = [] + for _, row in top_df.iterrows(): + transactions.append({ + 'date': row['Дата операции'].strftime('%d.%m.%Y'), + 'amount': round(row['Сумма платежа'], 2), + 'category': row.get('Категория', 'Не указана'), + 'description': row.get('Описание', '')[:100] # Ограничение длины + }) + + return transactions - # Приветствие - greeting = get_greeting(date_str) + @staticmethod + def _get_expenses_data(df: pd.DataFrame) -> Dict[str, Any]: + """Данные по расходам""" + expenses_df = df[(df['Статус'] == 'OK') & (df['Сумма операции'] > 0)] - # Анализ по картам - cards_analysis = _analyze_cards(filtered_df) + if expenses_df.empty: + return { + 'total_amount': 0, + 'main': [], + 'transfers_and_cash': [] + } - # Топ транзакций - top_transactions = _get_top_transactions(filtered_df) + total_amount = expenses_df['Сумма операции'].sum() - # Курсы валют и акции - currency_rates = get_currency_rates(user_settings.get("user_currencies", [])) - stock_prices = get_stock_prices(user_settings.get("user_stocks", [])) + # Основные категории (топ-6 + остальное) + category_expenses = expenses_df.groupby('Категория')['Сумма операции'].sum() + top_categories = category_expenses.nlargest(6) + other_categories = category_expenses.iloc[6:].sum() if len(category_expenses) > 6 else 0 + + main_categories = [ + {'category': cat, 'amount': round(amount, 0)} + for cat, amount in top_categories.items() if not pd.isna(cat) + ] + + if other_categories > 0: + main_categories.append({'category': 'Остальное', 'amount': round(other_categories, 0)}) + + # Переводы и наличные + transfers_cash = expenses_df[expenses_df['Категория'].isin(['Наличные', 'Переводы'])] + transfers_data = transfers_cash.groupby('Категория')['Сумма операции'].sum() + + transfers_list = [ + {'category': cat, 'amount': round(amount, 0)} + for cat, amount in transfers_data.items() + ] return { - "greeting": greeting, - "cards": cards_analysis, - "top_transactions": top_transactions, - "currency_rates": currency_rates, - "stock_prices": stock_prices + 'total_amount': round(total_amount, 0), + 'main': main_categories, + 'transfers_and_cash': transfers_list } - except Exception as e: - logger.error(f"Ошибка генерации главной страницы: {e}") - return {"error": str(e)} + @staticmethod + def _get_income_data(df: pd.DataFrame) -> Dict[str, Any]: + """Данные по поступлениям""" + income_df = df[(df['Статус'] == 'OK') & (df['Сумма операции'] < 0)] -def _analyze_cards(df: pd.DataFrame) -> List[Dict[str, Any]]: - """Анализ транзакций по картам.""" - cards_analysis = [] + if income_df.empty: + return { + 'total_amount': 0, + 'main': [] + } - # Группировка по последним цифрам карты - if 'Номер карты' in df.columns: - for card in df['Номер карты'].dropna().unique(): - card_transactions = df[df['Номер карты'] == card] - total_spent = card_transactions['Сумма операции'].sum() - cashback = total_spent * 0.01 # 1% кешбэк + # Преобразуем отрицательные суммы в положительные + income_df = income_df.copy() + income_df['Сумма операции'] = income_df['Сумма операции'].abs() - cards_analysis.append({ - "last_digits": str(card)[-4:], - "total_spent": round(total_spent, 2), - "cashback": round(cashback, 2) - }) + total_amount = income_df['Сумма операции'].sum() - return cards_analysis + category_income = income_df.groupby('Категория')['Сумма операции'].sum() + main_income = [ + {'category': cat, 'amount': round(amount, 0)} + for cat, amount in category_income.items() if not pd.isna(cat) + ] -def _get_top_transactions(df: pd.DataFrame, top_n: int = 5) -> List[Dict[str, Any]]: - """Получение топ-N транзакций по сумме.""" + return { + 'total_amount': round(total_amount, 0), + 'main': sorted(main_income, key=lambda x: x['amount'], reverse=True) + } + + +def main_page(date_time: str, data_file: Optional[str] = None) -> Dict[str, Any]: + """Главная страница - генерация JSON данных""" try: - # Берем абсолютные значения для сортировки - df_sorted = df.nlargest(top_n, 'Сумма операции', keep='first') - - top_transactions = [] - for _, row in df_sorted.iterrows(): - top_transactions.append({ - "date": row['Дата операции'].strftime("%d.%m.%Y"), - "amount": round(row['Сумма операции'], 2), - "category": row.get('Категория', 'Неизвестно'), - "description": row.get('Описание', '') - }) + df = load_transactions(data_file) if data_file else load_transactions() + settings = load_user_settings() + + processor = DataProcessor() + return processor.process_main_page_data(df, date_time, settings) + + except Exception as e: + logger.error(f"Ошибка генерации главной страницы: {e}") + return {'error': str(e), 'greeting': 'Добрый день'} + + +def events_page(date: str, period: str = 'M', + data_file: Optional[str] = None) -> Dict[str, Any]: + """Страница событий - генерация JSON данных""" + try: + df = load_transactions(data_file) if data_file else load_transactions() + settings = load_user_settings() + + processor = DataProcessor() + return processor.process_events_page_data(df, date, period, settings) - return top_transactions except Exception as e: - logger.error(f"Ошибка получения топ транзакций: {e}") - return [] + logger.error(f"Ошибка генерации страницы событий: {e}") + return {'error': str(e)} diff --git a/test_app.py b/test_app.py new file mode 100644 index 0000000..3410c30 --- /dev/null +++ b/test_app.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Простой скрипт для тестирования приложения +""" + +import sys +from pathlib import Path + +# Добавляем src в путь Python +sys.path.insert(0, str(Path(__file__).parent)) + +# Создаем необходимые директории +Path('logs').mkdir(exist_ok=True) +Path('reports').mkdir(exist_ok=True) +Path('data').mkdir(exist_ok=True) + + +def test_basic_functionality(): + """Тест базовой функциональности""" + try: + print("🧪 Тестирование Transaction Analyzer...") + + # Импортируем модули + from src.reports import ReportGenerator + from src.utils import load_transactions, load_user_settings + from src.views import events_page, main_page + + # 1. Загрузка данных + print("1. Загрузка данных...") + df = load_transactions() + settings = load_user_settings() + print(f" ✅ Транзакций: {len(df)}") + print(f" ✅ Настроек: {len(settings)}") + + # 2. Тест веб-страниц + print("2. Тест веб-страниц...") + main_data = main_page("2023-12-20 15:30:00") + events_data = events_page("2023-12-20", "M") + print(f" ✅ Главная страница: {main_data['greeting']}") + print(f" ✅ Страница событий: расходы {events_data['expenses']['total_amount']}") + + # 3. Тест отчетов + print("3. Тест отчетов...") + report = ReportGenerator.spending_by_category(df, "Супермаркеты") + print(f" ✅ Отчет по категории: {len(report)} записей") + + # 4. Тест сервисов + print("4. Тест сервисов...") + from src.services import simple_search + transactions_list = df.head(10).to_dict('records') + results = simple_search(transactions_list, "магазин") + print(f" ✅ Поиск: {len(results)} результатов") + + print("\n🎉 ВСЕ ТЕСТЫ ПРОЙДЕНЫ УСПЕШНО!") + return True + + except Exception as e: + print(f"\n❌ ОШИБКА: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + test_basic_functionality() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c1a7056 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,39 @@ +import pandas as pd +import pytest + + +@pytest.fixture +def sample_transactions(): + """Фикстура с примером транзакций""" + dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D') + return pd.DataFrame({ + 'Дата операции': dates[:100], + 'Номер карты': ['1234567812345814'] * 50 + ['1234567812347512'] * 50, + 'Статус': ['OK'] * 100, + 'Сумма операции': [1000.0, 500.0] * 50, + 'Категория': ['Супермаркеты', 'Фастфуд'] * 50, + 'Описание': [f'Транзакция {i}' for i in range(100)], + 'Сумма платежа': [1000.0, 500.0] * 50 + }) + + +@pytest.fixture +def sample_settings(): + """Фикстура с настройками""" + return { + "user_currencies": ["USD", "EUR"], + "user_stocks": ["AAPL", "AMZN"], + "cashback_rules": { + "Супермаркеты": 0.05, + "Фастфуд": 0.03, + "default": 0.01 + } + } + + +@pytest.fixture(autouse=True) +def setup_teardown(): + """Фикстура для настройки перед каждым тестом""" + # Настройка перед тестом + yield + # Очистка после теста diff --git a/tests/reports/monthly_summary_20231220_000000.json b/tests/reports/monthly_summary_20231220_000000.json new file mode 100644 index 0000000..c62910a --- /dev/null +++ b/tests/reports/monthly_summary_20231220_000000.json @@ -0,0 +1,3 @@ +{ + "error": "'numpy.float64' object has no attribute 'abs'" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_221851.json b/tests/reports/monthly_summary_20250930_221851.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_221851.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_222642.json b/tests/reports/monthly_summary_20250930_222642.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_222642.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_222950.json b/tests/reports/monthly_summary_20250930_222950.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_222950.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_222959.json b/tests/reports/monthly_summary_20250930_222959.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_222959.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_223102.json b/tests/reports/monthly_summary_20250930_223102.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_223102.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_224838.json b/tests/reports/monthly_summary_20250930_224838.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_224838.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_225857.json b/tests/reports/monthly_summary_20250930_225857.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_225857.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_225902.json b/tests/reports/monthly_summary_20250930_225902.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_225902.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_230017.json b/tests/reports/monthly_summary_20250930_230017.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_230017.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_230123.json b/tests/reports/monthly_summary_20250930_230123.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_230123.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_230203.json b/tests/reports/monthly_summary_20250930_230203.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_230203.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_230204.json b/tests/reports/monthly_summary_20250930_230204.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_230204.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/monthly_summary_20250930_230205.json b/tests/reports/monthly_summary_20250930_230205.json new file mode 100644 index 0000000..b19b0ab --- /dev/null +++ b/tests/reports/monthly_summary_20250930_230205.json @@ -0,0 +1,3 @@ +{ + "error": "Нет данных за указанный период" +} \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_221851.json b/tests/reports/spending_by_category_20250930_221851.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_category_20250930_221851.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_222642.json b/tests/reports/spending_by_category_20250930_222642.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_category_20250930_222642.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_222949.json b/tests/reports/spending_by_category_20250930_222949.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_category_20250930_222949.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_222959.json b/tests/reports/spending_by_category_20250930_222959.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_category_20250930_222959.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_223101.json b/tests/reports/spending_by_category_20250930_223101.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_category_20250930_223101.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_224838.json b/tests/reports/spending_by_category_20250930_224838.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_category_20250930_224838.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_category_20250930_225857.json b/tests/reports/spending_by_category_20250930_225857.json new file mode 100644 index 0000000..685786d --- /dev/null +++ b/tests/reports/spending_by_category_20250930_225857.json @@ -0,0 +1,14 @@ +[ + { + "Месяц": "2023-10", + "Сумма": 1000 + }, + { + "Месяц": "2023-11", + "Сумма": 2000 + }, + { + "Месяц": "2023-12", + "Сумма": 3000 + } +] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_221851.json b/tests/reports/spending_by_weekday_20250930_221851.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_221851.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_222642.json b/tests/reports/spending_by_weekday_20250930_222642.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_222642.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_222949.json b/tests/reports/spending_by_weekday_20250930_222949.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_222949.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_222959.json b/tests/reports/spending_by_weekday_20250930_222959.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_222959.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_223101.json b/tests/reports/spending_by_weekday_20250930_223101.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_223101.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_224838.json b/tests/reports/spending_by_weekday_20250930_224838.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_224838.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_weekday_20250930_225857.json b/tests/reports/spending_by_weekday_20250930_225857.json new file mode 100644 index 0000000..c0ffc11 --- /dev/null +++ b/tests/reports/spending_by_weekday_20250930_225857.json @@ -0,0 +1,30 @@ +[ + { + "День недели": "Понедельник", + "Средняя сумма": 100.0 + }, + { + "День недели": "Вторник", + "Средняя сумма": 200.0 + }, + { + "День недели": "Среда", + "Средняя сумма": 300.0 + }, + { + "День недели": "Четверг", + "Средняя сумма": 400.0 + }, + { + "День недели": "Пятница", + "Средняя сумма": 500.0 + }, + { + "День недели": "Суббота", + "Средняя сумма": 600.0 + }, + { + "День недели": "Воскресенье", + "Средняя сумма": 700.0 + } +] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_221851.json b/tests/reports/spending_by_workday_20250930_221851.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_221851.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_222642.json b/tests/reports/spending_by_workday_20250930_222642.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_222642.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_222950.json b/tests/reports/spending_by_workday_20250930_222950.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_222950.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_222959.json b/tests/reports/spending_by_workday_20250930_222959.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_222959.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_223102.json b/tests/reports/spending_by_workday_20250930_223102.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_223102.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_224838.json b/tests/reports/spending_by_workday_20250930_224838.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_224838.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_224839.json b/tests/reports/spending_by_workday_20250930_224839.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_224839.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/reports/spending_by_workday_20250930_225857.json b/tests/reports/spending_by_workday_20250930_225857.json new file mode 100644 index 0000000..ddf1af6 --- /dev/null +++ b/tests/reports/spending_by_workday_20250930_225857.json @@ -0,0 +1,10 @@ +[ + { + "Тип дня": "Выходной", + "Средняя сумма": 450.0 + }, + { + "Тип дня": "Рабочий", + "Средняя сумма": 200.0 + } +] \ No newline at end of file diff --git a/tests/reports/test_function_20231220_153000.json b/tests/reports/test_function_20231220_153000.json new file mode 100644 index 0000000..d6faa34 --- /dev/null +++ b/tests/reports/test_function_20231220_153000.json @@ -0,0 +1,10 @@ +[ + { + "col1": 1, + "col2": 3 + }, + { + "col1": 2, + "col2": 4 + } +] \ No newline at end of file diff --git a/tests/test_api_client.py b/tests/test_api_client.py new file mode 100644 index 0000000..1793af3 --- /dev/null +++ b/tests/test_api_client.py @@ -0,0 +1,46 @@ +from unittest.mock import AsyncMock, patch + +import pytest + + +class TestAPIClient: + @pytest.mark.asyncio + async def test_get_currency_rates(self): + async with AsyncMock() as mock_session: + with patch('aiohttp.ClientSession', return_value=mock_session): + from src.api_client import APIClient + client = APIClient() + client.session = mock_session + + # Мок успешного ответа + mock_response = AsyncMock() + mock_response.status = 200 + mock_response.json.return_value = { + "rates": {"USD": 0.0107, "EUR": 0.0099}, + "base": "RUB" + } + mock_session.get.return_value.__aenter__.return_value = mock_response + + rates = await client.get_currency_rates(["USD", "EUR"]) + # Ожидаем только запрошенные валюты, RUB не должен включаться если не запрошен + assert len(rates) == 2 + assert any(rate['currency'] == 'USD' for rate in rates) + assert any(rate['currency'] == 'EUR' for rate in rates) + + @pytest.mark.asyncio + async def test_get_currency_rates_fallback(self): + async with AsyncMock() as mock_session: + with patch('aiohttp.ClientSession', return_value=mock_session): + from src.api_client import APIClient + client = APIClient() + client.session = mock_session + + # Мок неудачного ответа + mock_response = AsyncMock() + mock_response.status = 500 + mock_session.get.return_value.__aenter__.return_value = mock_response + + rates = await client.get_currency_rates(["USD", "EUR"]) + # Fallback должен вернуть только запрошенные валюты + assert len(rates) == 2 + assert all(rate['rate'] > 0 for rate in rates) diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..e4eebaf --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,201 @@ +from unittest.mock import MagicMock, patch + +import pytest + +from src.main import TransactionAnalyzer, main + + +class TestTransactionAnalyzer: + """Тесты для основного класса приложения""" + + @pytest.fixture + def analyzer(self): + return TransactionAnalyzer() + + def test_initialization(self, analyzer): + """Тест инициализации анализатора""" + assert analyzer.data_file is not None + assert analyzer.transactions_df is None + assert analyzer.settings is None + + @patch('src.views.main_page') + def test_generate_main_page(self, mock_main_page, analyzer): + """Тест генерации главной страницы""" + # Мокируем данные + analyzer.transactions_df = MagicMock() + analyzer.settings = MagicMock() + expected_result = {'greeting': 'Добрый день'} + mock_main_page.return_value = expected_result + + # Генерируем страницу + result = analyzer.generate_main_page('2023-12-20 15:30:00') + + # Проверяем + assert result == expected_result + mock_main_page.assert_called_once_with('2023-12-20 15:30:00', 'data/operations.xlsx') + + @patch('src.views.events_page') + def test_generate_events_page(self, mock_events_page, analyzer): + """Тест генерации страницы событий""" + # Мокируем данные + analyzer.transactions_df = MagicMock() + analyzer.settings = MagicMock() + expected_result = {'expenses': {'total_amount': 1000}} + mock_events_page.return_value = expected_result + + # Генерируем страницу + result = analyzer.generate_events_page('2023-12-20', 'M') + + # Проверяем + assert result == expected_result + mock_events_page.assert_called_once_with('2023-12-20', 'M', 'data/operations.xlsx') + + @patch('src.services.profitable_cashback_categories') + def test_analyze_cashback_categories(self, mock_cashback, analyzer): + """Тест анализа кешбэка""" + # Мокируем данные + analyzer.transactions_df = MagicMock() + analyzer.settings = {'cashback_rules': {'default': 0.01}} + expected_result = {'Супермаркеты': 150.0} + mock_cashback.return_value = expected_result + + # Анализируем + result = analyzer.analyze_cashback_categories(2023, 12) + + # Проверяем + assert result == expected_result + mock_cashback.assert_called_once() + + @patch('src.services.investment_bank') + def test_calculate_investment(self, mock_investment, analyzer): + """Тест расчета инвесткопилки""" + # Мокируем данные + mock_df = MagicMock() + mock_df.to_dict.return_value = [{'Дата операции': '2023-12-01', 'Сумма операции': 1000}] + analyzer.transactions_df = mock_df + mock_investment.return_value = 50.0 + + # Рассчитываем + result = analyzer.calculate_investment('2023-12', 50) + + # Проверяем + assert result == 50.0 + mock_investment.assert_called_once() + + @patch('src.services.simple_search') + def test_search_transactions(self, mock_search, analyzer): + """Тест поиска транзакций""" + # Мокируем данные + mock_df = MagicMock() + mock_df.to_dict.return_value = [{'Описание': 'Магазин'}] + analyzer.transactions_df = mock_df + mock_search.return_value = [{'Описание': 'Магазин'}] + + # Ищем + result = analyzer.search_transactions('магазин') + + # Проверяем + assert len(result) == 1 + mock_search.assert_called_once() + + @patch('src.reports.ReportGenerator') + def test_generate_reports(self, mock_report_generator, analyzer): + """Тест генерации отчетов""" + # Мокируем данные + analyzer.transactions_df = MagicMock() + mock_report_generator.spending_by_category.return_value = MagicMock() + mock_report_generator.spending_by_weekday.return_value = MagicMock() + mock_report_generator.spending_by_workday.return_value = MagicMock() + mock_report_generator.monthly_summary.return_value = {'total': 1000} + + # Генерируем отчеты + reports = analyzer.generate_reports() + + # Проверяем + assert 'spending_by_category' in reports + assert 'monthly_summary' in reports + assert mock_report_generator.spending_by_category.call_count == 1 + + +class TestMainFunction: + """Тесты для основной функции""" + + @patch('src.main.TransactionAnalyzer') + @patch('builtins.print') + def test_main_web_command(self, mock_print, mock_analyzer): + """Тест main с командой web""" + # Мокируем анализатор + mock_instance = MagicMock() + mock_instance.load_data.return_value = None + mock_instance.generate_main_page.return_value = {'greeting': 'Добрый день'} + mock_instance.generate_events_page.return_value = {'expenses': {'total_amount': 1000}} + mock_analyzer.return_value = mock_instance + + # Запускаем с командой web + with patch('sys.argv', ['main.py', '--command', 'web']): + main() + + # Проверяем вызовы + mock_instance.load_data.assert_called_once() + mock_instance.generate_main_page.assert_called_once() + mock_instance.generate_events_page.assert_called_once() + + @patch('src.main.TransactionAnalyzer') + @patch('builtins.print') + def test_main_report_command(self, mock_print, mock_analyzer): + """Тест main с командой report""" + # Мокируем анализатор + mock_instance = MagicMock() + mock_instance.load_data.return_value = None + mock_instance.generate_reports.return_value = { + 'monthly_summary': {'total': 1000} + } + mock_analyzer.return_value = mock_instance + + # Запускаем с командой report + with patch('sys.argv', ['main.py', '--command', 'report']): + main() + + # Проверяем вызовы + mock_instance.load_data.assert_called_once() + mock_instance.generate_reports.assert_called_once() + + @patch('src.main.TransactionAnalyzer') + @patch('builtins.print') + def test_main_analyze_command(self, mock_print, mock_analyzer): + """Тест main с командой analyze""" + # Мокируем анализатор + mock_instance = MagicMock() + mock_instance.load_data.return_value = None + mock_instance.analyze_cashback_categories.return_value = {'Категория': 100} + mock_instance.calculate_investment.return_value = 50.0 + mock_analyzer.return_value = mock_instance + + # Запускаем с командой analyze + with patch('sys.argv', ['main.py', '--command', 'analyze']): + main() + + # Проверяем вызовы + mock_instance.load_data.assert_called_once() + mock_instance.analyze_cashback_categories.assert_called_once() + mock_instance.calculate_investment.assert_called_once() + + @patch('src.main.TransactionAnalyzer') + @patch('builtins.print') + def test_main_test_command(self, mock_print, mock_analyzer): + """Тест main с командой test""" + # Мокируем анализатор + mock_instance = MagicMock() + mock_instance.load_data.return_value = None + mock_instance.transactions_df = MagicMock() + mock_instance.settings = {'user_currencies': ['USD']} + mock_instance.search_transactions.return_value = [{'Описание': 'Магазин'}] + mock_analyzer.return_value = mock_instance + + # Запускаем с командой test + with patch('sys.argv', ['main.py', '--command', 'test']): + main() + + # Проверяем вызовы + mock_instance.load_data.assert_called_once() + mock_instance.search_transactions.assert_called_once() diff --git a/tests/test_reports.py b/tests/test_reports.py new file mode 100644 index 0000000..f89bee8 --- /dev/null +++ b/tests/test_reports.py @@ -0,0 +1,251 @@ +import json +from datetime import datetime +from unittest.mock import patch + +import numpy as np +import pandas as pd +import pytest + +from src.reports import ( + ReportGenerator, + report_decorator, + spending_by_category, + spending_by_weekday, + spending_by_workday, +) + + +class TestReportGeneratorExtended: + """Расширенные тесты для ReportGenerator""" + + @pytest.fixture + def sample_transactions_complex(self): + """Фикстура с комплексными данными для отчетов""" + dates = pd.date_range(start='2023-09-01', end='2023-12-31', freq='D') + return pd.DataFrame({ + 'Дата операции': dates, + 'Статус': ['OK'] * len(dates), + 'Категория': ['Супермаркеты'] * 30 + ['Фастфуд'] * 30 + ['Транспорт'] * 30 + ['Развлечения'] * 31, + 'Сумма операции': np.random.uniform(100, 5000, len(dates)) + }) + + def test_spending_by_category_empty_data(self): + """Тест отчета по категориям с пустыми данными""" + df = pd.DataFrame(columns=['Дата операции', 'Категория', 'Сумма операции', 'Статус']) + + result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20') + + assert result.empty + assert list(result.columns) == ['Месяц', 'Сумма'] + + def test_spending_by_category_no_matching_category(self): + """Тест отчета по категориям без совпадающих категорий""" + df = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-02']), + 'Статус': ['OK', 'OK'], + 'Категория': ['Фастфуд', 'Транспорт'], # Нет Супермаркетов + 'Сумма операции': [1000, 2000] + }) + + result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20') + + assert result.empty + + def test_spending_by_category_only_failed_transactions(self): + """Тест отчета по категориям только с неудачными транзакциями""" + df = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-02']), + 'Статус': ['FAILED', 'FAILED'], + 'Категория': ['Супермаркеты', 'Супермаркеты'], + 'Сумма операции': [1000, 2000] + }) + + result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20') + + assert result.empty + + def test_spending_by_category_only_income(self): + """Тест отчета по категориям только с доходами""" + df = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-02']), + 'Статус': ['OK', 'OK'], + 'Категория': ['Супермаркеты', 'Супермаркеты'], + 'Сумма операции': [-1000, -2000] # Доходы + }) + + result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20') + + assert result.empty + + def test_spending_by_category_different_months(self): + """Тест отчета по категориям за несколько месяцев""" + df = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-10-15', '2023-11-15', '2023-12-15']), + 'Статус': ['OK', 'OK', 'OK'], + 'Категория': ['Супермаркеты', 'Супермаркеты', 'Супермаркеты'], + 'Сумма операции': [1000, 2000, 3000] + }) + + result = ReportGenerator.spending_by_category(df, 'Супермаркеты', '2023-12-20') + + # Должны быть данные за 3 месяца (октябрь, ноябрь, декабрь) + assert len(result) == 3 + assert set(result['Месяц']) == {'2023-10', '2023-11', '2023-12'} + assert result['Сумма'].sum() == 6000 + + def test_spending_by_weekday_empty_data(self): + """Тест отчета по дням недели с пустыми данными""" + df = pd.DataFrame(columns=['Дата операции', 'Сумма операции', 'Статус']) + + result = ReportGenerator.spending_by_weekday(df, '2023-12-20') + + assert result.empty + assert list(result.columns) == ['День недели', 'Средняя сумма'] + + def test_spending_by_weekday_correct_ordering(self): + """Тест правильного порядка дней недели в отчете""" + df = pd.DataFrame({ + 'Дата операции': pd.to_datetime([ + '2023-12-18', '2023-12-19', '2023-12-20', # Пн, Вт, Ср + '2023-12-21', '2023-12-22', '2023-12-23', '2023-12-24' # Чт, Пт, Сб, Вс + ]), + 'Статус': ['OK'] * 7, + 'Сумма операции': [100, 200, 300, 400, 500, 600, 700] + }) + + result = ReportGenerator.spending_by_weekday(df, '2023-12-24') + + # Проверяем порядок дней недели + expected_order = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'] + assert list(result['День недели']) == expected_order + + # Проверяем средние значения (в данном случае они равны суммам, так по одной транзакции на день) + assert list(result['Средняя сумма']) == [100, 200, 300, 400, 500, 600, 700] + + def test_spending_by_workday_empty_data(self): + """Тест отчета по рабочим/выходным с пустыми данными""" + df = pd.DataFrame(columns=['Дата операции', 'Сумма операции', 'Статус']) + + result = ReportGenerator.spending_by_workday(df, '2023-12-20') + + assert result.empty + assert list(result.columns) == ['Тип дня', 'Средняя сумма'] + + def test_spending_by_workday_correct_classification(self): + """Тест правильной классификации рабочих и выходных дней""" + df = pd.DataFrame({ + 'Дата операции': pd.to_datetime([ + '2023-12-18', '2023-12-19', '2023-12-20', # Пн, Вт, Ср - рабочие + '2023-12-23', '2023-12-24' # Сб, Вс - выходные + ]), + 'Статус': ['OK'] * 5, + 'Сумма операции': [100, 200, 300, 400, 500] + }) + + result = ReportGenerator.spending_by_workday(df, '2023-12-24') + + assert len(result) == 2 + + workday_data = result[result['Тип дня'] == 'Рабочий'] + weekend_data = result[result['Тип дня'] == 'Выходной'] + + # Средняя за рабочие дни: (100 + 200 + 300) / 3 = 200 + assert workday_data['Средняя сумма'].iloc[0] == 200.0 + # Средняя за выходные: (400 + 500) / 2 = 450 + assert weekend_data['Средняя сумма'].iloc[0] == 450.0 + + def test_monthly_summary_empty_data(self): + """Тест сводного отчета с пустыми данными""" + df = pd.DataFrame(columns=['Дата операции', 'Сумма операции', 'Статус', 'Категория']) + + result = ReportGenerator.monthly_summary(df, 6) + + assert 'error' in result + assert result['error'] == "Нет данных за указанный период" + + +class TestReportDecorator: + """Тесты декоратора отчетов""" + + def test_report_decorator_with_filename(self, tmp_path): + """Тест декоратора с указанием имени файла""" + test_filename = tmp_path / "test_report.json" + + @report_decorator(filename=str(test_filename)) + def test_function(): + return {"test": "data"} + + result = test_function() + + assert result == {"test": "data"} + assert test_filename.exists() + + with open(test_filename, 'r') as f: + saved_data = json.load(f) + assert saved_data == {"test": "data"} + + def test_report_decorator_with_dataframe(self, tmp_path): + """Тест декоратора с DataFrame""" + + @report_decorator() + def test_function(): + return pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]}) + + with patch('src.reports.datetime') as mock_datetime: + mock_datetime.now.return_value = datetime(2023, 12, 20, 15, 30, 0) + + result = test_function() + + # Проверяем, что функция возвращает правильный результат + assert isinstance(result, pd.DataFrame) + assert len(result) == 2 + + def test_report_decorator_exception(self, tmp_path): + """Тест декоратора при исключении в функции""" + + @report_decorator(filename=str(tmp_path / "error_report.json")) + def failing_function(): + raise ValueError("Test error") + + with pytest.raises(ValueError, match="Test error"): + failing_function() + + # Файл не должен быть создан при исключении + assert not (tmp_path / "error_report.json").exists() + + +class TestReportsFunctions: + """Тесты функций-оберток отчетов""" + + def test_spending_by_category_wrapper(self): + """Тест обертки spending_by_category""" + with patch('src.reports.ReportGenerator.spending_by_category') as mock_method: + mock_method.return_value = pd.DataFrame({'test': [1, 2, 3]}) + + df = pd.DataFrame() + result = spending_by_category(df, 'Супермаркеты', '2023-12-20') + + assert isinstance(result, pd.DataFrame) + mock_method.assert_called_once_with(df, 'Супермаркеты', '2023-12-20') + + def test_spending_by_weekday_wrapper(self): + """Тест обертки spending_by_weekday""" + with patch('src.reports.ReportGenerator.spending_by_weekday') as mock_method: + mock_method.return_value = pd.DataFrame({'test': [1, 2, 3]}) + + df = pd.DataFrame() + result = spending_by_weekday(df, '2023-12-20') + + assert isinstance(result, pd.DataFrame) + mock_method.assert_called_once_with(df, '2023-12-20') + + def test_spending_by_workday_wrapper(self): + """Тест обертки spending_by_workday""" + with patch('src.reports.ReportGenerator.spending_by_workday') as mock_method: + mock_method.return_value = pd.DataFrame({'test': [1, 2, 3]}) + + df = pd.DataFrame() + result = spending_by_workday(df, '2023-12-20') + + assert isinstance(result, pd.DataFrame) + mock_method.assert_called_once_with(df, '2023-12-20') diff --git a/tests/test_services.py b/tests/test_services.py index 1454c13..69ab348 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -1,5 +1,18 @@ +from unittest.mock import patch + +import pandas as pd import pytest -from src.services import investment_bank, simple_search + +from src.services import ( + CashbackAnalyzer, + InvestmentCalculator, + TransactionSearcher, + compose, + investment_bank, + log_service_call, + profitable_cashback_categories, + simple_search, +) @pytest.fixture @@ -29,6 +42,233 @@ def test_investment_bank(sample_transactions_list): def test_simple_search(sample_transactions_list): """Тест простого поиска.""" - results = simple_search("кафе", sample_transactions_list) + # Исправляем порядок аргументов: сначала транзакции, потом строка поиска + results = simple_search(sample_transactions_list, "кафе") assert len(results) == 1 assert results[0]['Категория'] == 'Кафе' + + +class TestCashbackAnalyzer: + """Тесты для анализатора кешбэка""" + + def test_analyze_profitable_categories(self): + """Тест анализа выгодных категорий""" + analyzer = CashbackAnalyzer({'Супермаркеты': 0.05, 'default': 0.01}) + + # Создаем тестовые данные + data = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-05']), + 'Статус': ['OK', 'OK'], + 'Сумма операции': [1000.0, 2000.0], + 'Категория': ['Супермаркеты', 'Фастфуд'] + }) + + result = analyzer.analyze_profitable_categories(data, 2023, 12) + + assert 'Супермаркеты' in result + assert 'Фастфуд' in result + assert result['Супермаркеты'] == 50.0 # 5% от 1000 + assert result['Фастфуд'] == 20.0 # 1% от 2000 + + def test_analyze_profitable_categories_no_data(self): + """Тест анализа без данных""" + analyzer = CashbackAnalyzer({'default': 0.01}) + + # Пустые данные + data = pd.DataFrame(columns=['Дата операции', 'Статус', 'Сумма операции', 'Категория']) + + result = analyzer.analyze_profitable_categories(data, 2023, 12) + + assert result == {} + + def test_analyze_profitable_categories_with_empty_category(self): + """Тест анализа с пустыми категориями""" + analyzer = CashbackAnalyzer({'default': 0.01}) + + data = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01']), + 'Статус': ['OK'], + 'Сумма операции': [1000.0], + 'Категория': [''] + }) + + result = analyzer.analyze_profitable_categories(data, 2023, 12) + + # Пустые категории должны игнорироваться + assert '' not in result + + +class TestInvestmentCalculator: + """Тесты для калькулятора инвесткопилки""" + + def test_investment_bank_valid_transactions(self): + """Тест расчета с валидными транзакциями""" + transactions = [ + { + 'Дата операции': '2023-12-01', + 'Сумма операции': '1047.0' # Округление до 1050 = +3 + }, + { + 'Дата операции': '2023-12-15', + 'Сумма операции': '1982.0' # Округление до 2000 = +18 + } + ] + + result = InvestmentCalculator.investment_bank('2023-12', transactions, 50) + + assert result == 21.0 # 3 + 18 + + def test_investment_bank_invalid_transaction(self): + """Тест с невалидной транзакцией""" + transactions = [ + {'Дата операции': 'invalid-date', 'Сумма операции': 'not-a-number'}, + {'Дата операции': '2023-12-01', 'Сумма операции': '1000.0'} + ] + + result = InvestmentCalculator.investment_bank('2023-12', transactions, 50) + + # Только вторая транзакция должна учитываться + assert result == 0.0 # 1000 округляется до 1000 = 0 + + def test_validate_transaction_valid(self): + """Тест валидации корректной транзакции""" + transaction = {'Дата операции': '2023-12-01', 'Сумма операции': '1000.0'} + + assert InvestmentCalculator._validate_transaction(transaction) is True + + def test_validate_transaction_missing_fields(self): + """Тест валидации транзакции с отсутствующими полями""" + transaction = {'Дата операции': '2023-12-01'} # Нет Сумма операции + + assert InvestmentCalculator._validate_transaction(transaction) is False + + def test_validate_transaction_invalid_data(self): + """Тест валидации транзакции с невалидными данными""" + transaction = {'Дата операции': 'invalid-date', 'Сумма операции': 'not-a-number'} + + assert InvestmentCalculator._validate_transaction(transaction) is False + + +class TestTransactionSearcher: + """Тесты для поисковика транзакций""" + + @pytest.fixture + def sample_transactions(self): + return [ + {'Описание': 'Покупка в магазине', 'Категория': 'Супермаркеты'}, + {'Описание': 'Обед в кафе', 'Категория': 'Фастфуд'}, + {'Описание': 'Перевод Ивану И.', 'Категория': 'Переводы'}, + {'Описание': 'Пополнение +7 921 123-45-67', 'Категория': 'Мобильная связь'} + ] + + def test_simple_search_by_description(self, sample_transactions): + """Тест поиска по описанию""" + results = TransactionSearcher.simple_search(sample_transactions, 'магазин') + + assert len(results) == 1 + assert results[0]['Описание'] == 'Покупка в магазине' + + def test_simple_search_by_category(self, sample_transactions): + """Тест поиска по категории""" + results = TransactionSearcher.simple_search(sample_transactions, 'Фастфуд') + + assert len(results) == 1 + assert results[0]['Категория'] == 'Фастфуд' + + def test_simple_search_short_string(self): + """Тест поиска с короткой строкой""" + with pytest.raises(ValueError, match="Строка поиска должна содержать минимум 2 символа"): + TransactionSearcher.simple_search([], 'а') + + def test_search_by_phone(self, sample_transactions): + """Тест поиска по телефонным номерам""" + results = TransactionSearcher.search_by_phone(sample_transactions) + + assert len(results) == 1 + assert '+7 921 123-45-67' in results[0]['Описание'] + + def test_search_by_person_transfers(self, sample_transactions): + """Тест поиска переводов физлицам""" + results = TransactionSearcher.search_by_person_transfers(sample_transactions) + + assert len(results) == 1 + assert results[0]['Категория'] == 'Переводы' + assert 'Ивану И.' in results[0]['Описание'] + + +class TestFunctionalUtilities: + """Тесты функциональных утилит""" + + def test_compose(self): + """Тест композиции функций""" + + def double(x): + return x * 2 + + def square(x): + return x * x + + composed = compose(double, square) + result = composed(3) # square(3) = 9, double(9) = 18 + + assert result == 18 + + +class TestServiceDecorators: + """Тесты декораторов сервисов""" + + def test_log_service_call_success(self): + """Тест декоратора при успешном выполнении""" + + @log_service_call("test_service") + def test_function(): + return "success" + + with patch('src.services.logger') as mock_logger: + result = test_function() + + assert result == "success" + mock_logger.info.assert_any_call("Вызов сервиса test_service") + mock_logger.info.assert_any_call("Сервис test_service выполнен успешно") + + def test_log_service_call_exception(self): + """Тест декоратора при исключении""" + + @log_service_call("test_service") + def test_function(): + raise ValueError("Test error") + + with patch('src.services.logger') as mock_logger: + with pytest.raises(ValueError, match="Test error"): + test_function() + + mock_logger.info.assert_called_once_with("Вызов сервиса test_service") + mock_logger.error.assert_called_once_with("Ошибка в сервисе test_service: Test error") + + +# Тесты для основных функций с декораторами +def test_profitable_cashback_categories_integration(): + """Интеграционный тест для profitable_cashback_categories""" + data = pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01']), + 'Статус': ['OK'], + 'Сумма операции': [1000.0], + 'Категория': ['Супермаркеты'] + }) + + with patch('src.services.logger') as mock_logger: + result = profitable_cashback_categories(data, 2023, 12, {'Супермаркеты': 0.05}) + + assert 'Супермаркеты' in result + mock_logger.info.assert_called() + + +def test_investment_bank_integration(): + """Интеграционный тест для investment_bank""" + transactions = [{'Дата операции': '2023-12-01', 'Сумма операции': '1047.0'}] + + with patch('src.services.logger') as mock_logger: + result = investment_bank('2023-12', transactions, 50) + + assert result == 3.0 + mock_logger.info.assert_called() diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..383964b --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,84 @@ +# tests/test_utils.py +import json +import os +import tempfile +from datetime import datetime + +import pandas as pd + +from src.utils import DataValidator, calculate_cashback, get_date_range, get_greeting, load_user_settings + + +class TestUtils: + def test_get_greeting(self): + assert get_greeting('2023-12-20 08:30:00') == 'Доброе утро' + assert get_greeting('2023-12-20 14:30:00') == 'Добрый день' + assert get_greeting('2023-12-20 20:30:00') == 'Добрый вечер' + assert get_greeting('2023-12-20 02:30:00') == 'Доброй ночи' + + def test_get_date_range(self): + start, end = get_date_range('2023-12-20 15:30:00', 'M') + assert start == datetime(2023, 12, 1, 0, 0, 0) # Исправлено: добавили время + assert end == datetime(2023, 12, 20, 15, 30, 0) + + start, end = get_date_range('2023-12-20', 'W') + assert start.weekday() == 0 # Понедельник + assert end.weekday() == 6 # Воскресенье + + def test_calculate_cashback(self): + cashback_rules = { + 'Супермаркеты': 0.05, + 'default': 0.01 + } + + # Базовая логика + assert calculate_cashback(1000, 'Супермаркеты', cashback_rules) == 50.0 + assert calculate_cashback(1000, 'Другое', cashback_rules) == 10.0 + + # Бонус за большие покупки - ИСПРАВЛЕНО ожидание + # 6000 * (5% + 1%) = 6000 * 0.06 = 360.0 + assert calculate_cashback(6000, 'Супермаркеты', cashback_rules) == 360.0 + # 11000 * (5% + 2%) = 11000 * 0.07 = 770.0 + assert calculate_cashback(11000, 'Супермаркеты', cashback_rules) == 770.0 + + # Ограничение максимального кешбэка + high_cashback_rules = {'default': 0.20} + assert calculate_cashback(1000, 'Тест', high_cashback_rules) == 150.0 # Максимум 15% + + def test_data_validator(self): + validator = DataValidator() + + # Тестовые данные с корректными данными + test_data = pd.DataFrame({ + 'Дата операции': ['15.01.2023', '20.02.2023', '10.03.2023'], # Только корректные даты + 'Сумма операции': ['1000.50', '2000', '1500.75'], + 'Статус': ['OK', 'OK', 'OK'] + }) + + clean_data, errors = validator.validate_transaction_data(test_data) + + assert len(clean_data) == 3 # Все строки должны быть корректными + assert isinstance(errors, list) + + def test_load_user_settings(self): + # Создаем временный файл настроек + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: + json.dump({ + "user_currencies": ["USD", "EUR"], + "user_stocks": ["AAPL", "AMZN"] + }, f) + temp_path = f.name + + try: + # Мокаем путь к настройкам + import src.utils + original_path = src.utils.settings.user_settings_path + src.utils.settings.user_settings_path = temp_path + + settings = load_user_settings() + assert 'user_currencies' in settings + assert 'user_stocks' in settings + + finally: + src.utils.settings.user_settings_path = original_path + os.unlink(temp_path) diff --git a/tests/test_views.py b/tests/test_views.py index 9549685..2fadf0d 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,47 +1,87 @@ -import pytest +# tests/test_views.py +from unittest.mock import patch + import pandas as pd -from datetime import datetime -from src.views import home_page -from src.utils import get_greeting - - -@pytest.fixture -def sample_transactions(): - """Фикстура с тестовыми транзакциями.""" - return pd.DataFrame({ - 'Дата операции': ['2023-12-01', '2023-12-15', '2023-11-20'], - 'Номер карты': ['1234567812345678', '1234567812345678', '8765432187654321'], - 'Сумма операции': [1000.0, 500.0, 2000.0], - 'Категория': ['Супермаркеты', 'Кафе', 'Транспорт'], - 'Описание': ['Покупка в магазине', 'Обед в кафе', 'Такси'] - }) - - -@pytest.fixture -def user_settings(): - """Фикстура с настройками пользователя.""" - return { - "user_currencies": ["USD", "EUR"], - "user_stocks": ["AAPL", "GOOGL"] - } - - -def test_home_page(sample_transactions, user_settings): - """Тест главной страницы.""" - result = home_page("2023-12-20 15:30:00", sample_transactions, user_settings) - - assert "greeting" in result - assert "cards" in result - assert "top_transactions" in result - assert len(result["cards"]) > 0 - - -@pytest.mark.parametrize("time_str,expected_greeting", [ - ("2023-12-20 08:30:00", "Доброе утро"), - ("2023-12-20 14:30:00", "Добрый день"), - ("2023-12-20 20:30:00", "Добрый вечер"), - ("2023-12-20 02:30:00", "Доброй ночи"), -]) -def test_get_greeting(time_str, expected_greeting): - """Тест получения приветствия.""" - assert get_greeting(time_str) == expected_greeting +import pytest + +from src.views import DataProcessor, events_page, main_page + + +class TestViews: + @pytest.fixture + def sample_transactions(self): + """Фикстура с корректными тестовыми данными""" + return pd.DataFrame({ + 'Дата операции': pd.to_datetime(['2023-12-01', '2023-12-05', '2023-12-10', '2023-12-15']), + 'Номер карты': ['1234567812345814', '1234567812345814', '1234567812347512', '1234567812347512'], + 'Статус': ['OK', 'OK', 'OK', 'OK'], + 'Сумма операции': [1000.0, 500.0, 300.0, 200.0], # Все расходы (положительные) + 'Категория': ['Супермаркеты', 'Фастфуд', 'Транспорт', 'Развлечения'], + 'Описание': ['Магазин', 'Кафе', 'Такси', 'Кино'], + 'Сумма платежа': [1000.0, 500.0, 300.0, 200.0] + }) + + @pytest.fixture + def sample_settings(self): + return { + "user_currencies": ["USD", "EUR"], + "user_stocks": ["AAPL", "AMZN"], + "cashback_rules": {"default": 0.01} + } + + @patch('src.views.SyncAPIClient.get_currency_rates') + @patch('src.views.SyncAPIClient.get_stock_prices') + def test_main_page(self, mock_stocks, mock_currency, sample_transactions, sample_settings): + mock_currency.return_value = [{"currency": "USD", "rate": 93.45}] + mock_stocks.return_value = [{"stock": "AAPL", "price": 178.72}] + + with patch('src.views.load_transactions', return_value=sample_transactions): + with patch('src.views.load_user_settings', return_value=sample_settings): + result = main_page('2023-12-20 15:30:00') + + assert 'greeting' in result + assert 'cards' in result + assert 'top_transactions' in result + assert 'currency_rates' in result + assert 'stock_prices' in result + assert result['greeting'] == 'Добрый день' + + def test_data_processor(self, sample_transactions, sample_settings): + processor = DataProcessor() + + result = processor.process_main_page_data( + sample_transactions, '2023-12-20 15:30:00', sample_settings + ) + + # Теперь должно быть 2 карты с расходами (обе карты имеют расходы) + assert len(result['cards']) == 2 + assert len(result['top_transactions']) == 4 + + # Проверяем расчет кешбэка + # Карта 5814: 1000 + 500 = 1500 * 1% = 15.0 + # Карта 7512: 300 + 200 = 500 * 1% = 5.0 + + # Находим карты по last_digits + card_5814 = next(card for card in result['cards'] if card['last_digits'] == '5814') + card_7512 = next(card for card in result['cards'] if card['last_digits'] == '7512') + + assert card_5814['total_spent'] == 1500.0 + assert card_5814['cashback'] == 15.0 + assert card_7512['total_spent'] == 500.0 + assert card_7512['cashback'] == 5.0 + + @patch('src.views.SyncAPIClient.get_currency_rates') + @patch('src.views.SyncAPIClient.get_stock_prices') + def test_events_page(self, mock_stocks, mock_currency, sample_transactions, sample_settings): + mock_currency.return_value = [{"currency": "USD", "rate": 93.45}] + mock_stocks.return_value = [{"stock": "AAPL", "price": 178.72}] + + with patch('src.views.load_transactions', return_value=sample_transactions): + with patch('src.views.load_user_settings', return_value=sample_settings): + result = events_page('2023-12-20', 'M') + + assert 'expenses' in result + assert 'income' in result + # Все операции расходы, поэтому income = 0 + assert result['expenses']['total_amount'] == 2000 # 1000+500+300+200 + assert result['income']['total_amount'] == 0 # Нет отрицательных сумм diff --git a/user_settings.json b/user_settings.json new file mode 100644 index 0000000..9a7f774 --- /dev/null +++ b/user_settings.json @@ -0,0 +1,11 @@ +{ + "user_currencies": ["USD", "EUR", "GBP", "CNY"], + "user_stocks": ["AAPL", "AMZN", "GOOGL", "MSFT", "TSLA", "META", "NVDA"], + "cashback_rules": { + "Супермаркеты": 0.05, + "Фастфуд": 0.03, + "Транспорт": 0.02, + "Развлечения": 0.02, + "default": 0.01 + } +} \ No newline at end of file