mt5cli 1.2.3__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {mt5cli-1.2.3 → mt5cli-1.2.4}/.github/workflows/ci.yml +2 -2
  2. {mt5cli-1.2.3 → mt5cli-1.2.4}/PKG-INFO +1 -2
  3. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/cli.py +1 -122
  4. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/client.py +15 -8
  5. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/history.py +1 -5
  6. {mt5cli-1.2.3 → mt5cli-1.2.4}/pyproject.toml +65 -64
  7. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/conftest.py +2 -1
  8. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_cli.py +12 -101
  9. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_client.py +4 -10
  10. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_contracts.py +6 -3
  11. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_history.py +2 -1
  12. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_telemetry.py +4 -1
  13. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_trading.py +4 -2
  14. {mt5cli-1.2.3 → mt5cli-1.2.4}/uv.lock +1 -3
  15. {mt5cli-1.2.3 → mt5cli-1.2.4}/.agents/skills/local-qa/SKILL.md +0 -0
  16. {mt5cli-1.2.3 → mt5cli-1.2.4}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  17. {mt5cli-1.2.3 → mt5cli-1.2.4}/.agents/skills/mt5cli/SKILL.md +0 -0
  18. {mt5cli-1.2.3 → mt5cli-1.2.4}/.agents/skills/pr-feedback-triage/SKILL.md +0 -0
  19. {mt5cli-1.2.3 → mt5cli-1.2.4}/.claude/agents/codex.md +0 -0
  20. {mt5cli-1.2.3 → mt5cli-1.2.4}/.claude/settings.json +0 -0
  21. {mt5cli-1.2.3 → mt5cli-1.2.4}/.github/FUNDING.yml +0 -0
  22. {mt5cli-1.2.3 → mt5cli-1.2.4}/.github/dependabot.yml +0 -0
  23. {mt5cli-1.2.3 → mt5cli-1.2.4}/.github/renovate.json +0 -0
  24. {mt5cli-1.2.3 → mt5cli-1.2.4}/.github/workflows/claude.yml +0 -0
  25. {mt5cli-1.2.3 → mt5cli-1.2.4}/.github/workflows/release.yml +0 -0
  26. {mt5cli-1.2.3 → mt5cli-1.2.4}/.gitignore +0 -0
  27. {mt5cli-1.2.3 → mt5cli-1.2.4}/AGENTS.md +0 -0
  28. {mt5cli-1.2.3 → mt5cli-1.2.4}/CLAUDE.md +0 -0
  29. {mt5cli-1.2.3 → mt5cli-1.2.4}/LICENSE +0 -0
  30. {mt5cli-1.2.3 → mt5cli-1.2.4}/README.md +0 -0
  31. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/cli.md +0 -0
  32. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/client.md +0 -0
  33. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/converters.md +0 -0
  34. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/exceptions.md +0 -0
  35. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/grafana.md +0 -0
  36. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/history.md +0 -0
  37. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/index.md +0 -0
  38. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/marketdata.md +0 -0
  39. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/observability.md +0 -0
  40. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/public-contract.md +0 -0
  41. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/schemas.md +0 -0
  42. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/telemetry.md +0 -0
  43. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/trading.md +0 -0
  44. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/api/utils.md +0 -0
  45. {mt5cli-1.2.3 → mt5cli-1.2.4}/docs/index.md +0 -0
  46. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/README.md +0 -0
  47. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/compose.yml +0 -0
  48. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/dashboards/mt5cli-market.json +0 -0
  49. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/dashboards/mt5cli-overview.json +0 -0
  50. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/dashboards/mt5cli-trades.json +0 -0
  51. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/provisioning/dashboards/mt5cli.yml +0 -0
  52. {mt5cli-1.2.3 → mt5cli-1.2.4}/examples/grafana/provisioning/datasources/mt5cli-sqlite.yml +0 -0
  53. {mt5cli-1.2.3 → mt5cli-1.2.4}/mkdocs.yml +0 -0
  54. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/__init__.py +0 -0
  55. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/__main__.py +0 -0
  56. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/contract.py +0 -0
  57. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/converters.py +0 -0
  58. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/exceptions.py +0 -0
  59. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/grafana.py +0 -0
  60. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/marketdata.py +0 -0
  61. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/observability.py +0 -0
  62. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/retry.py +0 -0
  63. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/schemas.py +0 -0
  64. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/telemetry.py +0 -0
  65. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/trading.py +0 -0
  66. {mt5cli-1.2.3 → mt5cli-1.2.4}/mt5cli/utils.py +0 -0
  67. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/__init__.py +0 -0
  68. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_examples.py +0 -0
  69. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_grafana.py +0 -0
  70. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_marketdata.py +0 -0
  71. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_observability.py +0 -0
  72. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_packaging.py +0 -0
  73. {mt5cli-1.2.3 → mt5cli-1.2.4}/tests/test_utils.py +0 -0
@@ -59,10 +59,10 @@ jobs:
59
59
  contents: read
60
60
  runs-on: windows-latest
61
61
  steps:
62
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.2
62
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6.0.2
63
63
  with:
64
64
  persist-credentials: false
65
- - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
65
+ - uses: astral-sh/setup-uv@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1
66
66
  - name: Check CLI help with minimum Click and Typer versions
67
67
  run: uv run --with click==8.4.0 --with typer==0.26.0 mt5cli --help
68
68
  python-docs-deploy:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5cli
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Generic MT5 data and execution infrastructure for Python applications
5
5
  Project-URL: Repository, https://github.com/dceoy/mt5cli.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -16,7 +16,6 @@ Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Topic :: Office/Business :: Financial :: Investment
18
18
  Requires-Python: <3.14,>=3.11
19
- Requires-Dist: click>=8.4
20
19
  Requires-Dist: pandas>=2.2.2
21
20
  Requires-Dist: pdmt5>=1.2.0
22
21
  Requires-Dist: typer>=0.26.0
@@ -6,11 +6,10 @@ import json
6
6
  import logging
7
7
  from contextlib import closing
8
8
  from dataclasses import dataclass
9
- from datetime import datetime
9
+ from datetime import datetime # noqa: TC003
10
10
  from pathlib import Path # noqa: TC003
11
11
  from typing import TYPE_CHECKING, Annotated, Any, cast
12
12
 
13
- import click
14
13
  import pandas as pd
15
14
  import typer
16
15
 
@@ -46,126 +45,6 @@ if TYPE_CHECKING:
46
45
 
47
46
  logger = logging.getLogger(__name__)
48
47
 
49
- # ---------------------------------------------------------------------------
50
- # Click parameter types
51
- # ---------------------------------------------------------------------------
52
-
53
-
54
- class _DateTimeType(click.ParamType[datetime]):
55
- """Click parameter type for ISO 8601 datetime strings."""
56
-
57
- name = "DATETIME"
58
-
59
- def convert(
60
- self,
61
- value: object,
62
- param: click.Parameter | None = None,
63
- ctx: click.Context | None = None,
64
- ) -> datetime:
65
- """Convert a string value to a timezone-aware datetime.
66
-
67
- Args:
68
- value: Raw value from the command line.
69
- param: Click parameter instance.
70
- ctx: Click context.
71
-
72
- Returns:
73
- Parsed datetime.
74
- """
75
- if isinstance(value, datetime):
76
- return value
77
- try:
78
- return parse_datetime(str(value))
79
- except ValueError as exc:
80
- self.fail(str(exc), param, ctx)
81
-
82
-
83
- class _TimeframeType(click.ParamType[int]):
84
- """Click parameter type for MT5 timeframe values."""
85
-
86
- name = "TIMEFRAME"
87
-
88
- def convert(
89
- self,
90
- value: object,
91
- param: click.Parameter | None = None,
92
- ctx: click.Context | None = None,
93
- ) -> int:
94
- """Convert a string or integer value to a timeframe integer.
95
-
96
- Args:
97
- value: Raw value from the command line.
98
- param: Click parameter instance.
99
- ctx: Click context.
100
-
101
- Returns:
102
- Integer timeframe value.
103
- """
104
- try:
105
- return parse_timeframe(value)
106
- except ValueError as exc:
107
- self.fail(str(exc), param, ctx)
108
-
109
-
110
- class _TickFlagsType(click.ParamType[int]):
111
- """Click parameter type for MT5 tick copy flags."""
112
-
113
- name = "FLAGS"
114
-
115
- def convert(
116
- self,
117
- value: object,
118
- param: click.Parameter | None = None,
119
- ctx: click.Context | None = None,
120
- ) -> int:
121
- """Convert a string or integer value to a tick flags integer.
122
-
123
- Args:
124
- value: Raw value from the command line.
125
- param: Click parameter instance.
126
- ctx: Click context.
127
-
128
- Returns:
129
- Integer tick flag value.
130
- """
131
- try:
132
- return parse_tick_flags(value)
133
- except ValueError as exc:
134
- self.fail(str(exc), param, ctx)
135
-
136
-
137
- class _RequestType(click.ParamType[dict[str, Any]]):
138
- """Click parameter type for JSON order requests."""
139
-
140
- name = "REQUEST"
141
-
142
- def convert(
143
- self,
144
- value: object,
145
- param: click.Parameter | None = None,
146
- ctx: click.Context | None = None,
147
- ) -> dict[str, Any]:
148
- """Convert a raw CLI value to an order request dictionary.
149
-
150
- Args:
151
- value: Raw value from the command line.
152
- param: Click parameter instance.
153
- ctx: Click context.
154
-
155
- Returns:
156
- Parsed request dictionary.
157
- """
158
- try:
159
- return parse_request(str(value))
160
- except ValueError as exc:
161
- self.fail(str(exc), param, ctx)
162
-
163
-
164
- DATETIME_TYPE = _DateTimeType()
165
- TIMEFRAME_TYPE = _TimeframeType()
166
- TICK_FLAGS_TYPE = _TickFlagsType()
167
- REQUEST_TYPE = _RequestType()
168
-
169
48
 
170
49
  def _parse_datetime_parameter(value: str) -> datetime:
171
50
  try:
@@ -355,12 +355,14 @@ def _shutdown_client(client: Mt5DataClient, *, raise_on_error: bool) -> None:
355
355
  failure is raised as the stable normalized exception. When False (an
356
356
  initialization or session-body exception is already propagating), the
357
357
  failure is logged so the primary exception passes through unchanged.
358
- """ # noqa: DOC501
358
+
359
+ """
359
360
  try:
360
361
  client.shutdown()
361
362
  except Exception as exc:
362
363
  if raise_on_error:
363
- raise normalize_mt5_exception(exc) from exc
364
+ normalized = normalize_mt5_exception(exc)
365
+ raise normalized from exc
364
366
  logger.warning("MT5 shutdown failed during cleanup", exc_info=True)
365
367
 
366
368
 
@@ -385,7 +387,7 @@ def _connected_client(
385
387
  Yields:
386
388
  Connected ``Mt5DataClient`` instance.
387
389
 
388
- """ # noqa: DOC501
390
+ """
389
391
  client = (
390
392
  Mt5DataClient(config=config)
391
393
  if retry_count is None
@@ -395,7 +397,8 @@ def _connected_client(
395
397
  client.initialize_and_login_mt5()
396
398
  except Exception as exc:
397
399
  _shutdown_client(client, raise_on_error=False)
398
- raise normalize_mt5_exception(exc) from exc
400
+ normalized = normalize_mt5_exception(exc)
401
+ raise normalized from exc
399
402
  try:
400
403
  yield client
401
404
  except BaseException:
@@ -420,12 +423,14 @@ def _run_with_client(
420
423
 
421
424
  Returns:
422
425
  Value returned by ``fetch_fn``.
423
- """ # noqa: DOC501
426
+
427
+ """
424
428
  with _connected_client(config, retry_count=retry_count) as client:
425
429
  try:
426
430
  return fetch_fn(client)
427
431
  except Mt5RuntimeError as exc:
428
- raise normalize_mt5_exception(exc) from exc
432
+ normalized = normalize_mt5_exception(exc)
433
+ raise normalized from exc
429
434
 
430
435
 
431
436
  class _BaseMT5Client:
@@ -480,7 +485,8 @@ class _BaseMT5Client:
480
485
 
481
486
  Returns:
482
487
  This client instance.
483
- """ # noqa: DOC501
488
+
489
+ """
484
490
  if self._client is not None:
485
491
  return self
486
492
  client = Mt5DataClient(config=self._config, retry_count=self._retry_count)
@@ -488,7 +494,8 @@ class _BaseMT5Client:
488
494
  client.initialize_and_login_mt5()
489
495
  except Exception as exc:
490
496
  _shutdown_client(client, raise_on_error=False)
491
- raise normalize_mt5_exception(exc) from exc
497
+ normalized = normalize_mt5_exception(exc)
498
+ raise normalized from exc
492
499
  self._client = client
493
500
  self._owns_client = True # only set when this method created the client
494
501
  return self
@@ -2755,11 +2755,7 @@ class ThrottledHistoryUpdater:
2755
2755
  When ``suppress_errors`` is False, recoverable update failures
2756
2756
  propagate to the caller.
2757
2757
 
2758
- Raises:
2759
- Mt5ConnectionError: When ``suppress_errors`` is False and the
2760
- update fails with a recoverable MT5, SQLite, validation, or
2761
- I/O error.
2762
- """ # noqa: DOC502
2758
+ """
2763
2759
  if not self.should_update():
2764
2760
  return False
2765
2761
  try:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5cli"
3
- version = "1.2.3"
3
+ version = "1.2.4"
4
4
  description = "Generic MT5 data and execution infrastructure for Python applications"
5
5
  authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
6
6
  maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
@@ -11,7 +11,6 @@ requires-python = ">= 3.11, < 3.14"
11
11
  dependencies = [
12
12
  "pdmt5 >= 1.2.0",
13
13
  "pandas >= 2.2.2",
14
- "click >= 8.4",
15
14
  "typer >= 0.26.0",
16
15
  ]
17
16
  classifiers = [
@@ -66,67 +65,67 @@ preview = true
66
65
 
67
66
  [tool.ruff.lint]
68
67
  select = [
69
- "F", # Pyflakes (F)
70
- "E", # pycodestyle error (E)
71
- "W", # pycodestyle warning (W)
72
- "C90", # mccabe (C90)
73
- "I", # isort (I)
74
- "N", # pep8-naming (N)
75
- "D", # pydocstyle (D)
76
- "UP", # pyupgrade (UP)
77
- "S", # flake8-bandit (S)
78
- "B", # flake8-bugbear (B)
79
- "C4", # flake8-comprehensions (C4)
80
- "SIM", # flake8-simplify (SIM)
81
- "ARG", # flake8-unused-arguments (ARG)
82
- "PD", # pandas-vet (PD)
83
- "PLC", # Pylint convention (PLC)
84
- "PLE", # Pylint error (PLE)
85
- "PLR", # Pylint refactor (PLR)
86
- "PLW", # Pylint warning (PLW)
87
- "FLY", # flynt (FLY)
88
- "NPY", # NumPy-specific rules (NPY)
89
- "PERF", # Perflint (PERF)
90
- "FURB", # refurb (FURB)
91
- "RUF", # Ruff-specific rules (RUF)
92
- "YTT", # flake8-2020 (YTT)
93
- "ANN", # flake8-annotations (ANN)
94
- "ASYNC", # flake8-async (ASYNC)
95
- "BLE", # flake8-blind-except (BLE)
96
- "FBT", # flake8-boolean-trap (FBT)
97
- "A", # flake8-builtins (A)
98
- "COM", # flake8-commas (COM)
99
- "DTZ", # flake8-datetimez (DTZ)
100
- "T10", # flake8-debugger (T10)
101
- "DJ", # flake8-django (DJ)
102
- "EM", # flake8-errmsg (EM)
103
- "EXE", # flake8-executable (EXE)
104
- "FA", # flake8-future-annotations (FA)
105
- "ISC", # flake8-implicit-str-concat (ISC)
106
- "ICN", # flake8-import-conventions (ICN)
107
- "LOG", # flake8-logging (LOG)
108
- "G", # flake8-logging-format (G)
109
- "INP", # flake8-no-pep420 (INP)
110
- "PIE", # flake8-pie (PIE)
111
- "T20", # flake8-print (T20)
112
- "PYI", # flake8-pyi (PYI)
113
- "PT", # flake8-pytest-style (PT)
114
- "Q", # flake8-quotes (Q)
115
- "RSE", # flake8-raise (RSE)
116
- "SLF", # flake8-self (SLF)
117
- "SLOT", # flake8-slots (SLOT)
118
- "TID", # flake8-tidy-imports (TID)
119
- "TCH", # flake8-type-checking (TCH)
120
- "INT", # flake8-gettext (INT)
121
- "PTH", # flake8-use-pathlib (PTH)
122
- "TD", # flake8-todos (TD)
123
- "FIX", # flake8-fixme (FIX)
124
- "ERA", # eradicate (ERA)
125
- "PGH", # pygrep-hooks (PGH)
126
- "TRY", # tryceratops (TRY)
127
- "FAST", # FastAPI (FAST)
128
- "AIR", # Airflow (AIR)
129
- "DOC", # pydoclint (DOC)
68
+ "F", # Pyflakes (F)
69
+ "E", # pycodestyle error (E)
70
+ "W", # pycodestyle warning (W)
71
+ "C90", # mccabe (C90)
72
+ "I", # isort (I)
73
+ "N", # pep8-naming (N)
74
+ "D", # pydocstyle (D)
75
+ "UP", # pyupgrade (UP)
76
+ "S", # flake8-bandit (S)
77
+ "B", # flake8-bugbear (B)
78
+ "C4", # flake8-comprehensions (C4)
79
+ "SIM", # flake8-simplify (SIM)
80
+ "ARG", # flake8-unused-arguments (ARG)
81
+ "PD", # pandas-vet (PD)
82
+ "PLC", # Pylint convention (PLC)
83
+ "PLE", # Pylint error (PLE)
84
+ "PLR", # Pylint refactor (PLR)
85
+ "PLW", # Pylint warning (PLW)
86
+ "FLY", # flynt (FLY)
87
+ "NPY", # NumPy-specific rules (NPY)
88
+ "PERF", # Perflint (PERF)
89
+ "FURB", # refurb (FURB)
90
+ "RUF", # Ruff-specific rules (RUF)
91
+ "YTT", # flake8-2020 (YTT)
92
+ "ANN", # flake8-annotations (ANN)
93
+ "ASYNC", # flake8-async (ASYNC)
94
+ "BLE", # flake8-blind-except (BLE)
95
+ "FBT", # flake8-boolean-trap (FBT)
96
+ "A", # flake8-builtins (A)
97
+ "COM", # flake8-commas (COM)
98
+ "DTZ", # flake8-datetimez (DTZ)
99
+ "T10", # flake8-debugger (T10)
100
+ "DJ", # flake8-django (DJ)
101
+ "EM", # flake8-errmsg (EM)
102
+ "EXE", # flake8-executable (EXE)
103
+ "FA", # flake8-future-annotations (FA)
104
+ "ISC", # flake8-implicit-str-concat (ISC)
105
+ "ICN", # flake8-import-conventions (ICN)
106
+ "LOG", # flake8-logging (LOG)
107
+ "G", # flake8-logging-format (G)
108
+ "INP", # flake8-no-pep420 (INP)
109
+ "PIE", # flake8-pie (PIE)
110
+ "T20", # flake8-print (T20)
111
+ "PYI", # flake8-pyi (PYI)
112
+ "PT", # flake8-pytest-style (PT)
113
+ "Q", # flake8-quotes (Q)
114
+ "RSE", # flake8-raise (RSE)
115
+ "SLF", # flake8-self (SLF)
116
+ "SLOT", # flake8-slots (SLOT)
117
+ "TID", # flake8-tidy-imports (TID)
118
+ "TCH", # flake8-type-checking (TCH)
119
+ "INT", # flake8-gettext (INT)
120
+ "PTH", # flake8-use-pathlib (PTH)
121
+ "TD", # flake8-todos (TD)
122
+ "FIX", # flake8-fixme (FIX)
123
+ "ERA", # eradicate (ERA)
124
+ "PGH", # pygrep-hooks (PGH)
125
+ "TRY", # tryceratops (TRY)
126
+ "FAST", # FastAPI (FAST)
127
+ "AIR", # Airflow (AIR)
128
+ "DOC", # pydoclint (DOC)
130
129
  ]
131
130
  ignore = [
132
131
  "COM812", # missing-trailing-comma
@@ -137,7 +136,9 @@ ignore = [
137
136
  [tool.ruff.lint.per-file-ignores]
138
137
  # __all__ is derived from contract.STABLE_SDK_EXPORTS, which Ruff cannot
139
138
  # resolve statically; contract tests enforce that imports and exports match.
140
- "mt5cli/__init__.py" = ["F401"]
139
+ "mt5cli/__init__.py" = [
140
+ "F401", # Unused import (for __all__)
141
+ ]
141
142
  "tests/**/*.py" = [
142
143
  "DOC201", # Missing return documentation
143
144
  "DOC501", # Raised exception missing from docstring
@@ -8,11 +8,12 @@ from unittest.mock import MagicMock
8
8
 
9
9
  import pandas as pd
10
10
  import pytest
11
- from pytest_mock import MockerFixture # noqa: TC002
12
11
 
13
12
  if TYPE_CHECKING:
14
13
  from types import TracebackType
15
14
 
15
+ from pytest_mock import MockerFixture
16
+
16
17
  _DATAFRAME_METHODS = (
17
18
  "copy_rates_from_as_df",
18
19
  "copy_rates_from_pos_as_df",
@@ -12,17 +12,14 @@ from unittest.mock import MagicMock
12
12
 
13
13
  import pandas as pd
14
14
  import pytest
15
- from pytest_mock import MockerFixture # noqa: TC002
16
15
  from typer.testing import CliRunner
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from pathlib import Path
20
19
 
20
+ from pytest_mock import MockerFixture
21
+
21
22
  from mt5cli.cli import (
22
- DATETIME_TYPE, # type: ignore[reportPrivateUsage]
23
- REQUEST_TYPE, # type: ignore[reportPrivateUsage]
24
- TICK_FLAGS_TYPE, # type: ignore[reportPrivateUsage]
25
- TIMEFRAME_TYPE, # type: ignore[reportPrivateUsage]
26
23
  _execute_export, # type: ignore[reportPrivateUsage]
27
24
  _ExportContext, # type: ignore[reportPrivateUsage]
28
25
  _parse_datetime_parameter, # type: ignore[reportPrivateUsage]
@@ -2181,111 +2178,25 @@ class TestMain:
2181
2178
  mock_app.assert_called_once()
2182
2179
 
2183
2180
 
2184
- class TestDateTimeType:
2185
- """Tests for _DateTimeType."""
2186
-
2187
- def test_convert_string(self) -> None:
2188
- """Test converting a string to datetime."""
2189
- result = DATETIME_TYPE.convert("2024-06-15", None, None)
2190
- assert result == datetime(2024, 6, 15, tzinfo=UTC)
2191
-
2192
- def test_convert_datetime_passthrough(self) -> None:
2193
- """Test that datetime values pass through unchanged."""
2194
- dt = datetime(2024, 1, 1, tzinfo=UTC)
2195
- assert DATETIME_TYPE.convert(dt, None, None) is dt
2196
-
2197
- def test_convert_invalid(self) -> None:
2198
- """Test that invalid values raise BadParameter."""
2199
- with pytest.raises(Exception, match="Invalid datetime"):
2200
- DATETIME_TYPE.convert("bad", None, None)
2181
+ class TestTyperParameterParsers:
2182
+ """Tests for Typer parameter parser callbacks."""
2201
2183
 
2202
- def test_parser_invalid(self) -> None:
2203
- """Test that Typer receives a BadParameter with validation details."""
2184
+ def test_datetime_parser_invalid(self) -> None:
2185
+ """Test that invalid datetimes raise a Typer BadParameter."""
2204
2186
  with pytest.raises(Exception, match="Invalid datetime"):
2205
2187
  _parse_datetime_parameter("bad")
2206
2188
 
2207
-
2208
- class TestTimeframeType:
2209
- """Tests for _TimeframeType."""
2210
-
2211
- @pytest.mark.parametrize(
2212
- ("value", "expected"),
2213
- [("H1", 16385), (16385, 16385)],
2214
- ids=["string", "int"],
2215
- )
2216
- def test_convert_valid(self, value: str | int, expected: int) -> None:
2217
- """Test converting valid string and integer timeframe values."""
2218
- assert TIMEFRAME_TYPE.convert(value, None, None) == expected
2219
-
2220
- @pytest.mark.parametrize(
2221
- "value",
2222
- [42, "bad"],
2223
- ids=["unsupported-int", "invalid-string"],
2224
- )
2225
- def test_convert_invalid(self, value: object) -> None:
2226
- """Test that unsupported int and invalid string values raise BadParameter."""
2227
- with pytest.raises(Exception, match="Invalid timeframe"):
2228
- TIMEFRAME_TYPE.convert(value, None, None)
2229
-
2230
- @pytest.mark.parametrize("value", [True, False, None, 1.5])
2231
- def test_convert_invalid_types(self, value: object) -> None:
2232
- """Test that bool, float, and None values raise BadParameter."""
2233
- with pytest.raises(Exception, match="Invalid timeframe"):
2234
- TIMEFRAME_TYPE.convert(value, None, None)
2235
-
2236
- def test_parser_invalid(self) -> None:
2237
- """Test that Typer receives a BadParameter with validation details."""
2189
+ def test_timeframe_parser_invalid(self) -> None:
2190
+ """Test that invalid timeframes raise a Typer BadParameter."""
2238
2191
  with pytest.raises(Exception, match="Invalid timeframe"):
2239
2192
  _parse_timeframe_parameter("bad")
2240
2193
 
2241
-
2242
- class TestTickFlagsType:
2243
- """Tests for _TickFlagsType."""
2244
-
2245
- @pytest.mark.parametrize(
2246
- ("value", "expected"),
2247
- [("ALL", -1), (2, 2)],
2248
- ids=["string", "int"],
2249
- )
2250
- def test_convert_valid(self, value: str | int, expected: int) -> None:
2251
- """Test converting valid string and integer tick flag values."""
2252
- assert TICK_FLAGS_TYPE.convert(value, None, None) == expected
2253
-
2254
- @pytest.mark.parametrize(
2255
- "value",
2256
- [7, "bad"],
2257
- ids=["unsupported-int", "invalid-string"],
2258
- )
2259
- def test_convert_invalid(self, value: object) -> None:
2260
- """Test that unsupported int and invalid string values raise BadParameter."""
2261
- with pytest.raises(Exception, match="Invalid tick flags"):
2262
- TICK_FLAGS_TYPE.convert(value, None, None)
2263
-
2264
- @pytest.mark.parametrize("value", [True, False, None, 1.5])
2265
- def test_convert_invalid_types(self, value: object) -> None:
2266
- """Test that bool, float, and None values raise BadParameter."""
2267
- with pytest.raises(Exception, match="Invalid tick flags"):
2268
- TICK_FLAGS_TYPE.convert(value, None, None)
2269
-
2270
- def test_parser_invalid(self) -> None:
2271
- """Test that Typer receives a BadParameter with validation details."""
2194
+ def test_tick_flags_parser_invalid(self) -> None:
2195
+ """Test that invalid tick flags raise a Typer BadParameter."""
2272
2196
  with pytest.raises(Exception, match="Invalid tick flags"):
2273
2197
  _parse_tick_flags_parameter("bad")
2274
2198
 
2275
-
2276
- class TestRequestType:
2277
- """Tests for _RequestType."""
2278
-
2279
- def test_convert_string(self) -> None:
2280
- """Test converting a JSON string to a request dictionary."""
2281
- assert REQUEST_TYPE.convert('{"action": 1}', None, None) == {"action": 1}
2282
-
2283
- def test_convert_invalid(self) -> None:
2284
- """Test that invalid values raise BadParameter."""
2285
- with pytest.raises(Exception, match="Invalid JSON request"):
2286
- REQUEST_TYPE.convert("bad", None, None)
2287
-
2288
- def test_parser_invalid(self) -> None:
2289
- """Test that Typer receives a BadParameter with validation details."""
2199
+ def test_request_parser_invalid(self) -> None:
2200
+ """Test that invalid requests raise a Typer BadParameter."""
2290
2201
  with pytest.raises(Exception, match="Invalid JSON request"):
2291
2202
  _parse_request_parameter("bad")
@@ -24,7 +24,11 @@ from mt5cli.exceptions import Mt5ConnectionError
24
24
  from mt5cli.marketdata import (
25
25
  collect_latest_rates,
26
26
  copy_rates_range,
27
+ minimum_margins,
28
+ mt5_summary,
29
+ mt5_summary_as_df,
27
30
  recent_history_deals,
31
+ recent_ticks,
28
32
  )
29
33
 
30
34
  if TYPE_CHECKING:
@@ -774,8 +778,6 @@ class TestMT5ClientMethods:
774
778
  mock_client.account_info.return_value = account_info_value
775
779
  mock_client.symbols_total.return_value = 42
776
780
 
777
- from mt5cli.marketdata import mt5_summary # noqa: PLC0415
778
-
779
781
  assert mt5_summary() == expected
780
782
 
781
783
  def test_mt5_summary_as_df_stringifies_nested_values(
@@ -794,8 +796,6 @@ class TestMT5ClientMethods:
794
796
  )
795
797
  mock_client.symbols_total.return_value = 42
796
798
 
797
- from mt5cli.marketdata import mt5_summary_as_df # noqa: PLC0415
798
-
799
799
  result = mt5_summary_as_df()
800
800
 
801
801
  assert len(result) == 1
@@ -853,8 +853,6 @@ class TestRecentTicks:
853
853
  "bid": [1.0],
854
854
  })
855
855
  mocker.patch("mt5cli.client.Mt5DataClient", return_value=client)
856
- from mt5cli.marketdata import recent_ticks # noqa: PLC0415
857
-
858
856
  result = recent_ticks(
859
857
  "EURUSD",
860
858
  60,
@@ -955,8 +953,6 @@ class TestRecentTicks:
955
953
  "bid": [1.0, 1.1, 1.2],
956
954
  })
957
955
  mocker.patch("mt5cli.client.Mt5DataClient", return_value=client)
958
- from mt5cli.marketdata import recent_ticks # noqa: PLC0415
959
-
960
956
  result = recent_ticks(
961
957
  "EURUSD",
962
958
  60,
@@ -993,8 +989,6 @@ class TestMinimumMargins:
993
989
  client.mt5.ORDER_TYPE_BUY = 0
994
990
  client.mt5.ORDER_TYPE_SELL = 1
995
991
  mocker.patch("mt5cli.client.Mt5DataClient", return_value=client)
996
- from mt5cli.marketdata import minimum_margins # noqa: PLC0415
997
-
998
992
  result = minimum_margins("EURUSD", config=build_config(login=123))
999
993
 
1000
994
  pd.testing.assert_frame_equal(
@@ -4,13 +4,16 @@ from __future__ import annotations
4
4
 
5
5
  import inspect
6
6
  from datetime import UTC, datetime
7
+ from importlib import import_module
7
8
  from pathlib import Path
8
- from typing import Protocol
9
+ from typing import TYPE_CHECKING, Protocol
9
10
 
10
11
  import pandas as pd
11
12
  import pytest
12
13
  from pdmt5 import Mt5RuntimeError
13
- from pytest_mock import MockerFixture # noqa: TC002
14
+
15
+ if TYPE_CHECKING:
16
+ from pytest_mock import MockerFixture
14
17
 
15
18
  import mt5cli
16
19
  import mt5cli.cli
@@ -564,7 +567,7 @@ class TestSingleConnectionLifecycleContract:
564
567
  def test_sdk_compatibility_module_no_longer_exists(self) -> None:
565
568
  """The removed mt5cli.sdk module cannot be imported."""
566
569
  with pytest.raises(ModuleNotFoundError):
567
- import mt5cli.sdk # pyright: ignore[reportMissingImports,reportUnusedImport] # noqa: F401, PLC0415
570
+ import_module("mt5cli.sdk")
568
571
 
569
572
  def test_package_root_mt5_session_is_the_client_module_session(self) -> None:
570
573
  """The package-root mt5_session is the same object as mt5cli.client's."""
@@ -10,12 +10,13 @@ from unittest.mock import MagicMock
10
10
 
11
11
  import pandas as pd
12
12
  import pytest
13
- from pytest_mock import MockerFixture # noqa: TC002
14
13
 
15
14
  if TYPE_CHECKING:
16
15
  from collections.abc import Callable
17
16
  from pathlib import Path
18
17
 
18
+ from pytest_mock import MockerFixture
19
+
19
20
  from pdmt5 import TIMEFRAME_MAP, Mt5RuntimeError
20
21
 
21
22
  from mt5cli import history
@@ -2,11 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from typing import TYPE_CHECKING
5
6
  from unittest.mock import MagicMock
6
7
 
7
8
  import pytest
8
9
  from opentelemetry.sdk.metrics.export import InMemoryMetricReader
9
- from pytest_mock import MockerFixture # noqa: TC002
10
+
11
+ if TYPE_CHECKING:
12
+ from pytest_mock import MockerFixture
10
13
 
11
14
  from mt5cli.telemetry import (
12
15
  _Mt5Metrics, # type: ignore[reportPrivateUsage]
@@ -5,14 +5,16 @@ from __future__ import annotations
5
5
  import logging
6
6
  from datetime import UTC, datetime
7
7
  from types import SimpleNamespace
8
- from typing import Any, cast, get_args
8
+ from typing import TYPE_CHECKING, Any, cast, get_args
9
9
  from unittest.mock import MagicMock
10
10
 
11
11
  import pandas as pd
12
12
  import pytest
13
13
  from numpy import int64 as np_int64
14
14
  from pdmt5 import Mt5RuntimeError
15
- from pytest_mock import MockerFixture # noqa: TC002
15
+
16
+ if TYPE_CHECKING:
17
+ from pytest_mock import MockerFixture
16
18
 
17
19
  from mt5cli import trading
18
20
  from mt5cli.client import MT5Client
@@ -499,10 +499,9 @@ wheels = [
499
499
 
500
500
  [[package]]
501
501
  name = "mt5cli"
502
- version = "1.2.3"
502
+ version = "1.2.4"
503
503
  source = { editable = "." }
504
504
  dependencies = [
505
- { name = "click" },
506
505
  { name = "pandas" },
507
506
  { name = "pdmt5" },
508
507
  { name = "typer" },
@@ -537,7 +536,6 @@ dev = [
537
536
 
538
537
  [package.metadata]
539
538
  requires-dist = [
540
- { name = "click", specifier = ">=8.4" },
541
539
  { name = "opentelemetry-api", marker = "extra == 'otel'" },
542
540
  { name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'otel'" },
543
541
  { name = "opentelemetry-sdk", marker = "extra == 'otel'" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes