From 527f9213da95ac32ad05bdf12f25baa658d5d827 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 12:17:16 +0000 Subject: [PATCH 1/3] Add mt5cli package with CLI for MetaTrader 5 data export Create a standalone CLI package that uses pdmt5 as a dependency to export MetaTrader 5 data to CSV, JSON, Parquet, and SQLite3 formats. Includes 12 subcommands for rates, ticks, account/terminal info, symbols, orders, positions, and trading history, along with comprehensive test coverage. https://claude.ai/code/session_01YW3YHru8wRH9dvHnBX7xf1 --- mt5cli/__init__.py | 12 + mt5cli/__main__.py | 5 + mt5cli/cli.py | 753 +++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 179 +++++++++++ tests/__init__.py | 0 tests/test_cli.py | 751 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1700 insertions(+) create mode 100644 mt5cli/__init__.py create mode 100644 mt5cli/__main__.py create mode 100644 mt5cli/cli.py create mode 100644 pyproject.toml create mode 100644 tests/__init__.py create mode 100644 tests/test_cli.py diff --git a/mt5cli/__init__.py b/mt5cli/__init__.py new file mode 100644 index 0000000..c4f174e --- /dev/null +++ b/mt5cli/__init__.py @@ -0,0 +1,12 @@ +"""mt5cli: Command-line tool for MetaTrader 5.""" + +from importlib.metadata import version + +from .cli import detect_format, export_dataframe + +__version__ = version(__package__) if __package__ else None + +__all__ = [ + "detect_format", + "export_dataframe", +] diff --git a/mt5cli/__main__.py b/mt5cli/__main__.py new file mode 100644 index 0000000..08b5116 --- /dev/null +++ b/mt5cli/__main__.py @@ -0,0 +1,5 @@ +"""Entry point for running mt5cli as a module via ``python -m mt5cli``.""" + +from mt5cli.cli import main + +main() diff --git a/mt5cli/cli.py b/mt5cli/cli.py new file mode 100644 index 0000000..340f6a7 --- /dev/null +++ b/mt5cli/cli.py @@ -0,0 +1,753 @@ +"""Command-line interface for MetaTrader 5 data export.""" + +from __future__ import annotations + +import logging +import sqlite3 +from dataclasses import dataclass +from datetime import UTC, datetime +from enum import StrEnum +from pathlib import Path # noqa: TC003 +from typing import TYPE_CHECKING, Annotated, cast + +import click +import typer + +from pdmt5 import Mt5Config, Mt5DataClient + +if TYPE_CHECKING: + from collections.abc import Callable + + import pandas as pd + +logger = logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +TIMEFRAME_MAP: dict[str, int] = { + "M1": 1, + "M2": 2, + "M3": 3, + "M4": 4, + "M5": 5, + "M6": 6, + "M10": 10, + "M12": 12, + "M15": 15, + "M20": 20, + "M30": 30, + "H1": 16385, + "H2": 16386, + "H3": 16387, + "H4": 16388, + "H6": 16390, + "H8": 16392, + "H12": 16396, + "D1": 16408, + "W1": 32769, + "MN1": 49153, +} + +TICK_FLAG_MAP: dict[str, int] = { + "ALL": 1, + "INFO": 2, + "TRADE": 4, +} + +_FORMAT_EXTENSIONS: dict[str, str] = { + ".csv": "csv", + ".json": "json", + ".parquet": "parquet", + ".pq": "parquet", + ".db": "sqlite3", + ".sqlite": "sqlite3", + ".sqlite3": "sqlite3", +} + +# --------------------------------------------------------------------------- +# Enums +# --------------------------------------------------------------------------- + + +class OutputFormat(StrEnum): + """Supported output file formats.""" + + csv = "csv" + json = "json" + parquet = "parquet" + sqlite3 = "sqlite3" + + +class LogLevel(StrEnum): + """Logging verbosity levels.""" + + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + + +# --------------------------------------------------------------------------- +# Click parameter types +# --------------------------------------------------------------------------- + + +class _DateTimeType(click.ParamType): + """Click parameter type for ISO 8601 datetime strings.""" + + name = "DATETIME" + + def convert( + self, + value: object, + param: click.Parameter | None, + ctx: click.Context | None, + ) -> datetime: + """Convert a string value to a timezone-aware datetime. + + Args: + value: Raw value from the command line. + param: Click parameter instance. + ctx: Click context. + + Returns: + Parsed datetime. + """ + if isinstance(value, datetime): + return value + try: + return parse_datetime(str(value)) + except ValueError as exc: + self.fail(str(exc), param, ctx) + + +class _TimeframeType(click.ParamType): + """Click parameter type for MT5 timeframe values.""" + + name = "TIMEFRAME" + + def convert( + self, + value: object, + param: click.Parameter | None, + ctx: click.Context | None, + ) -> int: + """Convert a string or integer value to a timeframe integer. + + Args: + value: Raw value from the command line. + param: Click parameter instance. + ctx: Click context. + + Returns: + Integer timeframe value. + """ + if isinstance(value, int): + return value + try: + return parse_timeframe(str(value)) + except ValueError as exc: + self.fail(str(exc), param, ctx) + + +class _TickFlagsType(click.ParamType): + """Click parameter type for MT5 tick copy flags.""" + + name = "FLAGS" + + def convert( + self, + value: object, + param: click.Parameter | None, + ctx: click.Context | None, + ) -> int: + """Convert a string or integer value to a tick flags integer. + + Args: + value: Raw value from the command line. + param: Click parameter instance. + ctx: Click context. + + Returns: + Integer tick flag value. + """ + if isinstance(value, int): + return value + try: + return parse_tick_flags(str(value)) + except ValueError as exc: + self.fail(str(exc), param, ctx) + + +DATETIME_TYPE = _DateTimeType() +TIMEFRAME_TYPE = _TimeframeType() +TICK_FLAGS_TYPE = _TickFlagsType() + +# --------------------------------------------------------------------------- +# Export context +# --------------------------------------------------------------------------- + + +@dataclass +class _ExportContext: + """Shared context data passed from the callback to each subcommand.""" + + output: Path + output_format: str + table: str + config: Mt5Config + + +# --------------------------------------------------------------------------- +# Public utility functions +# --------------------------------------------------------------------------- + + +def detect_format( + output_path: Path, + explicit_format: str | None = None, +) -> str: + """Detect the output format from a file extension or explicit format string. + + Args: + output_path: Path to the output file. + explicit_format: Explicitly specified format, if any. + + Returns: + The detected format string. + + Raises: + ValueError: If the format cannot be determined. + """ + if explicit_format is not None: + return explicit_format + suffix = output_path.suffix.lower() + if suffix in _FORMAT_EXTENSIONS: + return _FORMAT_EXTENSIONS[suffix] + msg = ( + f"Cannot detect format from extension '{suffix}'." + " Use --format to specify the output format." + ) + raise ValueError(msg) + + +def export_dataframe( + df: pd.DataFrame, + output_path: Path, + output_format: str, + table_name: str = "data", +) -> None: + """Export a pandas DataFrame to the specified file format. + + Args: + df: DataFrame to export. + output_path: Path to the output file. + output_format: Output format (csv, json, parquet, or sqlite3). + table_name: Table name for SQLite3 output. + + Raises: + ValueError: If the output format is not supported. + """ + if output_format == "csv": + df.to_csv(output_path, index=False) + elif output_format == "json": + df.to_json( + output_path, + orient="records", + date_format="iso", + indent=2, + ) + elif output_format == "parquet": + df.to_parquet(output_path, index=False) + elif output_format == "sqlite3": + with sqlite3.connect(output_path) as conn: + df.to_sql( # type: ignore[reportUnknownMemberType] + table_name, + conn, + if_exists="replace", + index=False, + ) + else: + msg = f"Unsupported output format: {output_format}" + raise ValueError(msg) + + +def parse_datetime(value: str) -> datetime: + """Parse an ISO 8601 datetime string to a timezone-aware datetime. + + Args: + value: ISO 8601 datetime string (e.g., '2024-01-01' or + '2024-01-01T12:00:00+00:00'). + + Returns: + Parsed datetime with UTC timezone if no timezone is specified. + + Raises: + ValueError: If the string cannot be parsed. + """ + try: + dt = datetime.fromisoformat(value) + except ValueError: + msg = f"Invalid datetime format: '{value}'. Use ISO 8601 format." + raise ValueError(msg) from None + if dt.tzinfo is None: + dt = dt.replace(tzinfo=UTC) + return dt + + +def parse_timeframe(value: str) -> int: + """Parse a timeframe string or integer value. + + Args: + value: Timeframe name (e.g., 'M1', 'H1', 'D1') or integer value. + + Returns: + Integer timeframe value. + + Raises: + ValueError: If the timeframe is invalid. + """ + upper = value.upper() + if upper in TIMEFRAME_MAP: + return TIMEFRAME_MAP[upper] + try: + return int(value) + except ValueError: + valid = ", ".join(TIMEFRAME_MAP) + msg = f"Invalid timeframe: '{value}'. Use one of: {valid}, or an integer." + raise ValueError(msg) from None + + +def parse_tick_flags(value: str) -> int: + """Parse tick flags string or integer value. + + Args: + value: Tick flag name (ALL, INFO, TRADE) or integer value. + + Returns: + Integer tick flag value. + + Raises: + ValueError: If the flag is invalid. + """ + upper = value.upper() + if upper in TICK_FLAG_MAP: + return TICK_FLAG_MAP[upper] + try: + return int(value) + except ValueError: + valid = ", ".join(TICK_FLAG_MAP) + msg = f"Invalid tick flags: '{value}'. Use one of: {valid}, or an integer." + raise ValueError(msg) from None + + +# --------------------------------------------------------------------------- +# Typer application +# --------------------------------------------------------------------------- + +app = typer.Typer( + name="mt5cli", + help="Export MetaTrader5 data to CSV, JSON, Parquet, or SQLite3.", +) + + +def _get_export_context(ctx: typer.Context) -> _ExportContext: + return cast("_ExportContext", ctx.obj) + + +def _execute_export( + ctx: typer.Context, + fetch_fn: Callable[[Mt5DataClient], pd.DataFrame], +) -> None: + """Execute the common connect-fetch-export-shutdown workflow. + + Args: + ctx: Typer context carrying shared options. + fetch_fn: Callable that receives a connected client and returns a + DataFrame. + """ + export_ctx = _get_export_context(ctx) + client = Mt5DataClient(config=export_ctx.config) + client.initialize_and_login_mt5() + try: + df = fetch_fn(client) + export_dataframe( + df=df, + output_path=export_ctx.output, + output_format=export_ctx.output_format, + table_name=export_ctx.table, + ) + logger.info( + "Exported %d rows to %s (%s)", + len(df), + export_ctx.output, + export_ctx.output_format, + ) + finally: + client.shutdown() + + +@app.callback() +def _callback( # pyright: ignore[reportUnusedFunction] + ctx: typer.Context, + output: Annotated[ + Path, + typer.Option("--output", "-o", help="Output file path."), + ], + fmt: Annotated[ + OutputFormat | None, + typer.Option( + "--format", + "-f", + help="Output format (auto-detected from extension if omitted).", + ), + ] = None, + table: Annotated[ + str, + typer.Option(help="Table name for SQLite3 output."), + ] = "data", + login: Annotated[ + int | None, + typer.Option(help="Trading account login."), + ] = None, + password: Annotated[ + str | None, + typer.Option(help="Trading account password."), + ] = None, + server: Annotated[ + str | None, + typer.Option(help="Trading server name."), + ] = None, + path: Annotated[ + str | None, + typer.Option(help="Path to MetaTrader5 terminal EXE file."), + ] = None, + timeout: Annotated[ + int | None, + typer.Option(help="Connection timeout in milliseconds."), + ] = None, + log_level: Annotated[ + LogLevel, + typer.Option("--log-level", help="Logging level."), + ] = LogLevel.WARNING, +) -> None: + """Configure shared options for all export commands. + + Raises: + typer.BadParameter: If the output format cannot be determined. + """ + logging.basicConfig(level=getattr(logging, log_level.value)) + try: + output_format = detect_format( + output, + explicit_format=fmt.value if fmt is not None else None, + ) + except ValueError as exc: + raise typer.BadParameter(str(exc)) from exc + ctx.obj = _ExportContext( + output=output, + output_format=output_format, + table=table, + config=Mt5Config( + path=path, + login=login, + password=password, + server=server, + timeout=timeout, + ), + ) + + +# --------------------------------------------------------------------------- +# Subcommands +# --------------------------------------------------------------------------- + + +@app.command() +def rates_from( + ctx: typer.Context, + symbol: Annotated[str, typer.Option(help="Symbol name.")], + timeframe: Annotated[ + int, + typer.Option( + click_type=TIMEFRAME_TYPE, + help="Timeframe (e.g., M1, H1, D1, or integer).", + ), + ], + date_from: Annotated[ + datetime, + typer.Option( + click_type=DATETIME_TYPE, + help="Start date in ISO 8601 format.", + ), + ], + count: Annotated[int, typer.Option(help="Number of records.")], +) -> None: + """Export rates from a start date.""" + _execute_export( + ctx, + lambda c: c.copy_rates_from_as_df( + symbol=symbol, + timeframe=timeframe, + date_from=date_from, + count=count, + ), + ) + + +@app.command() +def rates_from_pos( + ctx: typer.Context, + symbol: Annotated[str, typer.Option(help="Symbol name.")], + timeframe: Annotated[ + int, + typer.Option( + click_type=TIMEFRAME_TYPE, + help="Timeframe.", + ), + ], + start_pos: Annotated[int, typer.Option(help="Start position (0 = current bar).")], + count: Annotated[int, typer.Option(help="Number of records.")], +) -> None: + """Export rates from a start position.""" + _execute_export( + ctx, + lambda c: c.copy_rates_from_pos_as_df( + symbol=symbol, + timeframe=timeframe, + start_pos=start_pos, + count=count, + ), + ) + + +@app.command() +def rates_range( + ctx: typer.Context, + symbol: Annotated[str, typer.Option(help="Symbol name.")], + timeframe: Annotated[ + int, + typer.Option( + click_type=TIMEFRAME_TYPE, + help="Timeframe.", + ), + ], + date_from: Annotated[ + datetime, + typer.Option(click_type=DATETIME_TYPE, help="Start date."), + ], + date_to: Annotated[ + datetime, + typer.Option(click_type=DATETIME_TYPE, help="End date."), + ], +) -> None: + """Export rates for a date range.""" + _execute_export( + ctx, + lambda c: c.copy_rates_range_as_df( + symbol=symbol, + timeframe=timeframe, + date_from=date_from, + date_to=date_to, + ), + ) + + +@app.command() +def ticks_from( + ctx: typer.Context, + symbol: Annotated[str, typer.Option(help="Symbol name.")], + date_from: Annotated[ + datetime, + typer.Option(click_type=DATETIME_TYPE, help="Start date."), + ], + count: Annotated[int, typer.Option(help="Number of ticks.")], + flags: Annotated[ + int, + typer.Option( + click_type=TICK_FLAGS_TYPE, + help="Tick flags (ALL, INFO, TRADE, or integer).", + ), + ], +) -> None: + """Export ticks from a start date.""" + _execute_export( + ctx, + lambda c: c.copy_ticks_from_as_df( + symbol=symbol, + date_from=date_from, + count=count, + flags=flags, + ), + ) + + +@app.command() +def ticks_range( + ctx: typer.Context, + symbol: Annotated[str, typer.Option(help="Symbol name.")], + date_from: Annotated[ + datetime, + typer.Option(click_type=DATETIME_TYPE, help="Start date."), + ], + date_to: Annotated[ + datetime, + typer.Option(click_type=DATETIME_TYPE, help="End date."), + ], + flags: Annotated[ + int, + typer.Option(click_type=TICK_FLAGS_TYPE, help="Tick flags."), + ], +) -> None: + """Export ticks for a date range.""" + _execute_export( + ctx, + lambda c: c.copy_ticks_range_as_df( + symbol=symbol, + date_from=date_from, + date_to=date_to, + flags=flags, + ), + ) + + +@app.command() +def account_info(ctx: typer.Context) -> None: + """Export account information.""" + _execute_export(ctx, lambda c: c.account_info_as_df()) + + +@app.command() +def terminal_info(ctx: typer.Context) -> None: + """Export terminal information.""" + _execute_export(ctx, lambda c: c.terminal_info_as_df()) + + +@app.command() +def symbols( + ctx: typer.Context, + group: Annotated[ + str | None, + typer.Option(help="Symbol group filter (e.g., *USD*)."), + ] = None, +) -> None: + """Export symbol list.""" + _execute_export( + ctx, + lambda c: c.symbols_get_as_df(group=group), + ) + + +@app.command() +def symbol_info( + ctx: typer.Context, + symbol: Annotated[str, typer.Option(help="Symbol name.")], +) -> None: + """Export symbol details.""" + _execute_export( + ctx, + lambda c: c.symbol_info_as_df(symbol=symbol), + ) + + +@app.command() +def orders( + ctx: typer.Context, + symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None, + group: Annotated[str | None, typer.Option(help="Group filter.")] = None, + ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None, +) -> None: + """Export active orders.""" + _execute_export( + ctx, + lambda c: c.orders_get_as_df( + symbol=symbol, + group=group, + ticket=ticket, + ), + ) + + +@app.command() +def positions( + ctx: typer.Context, + symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None, + group: Annotated[str | None, typer.Option(help="Group filter.")] = None, + ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None, +) -> None: + """Export open positions.""" + _execute_export( + ctx, + lambda c: c.positions_get_as_df( + symbol=symbol, + group=group, + ticket=ticket, + ), + ) + + +@app.command() +def history_orders( + ctx: typer.Context, + date_from: Annotated[ + datetime | None, + typer.Option(click_type=DATETIME_TYPE, help="Start date."), + ] = None, + date_to: Annotated[ + datetime | None, + typer.Option(click_type=DATETIME_TYPE, help="End date."), + ] = None, + group: Annotated[str | None, typer.Option(help="Group filter.")] = None, + symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None, + ticket: Annotated[int | None, typer.Option(help="Order ticket.")] = None, + position: Annotated[int | None, typer.Option(help="Position ticket.")] = None, +) -> None: + """Export historical orders.""" + _execute_export( + ctx, + lambda c: c.history_orders_get_as_df( + date_from=date_from, + date_to=date_to, + group=group, + symbol=symbol, + ticket=ticket, + position=position, + ), + ) + + +@app.command() +def history_deals( + ctx: typer.Context, + date_from: Annotated[ + datetime | None, + typer.Option(click_type=DATETIME_TYPE, help="Start date."), + ] = None, + date_to: Annotated[ + datetime | None, + typer.Option(click_type=DATETIME_TYPE, help="End date."), + ] = None, + group: Annotated[str | None, typer.Option(help="Group filter.")] = None, + symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None, + ticket: Annotated[int | None, typer.Option(help="Order ticket.")] = None, + position: Annotated[int | None, typer.Option(help="Position ticket.")] = None, +) -> None: + """Export historical deals.""" + _execute_export( + ctx, + lambda c: c.history_deals_get_as_df( + date_from=date_from, + date_to=date_to, + group=group, + symbol=symbol, + ticket=ticket, + position=position, + ), + ) + + +def main() -> None: + """Run the mt5cli CLI.""" + app() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c605061 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,179 @@ +[project] +name = "mt5cli" +version = "0.1.0" +description = "Command-line tool for MetaTrader 5" +authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}] +maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}] +license = "MIT" +license-files = ["LICENSE"] +readme = "README.md" +requires-python = ">= 3.11, < 3.14" +dependencies = [ + "pdmt5 >= 0.2.3", + "click >= 8.1.0", + "typer >= 0.15.0", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "License :: OSI Approved :: MIT License", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Intended Audience :: Financial and Insurance Industry", + "Topic :: Office/Business :: Financial :: Investment", +] + +[project.scripts] +mt5cli = "mt5cli.cli:main" + +[project.urls] +Repository = "https://github.com/dceoy/mt5cli.git" + +[tool.uv] +required-environments = ["platform_system == 'Windows'"] + +[dependency-groups] +dev = [ + "ruff >= 0.11.0", + "pyright >= 1.1.407", + "pytest >= 8.0.0", + "pytest-mock >= 3.12.0", + "pytest-cov >= 5.0.0", + "pandas-stubs >= 2.2.3.250527", +] + +[tool.uv.build-backend] +source-include = ["mt5cli/**", "LICENSE"] +source-exclude = ["tests/**"] + +[tool.ruff] +line-length = 88 +exclude = ["build", ".venv"] +preview = true + +[tool.ruff.lint] +select = [ + "F", # Pyflakes (F) + "E", # pycodestyle error (E) + "W", # pycodestyle warning (W) + "C90", # mccabe (C90) + "I", # isort (I) + "N", # pep8-naming (N) + "D", # pydocstyle (D) + "UP", # pyupgrade (UP) + "S", # flake8-bandit (S) + "B", # flake8-bugbear (B) + "C4", # flake8-comprehensions (C4) + "SIM", # flake8-simplify (SIM) + "ARG", # flake8-unused-arguments (ARG) + "PD", # pandas-vet (PD) + "PLC", # Pylint convention (PLC) + "PLE", # Pylint error (PLE) + "PLR", # Pylint refactor (PLR) + "PLW", # Pylint warning (PLW) + "FLY", # flynt (FLY) + "NPY", # NumPy-specific rules (NPY) + "PERF", # Perflint (PERF) + "FURB", # refurb (FURB) + "RUF", # Ruff-specific rules (RUF) + "YTT", # flake8-2020 (YTT) + "ANN", # flake8-annotations (ANN) + "ASYNC", # flake8-async (ASYNC) + "BLE", # flake8-blind-except (BLE) + "FBT", # flake8-boolean-trap (FBT) + "A", # flake8-builtins (A) + "COM", # flake8-commas (COM) + "DTZ", # flake8-datetimez (DTZ) + "T10", # flake8-debugger (T10) + "DJ", # flake8-django (DJ) + "EM", # flake8-errmsg (EM) + "EXE", # flake8-executable (EXE) + "FA", # flake8-future-annotations (FA) + "ISC", # flake8-implicit-str-concat (ISC) + "ICN", # flake8-import-conventions (ICN) + "LOG", # flake8-logging (LOG) + "G", # flake8-logging-format (G) + "INP", # flake8-no-pep420 (INP) + "PIE", # flake8-pie (PIE) + "T20", # flake8-print (T20) + "PYI", # flake8-pyi (PYI) + "PT", # flake8-pytest-style (PT) + "Q", # flake8-quotes (Q) + "RSE", # flake8-raise (RSE) + "SLF", # flake8-self (SLF) + "SLOT", # flake8-slots (SLOT) + "TID", # flake8-tidy-imports (TID) + "TCH", # flake8-type-checking (TCH) + "INT", # flake8-gettext (INT) + "PTH", # flake8-use-pathlib (PTH) + "TD", # flake8-todos (TD) + "FIX", # flake8-fixme (FIX) + "ERA", # eradicate (ERA) + "PGH", # pygrep-hooks (PGH) + "TRY", # tryceratops (TRY) + "FAST", # FastAPI (FAST) + "AIR", # Airflow (AIR) + "DOC", # pydoclint (DOC) +] +ignore = [ + "COM812", # missing-trailing-comma + "FBT001", # boolean-type-hint-positional-argument + "FBT002", # boolean-default-value-positional-argument +] + +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = [ + "DOC201", # Missing return documentation + "DOC501", # Raised exception missing from docstring + "PLC2701", # Private name import + "PLR0904", # Too many public methods + "PLR2004", # Magic value used in comparison + "PLR6301", # Method could be function/static/classmethod + "S101", # Use of assert (acceptable in tests) + "S106", # Possible hardcoded password + "SLF001", # Private member accessed +] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.pylint] +max-args = 10 +max-public-methods = 40 + +[tool.pyright] +exclude = ["build", ".venv"] +venvPath = "." +venv = ".venv" +typeCheckingMode = "strict" + +[tool.pytest.ini_options] +addopts = [ + "--cov=mt5cli", + "--cov-branch", + "--doctest-modules", + "--capture=no", +] +pythonpath = ["."] +testpaths = ["tests"] +python_files = ["test_*.py", "*_test.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +minversion = "6.0" + +[tool.coverage.run] +source = ["mt5cli"] +omit = [ + "**/__init__.py", + "tests/**", +] + +[tool.coverage.report] +show_missing = true +fail_under = 100 +exclude_lines = ["if TYPE_CHECKING:"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..29e8d82 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,751 @@ +"""Tests for mt5cli.cli module.""" + +from __future__ import annotations + +import json +import sqlite3 +from datetime import UTC, datetime +from typing import TYPE_CHECKING +from unittest.mock import MagicMock + +import pandas as pd +import pytest +from pytest_mock import MockerFixture # noqa: TC002 +from typer.testing import CliRunner + +if TYPE_CHECKING: + from pathlib import Path + +from mt5cli.cli import ( + DATETIME_TYPE, + TICK_FLAG_MAP, + TICK_FLAGS_TYPE, + TIMEFRAME_MAP, + TIMEFRAME_TYPE, + _execute_export, # type: ignore[reportPrivateUsage] + _ExportContext, # type: ignore[reportPrivateUsage] + app, + detect_format, + export_dataframe, + main, + parse_datetime, + parse_tick_flags, + parse_timeframe, +) + +runner = CliRunner() + + +# --------------------------------------------------------------------------- +# detect_format +# --------------------------------------------------------------------------- + + +class TestDetectFormat: + """Tests for detect_format.""" + + def test_explicit_format_returned(self, tmp_path: Path) -> None: + """Test that explicit format overrides extension.""" + result = detect_format(tmp_path / "data.txt", explicit_format="csv") + assert result == "csv" + + @pytest.mark.parametrize( + ("filename", "expected"), + [ + ("data.csv", "csv"), + ("data.json", "json"), + ("data.parquet", "parquet"), + ("data.pq", "parquet"), + ("data.db", "sqlite3"), + ("data.sqlite", "sqlite3"), + ("data.sqlite3", "sqlite3"), + ("DATA.CSV", "csv"), + ("DATA.JSON", "json"), + ("DATA.PARQUET", "parquet"), + ], + ) + def test_auto_detect_from_extension( + self, + tmp_path: Path, + filename: str, + expected: str, + ) -> None: + """Test format auto-detection from file extension.""" + result = detect_format(tmp_path / filename) + assert result == expected + + def test_unknown_extension_raises(self, tmp_path: Path) -> None: + """Test that unknown extension raises ValueError.""" + with pytest.raises(ValueError, match="Cannot detect format"): + detect_format(tmp_path / "data.xyz") + + +# --------------------------------------------------------------------------- +# export_dataframe +# --------------------------------------------------------------------------- + + +class TestExportDataframe: + """Tests for export_dataframe.""" + + @pytest.fixture + def sample_df(self) -> pd.DataFrame: + """Create a sample DataFrame for testing.""" + return pd.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]}) + + def test_export_csv(self, tmp_path: Path, sample_df: pd.DataFrame) -> None: + """Test CSV export.""" + output = tmp_path / "out.csv" + export_dataframe(sample_df, output, "csv") + result = pd.read_csv(output) + pd.testing.assert_frame_equal(result, sample_df) + + def test_export_json(self, tmp_path: Path, sample_df: pd.DataFrame) -> None: + """Test JSON export.""" + output = tmp_path / "out.json" + export_dataframe(sample_df, output, "json") + with output.open() as f: + records = json.load(f) + assert len(records) == 3 + assert records[0]["a"] == 1 + + def test_export_parquet(self, tmp_path: Path, sample_df: pd.DataFrame) -> None: + """Test Parquet export.""" + output = tmp_path / "out.parquet" + export_dataframe(sample_df, output, "parquet") + result = pd.read_parquet(output) + pd.testing.assert_frame_equal(result, sample_df) + + def test_export_sqlite3(self, tmp_path: Path, sample_df: pd.DataFrame) -> None: + """Test SQLite3 export.""" + output = tmp_path / "out.db" + export_dataframe(sample_df, output, "sqlite3", table_name="test_table") + with sqlite3.connect(output) as conn: + result = pd.read_sql( # type: ignore[reportUnknownMemberType] + "SELECT * FROM test_table", + conn, + ) + pd.testing.assert_frame_equal(result, sample_df) + + def test_unsupported_format_raises( + self, + tmp_path: Path, + sample_df: pd.DataFrame, + ) -> None: + """Test that unsupported format raises ValueError.""" + with pytest.raises(ValueError, match="Unsupported output format"): + export_dataframe(sample_df, tmp_path / "out.txt", "xml") + + +# --------------------------------------------------------------------------- +# Parse helpers +# --------------------------------------------------------------------------- + + +class TestParseDatetime: + """Tests for parse_datetime.""" + + def test_valid_date(self) -> None: + """Test parsing a date string.""" + result = parse_datetime("2024-01-15") + assert result == datetime(2024, 1, 15, tzinfo=UTC) + + def test_valid_datetime_with_tz(self) -> None: + """Test parsing a datetime with timezone.""" + result = parse_datetime("2024-01-15T12:00:00+00:00") + assert result == datetime(2024, 1, 15, 12, 0, 0, tzinfo=UTC) + + def test_invalid_format_raises(self) -> None: + """Test that invalid format raises ValueError.""" + with pytest.raises(ValueError, match="Invalid datetime"): + parse_datetime("not-a-date") + + +class TestParseTimeframe: + """Tests for parse_timeframe.""" + + @pytest.mark.parametrize( + ("value", "expected"), + [("M1", 1), ("h1", 16385), ("D1", 16408), ("MN1", 49153)], + ) + def test_named_timeframe(self, value: str, expected: int) -> None: + """Test parsing named timeframes.""" + assert parse_timeframe(value) == expected + + def test_integer_timeframe(self) -> None: + """Test parsing integer timeframe.""" + assert parse_timeframe("42") == 42 + + def test_invalid_timeframe_raises(self) -> None: + """Test that invalid timeframe raises ValueError.""" + with pytest.raises(ValueError, match="Invalid timeframe"): + parse_timeframe("INVALID") + + +class TestParseTickFlags: + """Tests for parse_tick_flags.""" + + @pytest.mark.parametrize( + ("value", "expected"), + [("ALL", 1), ("info", 2), ("TRADE", 4)], + ) + def test_named_flag(self, value: str, expected: int) -> None: + """Test parsing named tick flags.""" + assert parse_tick_flags(value) == expected + + def test_integer_flag(self) -> None: + """Test parsing integer tick flag.""" + assert parse_tick_flags("7") == 7 + + def test_invalid_flag_raises(self) -> None: + """Test that invalid flag raises ValueError.""" + with pytest.raises(ValueError, match="Invalid tick flags"): + parse_tick_flags("INVALID") + + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + + +class TestConstants: + """Tests for module constants.""" + + def test_timeframe_map_has_expected_keys(self) -> None: + """Test that TIMEFRAME_MAP contains standard timeframes.""" + for key in ("M1", "M5", "M15", "M30", "H1", "H4", "D1", "W1", "MN1"): + assert key in TIMEFRAME_MAP + + def test_tick_flag_map_has_expected_keys(self) -> None: + """Test that TICK_FLAG_MAP contains standard flags.""" + assert set(TICK_FLAG_MAP) == {"ALL", "INFO", "TRADE"} + + +# --------------------------------------------------------------------------- +# Click ParamTypes +# --------------------------------------------------------------------------- + + +class TestDateTimeType: + """Tests for _DateTimeType.""" + + def test_convert_string(self) -> None: + """Test converting a string to datetime.""" + result = DATETIME_TYPE.convert("2024-06-15", None, None) + assert result == datetime(2024, 6, 15, tzinfo=UTC) + + def test_convert_datetime_passthrough(self) -> None: + """Test that datetime values pass through unchanged.""" + dt = datetime(2024, 1, 1, tzinfo=UTC) + assert DATETIME_TYPE.convert(dt, None, None) is dt + + def test_convert_invalid(self) -> None: + """Test that invalid values raise BadParameter.""" + with pytest.raises(Exception, match="Invalid datetime"): + DATETIME_TYPE.convert("bad", None, None) + + +class TestTimeframeType: + """Tests for _TimeframeType.""" + + def test_convert_string(self) -> None: + """Test converting a string to timeframe integer.""" + assert TIMEFRAME_TYPE.convert("H1", None, None) == 16385 + + def test_convert_int_passthrough(self) -> None: + """Test that integer values pass through unchanged.""" + assert TIMEFRAME_TYPE.convert(42, None, None) == 42 + + def test_convert_invalid(self) -> None: + """Test that invalid values raise BadParameter.""" + with pytest.raises(Exception, match="Invalid timeframe"): + TIMEFRAME_TYPE.convert("bad", None, None) + + +class TestTickFlagsType: + """Tests for _TickFlagsType.""" + + def test_convert_string(self) -> None: + """Test converting a string to tick flags integer.""" + assert TICK_FLAGS_TYPE.convert("ALL", None, None) == 1 + + def test_convert_int_passthrough(self) -> None: + """Test that integer values pass through unchanged.""" + assert TICK_FLAGS_TYPE.convert(7, None, None) == 7 + + def test_convert_invalid(self) -> None: + """Test that invalid values raise BadParameter.""" + with pytest.raises(Exception, match="Invalid tick flags"): + TICK_FLAGS_TYPE.convert("bad", None, None) + + +# --------------------------------------------------------------------------- +# _execute_export +# --------------------------------------------------------------------------- + + +class TestExecuteExport: + """Tests for _execute_export.""" + + def test_shutdown_on_error( + self, + tmp_path: Path, + mocker: MockerFixture, + ) -> None: + """Test that shutdown is called even when fetch raises.""" + mock_client = MagicMock() + mock_client.account_info_as_df.side_effect = RuntimeError("boom") + mocker.patch("mt5cli.cli.Mt5DataClient", return_value=mock_client) + ctx = MagicMock() + ctx.obj = _ExportContext( + output=tmp_path / "out.csv", + output_format="csv", + table="data", + config=MagicMock(), + ) + with pytest.raises(RuntimeError, match="boom"): + _execute_export(ctx, lambda c: c.account_info_as_df()) + mock_client.shutdown.assert_called_once() + + +# --------------------------------------------------------------------------- +# CLI commands via CliRunner +# --------------------------------------------------------------------------- + + +@pytest.fixture +def mock_client(mocker: MockerFixture) -> MagicMock: + """Create and patch a mock Mt5DataClient for CLI tests.""" + client = MagicMock() + sample_df = pd.DataFrame({"col": [1]}) + client.copy_rates_from_as_df.return_value = sample_df + client.copy_rates_from_pos_as_df.return_value = sample_df + client.copy_rates_range_as_df.return_value = sample_df + client.copy_ticks_from_as_df.return_value = sample_df + client.copy_ticks_range_as_df.return_value = sample_df + client.account_info_as_df.return_value = sample_df + client.terminal_info_as_df.return_value = sample_df + client.symbols_get_as_df.return_value = sample_df + client.symbol_info_as_df.return_value = sample_df + client.orders_get_as_df.return_value = sample_df + client.positions_get_as_df.return_value = sample_df + client.history_orders_get_as_df.return_value = sample_df + client.history_deals_get_as_df.return_value = sample_df + mocker.patch("mt5cli.cli.Mt5DataClient", return_value=client) + return client + + +class TestCommands: + """Tests for all CLI subcommands via CliRunner.""" + + def test_account_info( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test account-info command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + ["-o", str(output), "account-info"], + ) + assert result.exit_code == 0, result.output + mock_client.account_info_as_df.assert_called_once() + assert output.exists() + + def test_terminal_info( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test terminal-info command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + ["-o", str(output), "terminal-info"], + ) + assert result.exit_code == 0, result.output + mock_client.terminal_info_as_df.assert_called_once() + + def test_symbols( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test symbols command.""" + output = tmp_path / "out.json" + result = runner.invoke( + app, + ["-o", str(output), "symbols", "--group", "*USD*"], + ) + assert result.exit_code == 0, result.output + mock_client.symbols_get_as_df.assert_called_once_with( + group="*USD*", + ) + + def test_symbol_info( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test symbol-info command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + ["-o", str(output), "symbol-info", "--symbol", "EURUSD"], + ) + assert result.exit_code == 0, result.output + mock_client.symbol_info_as_df.assert_called_once_with( + symbol="EURUSD", + ) + + def test_rates_from( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test rates-from command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "rates-from", + "--symbol", + "EURUSD", + "--timeframe", + "M1", + "--date-from", + "2024-01-01", + "--count", + "100", + ], + ) + assert result.exit_code == 0, result.output + mock_client.copy_rates_from_as_df.assert_called_once_with( + symbol="EURUSD", + timeframe=1, + date_from=datetime(2024, 1, 1, tzinfo=UTC), + count=100, + ) + + def test_rates_from_pos( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test rates-from-pos command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "rates-from-pos", + "--symbol", + "GBPUSD", + "--timeframe", + "H1", + "--start-pos", + "0", + "--count", + "50", + ], + ) + assert result.exit_code == 0, result.output + mock_client.copy_rates_from_pos_as_df.assert_called_once_with( + symbol="GBPUSD", + timeframe=16385, + start_pos=0, + count=50, + ) + + def test_rates_range( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test rates-range command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "rates-range", + "--symbol", + "USDJPY", + "--timeframe", + "D1", + "--date-from", + "2024-01-01", + "--date-to", + "2024-02-01", + ], + ) + assert result.exit_code == 0, result.output + mock_client.copy_rates_range_as_df.assert_called_once_with( + symbol="USDJPY", + timeframe=16408, + date_from=datetime(2024, 1, 1, tzinfo=UTC), + date_to=datetime(2024, 2, 1, tzinfo=UTC), + ) + + def test_ticks_from( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test ticks-from command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "ticks-from", + "--symbol", + "EURUSD", + "--date-from", + "2024-01-01", + "--count", + "100", + "--flags", + "ALL", + ], + ) + assert result.exit_code == 0, result.output + mock_client.copy_ticks_from_as_df.assert_called_once_with( + symbol="EURUSD", + date_from=datetime(2024, 1, 1, tzinfo=UTC), + count=100, + flags=1, + ) + + def test_ticks_range( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test ticks-range command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "ticks-range", + "--symbol", + "EURUSD", + "--date-from", + "2024-01-01", + "--date-to", + "2024-02-01", + "--flags", + "INFO", + ], + ) + assert result.exit_code == 0, result.output + mock_client.copy_ticks_range_as_df.assert_called_once_with( + symbol="EURUSD", + date_from=datetime(2024, 1, 1, tzinfo=UTC), + date_to=datetime(2024, 2, 1, tzinfo=UTC), + flags=2, + ) + + def test_orders( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test orders command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "orders", + "--symbol", + "EURUSD", + ], + ) + assert result.exit_code == 0, result.output + mock_client.orders_get_as_df.assert_called_once() + + def test_positions( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test positions command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + ["-o", str(output), "positions"], + ) + assert result.exit_code == 0, result.output + mock_client.positions_get_as_df.assert_called_once() + + def test_history_orders( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test history-orders command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "history-orders", + "--date-from", + "2024-01-01", + "--date-to", + "2024-02-01", + ], + ) + assert result.exit_code == 0, result.output + mock_client.history_orders_get_as_df.assert_called_once() + + def test_history_deals( + self, + tmp_path: Path, + mock_client: MagicMock, + ) -> None: + """Test history-deals command.""" + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "-o", + str(output), + "history-deals", + "--ticket", + "12345", + ], + ) + assert result.exit_code == 0, result.output + mock_client.history_deals_get_as_df.assert_called_once() + + +# --------------------------------------------------------------------------- +# Callback / shared options +# --------------------------------------------------------------------------- + + +class TestCallback: + """Tests for callback (shared options).""" + + def test_format_detection_error(self, tmp_path: Path) -> None: + """Test that bad extension triggers a user-friendly error.""" + output = tmp_path / "out.xyz" + result = runner.invoke( + app, + ["-o", str(output), "account-info"], + ) + assert result.exit_code != 0 + assert "Cannot detect format" in result.output + + def test_connection_args_forwarded( + self, + tmp_path: Path, + mocker: MockerFixture, + ) -> None: + """Test that connection arguments reach Mt5Config.""" + mock_client = MagicMock() + mock_client.account_info_as_df.return_value = pd.DataFrame({"a": [1]}) + mocker.patch( + "mt5cli.cli.Mt5DataClient", + return_value=mock_client, + ) + mock_config = mocker.patch("mt5cli.cli.Mt5Config") + output = tmp_path / "out.csv" + result = runner.invoke( + app, + [ + "--login", + "123", + "--password", + "pw", + "--server", + "srv", + "-o", + str(output), + "account-info", + ], + ) + assert result.exit_code == 0, result.output + mock_config.assert_called_once_with( + path=None, + login=123, + password="pw", + server="srv", + timeout=None, + ) + + def test_explicit_format( + self, + tmp_path: Path, + mock_client: MagicMock, # noqa: ARG002 + ) -> None: + """Test explicit --format flag.""" + output = tmp_path / "out.txt" + result = runner.invoke( + app, + ["-o", str(output), "--format", "json", "account-info"], + ) + assert result.exit_code == 0, result.output + assert output.exists() + + def test_sqlite3_with_table( + self, + tmp_path: Path, + mocker: MockerFixture, + ) -> None: + """Test SQLite3 output with custom table name.""" + mock_client = MagicMock() + mock_client.symbols_get_as_df.return_value = pd.DataFrame( + {"s": ["EURUSD"]}, + ) + mocker.patch( + "mt5cli.cli.Mt5DataClient", + return_value=mock_client, + ) + output = tmp_path / "out.db" + result = runner.invoke( + app, + [ + "-o", + str(output), + "--table", + "symbols", + "symbols", + "--group", + "*USD*", + ], + ) + assert result.exit_code == 0, result.output + with sqlite3.connect(output) as conn: + result_df = pd.read_sql( # type: ignore[reportUnknownMemberType] + "SELECT * FROM symbols", + conn, + ) + assert len(result_df) == 1 + + +# --------------------------------------------------------------------------- +# main entry point +# --------------------------------------------------------------------------- + + +class TestMain: + """Tests for the main entry point.""" + + def test_main_invokes_app(self, mocker: MockerFixture) -> None: + """Test that main() calls the typer app.""" + mock_app = mocker.patch("mt5cli.cli.app") + main() + mock_app.assert_called_once() From 0f15b28ea392b37b8519f879d1ab57057ff87aee Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 14:00:48 +0000 Subject: [PATCH 2/3] Add docs, CI workflows, agent config, and supporting files - Add MkDocs documentation with API reference for the CLI module - Add CI/CD and Claude Code review GitHub Actions workflows - Add Dependabot and Renovate configuration for dependency updates - Add .claude settings, agents, and local-qa skill with QA script - Add AGENTS.md with repository guidelines and CLAUDE.md symlink - Update README.md with installation, usage examples, and commands https://claude.ai/code/session_01YW3YHru8wRH9dvHnBX7xf1 --- .agents/skills/local-qa/SKILL.md | 25 ++++++ .agents/skills/local-qa/scripts/qa.sh | 19 +++++ .claude/agents/codex.md | 46 ++++++++++ .claude/settings.json | 18 ++++ .claude/skills | 1 + .github/FUNDING.yml | 3 + .github/dependabot.yml | 17 ++++ .github/renovate.json | 13 +++ .github/workflows/ci.yml | 98 +++++++++++++++++++++ .github/workflows/claude-code.yml | 57 +++++++++++++ AGENTS.md | 79 +++++++++++++++++ CLAUDE.md | 1 + README.md | 85 ++++++++++++++++++- docs/api/cli.md | 3 + docs/api/index.md | 63 ++++++++++++++ docs/index.md | 117 ++++++++++++++++++++++++++ mkdocs.yml | 71 ++++++++++++++++ 17 files changed, 715 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/local-qa/SKILL.md create mode 100755 .agents/skills/local-qa/scripts/qa.sh create mode 100644 .claude/agents/codex.md create mode 100644 .claude/settings.json create mode 120000 .claude/skills create mode 100644 .github/FUNDING.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/renovate.json create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/claude-code.yml create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 docs/api/cli.md create mode 100644 docs/api/index.md create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/.agents/skills/local-qa/SKILL.md b/.agents/skills/local-qa/SKILL.md new file mode 100644 index 0000000..7c0dd86 --- /dev/null +++ b/.agents/skills/local-qa/SKILL.md @@ -0,0 +1,25 @@ +# local-qa + +Run local QA checks (format, lint, test) on the repository. + +## When to use + +After making changes to repository files, run `scripts/qa.sh` to validate formatting, linting, and tests. + +## Steps + +1. Execute `scripts/qa.sh` and capture the results. +2. Report successes, failures, warnings, and any modified files. + +## If tools are missing + +Install them following this priority order: + +1. Project package managers (`uv`, `poetry`, npm scripts) +2. System package managers (`brew`, `apt`) +3. Language-specific installers (`pipx`, `pip`, `npm`, `go install`) + +## Constraints + +- Only execute QA and tool-installation commands. +- If installation fails or requires unavailable privileges, report the attempt and exact failure, then stop. diff --git a/.agents/skills/local-qa/scripts/qa.sh b/.agents/skills/local-qa/scripts/qa.sh new file mode 100755 index 0000000..585728e --- /dev/null +++ b/.agents/skills/local-qa/scripts/qa.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euox pipefail +cd "$(git rev-parse --show-toplevel)" + +# Python +uv run ruff format . +uv run ruff check --fix . +uv run pyright . +uv run pytest + +# Markdown +npx -y prettier --write './**/*.md' + +# GitHub Actions +zizmor --fix=safe .github/workflows +git ls-files -z -- '.github/workflows/*.yml' | xargs -0 -t actionlint +git ls-files -z -- '.github/workflows/*.yml' | xargs -0 -t yamllint -d '{"extends": "relaxed", "rules": {"line-length": "disable"}}' +checkov --framework=all --output=github_failed_only --directory=. diff --git a/.claude/agents/codex.md b/.claude/agents/codex.md new file mode 100644 index 0000000..8f353f4 --- /dev/null +++ b/.claude/agents/codex.md @@ -0,0 +1,46 @@ +# Codex Agent + +Specialized Claude agent for autonomous development work using OpenAI's Codex CLI. + +## Modes + +### Ask Mode + +Read-only code analysis: answer questions about implementation, architecture, and debugging with specific file references and code examples. + +### Exec Mode + +Generate and modify code: create new components, refactor existing code, fix bugs, and write tests while maintaining quality standards. + +### Review Mode + +Comprehensive code review: identify security vulnerabilities, bugs, performance issues, and quality improvements without making changes. + +### Search Mode + +Research current documentation, best practices, solutions, and technology comparisons using web resources. + +## Core Requirements + +- Prioritize Codex CLI as the primary execution engine. +- Ask, Review, and Search modes are read-only; only Exec mode modifies code. +- All answers require verification: + - Ask mode must confirm file paths exist. + - Exec mode requires test passage and linting. + - Review mode needs severity prioritization. + - Search mode demands sourced citations. + +## Workflow + +1. Understand the task. +2. Gather context from the codebase. +3. Execute via Codex CLI with specific parameters. +4. Verify results. +5. Communicate findings with appropriate structure and detail. + +## Constraints + +- No hardcoded secrets. +- Thorough testing. +- Specific file references. +- Honest communication about limitations. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..1c24547 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,18 @@ +{ + "hooks": { + "Stop": [ + { + "matcher": "", + "hooks": [ + { + "type": "command", + "command": ".claude/skills/local-qa/scripts/qa.sh" + } + ] + } + ] + }, + "enabledPlugins": { + "code-simplifier@claude-plugins-official": true + } +} diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000..2b7a412 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..1fbf672 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +--- +github: + - dceoy diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2bf387f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +--- +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + cooldown: + default-days: 7 + open-pull-requests-limit: 10 + - package-ecosystem: pip + directory: / + schedule: + interval: daily + cooldown: + default-days: 7 + open-pull-requests-limit: 10 diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..aa6e565 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "minimumReleaseAge": "7 days", + "packageRules": [ + { + "description": "Automatically merge minor and patch-level updates", + "matchUpdateTypes": ["minor", "patch", "digest"], + "automerge": true, + "automergeType": "branch" + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..15b5c8b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,98 @@ +--- +name: CI/CD +on: + push: + branches: + - main + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + workflow_dispatch: # checkov:skip=CKV_GHA_7:workflow_dispatch inputs are required for manual runs + inputs: + workflow: + required: true + type: choice + options: + - lint-and-test + - docs-deploy + - release + description: Choose the workflow to run + default: lint-and-test +permissions: + contents: read +defaults: + run: + shell: bash -euo pipefail {0} + working-directory: . +jobs: + python-lint-and-scan: + if: > + github.event_name == 'push' + || github.event_name == 'pull_request' + || (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test') + permissions: + contents: read + uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-lint-and-scan.yml@main # zizmor: ignore[unpinned-uses] + with: + package-path: . + runs-on: windows-latest + python-test: + if: > + github.event_name == 'push' + || github.event_name == 'pull_request' + || (github.event_name == 'workflow_dispatch' && inputs.workflow == 'lint-and-test') + permissions: + contents: read + uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-test.yml@main # zizmor: ignore[unpinned-uses] + with: + package-path: . + runs-on: windows-latest + python-docs-deploy: + if: > + github.event_name == 'push' + || ( + github.event_name == 'workflow_dispatch' + && (inputs.workflow == 'docs-deploy' || inputs.workflow == 'release') + ) + permissions: + contents: write + uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-mkdocs-gh-deploy.yml@main # zizmor: ignore[unpinned-uses] + with: + package-path: . + runs-on: ubuntu-slim + secrets: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + python-package-release: + if: > + github.event_name == 'push' + || ( + github.event_name == 'workflow_dispatch' + && (inputs.workflow == 'release' || inputs.workflow == 'lint-and-test') + ) + permissions: + contents: write + id-token: write + uses: dceoy/gh-actions-for-devops/.github/workflows/python-package-release-on-pypi-and-github.yml@main # zizmor: ignore[unpinned-uses] + with: + package-path: . + create-releases: ${{ github.event_name == 'workflow_dispatch' && inputs.workflow == 'release' }} + secrets: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + dependabot-auto-merge: + if: > + github.event_name == 'pull_request' && github.actor == 'dependabot[bot]' + needs: + - python-lint-and-scan + - python-test + uses: dceoy/gh-actions-for-devops/.github/workflows/dependabot-auto-merge.yml@main # zizmor: ignore[unpinned-uses] + permissions: + contents: write + pull-requests: write + actions: read + with: + unconditional: true diff --git a/.github/workflows/claude-code.yml b/.github/workflows/claude-code.yml new file mode 100644 index 0000000..40bc1e6 --- /dev/null +++ b/.github/workflows/claude-code.yml @@ -0,0 +1,57 @@ +--- +name: Claude Code review and mention bot +on: + pull_request: + branches: + - main + types: + - opened + - ready_for_review + issue_comment: + types: + - created + pull_request_review_comment: + types: + - created + issues: + types: + - opened + - assigned + pull_request_review: + types: + - submitted +permissions: + contents: read +jobs: + claude-code-review: + if: > + github.event_name == 'pull_request' + && github.event.pull_request.draft == false + && (! startsWith(github.head_ref, 'dependabot/')) + && (! startsWith(github.head_ref, 'renovate/')) + permissions: + contents: read + pull-requests: write + issues: write + id-token: write + actions: read + uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-review.yml@main # zizmor: ignore[unpinned-uses] + secrets: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env] + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + claude-code-bot: + if: > + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) + || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) + || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) + || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + permissions: + contents: read + pull-requests: write + issues: write + id-token: write + actions: read + uses: dceoy/gh-actions-for-devops/.github/workflows/claude-code-bot.yml@main # zizmor: ignore[unpinned-uses] + secrets: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # zizmor: ignore[secrets-outside-env] + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8852bd9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,79 @@ +# Repository Guidelines + +## Commands + +### Development Setup + +```bash +uv sync +``` + +### Code Quality and Documentation + +**Important**: Run these before committing or creating a PR. + +1. **format, lint, and test**: Use `local-qa` skill. +2. **Documentation build** (if any public API changes): `uv run mkdocs build` + +## Architecture + +### Key Dependencies + +- **pdmt5**: Pandas-based data handler for MetaTrader 5 (core library) +- **typer**: CLI framework for building command-line interfaces +- **click**: Parameter type customization for CLI options +- **pandas**: Core data manipulation and analysis + +### Package Structure + +- `mt5cli/`: Main package directory + - `__init__.py`: Package initialization and exports (`detect_format`, `export_dataframe`) + - `cli.py`: CLI application with typer-based commands for data export + - `__main__.py`: Entry point for `python -m mt5cli` +- `tests/`: Comprehensive test suite (pytest-based) + - `test_cli.py`: Tests for CLI commands, parameter types, and export functions +- `docs/`: MkDocs documentation with API reference + - `docs/index.md`: Main documentation + - `docs/api/`: Auto-generated API documentation for all modules +- Modern Python packaging with `pyproject.toml` and uv dependency management + +### Quality Standards + +- Type hints required (pyright strict mode) +- Comprehensive linting with 35+ rule categories (ruff) +- Test coverage tracking with 100% (pytest-cov) +- Parametrized tests for input/result matrices using `pytest.mark.parametrize` (pytest) +- Test doubles (mocks, stubs) using `pytest_mock` for external dependencies (pytest-mock) +- Pydantic models for data validation and configuration + +### Documentation workflow + +1. Add Google-style docstrings to functions/classes +2. Local preview: `uv run mkdocs serve` +3. Build: `uv run mkdocs build` +4. Deploy: `uv run mkdocs gh-deploy` + +## Commit & Pull Request Guidelines + +- Run QA checks using `local-qa` skill before committing or creating a PR. +- Branch names use appropriate prefixes on creation (e.g., `feature/...`, `bugfix/...`, `refactor/...`, `docs/...`, `chore/...`). +- When instructed to create a PR, create it as a draft with appropriate labels by default. + +## Code Design Principles + +Always prefer the simplest design that works. + +- **KISS**: Choose straightforward solutions and avoid unnecessary abstraction. +- **DRY**: Remove duplication when it improves clarity and maintainability. +- **YAGNI**: Do not add features, hooks, or flexibility until they are needed. +- **SOLID/Clean Code**: Apply these as tools, only when they keep the design simpler and easier to change. + +## Development Methodology + +Keep delivery incremental, test-backed, and easy to review. + +- Make small, safe, reversible changes. +- Prefer `Red -> Green -> Refactor`. +- Do not mix feature work and refactoring in the same commit. +- Refactor when it improves clarity or removes real duplication (Rule of Three). +- Keep tests fast, focused, and self-validating. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md index d9031a2..12e1d39 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,85 @@ # mt5cli -Command-line tool for MetaTrader 5 + +[![CI/CD](https://github.com/dceoy/mt5cli/actions/workflows/ci.yml/badge.svg)](https://github.com/dceoy/mt5cli/actions/workflows/ci.yml) + +Command-line tool for exporting MetaTrader 5 data to CSV, JSON, Parquet, and SQLite3. + +Built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5. + +## Features + +- **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats +- **Auto-detection**: Format detection from file extensions +- **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history +- **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values +- **Connection management**: Optional credentials, server, and timeout configuration + +## Installation + +```bash +pip install -U mt5cli MetaTrader5 +``` + +## Usage + +```bash +# Export account information to CSV +mt5cli -o account.csv account-info + +# Export EURUSD M1 rates to Parquet +mt5cli -o rates.parquet rates-from --symbol EURUSD --timeframe M1 \ + --date-from 2024-01-01 --count 1000 + +# Export ticks to JSON +mt5cli -o ticks.json ticks-from --symbol EURUSD \ + --date-from 2024-01-01 --count 500 --flags ALL + +# Export symbols to SQLite3 with custom table name +mt5cli -o data.db --table symbols symbols --group "*USD*" + +# Export with connection credentials +mt5cli --login 12345 --password mypass --server MyBroker-Demo \ + -o positions.csv positions +``` + +Run as a Python module: + +```bash +python -m mt5cli -o account.csv account-info +``` + +## Commands + +| Command | Description | +|---------|-------------| +| `rates-from` | Export rates from a start date | +| `rates-from-pos` | Export rates from a start position | +| `rates-range` | Export rates for a date range | +| `ticks-from` | Export ticks from a start date | +| `ticks-range` | Export ticks for a date range | +| `account-info` | Export account information | +| `terminal-info` | Export terminal information | +| `symbols` | Export symbol list | +| `symbol-info` | Export symbol details | +| `orders` | Export active orders | +| `positions` | Export open positions | +| `history-orders` | Export historical orders | +| `history-deals` | Export historical deals | + +## Requirements + +- Python 3.11+ +- Windows OS (MetaTrader 5 requirement) +- MetaTrader 5 platform installed + +## Development + +```bash +git clone https://github.com/dceoy/mt5cli.git +cd mt5cli +uv sync +``` + +## License + +[MIT](LICENSE) diff --git a/docs/api/cli.md b/docs/api/cli.md new file mode 100644 index 0000000..89b885b --- /dev/null +++ b/docs/api/cli.md @@ -0,0 +1,3 @@ +# CLI Module + +::: mt5cli.cli diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..8013438 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,63 @@ +# API Reference + +This section contains the complete API documentation for mt5cli. + +## Modules + +The mt5cli package consists of the following modules: + +### [CLI](cli.md) + +Command-line interface module providing typer-based commands for exporting MetaTrader 5 data to CSV, JSON, Parquet, and SQLite3 formats. + +## Architecture Overview + +The package follows a simple architecture built on top of pdmt5: + +1. **CLI Layer** (`cli.py`): Typer application with subcommands for each data type, custom Click parameter types for datetime/timeframe/tick flags parsing, and format detection/export utilities. +2. **Data Layer** (via `pdmt5`): Uses `Mt5DataClient` and `Mt5Config` from the pdmt5 package for all MetaTrader 5 data access. + +## Usage Guidelines + +All modules follow these conventions: + +- **Type Safety**: All functions include comprehensive type hints +- **Error Handling**: User-friendly error messages via typer +- **Documentation**: Google-style docstrings with examples +- **Validation**: Custom Click parameter types for input validation + +## Quick Start + +```bash +# Export account information to CSV +mt5cli -o account.csv account-info + +# Export EURUSD H1 rates to Parquet +mt5cli -o rates.parquet rates-from --symbol EURUSD --timeframe H1 \ + --date-from 2024-01-01 --count 1000 + +# Export ticks to JSON +mt5cli -o ticks.json ticks-from --symbol EURUSD \ + --date-from 2024-01-01 --count 500 --flags ALL + +# Export to SQLite3 with custom table name +mt5cli -o data.db --table symbols symbols --group "*USD*" +``` + +## Python API + +```python +from mt5cli import detect_format, export_dataframe +import pandas as pd + +# Detect output format from file extension +fmt = detect_format(Path("output.parquet")) # Returns "parquet" + +# Export a DataFrame +df = pd.DataFrame({"symbol": ["EURUSD"], "bid": [1.1234]}) +export_dataframe(df, Path("output.csv"), "csv") +``` + +## Examples + +See individual module pages for detailed usage examples and code samples. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..bba731f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,117 @@ +# mt5cli + +Command-line tool for MetaTrader 5 data export. + +## Overview + +mt5cli is a CLI application that exports MetaTrader 5 trading data to multiple file formats. It is built on top of [pdmt5](https://github.com/dceoy/pdmt5), a pandas-based data handler for MetaTrader 5. + +## Features + +- **Multi-format export**: CSV, JSON, Parquet, and SQLite3 output formats +- **Auto-detection**: Format detection from file extensions +- **Comprehensive data access**: Rates, ticks, account info, symbols, orders, positions, and trading history +- **Flexible timeframes**: Named timeframes (M1, H1, D1, etc.) and numeric values +- **Connection management**: Optional credentials, server, and timeout configuration + +## Installation + +```bash +pip install mt5cli +``` + +## Quick Start + +```bash +# Export account information to CSV +mt5cli -o account.csv account-info + +# Export EURUSD M1 rates to Parquet +mt5cli -o rates.parquet rates-from --symbol EURUSD --timeframe M1 \ + --date-from 2024-01-01 --count 1000 + +# Export ticks to JSON +mt5cli -o ticks.json ticks-from --symbol EURUSD \ + --date-from 2024-01-01 --count 500 --flags ALL + +# Export symbols to SQLite3 with custom table name +mt5cli -o data.db --table symbols symbols --group "*USD*" + +# Export with connection credentials +mt5cli --login 12345 --password mypass --server MyBroker-Demo \ + -o positions.csv positions +``` + +## Commands + +### Rates + +| Command | Description | +|---------|-------------| +| `rates-from` | Export rates from a start date | +| `rates-from-pos` | Export rates from a start position | +| `rates-range` | Export rates for a date range | + +### Ticks + +| Command | Description | +|---------|-------------| +| `ticks-from` | Export ticks from a start date | +| `ticks-range` | Export ticks for a date range | + +### Information + +| Command | Description | +|---------|-------------| +| `account-info` | Export account information | +| `terminal-info` | Export terminal information | +| `symbols` | Export symbol list | +| `symbol-info` | Export symbol details | + +### Trading + +| Command | Description | +|---------|-------------| +| `orders` | Export active orders | +| `positions` | Export open positions | +| `history-orders` | Export historical orders | +| `history-deals` | Export historical deals | + +## Global Options + +| Option | Description | +|--------|-------------| +| `-o, --output` | Output file path (required) | +| `-f, --format` | Output format (auto-detected from extension if omitted) | +| `--table` | Table name for SQLite3 output (default: "data") | +| `--login` | Trading account login | +| `--password` | Trading account password | +| `--server` | Trading server name | +| `--path` | Path to MetaTrader5 terminal EXE file | +| `--timeout` | Connection timeout in milliseconds | +| `--log-level` | Logging level (DEBUG, INFO, WARNING, ERROR) | + +## Requirements + +- Python 3.11+ +- Windows OS (MetaTrader 5 requirement) +- MetaTrader 5 platform + +## API Reference + +Browse the API documentation for detailed module information: + +- [CLI Module](api/cli.md) - CLI application with export commands and utility functions + +## Development + +This project follows strict code quality standards: + +- Type hints required (strict mode) +- Comprehensive linting with Ruff +- Test coverage tracking +- Google-style docstrings + +## License + +MIT License - see [LICENSE](https://github.com/dceoy/mt5cli/blob/main/LICENSE) file for details. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..6a128b6 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,71 @@ +site_name: mt5cli API Documentation +site_description: Command-line tool for MetaTrader 5 +site_author: dceoy +site_url: https://github.com/dceoy/mt5cli + +repo_name: dceoy/mt5cli +repo_url: https://github.com/dceoy/mt5cli + +theme: + name: material + palette: + - scheme: default + primary: blue + accent: blue + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: blue + accent: blue + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - content.code.annotate + - content.code.copy + - navigation.indexes + - navigation.sections + - navigation.tabs + - navigation.top + - search.highlight + - search.share + - search.suggest + - toc.follow + +plugins: + - search + - mkdocstrings: + handlers: + python: + paths: [.] + options: + show_source: true + show_root_heading: true + show_root_toc_entry: true + docstring_style: google + docstring_section_style: table + separate_signature: true + show_signature_annotations: true + signature_crossrefs: true + merge_init_into_class: true + show_if_no_docstring: true + +nav: + - Home: index.md + - API Reference: + - Overview: api/index.md + - CLI: api/cli.md + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - toc: + permalink: true From 5e2b71977dd7d685aa46418a6f5729cdf953be09 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 28 Mar 2026 14:59:17 +0000 Subject: [PATCH 3/3] Fix QA issues: add pyarrow dep, docstring, type stubs, and coverage config - Add pyarrow dependency for parquet export support - Add docstring to tests/__init__.py (ruff D104) - Disable reportMissingTypeStubs for pdmt5 in pyright config - Exclude __main__.py from coverage measurement - Format markdown tables with prettier - Add uv.lock https://claude.ai/code/session_01YW3YHru8wRH9dvHnBX7xf1 --- README.md | 28 +- docs/index.md | 56 ++-- mt5cli/cli.py | 1 - pyproject.toml | 3 + tests/__init__.py | 1 + uv.lock | 694 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 740 insertions(+), 43 deletions(-) create mode 100644 uv.lock diff --git a/README.md b/README.md index 12e1d39..4762b54 100644 --- a/README.md +++ b/README.md @@ -50,21 +50,21 @@ python -m mt5cli -o account.csv account-info ## Commands -| Command | Description | -|---------|-------------| -| `rates-from` | Export rates from a start date | +| Command | Description | +| ---------------- | ---------------------------------- | +| `rates-from` | Export rates from a start date | | `rates-from-pos` | Export rates from a start position | -| `rates-range` | Export rates for a date range | -| `ticks-from` | Export ticks from a start date | -| `ticks-range` | Export ticks for a date range | -| `account-info` | Export account information | -| `terminal-info` | Export terminal information | -| `symbols` | Export symbol list | -| `symbol-info` | Export symbol details | -| `orders` | Export active orders | -| `positions` | Export open positions | -| `history-orders` | Export historical orders | -| `history-deals` | Export historical deals | +| `rates-range` | Export rates for a date range | +| `ticks-from` | Export ticks from a start date | +| `ticks-range` | Export ticks for a date range | +| `account-info` | Export account information | +| `terminal-info` | Export terminal information | +| `symbols` | Export symbol list | +| `symbol-info` | Export symbol details | +| `orders` | Export active orders | +| `positions` | Export open positions | +| `history-orders` | Export historical orders | +| `history-deals` | Export historical deals | ## Requirements diff --git a/docs/index.md b/docs/index.md index bba731f..901c5c0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,50 +46,50 @@ mt5cli --login 12345 --password mypass --server MyBroker-Demo \ ### Rates -| Command | Description | -|---------|-------------| -| `rates-from` | Export rates from a start date | +| Command | Description | +| ---------------- | ---------------------------------- | +| `rates-from` | Export rates from a start date | | `rates-from-pos` | Export rates from a start position | -| `rates-range` | Export rates for a date range | +| `rates-range` | Export rates for a date range | ### Ticks -| Command | Description | -|---------|-------------| -| `ticks-from` | Export ticks from a start date | -| `ticks-range` | Export ticks for a date range | +| Command | Description | +| ------------- | ------------------------------ | +| `ticks-from` | Export ticks from a start date | +| `ticks-range` | Export ticks for a date range | ### Information -| Command | Description | -|---------|-------------| -| `account-info` | Export account information | +| Command | Description | +| --------------- | --------------------------- | +| `account-info` | Export account information | | `terminal-info` | Export terminal information | -| `symbols` | Export symbol list | -| `symbol-info` | Export symbol details | +| `symbols` | Export symbol list | +| `symbol-info` | Export symbol details | ### Trading -| Command | Description | -|---------|-------------| -| `orders` | Export active orders | -| `positions` | Export open positions | +| Command | Description | +| ---------------- | ------------------------ | +| `orders` | Export active orders | +| `positions` | Export open positions | | `history-orders` | Export historical orders | -| `history-deals` | Export historical deals | +| `history-deals` | Export historical deals | ## Global Options -| Option | Description | -|--------|-------------| -| `-o, --output` | Output file path (required) | +| Option | Description | +| -------------- | ------------------------------------------------------- | +| `-o, --output` | Output file path (required) | | `-f, --format` | Output format (auto-detected from extension if omitted) | -| `--table` | Table name for SQLite3 output (default: "data") | -| `--login` | Trading account login | -| `--password` | Trading account password | -| `--server` | Trading server name | -| `--path` | Path to MetaTrader5 terminal EXE file | -| `--timeout` | Connection timeout in milliseconds | -| `--log-level` | Logging level (DEBUG, INFO, WARNING, ERROR) | +| `--table` | Table name for SQLite3 output (default: "data") | +| `--login` | Trading account login | +| `--password` | Trading account password | +| `--server` | Trading server name | +| `--path` | Path to MetaTrader5 terminal EXE file | +| `--timeout` | Connection timeout in milliseconds | +| `--log-level` | Logging level (DEBUG, INFO, WARNING, ERROR) | ## Requirements diff --git a/mt5cli/cli.py b/mt5cli/cli.py index 340f6a7..c72e746 100644 --- a/mt5cli/cli.py +++ b/mt5cli/cli.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING, Annotated, cast import click import typer - from pdmt5 import Mt5Config, Mt5DataClient if TYPE_CHECKING: diff --git a/pyproject.toml b/pyproject.toml index c605061..bc71d38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ requires-python = ">= 3.11, < 3.14" dependencies = [ "pdmt5 >= 0.2.3", "click >= 8.1.0", + "pyarrow >= 19.0.0", "typer >= 0.15.0", ] classifiers = [ @@ -147,6 +148,7 @@ exclude = ["build", ".venv"] venvPath = "." venv = ".venv" typeCheckingMode = "strict" +reportMissingTypeStubs = false [tool.pytest.ini_options] addopts = [ @@ -166,6 +168,7 @@ minversion = "6.0" source = ["mt5cli"] omit = [ "**/__init__.py", + "**/__main__.py", "tests/**", ] diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..4936792 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for mt5cli.""" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..c5e34b9 --- /dev/null +++ b/uv.lock @@ -0,0 +1,694 @@ +version = 1 +revision = 3 +requires-python = ">=3.11, <3.14" +resolution-markers = [ + "sys_platform == 'win32'", + "sys_platform == 'emscripten'", + "sys_platform != 'emscripten' and sys_platform != 'win32'", +] +required-markers = [ + "sys_platform == 'win32'", +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "metatrader5" +version = "5.0.5640" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'win32'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a0/3b764c6743ef601ff12f7d8d62ca5768eb25e90d3758ac7e1d08e667af85/metatrader5-5.0.5640-cp311-cp311-win_amd64.whl", hash = "sha256:4057255f2d63138a3ea1a5d492715038a71d3177cad793fca97a5c58771b4eb1", size = 48091, upload-time = "2026-02-20T23:31:12.289Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/b158bece0322ccf426b2879742bfce00104e53291a3c057a333c90633a88/metatrader5-5.0.5640-cp312-cp312-win_amd64.whl", hash = "sha256:24fab41178067e67c4d9ceb70bc0923d1814090a6e1f9bc5d7d13fa83737ddee", size = 48067, upload-time = "2026-02-20T23:31:13.815Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/a735f26d826c6a5dc8c8d665d2bca83f22dc20f92543647712c210800be1/metatrader5-5.0.5640-cp313-cp313-win_amd64.whl", hash = "sha256:08431a9d02a26d517dcde19fccd571a57ec4454acb798ee246d95eae8946e60e", size = 48073, upload-time = "2026-02-20T23:31:15.35Z" }, +] + +[[package]] +name = "mt5cli" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "click" }, + { name = "pdmt5" }, + { name = "pyarrow" }, + { name = "typer" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pandas-stubs" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.1.0" }, + { name = "pdmt5", specifier = ">=0.2.3" }, + { name = "pyarrow", specifier = ">=19.0.0" }, + { name = "typer", specifier = ">=0.15.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pandas-stubs", specifier = ">=2.2.3.250527" }, + { name = "pyright", specifier = ">=1.1.407" }, + { name = "pytest", specifier = ">=8.0.0" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, + { name = "pytest-mock", specifier = ">=3.12.0" }, + { name = "ruff", specifier = ">=0.11.0" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" }, + { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" }, + { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" }, + { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" }, + { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" }, + { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" }, + { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" }, + { url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, + { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, + { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, + { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, + { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, + { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, + { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, + { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, + { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "3.0.0.260204" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/1d/297ff2c7ea50a768a2247621d6451abb2a07c0e9be7ca6d36ebe371658e5/pandas_stubs-3.0.0.260204.tar.gz", hash = "sha256:bf9294b76352effcffa9cb85edf0bed1339a7ec0c30b8e1ac3d66b4228f1fbc3", size = 109383, upload-time = "2026-02-04T15:17:17.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl", hash = "sha256:5ab9e4d55a6e2752e9720828564af40d48c4f709e6a2c69b743014a6fcb6c241", size = 168540, upload-time = "2026-02-04T15:17:15.615Z" }, +] + +[[package]] +name = "pdmt5" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "metatrader5", marker = "sys_platform == 'win32'" }, + { name = "pandas" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/25/52d9d954504ccdd0fe91f715ab74c424d61234b237cc4160d3ebe20070f1/pdmt5-0.2.3.tar.gz", hash = "sha256:21384f5826fb0125fee3f93c90b108340f55ab53b1c819d229ceac162289d2ec", size = 226665, upload-time = "2026-02-05T13:28:21.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/75/c5e52a9cf459b85b2dd52f83e70857571b1b45805c9fe610b3959a26ac15/pdmt5-0.2.3-py3-none-any.whl", hash = "sha256:f92246a05cfc3b7feb3ab0cc5b48768a4d84aad6b02e7a68060948f5828718a1", size = 22967, upload-time = "2026-02-05T13:28:19.523Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pyarrow" +version = "23.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/41/8e6b6ef7e225d4ceead8459427a52afdc23379768f54dd3566014d7618c1/pyarrow-23.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6f0147ee9e0386f519c952cc670eb4a8b05caa594eeffe01af0e25f699e4e9bb", size = 34302230, upload-time = "2026-02-16T10:09:03.859Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4a/1472c00392f521fea03ae93408bf445cc7bfa1ab81683faf9bc188e36629/pyarrow-23.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:0ae6e17c828455b6265d590100c295193f93cc5675eb0af59e49dbd00d2de350", size = 35850050, upload-time = "2026-02-16T10:09:11.877Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b2/bd1f2f05ded56af7f54d702c8364c9c43cd6abb91b0e9933f3d77b4f4132/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:fed7020203e9ef273360b9e45be52a2a47d3103caf156a30ace5247ffb51bdbd", size = 44491918, upload-time = "2026-02-16T10:09:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/0b/62/96459ef5b67957eac38a90f541d1c28833d1b367f014a482cb63f3b7cd2d/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:26d50dee49d741ac0e82185033488d28d35be4d763ae6f321f97d1140eb7a0e9", size = 47562811, upload-time = "2026-02-16T10:09:25.792Z" }, + { url = "https://files.pythonhosted.org/packages/7d/94/1170e235add1f5f45a954e26cd0e906e7e74e23392dcb560de471f7366ec/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c30143b17161310f151f4a2bcfe41b5ff744238c1039338779424e38579d701", size = 48183766, upload-time = "2026-02-16T10:09:34.645Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/39a42af4570377b99774cdb47f63ee6c7da7616bd55b3d5001aa18edfe4f/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db2190fa79c80a23fdd29fef4b8992893f024ae7c17d2f5f4db7171fa30c2c78", size = 50607669, upload-time = "2026-02-16T10:09:44.153Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/db94101c187f3df742133ac837e93b1f269ebdac49427f8310ee40b6a58f/pyarrow-23.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:f00f993a8179e0e1c9713bcc0baf6d6c01326a406a9c23495ec1ba9c9ebf2919", size = 27527698, upload-time = "2026-02-16T10:09:50.263Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" }, + { url = "https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7", size = 35832540, upload-time = "2026-02-16T10:10:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/88/7c/3d841c366620e906d54430817531b877ba646310296df42ef697308c2705/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86ff03fb9f1a320266e0de855dee4b17da6794c595d207f89bba40d16b5c78b9", size = 44470940, upload-time = "2026-02-16T10:10:10.704Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05", size = 47586063, upload-time = "2026-02-16T10:10:17.95Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/b7d2ebcff47a514f47f9da1e74b7949138c58cfeb108cdd4ee62f43f0cf3/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf5842f960cddd2ef757d486041d57c96483efc295a8c4a0e20e704cbbf39c67", size = 48173045, upload-time = "2026-02-16T10:10:25.363Z" }, + { url = "https://files.pythonhosted.org/packages/43/b2/b40961262213beaba6acfc88698eb773dfce32ecdf34d19291db94c2bd73/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564baf97c858ecc03ec01a41062e8f4698abc3e6e2acd79c01c2e97880a19730", size = 50621741, upload-time = "2026-02-16T10:10:33.477Z" }, + { url = "https://files.pythonhosted.org/packages/f6/70/1fdda42d65b28b078e93d75d371b2185a61da89dda4def8ba6ba41ebdeb4/pyarrow-23.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07deae7783782ac7250989a7b2ecde9b3c343a643f82e8a4df03d93b633006f0", size = 27620678, upload-time = "2026-02-16T10:10:39.31Z" }, + { url = "https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8", size = 34210066, upload-time = "2026-02-16T10:10:45.487Z" }, + { url = "https://files.pythonhosted.org/packages/cb/4f/679fa7e84dadbaca7a65f7cdba8d6c83febbd93ca12fa4adf40ba3b6362b/pyarrow-23.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:8ff51b1addc469b9444b7c6f3548e19dc931b172ab234e995a60aea9f6e6025f", size = 35825526, upload-time = "2026-02-16T10:10:52.266Z" }, + { url = "https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677", size = 44473279, upload-time = "2026-02-16T10:11:01.557Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2", size = 47585798, upload-time = "2026-02-16T10:11:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/476943001c54ef078dbf9542280e22741219a184a0632862bca4feccd666/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fc13fc6c403d1337acab46a2c4346ca6c9dec5780c3c697cf8abfd5e19b6b37", size = 48179446, upload-time = "2026-02-16T10:11:17.781Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b6/5dd0c47b335fcd8edba9bfab78ad961bd0fd55ebe53468cc393f45e0be60/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c16ed4f53247fa3ffb12a14d236de4213a4415d127fe9cebed33d51671113e2", size = 50623972, upload-time = "2026-02-16T10:11:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/d5/09/a532297c9591a727d67760e2e756b83905dd89adb365a7f6e9c72578bcc1/pyarrow-23.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:cecfb12ef629cf6be0b1887f9f86463b0dd3dc3195ae6224e74006be4736035a", size = 27540749, upload-time = "2026-02-16T10:12:23.297Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8e/38749c4b1303e6ae76b3c80618f84861ae0c55dd3c2273842ea6f8258233/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:29f7f7419a0e30264ea261fdc0e5fe63ce5a6095003db2945d7cd78df391a7e1", size = 34471544, upload-time = "2026-02-16T10:11:32.535Z" }, + { url = "https://files.pythonhosted.org/packages/a3/73/f237b2bc8c669212f842bcfd842b04fc8d936bfc9d471630569132dc920d/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:33d648dc25b51fd8055c19e4261e813dfc4d2427f068bcecc8b53d01b81b0500", size = 35949911, upload-time = "2026-02-16T10:11:39.813Z" }, + { url = "https://files.pythonhosted.org/packages/0c/86/b912195eee0903b5611bf596833def7d146ab2d301afeb4b722c57ffc966/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd395abf8f91c673dd3589cadc8cc1ee4e8674fa61b2e923c8dd215d9c7d1f41", size = 44520337, upload-time = "2026-02-16T10:11:47.764Z" }, + { url = "https://files.pythonhosted.org/packages/69/c2/f2a717fb824f62d0be952ea724b4f6f9372a17eed6f704b5c9526f12f2f1/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:00be9576d970c31defb5c32eb72ef585bf600ef6d0a82d5eccaae96639cf9d07", size = 47548944, upload-time = "2026-02-16T10:11:56.607Z" }, + { url = "https://files.pythonhosted.org/packages/84/a7/90007d476b9f0dc308e3bc57b832d004f848fd6c0da601375d20d92d1519/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c2139549494445609f35a5cda4eb94e2c9e4d704ce60a095b342f82460c73a83", size = 48236269, upload-time = "2026-02-16T10:12:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3f/b16fab3e77709856eb6ac328ce35f57a6d4a18462c7ca5186ef31b45e0e0/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7044b442f184d84e2351e5084600f0d7343d6117aabcbc1ac78eb1ae11eb4125", size = 50604794, upload-time = "2026-02-16T10:12:11.797Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/22df0620a9fac31d68397a75465c344e83c3dfe521f7612aea33e27ab6c0/pyarrow-23.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a35581e856a2fafa12f3f54fce4331862b1cfb0bef5758347a858a4aa9d6bae8", size = 27660642, upload-time = "2026-02-16T10:12:17.746Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.408" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +]