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.
- {mt5api-0.0.4 → mt5api-0.1.0}/AGENTS.md +4 -2
- {mt5api-0.0.4 → mt5api-0.1.0}/PKG-INFO +6 -7
- {mt5api-0.0.4 → mt5api-0.1.0}/README.md +5 -5
- {mt5api-0.0.4 → mt5api-0.1.0}/docs/api/deployment.md +0 -2
- {mt5api-0.0.4 → mt5api-0.1.0}/docs/api/rest-api.md +5 -11
- {mt5api-0.0.4 → mt5api-0.1.0}/docs/index.md +3 -3
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/config.py +0 -22
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/constants.py +0 -4
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/main.py +0 -25
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/middleware.py +1 -54
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/market.py +2 -2
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/trading.py +4 -4
- {mt5api-0.0.4 → mt5api-0.1.0}/pyproject.toml +1 -2
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_config.py +0 -28
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_middleware.py +0 -24
- {mt5api-0.0.4 → mt5api-0.1.0}/uv.lock +4 -101
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/local-qa/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/local-qa/scripts/qa.sh +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/mt5api/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-analyze/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-checklist/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-clarify/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-constitution/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-implement/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-plan/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-specify/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-tasks/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.agents/skills/speckit-taskstoissues/SKILL.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/agents/codex.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/agents/copilot.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.analyze.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.checklist.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.clarify.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.constitution.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.implement.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.plan.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.specify.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.tasks.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/commands/speckit.taskstoissues.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.claude/settings.json +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.github/FUNDING.yml +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.github/dependabot.yml +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.github/renovate.json +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.github/workflows/ci.yml +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.github/workflows/claude-code.yml +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.gitignore +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/memory/constitution.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/common.sh +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/setup-plan.sh +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/agent-file-template.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/checklist-template.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/plan-template.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/spec-template.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/.specify/templates/tasks-template.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/CLAUDE.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/LICENSE +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/docs/api/index.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mkdocs.yml +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/__init__.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/__main__.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/auth.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/dependencies.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/formatters.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/models.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/__init__.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/account.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/calc.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/health.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/history.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/mt5api/routers/symbols.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/contracts/openapi.yaml +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/data-model.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/plan.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/quickstart.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/research.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/041-mt5-rest-api/tasks.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/042-mt5-core-client/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/043-mt5-dataframe-client/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/044-mt5-trading-client/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/045-mt5-utils/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/046-mt5-api-service/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/047-api-auth-rate-limit/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/048-api-response-format/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/049-api-routing-models/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/050-api-runtime-deploy/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/051-api-error-logging/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/specs/052-api-dependencies/spec.md +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/__init__.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/conftest.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/mt5_constants.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_account.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_auth.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_calc.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_cli.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_dependencies.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_formatters.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_health.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_history.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_integration.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_lifespan.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_main.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_market.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_models.py +0 -0
- {mt5api-0.0.4 → mt5api-0.1.0}/tests/test_symbols.py +0 -0
- {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,
|
|
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`, `
|
|
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
|
|
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
|
|
40
|
-
|
|
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/>
|
|
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
|
|
79
|
-
- Structured JSON logging
|
|
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
|
|
10
|
-
|
|
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/>
|
|
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
|
|
49
|
-
- Structured JSON logging
|
|
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
|
-
##
|
|
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
|
-
-
|
|
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
|
|
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
|
|
23
|
-
- Structured JSON logging
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1016
|
+
version = "0.6.3"
|
|
1045
1017
|
source = { registry = "https://pypi.org/simple" }
|
|
1046
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
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/
|
|
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
|
|
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
|
|
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
|