mt5api 0.0.4__tar.gz → 0.1.0__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 (109) hide show
  1. {mt5api-0.0.4 → mt5api-0.1.0}/AGENTS.md +4 -2
  2. {mt5api-0.0.4 → mt5api-0.1.0}/PKG-INFO +6 -7
  3. {mt5api-0.0.4 → mt5api-0.1.0}/README.md +5 -5
  4. {mt5api-0.0.4 → mt5api-0.1.0}/docs/api/deployment.md +0 -2
  5. {mt5api-0.0.4 → mt5api-0.1.0}/docs/api/rest-api.md +5 -11
  6. {mt5api-0.0.4 → mt5api-0.1.0}/docs/index.md +3 -3
  7. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/config.py +0 -22
  8. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/constants.py +0 -4
  9. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/main.py +0 -25
  10. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/middleware.py +1 -54
  11. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/market.py +2 -2
  12. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/trading.py +4 -4
  13. {mt5api-0.0.4 → mt5api-0.1.0}/pyproject.toml +1 -2
  14. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_config.py +0 -28
  15. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_middleware.py +0 -24
  16. {mt5api-0.0.4 → mt5api-0.1.0}/uv.lock +4 -101
  17. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/local-qa/SKILL.md +0 -0
  18. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
  19. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/mt5api/SKILL.md +0 -0
  20. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-analyze/SKILL.md +0 -0
  21. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-checklist/SKILL.md +0 -0
  22. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-clarify/SKILL.md +0 -0
  23. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-constitution/SKILL.md +0 -0
  24. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-implement/SKILL.md +0 -0
  25. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-plan/SKILL.md +0 -0
  26. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-specify/SKILL.md +0 -0
  27. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-tasks/SKILL.md +0 -0
  28. {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-taskstoissues/SKILL.md +0 -0
  29. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/agents/codex.md +0 -0
  30. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/agents/copilot.md +0 -0
  31. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.analyze.md +0 -0
  32. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.checklist.md +0 -0
  33. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.clarify.md +0 -0
  34. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.constitution.md +0 -0
  35. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.implement.md +0 -0
  36. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.plan.md +0 -0
  37. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.specify.md +0 -0
  38. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.tasks.md +0 -0
  39. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.taskstoissues.md +0 -0
  40. {mt5api-0.0.4 → mt5api-0.1.0}/.claude/settings.json +0 -0
  41. {mt5api-0.0.4 → mt5api-0.1.0}/.github/FUNDING.yml +0 -0
  42. {mt5api-0.0.4 → mt5api-0.1.0}/.github/dependabot.yml +0 -0
  43. {mt5api-0.0.4 → mt5api-0.1.0}/.github/renovate.json +0 -0
  44. {mt5api-0.0.4 → mt5api-0.1.0}/.github/workflows/ci.yml +0 -0
  45. {mt5api-0.0.4 → mt5api-0.1.0}/.github/workflows/claude-code.yml +0 -0
  46. {mt5api-0.0.4 → mt5api-0.1.0}/.gitignore +0 -0
  47. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/memory/constitution.md +0 -0
  48. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/check-prerequisites.sh +0 -0
  49. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/common.sh +0 -0
  50. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/create-new-feature.sh +0 -0
  51. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/setup-plan.sh +0 -0
  52. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/update-agent-context.sh +0 -0
  53. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/agent-file-template.md +0 -0
  54. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/checklist-template.md +0 -0
  55. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/plan-template.md +0 -0
  56. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/spec-template.md +0 -0
  57. {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/tasks-template.md +0 -0
  58. {mt5api-0.0.4 → mt5api-0.1.0}/CLAUDE.md +0 -0
  59. {mt5api-0.0.4 → mt5api-0.1.0}/LICENSE +0 -0
  60. {mt5api-0.0.4 → mt5api-0.1.0}/docs/api/index.md +0 -0
  61. {mt5api-0.0.4 → mt5api-0.1.0}/mkdocs.yml +0 -0
  62. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/__init__.py +0 -0
  63. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/__main__.py +0 -0
  64. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/auth.py +0 -0
  65. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/dependencies.py +0 -0
  66. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/formatters.py +0 -0
  67. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/models.py +0 -0
  68. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/__init__.py +0 -0
  69. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/account.py +0 -0
  70. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/calc.py +0 -0
  71. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/health.py +0 -0
  72. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/history.py +0 -0
  73. {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/symbols.py +0 -0
  74. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/contracts/openapi.yaml +0 -0
  75. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/data-model.md +0 -0
  76. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/plan.md +0 -0
  77. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/quickstart.md +0 -0
  78. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/research.md +0 -0
  79. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/spec.md +0 -0
  80. {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/tasks.md +0 -0
  81. {mt5api-0.0.4 → mt5api-0.1.0}/specs/042-mt5-core-client/spec.md +0 -0
  82. {mt5api-0.0.4 → mt5api-0.1.0}/specs/043-mt5-dataframe-client/spec.md +0 -0
  83. {mt5api-0.0.4 → mt5api-0.1.0}/specs/044-mt5-trading-client/spec.md +0 -0
  84. {mt5api-0.0.4 → mt5api-0.1.0}/specs/045-mt5-utils/spec.md +0 -0
  85. {mt5api-0.0.4 → mt5api-0.1.0}/specs/046-mt5-api-service/spec.md +0 -0
  86. {mt5api-0.0.4 → mt5api-0.1.0}/specs/047-api-auth-rate-limit/spec.md +0 -0
  87. {mt5api-0.0.4 → mt5api-0.1.0}/specs/048-api-response-format/spec.md +0 -0
  88. {mt5api-0.0.4 → mt5api-0.1.0}/specs/049-api-routing-models/spec.md +0 -0
  89. {mt5api-0.0.4 → mt5api-0.1.0}/specs/050-api-runtime-deploy/spec.md +0 -0
  90. {mt5api-0.0.4 → mt5api-0.1.0}/specs/051-api-error-logging/spec.md +0 -0
  91. {mt5api-0.0.4 → mt5api-0.1.0}/specs/052-api-dependencies/spec.md +0 -0
  92. {mt5api-0.0.4 → mt5api-0.1.0}/tests/__init__.py +0 -0
  93. {mt5api-0.0.4 → mt5api-0.1.0}/tests/conftest.py +0 -0
  94. {mt5api-0.0.4 → mt5api-0.1.0}/tests/mt5_constants.py +0 -0
  95. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_account.py +0 -0
  96. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_auth.py +0 -0
  97. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_calc.py +0 -0
  98. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_cli.py +0 -0
  99. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_dependencies.py +0 -0
  100. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_formatters.py +0 -0
  101. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_health.py +0 -0
  102. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_history.py +0 -0
  103. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_integration.py +0 -0
  104. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_lifespan.py +0 -0
  105. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_main.py +0 -0
  106. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_market.py +0 -0
  107. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_models.py +0 -0
  108. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_symbols.py +0 -0
  109. {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_trading.py +0 -0
@@ -31,7 +31,7 @@
31
31
  ### Package Structure
32
32
 
33
33
  - `mt5api/`: Main FastAPI package.
34
- - `main.py`: App wiring, lifespan handling, middleware, CORS, and router registration.
34
+ - `main.py`: App wiring, lifespan handling, middleware, and router registration.
35
35
  - `__main__.py`: CLI entry point for launching the API from environment-backed configuration.
36
36
  - `config.py`: Environment-backed configuration normalization and validation.
37
37
  - `auth.py`: Optional API key authentication helpers.
@@ -50,6 +50,8 @@
50
50
  - Comprehensive linting with 35+ rule categories (ruff)
51
51
  - Test coverage tracking with 100% (pytest-cov)
52
52
  - Parametrized tests for input/result matrices using `pytest.mark.parametrize` (pytest)
53
+ - Test doubles (mocks, stubs) using `pytest_mock` for external dependencies (pytest-mock)
54
+ - Pydantic models for data validation and configuration
53
55
 
54
56
  ## Documentation Workflow
55
57
 
@@ -66,7 +68,7 @@
66
68
  ## Security & Configuration Tips
67
69
 
68
70
  - Do not commit real MT5 credentials or API keys.
69
- - Configure `MT5API_SECRET_KEY`, `MT5API_RATE_LIMIT`, `MT5API_CORS_ORIGINS`, `MT5API_ROUTER_PREFIX`, and `MT5API_LOG_LEVEL` through environment variables.
71
+ - Configure `MT5API_SECRET_KEY`, `MT5API_ROUTER_PREFIX`, and `MT5API_LOG_LEVEL` through environment variables.
70
72
  - Keep runtime configuration in the environment instead of hardcoding deployment values.
71
73
  - Authentication mode is fixed at process startup; cover both authenticated and unauthenticated behavior when changing auth-related code.
72
74
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mt5api
3
- Version: 0.0.4
3
+ Version: 0.1.0
4
4
  Summary: MetaTrader 5 REST API
5
5
  Project-URL: Repository, https://github.com/dceoy/mt5api.git
6
6
  Author-email: dceoy <dceoy@users.noreply.github.com>
@@ -24,7 +24,6 @@ Requires-Dist: pdmt5>=0.2.1
24
24
  Requires-Dist: pyarrow>=18.0.0
25
25
  Requires-Dist: python-jose[cryptography]>=3.3.0
26
26
  Requires-Dist: python-multipart>=0.0.9
27
- Requires-Dist: slowapi>=0.1.9
28
27
  Requires-Dist: uvicorn[standard]>=0.32.0
29
28
  Description-Content-Type: text/markdown
30
29
 
@@ -36,8 +35,8 @@ MetaTrader 5 REST API
36
35
 
37
36
  mt5api exposes MT5 market data, account info, trading history, and trading
38
37
  operations over HTTP. It uses the [`pdmt5`](https://github.com/dceoy/pdmt5)
39
- client internally and adds optional API-key auth, rate limiting, and
40
- JSON/Parquet response formatting.
38
+ client internally and adds optional API-key auth and JSON/Parquet response
39
+ formatting.
41
40
 
42
41
  The API server must run on Windows. The `MetaTrader5` Python package used by
43
42
  `pdmt5` is supported only on Windows, so you must host `mt5api` on a Windows
@@ -52,7 +51,7 @@ graph TB
52
51
 
53
52
  subgraph "Windows Host"
54
53
  subgraph "FastAPI Application"
55
- Middleware["Middleware Stack<br/>CORS · Logging · Error Handler · Rate Limiter"]
54
+ Middleware["Middleware Stack<br/>Logging · Error Handler"]
56
55
  Routers["Routers<br/>health · symbols · market · account · history · calc · trading"]
57
56
  Auth["API Key Security Dependency<br/>Security(api_key_header) · verify_api_key"]
58
57
  Deps["FastAPI Dependencies<br/>MT5 Client Singleton · Format Negotiation"]
@@ -75,8 +74,8 @@ graph TB
75
74
  - REST endpoints for symbols, market data, account info, orders, history,
76
75
  calculations, and trading operations
77
76
  - JSON and Apache Parquet responses (content negotiation)
78
- - Optional API key authentication with per-minute rate limiting
79
- - Structured JSON logging and configurable CORS
77
+ - Optional API key authentication
78
+ - Structured JSON logging
80
79
  - OpenAPI/Swagger docs built into the API
81
80
 
82
81
  ## Requirements
@@ -6,8 +6,8 @@ MetaTrader 5 REST API
6
6
 
7
7
  mt5api exposes MT5 market data, account info, trading history, and trading
8
8
  operations over HTTP. It uses the [`pdmt5`](https://github.com/dceoy/pdmt5)
9
- client internally and adds optional API-key auth, rate limiting, and
10
- JSON/Parquet response formatting.
9
+ client internally and adds optional API-key auth and JSON/Parquet response
10
+ formatting.
11
11
 
12
12
  The API server must run on Windows. The `MetaTrader5` Python package used by
13
13
  `pdmt5` is supported only on Windows, so you must host `mt5api` on a Windows
@@ -22,7 +22,7 @@ graph TB
22
22
 
23
23
  subgraph "Windows Host"
24
24
  subgraph "FastAPI Application"
25
- Middleware["Middleware Stack<br/>CORS · Logging · Error Handler · Rate Limiter"]
25
+ Middleware["Middleware Stack<br/>Logging · Error Handler"]
26
26
  Routers["Routers<br/>health · symbols · market · account · history · calc · trading"]
27
27
  Auth["API Key Security Dependency<br/>Security(api_key_header) · verify_api_key"]
28
28
  Deps["FastAPI Dependencies<br/>MT5 Client Singleton · Format Negotiation"]
@@ -45,8 +45,8 @@ graph TB
45
45
  - REST endpoints for symbols, market data, account info, orders, history,
46
46
  calculations, and trading operations
47
47
  - JSON and Apache Parquet responses (content negotiation)
48
- - Optional API key authentication with per-minute rate limiting
49
- - Structured JSON logging and configurable CORS
48
+ - Optional API key authentication
49
+ - Structured JSON logging
50
50
  - OpenAPI/Swagger docs built into the API
51
51
 
52
52
  ## Requirements
@@ -36,9 +36,7 @@ nssm install mt5api
36
36
  # Optional: set MT5API_SECRET_KEY only when you want to require X-API-Key headers.
37
37
  MT5API_SECRET_KEY=your-secret-api-key
38
38
  MT5API_LOG_LEVEL=INFO
39
- MT5API_RATE_LIMIT=100
40
39
  MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS=100
41
- MT5API_CORS_ORIGINS=*
42
40
  MT5API_ROUTER_PREFIX=/api/v1
43
41
  ```
44
42
 
@@ -30,9 +30,7 @@ Set the optional API key and other limits via environment variables:
30
30
  ```powershell
31
31
  $env:MT5API_SECRET_KEY = "your-secret-api-key" # Optional: omit to disable auth
32
32
  $env:MT5API_LOG_LEVEL = "INFO"
33
- $env:MT5API_RATE_LIMIT = "100"
34
33
  $env:MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS = "100"
35
- $env:MT5API_CORS_ORIGINS = "*"
36
34
  $env:MT5API_ROUTER_PREFIX = "/api/v1" # Optional: omit for root-level routes
37
35
  ```
38
36
 
@@ -70,10 +68,7 @@ disabled and those endpoints are accessible without authorization.
70
68
  curl -H "X-API-Key: your-secret-api-key" "http://windows-host:8000/symbols"
71
69
  ```
72
70
 
73
- ## Rate Limiting
74
-
75
- Rate limiting uses `slowapi` with a default limit of `100/minute`. Set
76
- `MT5API_RATE_LIMIT` to an integer for a different per-minute cap.
71
+ ## Subscription Limits
77
72
 
78
73
  Active market-book subscriptions are capped at `100` symbols by default. Set
79
74
  `MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS` to a positive integer to adjust that
@@ -121,9 +116,9 @@ If `MT5API_ROUTER_PREFIX` is configured, prepend it to each API route below.
121
116
  - `GET /rates/range` (`symbol`, `timeframe`, `date_from`, `date_to`, `format`)
122
117
  - `GET /ticks/from` (`symbol`, `date_from`, `count`, `flags`, `format`)
123
118
  - `GET /ticks/range` (`symbol`, `date_from`, `date_to`, `flags`, `format`)
124
- - `GET /market-book/{symbol}` (`format`) — Market depth (DOM)
125
- - `POST /market-book/{symbol}/subscribe` — Subscribe to DOM events
126
- - `POST /market-book/{symbol}/unsubscribe` — Unsubscribe from DOM events
119
+ - `GET /market-book/{symbol}` (`format`) — Market depth (DOM) *(experimental)*
120
+ - `POST /market-book/{symbol}/subscribe` — Subscribe to DOM events *(experimental)*
121
+ - `POST /market-book/{symbol}/unsubscribe` — Unsubscribe from DOM events *(experimental)*
127
122
 
128
123
  ### Calculations
129
124
 
@@ -315,9 +310,8 @@ Errors follow RFC 7807 Problem Details:
315
310
  Minimum security posture for deployments:
316
311
 
317
312
  - Set `MT5API_SECRET_KEY` to enable API key authentication when needed
318
- - Rate limiting enabled (`MT5API_RATE_LIMIT`)
313
+ - Configure rate limiting at the reverse proxy or API gateway level
319
314
  - Run behind HTTPS in production
320
- - Restrict CORS origins (`MT5API_CORS_ORIGINS`) for public deployments
321
315
  - Restrict access to operational endpoints (`/order/check`,
322
316
  `/symbols/{symbol}/select`, `/market-book/{symbol}/subscribe`,
323
317
  `/market-book/{symbol}/unsubscribe`) to trusted clients only
@@ -7,7 +7,7 @@ trading operations.
7
7
 
8
8
  mt5api exposes MT5 data and trading operations over HTTP using FastAPI. It
9
9
  relies on the underlying MT5 client library for connectivity and adds optional
10
- authentication, rate limiting, and response formatting suitable for analytics
10
+ authentication and response formatting suitable for analytics
11
11
  workflows.
12
12
 
13
13
  The API server must run on Windows because the `MetaTrader5` Python package is
@@ -19,8 +19,8 @@ logged in. API clients can connect from any operating system.
19
19
  - REST endpoints for symbols, market data, account info, orders, history,
20
20
  calculations, and trading operations
21
21
  - JSON and Apache Parquet responses
22
- - Optional API key authentication and rate limiting
23
- - Structured JSON logging and configurable CORS
22
+ - Optional API key authentication
23
+ - Structured JSON logging
24
24
  - OpenAPI/Swagger docs built in
25
25
 
26
26
  ## Requirements
@@ -6,18 +6,14 @@ import os
6
6
  import re
7
7
 
8
8
  from .constants import (
9
- DEFAULT_API_CORS_ORIGINS,
10
9
  DEFAULT_API_HOST,
11
10
  DEFAULT_API_LOG_LEVEL,
12
- DEFAULT_API_RATE_LIMIT,
13
11
  DEFAULT_API_ROUTER_PREFIX,
14
12
  DEFAULT_MAX_MARKET_BOOK_SUBSCRIPTIONS,
15
- ENV_MT5API_CORS_ORIGINS,
16
13
  ENV_MT5API_HOST,
17
14
  ENV_MT5API_LOG_LEVEL,
18
15
  ENV_MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS,
19
16
  ENV_MT5API_PORT,
20
- ENV_MT5API_RATE_LIMIT,
21
17
  ENV_MT5API_ROUTER_PREFIX,
22
18
  ENV_MT5API_SECRET_KEY,
23
19
  )
@@ -81,24 +77,6 @@ def get_configured_api_log_level() -> str:
81
77
  return os.getenv(ENV_MT5API_LOG_LEVEL, DEFAULT_API_LOG_LEVEL)
82
78
 
83
79
 
84
- def get_configured_api_rate_limit() -> str:
85
- """Get the configured API rate limit string.
86
-
87
- Returns:
88
- Raw per-minute rate-limit string from configuration.
89
- """
90
- return os.getenv(ENV_MT5API_RATE_LIMIT, str(DEFAULT_API_RATE_LIMIT))
91
-
92
-
93
- def get_configured_api_cors_origins() -> str:
94
- """Get the configured CORS origins string.
95
-
96
- Returns:
97
- Raw CORS origins configuration string.
98
- """
99
- return os.getenv(ENV_MT5API_CORS_ORIGINS, DEFAULT_API_CORS_ORIGINS)
100
-
101
-
102
80
  def get_configured_api_router_prefix() -> str:
103
81
  """Get the configured API router prefix.
104
82
 
@@ -20,8 +20,6 @@ MAX_MARKET_BOOK_SUBSCRIPTIONS_STATE_KEY = "max_market_book_subscriptions"
20
20
  ENV_MT5API_HOST = "MT5API_HOST"
21
21
  ENV_MT5API_PORT = "MT5API_PORT"
22
22
  ENV_MT5API_LOG_LEVEL = "MT5API_LOG_LEVEL"
23
- ENV_MT5API_RATE_LIMIT = "MT5API_RATE_LIMIT"
24
- ENV_MT5API_CORS_ORIGINS = "MT5API_CORS_ORIGINS"
25
23
  ENV_MT5API_ROUTER_PREFIX = "MT5API_ROUTER_PREFIX"
26
24
  ENV_MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS = "MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS"
27
25
  ENV_MT5API_SECRET_KEY = "MT5API_SECRET_KEY" # noqa: S105
@@ -29,8 +27,6 @@ ENV_MT5API_SECRET_KEY = "MT5API_SECRET_KEY" # noqa: S105
29
27
  DEFAULT_API_HOST = "0.0.0.0" # noqa: S104
30
28
  DEFAULT_API_PORT = 8000
31
29
  DEFAULT_API_LOG_LEVEL = "INFO"
32
- DEFAULT_API_RATE_LIMIT = 100
33
- DEFAULT_API_CORS_ORIGINS = "*"
34
30
  DEFAULT_API_ROUTER_PREFIX = ""
35
31
  DEFAULT_MAX_MARKET_BOOK_SUBSCRIPTIONS = 100
36
32
  MAX_API_PORT = 65535
@@ -10,11 +10,9 @@ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from fastapi import FastAPI
12
12
  from fastapi.openapi.utils import get_openapi
13
- from starlette.middleware.cors import CORSMiddleware
14
13
 
15
14
  from .auth import is_auth_enabled
16
15
  from .config import (
17
- get_configured_api_cors_origins,
18
16
  get_configured_api_log_level,
19
17
  get_configured_api_router_prefix,
20
18
  get_configured_max_market_book_subscriptions,
@@ -28,7 +26,6 @@ from .constants import (
28
26
  API_REDOC_URL,
29
27
  API_TITLE,
30
28
  API_VERSION,
31
- DEFAULT_API_CORS_ORIGINS,
32
29
  MARKET_BOOK_CLEANUP_CLIENT_STATE_KEY,
33
30
  MAX_MARKET_BOOK_SUBSCRIPTIONS_STATE_KEY,
34
31
  )
@@ -70,19 +67,6 @@ def _configure_logging() -> None:
70
67
  root_logger.addHandler(handler)
71
68
 
72
69
 
73
- def _get_cors_origins() -> list[str]:
74
- """Get CORS origins from environment.
75
-
76
- Returns:
77
- List of allowed origins.
78
- """
79
- raw_origins = get_configured_api_cors_origins()
80
- if raw_origins.strip() == DEFAULT_API_CORS_ORIGINS:
81
- return [DEFAULT_API_CORS_ORIGINS]
82
-
83
- return [origin.strip() for origin in raw_origins.split(",") if origin.strip()]
84
-
85
-
86
70
  def _strip_auth_from_openapi(openapi_schema: dict[str, Any]) -> None:
87
71
  """Remove API key requirements from OpenAPI when auth is disabled."""
88
72
  openapi_schema.pop("security", None)
@@ -212,15 +196,6 @@ app = FastAPI(
212
196
  )
213
197
  app.openapi = _custom_openapi
214
198
 
215
- # Add CORS middleware
216
- app.add_middleware(
217
- CORSMiddleware,
218
- allow_origins=_get_cors_origins(),
219
- allow_credentials=True,
220
- allow_methods=["*"],
221
- allow_headers=["*"],
222
- )
223
-
224
199
  # Add middleware
225
200
  add_middleware(app)
226
201
 
@@ -1,4 +1,4 @@
1
- """Error handling, logging, and rate limiting middleware."""
1
+ """Error handling and logging middleware."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -10,13 +10,7 @@ from fastapi import Request, status
10
10
  from fastapi.responses import JSONResponse
11
11
  from pdmt5.mt5 import Mt5RuntimeError
12
12
  from pydantic import ValidationError
13
- from slowapi import Limiter
14
- from slowapi.errors import RateLimitExceeded
15
- from slowapi.middleware import SlowAPIMiddleware
16
- from slowapi.util import get_remote_address
17
13
 
18
- from .config import get_configured_api_rate_limit
19
- from .constants import DEFAULT_API_RATE_LIMIT
20
14
  from .models import ErrorResponse
21
15
 
22
16
  if TYPE_CHECKING:
@@ -168,58 +162,11 @@ async def logging_middleware(
168
162
  return response
169
163
 
170
164
 
171
- def _build_default_rate_limit() -> str:
172
- """Build the default rate limit string from environment config.
173
-
174
- Returns:
175
- Default rate limit string in slowapi format.
176
- """
177
- raw_limit = get_configured_api_rate_limit()
178
-
179
- try:
180
- limit_value = max(1, int(raw_limit))
181
- except ValueError:
182
- limit_value = DEFAULT_API_RATE_LIMIT
183
-
184
- return f"{limit_value}/minute"
185
-
186
-
187
165
  def add_middleware(app: FastAPI) -> None:
188
166
  """Add middleware and error handlers to the FastAPI application.
189
167
 
190
168
  Args:
191
169
  app: FastAPI application instance.
192
170
  """
193
- # Configure rate limiting
194
- limiter = Limiter(
195
- key_func=get_remote_address,
196
- default_limits=[_build_default_rate_limit()],
197
- )
198
- app.state.limiter = limiter
199
- app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
200
- app.add_middleware(SlowAPIMiddleware)
201
-
202
- # Add custom middleware
203
171
  app.middleware("http")(error_handler_middleware)
204
172
  app.middleware("http")(logging_middleware)
205
-
206
-
207
- def _rate_limit_exceeded_handler(
208
- request: Request, # noqa: ARG001
209
- exc: Exception, # noqa: ARG001
210
- ) -> JSONResponse:
211
- """Handle rate limiting errors.
212
-
213
- Returns:
214
- JSON response describing the rate limit error.
215
- """
216
- return JSONResponse(
217
- status_code=status.HTTP_429_TOO_MANY_REQUESTS,
218
- content={
219
- "type": "/errors/rate-limit",
220
- "title": "Rate Limit Exceeded",
221
- "status": status.HTTP_429_TOO_MANY_REQUESTS,
222
- "detail": "Too many requests. Please slow down.",
223
- "instance": None,
224
- },
225
- )
@@ -167,8 +167,8 @@ async def get_ticks_range(
167
167
  @router.get(
168
168
  "/market-book/{symbol}",
169
169
  response_model=DataResponse,
170
- summary="Get market book",
171
- description="Get market depth (DOM) for a symbol",
170
+ summary="Get market book (experimental)",
171
+ description="**Experimental.** Get market depth (DOM) for a symbol",
172
172
  )
173
173
  async def get_market_book(
174
174
  mt5_client: Annotated[Mt5DataClient, Depends(get_mt5_client)],
@@ -139,8 +139,8 @@ async def post_symbol_select(
139
139
  @router.post(
140
140
  "/market-book/{symbol}/subscribe",
141
141
  response_model=DataResponse,
142
- summary="Subscribe to market depth",
143
- description="Subscribe to Market Depth change events for a symbol",
142
+ summary="Subscribe to market depth (experimental)",
143
+ description="**Experimental.** Subscribe to Market Depth events for a symbol",
144
144
  )
145
145
  async def post_market_book_subscribe(
146
146
  app_request: Request,
@@ -188,8 +188,8 @@ async def post_market_book_subscribe(
188
188
  @router.post(
189
189
  "/market-book/{symbol}/unsubscribe",
190
190
  response_model=DataResponse,
191
- summary="Unsubscribe from market depth",
192
- description="Cancel Market Depth subscription for a symbol",
191
+ summary="Unsubscribe from market depth (experimental)",
192
+ description="**Experimental.** Cancel Market Depth subscription for a symbol",
193
193
  )
194
194
  async def post_market_book_unsubscribe(
195
195
  app_request: Request,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mt5api"
3
- version = "0.0.4"
3
+ version = "0.1.0"
4
4
  description = "MetaTrader 5 REST API"
5
5
  authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
6
6
  maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
@@ -17,7 +17,6 @@ dependencies = [
17
17
  "python-multipart >= 0.0.9",
18
18
  "python-jose[cryptography] >= 3.3.0",
19
19
  "passlib[bcrypt] >= 1.7.4",
20
- "slowapi >= 0.1.9",
21
20
  "httpx >= 0.27.0",
22
21
  ]
23
22
  classifiers = [
@@ -8,40 +8,12 @@ import pytest
8
8
 
9
9
  from mt5api.constants import (
10
10
  DEFAULT_MAX_MARKET_BOOK_SUBSCRIPTIONS,
11
- ENV_MT5API_CORS_ORIGINS,
12
11
  ENV_MT5API_MAX_MARKET_BOOK_SUBSCRIPTIONS,
13
- ENV_MT5API_RATE_LIMIT,
14
12
  ENV_MT5API_ROUTER_PREFIX,
15
13
  ENV_MT5API_SECRET_KEY,
16
14
  )
17
15
 
18
16
 
19
- def test_get_cors_origins_parses_list(monkeypatch: pytest.MonkeyPatch) -> None:
20
- """CORS origins should split on commas and trim whitespace."""
21
- monkeypatch.setenv(ENV_MT5API_CORS_ORIGINS, "https://a.example, https://b.example")
22
-
23
- from mt5api import main # noqa: PLC0415
24
-
25
- assert main._get_cors_origins() == [ # pyright: ignore[reportPrivateUsage]
26
- "https://a.example",
27
- "https://b.example",
28
- ]
29
-
30
-
31
- def test_build_default_rate_limit_handles_invalid_value(
32
- monkeypatch: pytest.MonkeyPatch,
33
- ) -> None:
34
- """Invalid rate limit values should default to 100/minute."""
35
- monkeypatch.setenv(ENV_MT5API_RATE_LIMIT, "not-a-number")
36
-
37
- from mt5api import middleware # noqa: PLC0415
38
-
39
- assert (
40
- middleware._build_default_rate_limit() # pyright: ignore[reportPrivateUsage]
41
- == "100/minute"
42
- )
43
-
44
-
45
17
  @pytest.mark.parametrize(
46
18
  ("raw_prefix", "expected_prefix"),
47
19
  [
@@ -11,14 +11,11 @@ from fastapi.testclient import TestClient
11
11
  from pdmt5.mt5 import Mt5RuntimeError
12
12
  from pydantic import BaseModel
13
13
 
14
- from mt5api.constants import ENV_MT5API_RATE_LIMIT
15
14
  from mt5api.middleware import _create_error_response, add_middleware
16
15
 
17
16
  if TYPE_CHECKING:
18
17
  from collections.abc import Callable
19
18
 
20
- import pytest
21
-
22
19
 
23
20
  def _create_app(handler: Callable[[], object]) -> FastAPI:
24
21
  app = FastAPI()
@@ -148,24 +145,3 @@ def test_logging_middleware_adds_process_time_header() -> None:
148
145
  response = client.get("/boom")
149
146
 
150
147
  assert "X-Process-Time" in response.headers
151
-
152
-
153
- def test_rate_limiting_enforced(monkeypatch: pytest.MonkeyPatch) -> None:
154
- """Test rate limiting returns 429 when limit exceeded."""
155
- monkeypatch.setenv(ENV_MT5API_RATE_LIMIT, "1")
156
-
157
- app = FastAPI()
158
- add_middleware(app)
159
-
160
- def limited() -> dict[str, str]:
161
- return {"status": "ok"}
162
-
163
- app.get("/limited")(limited)
164
-
165
- client = TestClient(app)
166
-
167
- first = client.get("/limited")
168
- second = client.get("/limited")
169
-
170
- assert first.status_code == 200
171
- assert second.status_code == 429
@@ -366,18 +366,6 @@ wheels = [
366
366
  { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" },
367
367
  ]
368
368
 
369
- [[package]]
370
- name = "deprecated"
371
- version = "1.3.1"
372
- source = { registry = "https://pypi.org/simple" }
373
- dependencies = [
374
- { name = "wrapt" },
375
- ]
376
- sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" }
377
- wheels = [
378
- { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
379
- ]
380
-
381
369
  [[package]]
382
370
  name = "ecdsa"
383
371
  version = "0.19.1"
@@ -525,20 +513,6 @@ wheels = [
525
513
  { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
526
514
  ]
527
515
 
528
- [[package]]
529
- name = "limits"
530
- version = "5.6.0"
531
- source = { registry = "https://pypi.org/simple" }
532
- dependencies = [
533
- { name = "deprecated" },
534
- { name = "packaging" },
535
- { name = "typing-extensions" },
536
- ]
537
- sdist = { url = "https://files.pythonhosted.org/packages/bb/e5/c968d43a65128cd54fb685f257aafb90cd5e4e1c67d084a58f0e4cbed557/limits-5.6.0.tar.gz", hash = "sha256:807fac75755e73912e894fdd61e2838de574c5721876a19f7ab454ae1fffb4b5", size = 182984, upload-time = "2025-09-29T17:15:22.689Z" }
538
- wheels = [
539
- { url = "https://files.pythonhosted.org/packages/40/96/4fcd44aed47b8fcc457653b12915fcad192cd646510ef3f29fd216f4b0ab/limits-5.6.0-py3-none-any.whl", hash = "sha256:b585c2104274528536a5b68864ec3835602b3c4a802cd6aa0b07419798394021", size = 60604, upload-time = "2025-09-29T17:15:18.419Z" },
540
- ]
541
-
542
516
  [[package]]
543
517
  name = "markdown"
544
518
  version = "3.10.1"
@@ -743,7 +717,7 @@ wheels = [
743
717
 
744
718
  [[package]]
745
719
  name = "mt5api"
746
- version = "0.0.4"
720
+ version = "0.1.0"
747
721
  source = { editable = "." }
748
722
  dependencies = [
749
723
  { name = "fastapi" },
@@ -754,7 +728,6 @@ dependencies = [
754
728
  { name = "pyarrow" },
755
729
  { name = "python-jose", extra = ["cryptography"] },
756
730
  { name = "python-multipart" },
757
- { name = "slowapi" },
758
731
  { name = "uvicorn", extra = ["standard"] },
759
732
  ]
760
733
 
@@ -785,7 +758,6 @@ requires-dist = [
785
758
  { name = "pyarrow", specifier = ">=18.0.0" },
786
759
  { name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0" },
787
760
  { name = "python-multipart", specifier = ">=0.0.9" },
788
- { name = "slowapi", specifier = ">=0.1.9" },
789
761
  { name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" },
790
762
  ]
791
763
 
@@ -1041,11 +1013,11 @@ wheels = [
1041
1013
 
1042
1014
  [[package]]
1043
1015
  name = "pyasn1"
1044
- version = "0.6.2"
1016
+ version = "0.6.3"
1045
1017
  source = { registry = "https://pypi.org/simple" }
1046
- sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" }
1018
+ sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
1047
1019
  wheels = [
1048
- { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" },
1020
+ { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
1049
1021
  ]
1050
1022
 
1051
1023
  [[package]]
@@ -1391,18 +1363,6 @@ wheels = [
1391
1363
  { 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" },
1392
1364
  ]
1393
1365
 
1394
- [[package]]
1395
- name = "slowapi"
1396
- version = "0.1.9"
1397
- source = { registry = "https://pypi.org/simple" }
1398
- dependencies = [
1399
- { name = "limits" },
1400
- ]
1401
- sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028, upload-time = "2024-02-05T12:11:52.13Z" }
1402
- wheels = [
1403
- { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670, upload-time = "2024-02-05T12:11:50.898Z" },
1404
- ]
1405
-
1406
1366
  [[package]]
1407
1367
  name = "starlette"
1408
1368
  version = "0.50.0"
@@ -1720,60 +1680,3 @@ wheels = [
1720
1680
  { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
1721
1681
  { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
1722
1682
  ]
1723
-
1724
- [[package]]
1725
- name = "wrapt"
1726
- version = "2.0.1"
1727
- source = { registry = "https://pypi.org/simple" }
1728
- sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" }
1729
- wheels = [
1730
- { url = "https://files.pythonhosted.org/packages/98/60/553997acf3939079dab022e37b67b1904b5b0cc235503226898ba573b10c/wrapt-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e17283f533a0d24d6e5429a7d11f250a58d28b4ae5186f8f47853e3e70d2590", size = 77480, upload-time = "2025-11-07T00:43:30.573Z" },
1731
- { url = "https://files.pythonhosted.org/packages/2d/50/e5b3d30895d77c52105c6d5cbf94d5b38e2a3dd4a53d22d246670da98f7c/wrapt-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85df8d92158cb8f3965aecc27cf821461bb5f40b450b03facc5d9f0d4d6ddec6", size = 60690, upload-time = "2025-11-07T00:43:31.594Z" },
1732
- { url = "https://files.pythonhosted.org/packages/f0/40/660b2898703e5cbbb43db10cdefcc294274458c3ca4c68637c2b99371507/wrapt-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1be685ac7700c966b8610ccc63c3187a72e33cab53526a27b2a285a662cd4f7", size = 61578, upload-time = "2025-11-07T00:43:32.918Z" },
1733
- { url = "https://files.pythonhosted.org/packages/5b/36/825b44c8a10556957bc0c1d84c7b29a40e05fcf1873b6c40aa9dbe0bd972/wrapt-2.0.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0b6d3b95932809c5b3fecc18fda0f1e07452d05e2662a0b35548985f256e28", size = 114115, upload-time = "2025-11-07T00:43:35.605Z" },
1734
- { url = "https://files.pythonhosted.org/packages/83/73/0a5d14bb1599677304d3c613a55457d34c344e9b60eda8a737c2ead7619e/wrapt-2.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da7384b0e5d4cae05c97cd6f94faaf78cc8b0f791fc63af43436d98c4ab37bb", size = 116157, upload-time = "2025-11-07T00:43:37.058Z" },
1735
- { url = "https://files.pythonhosted.org/packages/01/22/1c158fe763dbf0a119f985d945711d288994fe5514c0646ebe0eb18b016d/wrapt-2.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ec65a78fbd9d6f083a15d7613b2800d5663dbb6bb96003899c834beaa68b242c", size = 112535, upload-time = "2025-11-07T00:43:34.138Z" },
1736
- { url = "https://files.pythonhosted.org/packages/5c/28/4f16861af67d6de4eae9927799b559c20ebdd4fe432e89ea7fe6fcd9d709/wrapt-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7de3cc939be0e1174969f943f3b44e0d79b6f9a82198133a5b7fc6cc92882f16", size = 115404, upload-time = "2025-11-07T00:43:39.214Z" },
1737
- { url = "https://files.pythonhosted.org/packages/a0/8b/7960122e625fad908f189b59c4aae2d50916eb4098b0fb2819c5a177414f/wrapt-2.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fb1a5b72cbd751813adc02ef01ada0b0d05d3dcbc32976ce189a1279d80ad4a2", size = 111802, upload-time = "2025-11-07T00:43:40.476Z" },
1738
- { url = "https://files.pythonhosted.org/packages/3e/73/7881eee5ac31132a713ab19a22c9e5f1f7365c8b1df50abba5d45b781312/wrapt-2.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3fa272ca34332581e00bf7773e993d4f632594eb2d1b0b162a9038df0fd971dd", size = 113837, upload-time = "2025-11-07T00:43:42.921Z" },
1739
- { url = "https://files.pythonhosted.org/packages/45/00/9499a3d14e636d1f7089339f96c4409bbc7544d0889f12264efa25502ae8/wrapt-2.0.1-cp311-cp311-win32.whl", hash = "sha256:fc007fdf480c77301ab1afdbb6ab22a5deee8885f3b1ed7afcb7e5e84a0e27be", size = 58028, upload-time = "2025-11-07T00:43:47.369Z" },
1740
- { url = "https://files.pythonhosted.org/packages/70/5d/8f3d7eea52f22638748f74b102e38fdf88cb57d08ddeb7827c476a20b01b/wrapt-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:47434236c396d04875180171ee1f3815ca1eada05e24a1ee99546320d54d1d1b", size = 60385, upload-time = "2025-11-07T00:43:44.34Z" },
1741
- { url = "https://files.pythonhosted.org/packages/14/e2/32195e57a8209003587bbbad44d5922f13e0ced2a493bb46ca882c5b123d/wrapt-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:837e31620e06b16030b1d126ed78e9383815cbac914693f54926d816d35d8edf", size = 58893, upload-time = "2025-11-07T00:43:46.161Z" },
1742
- { url = "https://files.pythonhosted.org/packages/cb/73/8cb252858dc8254baa0ce58ce382858e3a1cf616acebc497cb13374c95c6/wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c", size = 78129, upload-time = "2025-11-07T00:43:48.852Z" },
1743
- { url = "https://files.pythonhosted.org/packages/19/42/44a0db2108526ee6e17a5ab72478061158f34b08b793df251d9fbb9a7eb4/wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841", size = 61205, upload-time = "2025-11-07T00:43:50.402Z" },
1744
- { url = "https://files.pythonhosted.org/packages/4d/8a/5b4b1e44b791c22046e90d9b175f9a7581a8cc7a0debbb930f81e6ae8e25/wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62", size = 61692, upload-time = "2025-11-07T00:43:51.678Z" },
1745
- { url = "https://files.pythonhosted.org/packages/11/53/3e794346c39f462bcf1f58ac0487ff9bdad02f9b6d5ee2dc84c72e0243b2/wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf", size = 121492, upload-time = "2025-11-07T00:43:55.017Z" },
1746
- { url = "https://files.pythonhosted.org/packages/c6/7e/10b7b0e8841e684c8ca76b462a9091c45d62e8f2de9c4b1390b690eadf16/wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9", size = 123064, upload-time = "2025-11-07T00:43:56.323Z" },
1747
- { url = "https://files.pythonhosted.org/packages/0e/d1/3c1e4321fc2f5ee7fd866b2d822aa89b84495f28676fd976c47327c5b6aa/wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b", size = 117403, upload-time = "2025-11-07T00:43:53.258Z" },
1748
- { url = "https://files.pythonhosted.org/packages/a4/b0/d2f0a413cf201c8c2466de08414a15420a25aa83f53e647b7255cc2fab5d/wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba", size = 121500, upload-time = "2025-11-07T00:43:57.468Z" },
1749
- { url = "https://files.pythonhosted.org/packages/bd/45/bddb11d28ca39970a41ed48a26d210505120f925918592283369219f83cc/wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684", size = 116299, upload-time = "2025-11-07T00:43:58.877Z" },
1750
- { url = "https://files.pythonhosted.org/packages/81/af/34ba6dd570ef7a534e7eec0c25e2615c355602c52aba59413411c025a0cb/wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb", size = 120622, upload-time = "2025-11-07T00:43:59.962Z" },
1751
- { url = "https://files.pythonhosted.org/packages/e2/3e/693a13b4146646fb03254636f8bafd20c621955d27d65b15de07ab886187/wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9", size = 58246, upload-time = "2025-11-07T00:44:03.169Z" },
1752
- { url = "https://files.pythonhosted.org/packages/a7/36/715ec5076f925a6be95f37917b66ebbeaa1372d1862c2ccd7a751574b068/wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75", size = 60492, upload-time = "2025-11-07T00:44:01.027Z" },
1753
- { url = "https://files.pythonhosted.org/packages/ef/3e/62451cd7d80f65cc125f2b426b25fbb6c514bf6f7011a0c3904fc8c8df90/wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b", size = 58987, upload-time = "2025-11-07T00:44:02.095Z" },
1754
- { url = "https://files.pythonhosted.org/packages/ad/fe/41af4c46b5e498c90fc87981ab2972fbd9f0bccda597adb99d3d3441b94b/wrapt-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9", size = 78132, upload-time = "2025-11-07T00:44:04.628Z" },
1755
- { url = "https://files.pythonhosted.org/packages/1c/92/d68895a984a5ebbbfb175512b0c0aad872354a4a2484fbd5552e9f275316/wrapt-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f", size = 61211, upload-time = "2025-11-07T00:44:05.626Z" },
1756
- { url = "https://files.pythonhosted.org/packages/e8/26/ba83dc5ae7cf5aa2b02364a3d9cf74374b86169906a1f3ade9a2d03cf21c/wrapt-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218", size = 61689, upload-time = "2025-11-07T00:44:06.719Z" },
1757
- { url = "https://files.pythonhosted.org/packages/cf/67/d7a7c276d874e5d26738c22444d466a3a64ed541f6ef35f740dbd865bab4/wrapt-2.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9", size = 121502, upload-time = "2025-11-07T00:44:09.557Z" },
1758
- { url = "https://files.pythonhosted.org/packages/0f/6b/806dbf6dd9579556aab22fc92908a876636e250f063f71548a8660382184/wrapt-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c", size = 123110, upload-time = "2025-11-07T00:44:10.64Z" },
1759
- { url = "https://files.pythonhosted.org/packages/e5/08/cdbb965fbe4c02c5233d185d070cabed2ecc1f1e47662854f95d77613f57/wrapt-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db", size = 117434, upload-time = "2025-11-07T00:44:08.138Z" },
1760
- { url = "https://files.pythonhosted.org/packages/2d/d1/6aae2ce39db4cb5216302fa2e9577ad74424dfbe315bd6669725569e048c/wrapt-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233", size = 121533, upload-time = "2025-11-07T00:44:12.142Z" },
1761
- { url = "https://files.pythonhosted.org/packages/79/35/565abf57559fbe0a9155c29879ff43ce8bd28d2ca61033a3a3dd67b70794/wrapt-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2", size = 116324, upload-time = "2025-11-07T00:44:13.28Z" },
1762
- { url = "https://files.pythonhosted.org/packages/e1/e0/53ff5e76587822ee33e560ad55876d858e384158272cd9947abdd4ad42ca/wrapt-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b", size = 120627, upload-time = "2025-11-07T00:44:14.431Z" },
1763
- { url = "https://files.pythonhosted.org/packages/7c/7b/38df30fd629fbd7612c407643c63e80e1c60bcc982e30ceeae163a9800e7/wrapt-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7", size = 58252, upload-time = "2025-11-07T00:44:17.814Z" },
1764
- { url = "https://files.pythonhosted.org/packages/85/64/d3954e836ea67c4d3ad5285e5c8fd9d362fd0a189a2db622df457b0f4f6a/wrapt-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3", size = 60500, upload-time = "2025-11-07T00:44:15.561Z" },
1765
- { url = "https://files.pythonhosted.org/packages/89/4e/3c8b99ac93527cfab7f116089db120fef16aac96e5f6cdb724ddf286086d/wrapt-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8", size = 58993, upload-time = "2025-11-07T00:44:16.65Z" },
1766
- { url = "https://files.pythonhosted.org/packages/f9/f4/eff2b7d711cae20d220780b9300faa05558660afb93f2ff5db61fe725b9a/wrapt-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3", size = 82028, upload-time = "2025-11-07T00:44:18.944Z" },
1767
- { url = "https://files.pythonhosted.org/packages/0c/67/cb945563f66fd0f61a999339460d950f4735c69f18f0a87ca586319b1778/wrapt-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1", size = 62949, upload-time = "2025-11-07T00:44:20.074Z" },
1768
- { url = "https://files.pythonhosted.org/packages/ec/ca/f63e177f0bbe1e5cf5e8d9b74a286537cd709724384ff20860f8f6065904/wrapt-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d", size = 63681, upload-time = "2025-11-07T00:44:21.345Z" },
1769
- { url = "https://files.pythonhosted.org/packages/39/a1/1b88fcd21fd835dca48b556daef750952e917a2794fa20c025489e2e1f0f/wrapt-2.0.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7", size = 152696, upload-time = "2025-11-07T00:44:24.318Z" },
1770
- { url = "https://files.pythonhosted.org/packages/62/1c/d9185500c1960d9f5f77b9c0b890b7fc62282b53af7ad1b6bd779157f714/wrapt-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3", size = 158859, upload-time = "2025-11-07T00:44:25.494Z" },
1771
- { url = "https://files.pythonhosted.org/packages/91/60/5d796ed0f481ec003220c7878a1d6894652efe089853a208ea0838c13086/wrapt-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b", size = 146068, upload-time = "2025-11-07T00:44:22.81Z" },
1772
- { url = "https://files.pythonhosted.org/packages/04/f8/75282dd72f102ddbfba137e1e15ecba47b40acff32c08ae97edbf53f469e/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10", size = 155724, upload-time = "2025-11-07T00:44:26.634Z" },
1773
- { url = "https://files.pythonhosted.org/packages/5a/27/fe39c51d1b344caebb4a6a9372157bdb8d25b194b3561b52c8ffc40ac7d1/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf", size = 144413, upload-time = "2025-11-07T00:44:27.939Z" },
1774
- { url = "https://files.pythonhosted.org/packages/83/2b/9f6b643fe39d4505c7bf926d7c2595b7cb4b607c8c6b500e56c6b36ac238/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e", size = 150325, upload-time = "2025-11-07T00:44:29.29Z" },
1775
- { url = "https://files.pythonhosted.org/packages/bb/b6/20ffcf2558596a7f58a2e69c89597128781f0b88e124bf5a4cadc05b8139/wrapt-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c", size = 59943, upload-time = "2025-11-07T00:44:33.211Z" },
1776
- { url = "https://files.pythonhosted.org/packages/87/6a/0e56111cbb3320151eed5d3821ee1373be13e05b376ea0870711f18810c3/wrapt-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92", size = 63240, upload-time = "2025-11-07T00:44:30.935Z" },
1777
- { url = "https://files.pythonhosted.org/packages/1d/54/5ab4c53ea1f7f7e5c3e7c1095db92932cc32fd62359d285486d00c2884c3/wrapt-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f", size = 60416, upload-time = "2025-11-07T00:44:32.002Z" },
1778
- { url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" },
1779
- ]
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