eltoque-python 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 (44) hide show
  1. eltoque_python-0.1.0/.gitignore +28 -0
  2. eltoque_python-0.1.0/CHANGELOG.md +34 -0
  3. eltoque_python-0.1.0/LICENSE +24 -0
  4. eltoque_python-0.1.0/PKG-INFO +286 -0
  5. eltoque_python-0.1.0/README.md +258 -0
  6. eltoque_python-0.1.0/docs/api-contract.md +49 -0
  7. eltoque_python-0.1.0/docs/configuration.md +74 -0
  8. eltoque_python-0.1.0/docs/errors.md +61 -0
  9. eltoque_python-0.1.0/docs/publishing.md +69 -0
  10. eltoque_python-0.1.0/docs/quickstart.es.md +127 -0
  11. eltoque_python-0.1.0/docs/security.md +38 -0
  12. eltoque_python-0.1.0/examples/async_basic.py +20 -0
  13. eltoque_python-0.1.0/examples/basic.py +23 -0
  14. eltoque_python-0.1.0/examples/date_range.py +24 -0
  15. eltoque_python-0.1.0/pyproject.toml +89 -0
  16. eltoque_python-0.1.0/src/eltoque/__init__.py +44 -0
  17. eltoque_python-0.1.0/src/eltoque/_async_client.py +131 -0
  18. eltoque_python-0.1.0/src/eltoque/_client.py +136 -0
  19. eltoque_python-0.1.0/src/eltoque/_config.py +106 -0
  20. eltoque_python-0.1.0/src/eltoque/_dates.py +53 -0
  21. eltoque_python-0.1.0/src/eltoque/_retry.py +59 -0
  22. eltoque_python-0.1.0/src/eltoque/_transport.py +165 -0
  23. eltoque_python-0.1.0/src/eltoque/_version.py +1 -0
  24. eltoque_python-0.1.0/src/eltoque/exceptions.py +77 -0
  25. eltoque_python-0.1.0/src/eltoque/models.py +78 -0
  26. eltoque_python-0.1.0/src/eltoque/py.typed +0 -0
  27. eltoque_python-0.1.0/tests/__init__.py +0 -0
  28. eltoque_python-0.1.0/tests/conftest.py +39 -0
  29. eltoque_python-0.1.0/tests/test_async_client.py +195 -0
  30. eltoque_python-0.1.0/tests/test_client.py +269 -0
  31. eltoque_python-0.1.0/tests/test_concurrency.py +102 -0
  32. eltoque_python-0.1.0/tests/test_config.py +119 -0
  33. eltoque_python-0.1.0/tests/test_dates.py +73 -0
  34. eltoque_python-0.1.0/tests/test_docs.py +67 -0
  35. eltoque_python-0.1.0/tests/test_errors.py +57 -0
  36. eltoque_python-0.1.0/tests/test_failures.py +116 -0
  37. eltoque_python-0.1.0/tests/test_integration.py +36 -0
  38. eltoque_python-0.1.0/tests/test_models.py +29 -0
  39. eltoque_python-0.1.0/tests/test_parity.py +99 -0
  40. eltoque_python-0.1.0/tests/test_public_api.py +24 -0
  41. eltoque_python-0.1.0/tests/test_rate_limits.py +38 -0
  42. eltoque_python-0.1.0/tests/test_retry.py +80 -0
  43. eltoque_python-0.1.0/tests/test_security.py +129 -0
  44. eltoque_python-0.1.0/tests/test_transport.py +187 -0
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # Tooling caches
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ .coverage.*
17
+ htmlcov/
18
+ coverage.xml
19
+
20
+ # Secrets and local config
21
+ .env
22
+ .env.*
23
+ !.env.example
24
+
25
+ # Editors and OS
26
+ .idea/
27
+ .vscode/
28
+ .DS_Store
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to
5
+ [Semantic Versioning](https://semver.org/). Before 1.0, minor versions may contain breaking
6
+ changes; they will always be listed here. The SDK version is independent of the elTOQUE API
7
+ version (`1.0.beta`).
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.1.0] - 2026-10-22
12
+
13
+ First release. Unpublished until the repository owner approves it (see docs/publishing.md).
14
+
15
+ ### Added
16
+
17
+ - `ElToqueClient` and `AsyncElToqueClient` for the documented `GET /v1/trmi` operation:
18
+ `get_latest_rates()`, `get_rates(date_from, date_to)` and `get_rates_for_day(day)`.
19
+ - `RatesResponse` envelope that exposes the provider JSON untouched, plus `RateLimitInfo`
20
+ parsed from `X-RateLimit-*` and `Retry-After` headers.
21
+ - Exception hierarchy rooted at `ElToqueError`, with one subclass per documented HTTP status.
22
+ - Strict validation: naive datetimes only, `date_from < date_to`, window shorter than 24 hours.
23
+ - Opt-in, bounded retries (`max_retries`, default 0) honoring `Retry-After`.
24
+ - Safety defaults: HTTPS-only base URL, redirects never followed, 5 MiB response cap, token
25
+ redacted from `repr`, errors and logs.
26
+ - Injectable `httpx` client/transport with explicit ownership semantics.
27
+ - Offline test suite (`httpx.MockTransport`), opt-in live integration test, README and Spanish
28
+ quickstart, configuration/errors/security guides, runnable examples.
29
+
30
+ ### Known limitations
31
+
32
+ - The provider does not publish the HTTP 200 schema; `RatesResponse.data` is untyped.
33
+ - The provider's time zone is undocumented.
34
+ - Not yet verified against the real API (no authorized token was used).
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 eltoque-python contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ Scope note: this license covers the source code of this repository only. It
24
+ grants no rights over the elTOQUE API, data, name or trademarks.
@@ -0,0 +1,286 @@
1
+ Metadata-Version: 2.5
2
+ Name: eltoque-python
3
+ Version: 0.1.0
4
+ Summary: Unofficial, typed Python client for the official elTOQUE exchange-rate API.
5
+ Project-URL: Homepage, https://github.com/Alejo2075/eltoque-python
6
+ Project-URL: Source, https://github.com/Alejo2075/eltoque-python
7
+ Project-URL: Issues, https://github.com/Alejo2075/eltoque-python/issues
8
+ Project-URL: Changelog, https://github.com/Alejo2075/eltoque-python/blob/main/CHANGELOG.md
9
+ Project-URL: elTOQUE API docs, https://tasas.eltoque.com/docs/
10
+ Author: eltoque-python contributors
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: api-client,async,cuba,eltoque,exchange-rate,httpx
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: httpx<1,>=0.28
27
+ Description-Content-Type: text/markdown
28
+
29
+ # eltoque-python
30
+
31
+ **Unofficial Python SDK for elTOQUE exchange-rate data.**
32
+ A modern, typed client for the official elTOQUE exchange-rate API, with sync and async interfaces.
33
+
34
+ [![PyPI](https://img.shields.io/pypi/v/eltoque-python)](https://pypi.org/project/eltoque-python/)
35
+ [![Python](https://img.shields.io/pypi/pyversions/eltoque-python)](https://pypi.org/project/eltoque-python/)
36
+ [![CI](https://github.com/Alejo2075/eltoque-python/actions/workflows/ci.yml/badge.svg)](https://github.com/Alejo2075/eltoque-python/actions/workflows/ci.yml)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
38
+
39
+ > **Independent project.** `eltoque-python` is community-maintained. It is not developed,
40
+ > sponsored, endorsed or supported by elTOQUE.
41
+ >
42
+ > **Exchange rates are indicative, not official. Source: elTOQUE.
43
+ > Access requires registration and an application-specific API token.**
44
+
45
+ ```python
46
+ import os
47
+
48
+ from eltoque import ElToqueClient
49
+
50
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
51
+ response = client.get_latest_rates()
52
+ print(response.data) # the provider's JSON, untouched
53
+ ```
54
+
55
+ Leer en español: [docs/quickstart.es.md](docs/quickstart.es.md)
56
+
57
+ ## Why use it?
58
+
59
+ - **Sync and async** clients with the same methods and behavior.
60
+ - **Typed** public API (`py.typed`, checked with `mypy --strict`).
61
+ - **Safe by default:** HTTPS only, explicit timeouts, no redirects followed (your token is never
62
+ forwarded to another host), bounded response size, and the token never appears in `repr`,
63
+ error messages or logs.
64
+ - **Predictable errors:** one exception family, one subclass per documented HTTP status.
65
+ - **Rate-limit aware:** parses `X-RateLimit-*` and `Retry-After`; retries are opt-in and bounded.
66
+ - **Pure Python**, one runtime dependency ([`httpx`](https://www.python-httpx.org/)), works in
67
+ plain scripts, notebooks, Django, Flask, FastAPI or AWS Lambda.
68
+ - **Tested offline** with mocked HTTP: no token needed to develop or run the test suite.
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ pip install eltoque-python
74
+ ```
75
+
76
+ Requires Python 3.11 or newer. To work on the SDK itself, install from a clone with `pip install -e .`.
77
+
78
+ ## Get your API token
79
+
80
+ The SDK does not grant API access. Each application that integrates the API needs **its own
81
+ token**, requested from elTOQUE:
82
+
83
+ 1. Fill in the official form: <https://tasas-token.eltoque.com/>
84
+ 2. Read the [terms of use](https://eltoque.com/api-tasas-de-eltoque-terminos-y-condiciones-de-uso).
85
+ 3. Keep the token secret and read it from the environment:
86
+
87
+ ```bash
88
+ export ELTOQUE_API_TOKEN="your-application-token"
89
+ ```
90
+
91
+ Never commit a token, share it with third parties, or ship it in a browser bundle.
92
+
93
+ ## Quickstart
94
+
95
+ ### Latest rates (rolling last 24 hours)
96
+
97
+ ```python
98
+ import os
99
+
100
+ from eltoque import ElToqueClient
101
+
102
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
103
+ response = client.get_latest_rates()
104
+ print(response.data)
105
+ ```
106
+
107
+ ### A specific window (shorter than 24 hours)
108
+
109
+ ```python
110
+ import os
111
+ from datetime import datetime
112
+
113
+ from eltoque import ElToqueClient
114
+
115
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
116
+ response = client.get_rates(
117
+ datetime(2022, 10, 27, 0, 0, 0),
118
+ datetime(2022, 10, 27, 23, 59, 59),
119
+ )
120
+ print(response.data)
121
+ ```
122
+
123
+ ### Async
124
+
125
+ ```python
126
+ import asyncio
127
+ import os
128
+
129
+ from eltoque import AsyncElToqueClient
130
+
131
+
132
+ async def main() -> None:
133
+ async with AsyncElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
134
+ response = await client.get_latest_rates()
135
+ print(response.data)
136
+
137
+
138
+ asyncio.run(main())
139
+ ```
140
+
141
+ ### Handling errors
142
+
143
+ ```python
144
+ import os
145
+
146
+ from eltoque import ElToqueClient, ElToqueError, ElToqueRateLimitError
147
+
148
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"], max_retries=2) as client:
149
+ try:
150
+ response = client.get_latest_rates()
151
+ except ElToqueRateLimitError as error:
152
+ print(f"Rate limited, retry in {error.retry_after} s")
153
+ except ElToqueError as error:
154
+ print(f"Request failed: {error}")
155
+ else:
156
+ print(response.rate_limit.remaining, "requests left in this window")
157
+ ```
158
+
159
+ ## API reference
160
+
161
+ ### Clients
162
+
163
+ `ElToqueClient` (sync) and `AsyncElToqueClient` (async, `await` every method) share this
164
+ constructor:
165
+
166
+ | Parameter | Default | Description |
167
+ | --- | --- | --- |
168
+ | `api_token` | required | Your application token. Printable ASCII, no spaces, no `Bearer ` prefix. |
169
+ | `base_url` | `https://tasas.eltoque.com` | HTTPS only. Plain `http://` is accepted for `localhost`/loopback (tests). |
170
+ | `timeout` | `10 s` (`5 s` connect) | Seconds, or an `httpx.Timeout`. |
171
+ | `max_retries` | `0` | Opt-in retries (max 5). See [Rate limits](#rate-limits-and-retries). |
172
+ | `transport` | `None` | Test seam, e.g. `httpx.MockTransport`. |
173
+ | `http_client` | `None` | Your own `httpx.Client` / `httpx.AsyncClient`. It is never closed by the SDK, and its own timeout applies. |
174
+
175
+ | Method | Description |
176
+ | --- | --- |
177
+ | `get_latest_rates()` | Rolling last 24 hours, computed by the provider at request time. |
178
+ | `get_rates(date_from, date_to)` | Explicit window, strictly shorter than 24 hours. |
179
+ | `get_rates_for_day(day)` | `day 00:00:00` to `day 23:59:59`. One request. |
180
+ | `close()` / `aclose()` | Release connections. Idempotent. Also called by `with` / `async with`. |
181
+
182
+ Using a client after it was closed raises `ElToqueError`.
183
+
184
+ ### `RatesResponse`
185
+
186
+ | Attribute | Description |
187
+ | --- | --- |
188
+ | `data` | The decoded JSON body, **exactly as the provider sent it** (object, list, scalar or `null`). |
189
+ | `status_code` | HTTP status (SDK metadata). |
190
+ | `rate_limit` | `RateLimitInfo` with optional `limit`, `remaining`, `reset`, `retry_after` (SDK metadata). |
191
+ | `received_at` | UTC timestamp of when the SDK received the response (SDK metadata). |
192
+
193
+ ### Exceptions
194
+
195
+ ```text
196
+ ElToqueError
197
+ ├── ElToqueConfigurationError invalid token, base_url, timeout, retries
198
+ ├── ElToqueValidationError invalid arguments, detected before any request
199
+ ├── ElToqueNetworkError connection, DNS, TLS, protocol failures
200
+ ├── ElToqueTimeoutError
201
+ ├── ElToqueDecodeError 200 response that is not valid JSON, or too large
202
+ └── ElToqueAPIError has .status_code, .body_excerpt, .rate_limit, .retry_after
203
+ ├── ElToqueBadRequestError 400
204
+ ├── ElToqueAuthenticationError 401
205
+ ├── ElToqueUnprocessableError 422
206
+ ├── ElToqueRateLimitError 429
207
+ └── ElToqueServerError 5xx
208
+ ```
209
+
210
+ Other 3xx/4xx statuses raise the base `ElToqueAPIError`. See [docs/errors.md](docs/errors.md).
211
+
212
+ ## Date and time rules
213
+
214
+ - `date_from` and `date_to` must be **naive** `datetime` objects. Timezone-aware values are
215
+ rejected, because the API does not document its time zone and the SDK will not guess one.
216
+ - They are sent as `YYYY-MM-DD HH:MM:SS` (microseconds are dropped).
217
+ - `date_from` must be earlier than `date_to`, and the window must be **shorter than 24 hours**,
218
+ otherwise the API answers HTTP 400. The SDK rejects invalid windows before any request.
219
+ - `get_rates_for_day` is a convenience; it is not guaranteed to be a complete local calendar day.
220
+
221
+ ## Rate limits and retries
222
+
223
+ By default each API key may make **60 requests/minute and up to 10 requests/second**; your key
224
+ may have different limits. Over the limit the API answers `429` with a `Retry-After` header.
225
+
226
+ - Retries are **off** (`max_retries=0`). Every retry consumes quota.
227
+ - With `max_retries > 0` the SDK retries only `429`, `502`, `503`, `504` and transport failures,
228
+ never `400`, `401` or `422`.
229
+ - `Retry-After` is honored. If the server asks for longer than 30 s (or the 60 s total budget),
230
+ the error is raised instead of waiting.
231
+ - There is no background polling, caching or bulk collection. You decide when to call.
232
+
233
+ ## Known limitations
234
+
235
+ - Only one operation is documented by the provider: `GET /v1/trmi`. That is all this SDK calls.
236
+ - The provider **does not publish the schema of a successful response**, so `RatesResponse.data`
237
+ is untyped and no field (currency names, timestamps, ...) is guaranteed. Typed rate models will
238
+ come only after the schema is validated with authorized access.
239
+ - The time zone of the API is undocumented (see above).
240
+ - Installing this SDK does not give you access to the API; you need your own approved token.
241
+
242
+ ## Supported Python versions
243
+
244
+ Python 3.11, 3.12, 3.13 and 3.14. No framework is required.
245
+
246
+ ## Development
247
+
248
+ ```bash
249
+ uv sync # or: pip install -e . && pip install pytest pytest-asyncio pytest-cov ruff mypy build twine
250
+ uv run ruff check .
251
+ uv run ruff format --check .
252
+ uv run mypy src
253
+ uv run pytest # offline, no token needed
254
+ uv run python -m build
255
+ uv run twine check dist/*
256
+ ```
257
+
258
+ Optional live check, only with your own authorized token (never run in CI):
259
+
260
+ ```bash
261
+ ELTOQUE_API_TOKEN=... ELTOQUE_ALLOW_LIVE_TESTS=1 uv run pytest -m integration --no-cov
262
+ ```
263
+
264
+ ## Security and legal
265
+
266
+ - Treat your token like a password. See [docs/security.md](docs/security.md).
267
+ - This project is not affiliated with elTOQUE. When you display rates, name **elTOQUE as the
268
+ source** and describe them as **reference estimates**, as the
269
+ [terms of use](https://eltoque.com/api-tasas-de-eltoque-terminos-y-condiciones-de-uso) require.
270
+ Do not modify the data, share your key, resell the data or mass-extract it.
271
+ - The [MIT license](LICENSE) covers this SDK's source code only. It grants no rights over
272
+ elTOQUE's API, data, name or trademarks.
273
+
274
+ ## Official resources
275
+
276
+ - API landing page: <https://tasas.eltoque.com/>
277
+ - Interactive docs: <https://tasas.eltoque.com/docs/>
278
+ - OpenAPI document: <https://tasas.eltoque.com/static/swagger.json>
279
+ - What is and is not verified: [docs/api-contract.md](docs/api-contract.md)
280
+
281
+ ## Community
282
+
283
+ - Contributing guide: [CONTRIBUTING.md](CONTRIBUTING.md)
284
+ - Code of conduct: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
285
+ - Security policy: [SECURITY.md](SECURITY.md)
286
+ - Release notes: [CHANGELOG.md](CHANGELOG.md)
@@ -0,0 +1,258 @@
1
+ # eltoque-python
2
+
3
+ **Unofficial Python SDK for elTOQUE exchange-rate data.**
4
+ A modern, typed client for the official elTOQUE exchange-rate API, with sync and async interfaces.
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/eltoque-python)](https://pypi.org/project/eltoque-python/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/eltoque-python)](https://pypi.org/project/eltoque-python/)
8
+ [![CI](https://github.com/Alejo2075/eltoque-python/actions/workflows/ci.yml/badge.svg)](https://github.com/Alejo2075/eltoque-python/actions/workflows/ci.yml)
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
+
11
+ > **Independent project.** `eltoque-python` is community-maintained. It is not developed,
12
+ > sponsored, endorsed or supported by elTOQUE.
13
+ >
14
+ > **Exchange rates are indicative, not official. Source: elTOQUE.
15
+ > Access requires registration and an application-specific API token.**
16
+
17
+ ```python
18
+ import os
19
+
20
+ from eltoque import ElToqueClient
21
+
22
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
23
+ response = client.get_latest_rates()
24
+ print(response.data) # the provider's JSON, untouched
25
+ ```
26
+
27
+ Leer en español: [docs/quickstart.es.md](docs/quickstart.es.md)
28
+
29
+ ## Why use it?
30
+
31
+ - **Sync and async** clients with the same methods and behavior.
32
+ - **Typed** public API (`py.typed`, checked with `mypy --strict`).
33
+ - **Safe by default:** HTTPS only, explicit timeouts, no redirects followed (your token is never
34
+ forwarded to another host), bounded response size, and the token never appears in `repr`,
35
+ error messages or logs.
36
+ - **Predictable errors:** one exception family, one subclass per documented HTTP status.
37
+ - **Rate-limit aware:** parses `X-RateLimit-*` and `Retry-After`; retries are opt-in and bounded.
38
+ - **Pure Python**, one runtime dependency ([`httpx`](https://www.python-httpx.org/)), works in
39
+ plain scripts, notebooks, Django, Flask, FastAPI or AWS Lambda.
40
+ - **Tested offline** with mocked HTTP: no token needed to develop or run the test suite.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install eltoque-python
46
+ ```
47
+
48
+ Requires Python 3.11 or newer. To work on the SDK itself, install from a clone with `pip install -e .`.
49
+
50
+ ## Get your API token
51
+
52
+ The SDK does not grant API access. Each application that integrates the API needs **its own
53
+ token**, requested from elTOQUE:
54
+
55
+ 1. Fill in the official form: <https://tasas-token.eltoque.com/>
56
+ 2. Read the [terms of use](https://eltoque.com/api-tasas-de-eltoque-terminos-y-condiciones-de-uso).
57
+ 3. Keep the token secret and read it from the environment:
58
+
59
+ ```bash
60
+ export ELTOQUE_API_TOKEN="your-application-token"
61
+ ```
62
+
63
+ Never commit a token, share it with third parties, or ship it in a browser bundle.
64
+
65
+ ## Quickstart
66
+
67
+ ### Latest rates (rolling last 24 hours)
68
+
69
+ ```python
70
+ import os
71
+
72
+ from eltoque import ElToqueClient
73
+
74
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
75
+ response = client.get_latest_rates()
76
+ print(response.data)
77
+ ```
78
+
79
+ ### A specific window (shorter than 24 hours)
80
+
81
+ ```python
82
+ import os
83
+ from datetime import datetime
84
+
85
+ from eltoque import ElToqueClient
86
+
87
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
88
+ response = client.get_rates(
89
+ datetime(2022, 10, 27, 0, 0, 0),
90
+ datetime(2022, 10, 27, 23, 59, 59),
91
+ )
92
+ print(response.data)
93
+ ```
94
+
95
+ ### Async
96
+
97
+ ```python
98
+ import asyncio
99
+ import os
100
+
101
+ from eltoque import AsyncElToqueClient
102
+
103
+
104
+ async def main() -> None:
105
+ async with AsyncElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"]) as client:
106
+ response = await client.get_latest_rates()
107
+ print(response.data)
108
+
109
+
110
+ asyncio.run(main())
111
+ ```
112
+
113
+ ### Handling errors
114
+
115
+ ```python
116
+ import os
117
+
118
+ from eltoque import ElToqueClient, ElToqueError, ElToqueRateLimitError
119
+
120
+ with ElToqueClient(api_token=os.environ["ELTOQUE_API_TOKEN"], max_retries=2) as client:
121
+ try:
122
+ response = client.get_latest_rates()
123
+ except ElToqueRateLimitError as error:
124
+ print(f"Rate limited, retry in {error.retry_after} s")
125
+ except ElToqueError as error:
126
+ print(f"Request failed: {error}")
127
+ else:
128
+ print(response.rate_limit.remaining, "requests left in this window")
129
+ ```
130
+
131
+ ## API reference
132
+
133
+ ### Clients
134
+
135
+ `ElToqueClient` (sync) and `AsyncElToqueClient` (async, `await` every method) share this
136
+ constructor:
137
+
138
+ | Parameter | Default | Description |
139
+ | --- | --- | --- |
140
+ | `api_token` | required | Your application token. Printable ASCII, no spaces, no `Bearer ` prefix. |
141
+ | `base_url` | `https://tasas.eltoque.com` | HTTPS only. Plain `http://` is accepted for `localhost`/loopback (tests). |
142
+ | `timeout` | `10 s` (`5 s` connect) | Seconds, or an `httpx.Timeout`. |
143
+ | `max_retries` | `0` | Opt-in retries (max 5). See [Rate limits](#rate-limits-and-retries). |
144
+ | `transport` | `None` | Test seam, e.g. `httpx.MockTransport`. |
145
+ | `http_client` | `None` | Your own `httpx.Client` / `httpx.AsyncClient`. It is never closed by the SDK, and its own timeout applies. |
146
+
147
+ | Method | Description |
148
+ | --- | --- |
149
+ | `get_latest_rates()` | Rolling last 24 hours, computed by the provider at request time. |
150
+ | `get_rates(date_from, date_to)` | Explicit window, strictly shorter than 24 hours. |
151
+ | `get_rates_for_day(day)` | `day 00:00:00` to `day 23:59:59`. One request. |
152
+ | `close()` / `aclose()` | Release connections. Idempotent. Also called by `with` / `async with`. |
153
+
154
+ Using a client after it was closed raises `ElToqueError`.
155
+
156
+ ### `RatesResponse`
157
+
158
+ | Attribute | Description |
159
+ | --- | --- |
160
+ | `data` | The decoded JSON body, **exactly as the provider sent it** (object, list, scalar or `null`). |
161
+ | `status_code` | HTTP status (SDK metadata). |
162
+ | `rate_limit` | `RateLimitInfo` with optional `limit`, `remaining`, `reset`, `retry_after` (SDK metadata). |
163
+ | `received_at` | UTC timestamp of when the SDK received the response (SDK metadata). |
164
+
165
+ ### Exceptions
166
+
167
+ ```text
168
+ ElToqueError
169
+ ├── ElToqueConfigurationError invalid token, base_url, timeout, retries
170
+ ├── ElToqueValidationError invalid arguments, detected before any request
171
+ ├── ElToqueNetworkError connection, DNS, TLS, protocol failures
172
+ ├── ElToqueTimeoutError
173
+ ├── ElToqueDecodeError 200 response that is not valid JSON, or too large
174
+ └── ElToqueAPIError has .status_code, .body_excerpt, .rate_limit, .retry_after
175
+ ├── ElToqueBadRequestError 400
176
+ ├── ElToqueAuthenticationError 401
177
+ ├── ElToqueUnprocessableError 422
178
+ ├── ElToqueRateLimitError 429
179
+ └── ElToqueServerError 5xx
180
+ ```
181
+
182
+ Other 3xx/4xx statuses raise the base `ElToqueAPIError`. See [docs/errors.md](docs/errors.md).
183
+
184
+ ## Date and time rules
185
+
186
+ - `date_from` and `date_to` must be **naive** `datetime` objects. Timezone-aware values are
187
+ rejected, because the API does not document its time zone and the SDK will not guess one.
188
+ - They are sent as `YYYY-MM-DD HH:MM:SS` (microseconds are dropped).
189
+ - `date_from` must be earlier than `date_to`, and the window must be **shorter than 24 hours**,
190
+ otherwise the API answers HTTP 400. The SDK rejects invalid windows before any request.
191
+ - `get_rates_for_day` is a convenience; it is not guaranteed to be a complete local calendar day.
192
+
193
+ ## Rate limits and retries
194
+
195
+ By default each API key may make **60 requests/minute and up to 10 requests/second**; your key
196
+ may have different limits. Over the limit the API answers `429` with a `Retry-After` header.
197
+
198
+ - Retries are **off** (`max_retries=0`). Every retry consumes quota.
199
+ - With `max_retries > 0` the SDK retries only `429`, `502`, `503`, `504` and transport failures,
200
+ never `400`, `401` or `422`.
201
+ - `Retry-After` is honored. If the server asks for longer than 30 s (or the 60 s total budget),
202
+ the error is raised instead of waiting.
203
+ - There is no background polling, caching or bulk collection. You decide when to call.
204
+
205
+ ## Known limitations
206
+
207
+ - Only one operation is documented by the provider: `GET /v1/trmi`. That is all this SDK calls.
208
+ - The provider **does not publish the schema of a successful response**, so `RatesResponse.data`
209
+ is untyped and no field (currency names, timestamps, ...) is guaranteed. Typed rate models will
210
+ come only after the schema is validated with authorized access.
211
+ - The time zone of the API is undocumented (see above).
212
+ - Installing this SDK does not give you access to the API; you need your own approved token.
213
+
214
+ ## Supported Python versions
215
+
216
+ Python 3.11, 3.12, 3.13 and 3.14. No framework is required.
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ uv sync # or: pip install -e . && pip install pytest pytest-asyncio pytest-cov ruff mypy build twine
222
+ uv run ruff check .
223
+ uv run ruff format --check .
224
+ uv run mypy src
225
+ uv run pytest # offline, no token needed
226
+ uv run python -m build
227
+ uv run twine check dist/*
228
+ ```
229
+
230
+ Optional live check, only with your own authorized token (never run in CI):
231
+
232
+ ```bash
233
+ ELTOQUE_API_TOKEN=... ELTOQUE_ALLOW_LIVE_TESTS=1 uv run pytest -m integration --no-cov
234
+ ```
235
+
236
+ ## Security and legal
237
+
238
+ - Treat your token like a password. See [docs/security.md](docs/security.md).
239
+ - This project is not affiliated with elTOQUE. When you display rates, name **elTOQUE as the
240
+ source** and describe them as **reference estimates**, as the
241
+ [terms of use](https://eltoque.com/api-tasas-de-eltoque-terminos-y-condiciones-de-uso) require.
242
+ Do not modify the data, share your key, resell the data or mass-extract it.
243
+ - The [MIT license](LICENSE) covers this SDK's source code only. It grants no rights over
244
+ elTOQUE's API, data, name or trademarks.
245
+
246
+ ## Official resources
247
+
248
+ - API landing page: <https://tasas.eltoque.com/>
249
+ - Interactive docs: <https://tasas.eltoque.com/docs/>
250
+ - OpenAPI document: <https://tasas.eltoque.com/static/swagger.json>
251
+ - What is and is not verified: [docs/api-contract.md](docs/api-contract.md)
252
+
253
+ ## Community
254
+
255
+ - Contributing guide: [CONTRIBUTING.md](CONTRIBUTING.md)
256
+ - Code of conduct: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
257
+ - Security policy: [SECURITY.md](SECURITY.md)
258
+ - Release notes: [CHANGELOG.md](CHANGELOG.md)
@@ -0,0 +1,49 @@
1
+ # elTOQUE API contract
2
+
3
+ Last validated against the official sources: **2026-10-22** (no token used; only the public
4
+ OpenAPI document was fetched).
5
+
6
+ ## Sources
7
+
8
+ - Landing page: <https://tasas.eltoque.com/>
9
+ - Interactive docs: <https://tasas.eltoque.com/docs/>
10
+ - OpenAPI JSON: <https://tasas.eltoque.com/static/swagger.json>
11
+ - API key form: <https://tasas-token.eltoque.com/>
12
+ - Terms of use: <https://eltoque.com/api-tasas-de-eltoque-terminos-y-condiciones-de-uso>
13
+
14
+ ## Verified (from the published OpenAPI document)
15
+
16
+ | Item | Value |
17
+ | --- | --- |
18
+ | Origin | `https://tasas.eltoque.com` |
19
+ | OpenAPI / API version | `3.0.0` / `1.0.beta` (not the SDK version) |
20
+ | Operations | 1: `GET /v1/trmi` (`trmiV1`) |
21
+ | Auth | HTTP bearer (`Authorization: Bearer <token>`), JWT |
22
+ | Query | `date_from`, `date_to`, optional, example `2022-10-27 00:00:01` |
23
+ | No dates | Rolling last 24 hours from request time |
24
+ | Explicit range | Must be less than 24 hours, otherwise HTTP 400 |
25
+ | Documented errors | 400, 401, 422, 429 |
26
+ | Default limits | 60 requests/minute and 10 requests/second, per API key |
27
+ | Rate-limit headers | `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on all responses |
28
+ | 429 | `Retry-After` (seconds) and JSON `{"error": "...", "limit": "60 per 1 minute"}` |
29
+
30
+ Terms highlights: name elTOQUE as the source, rates are referential, do not share your key,
31
+ do not modify the data (except to update it), one token per integrated application.
32
+
33
+ ## Unverified / unknown
34
+
35
+ - [ ] Schema of the HTTP 200 body (the OpenAPI document defines none).
36
+ - [ ] Currencies, field names, units, precision.
37
+ - [ ] Time zone of `date_from` / `date_to` and of returned timestamps.
38
+ - [ ] Behaviour when only one of `date_from` / `date_to` is sent.
39
+ - [ ] Behaviour of missing data for a requested window.
40
+ - [ ] Exact semantics of `X-RateLimit-Reset` (epoch vs. seconds remaining).
41
+
42
+ Consequence: `RatesResponse.data` returns the decoded JSON untouched. Typed rate models are
43
+ deferred until the 200 schema is validated with an authorized token (Milestone B).
44
+
45
+ ## Change log of the contract
46
+
47
+ | Date | Change |
48
+ | --- | --- |
49
+ | 2026-10-22 | Initial validation; matches `CLAUDE.md` section 2. |