narwhalapi 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.
- narwhalapi-0.1.0/CHANGELOG.md +14 -0
- narwhalapi-0.1.0/LICENSE +21 -0
- narwhalapi-0.1.0/PKG-INFO +157 -0
- narwhalapi-0.1.0/README.md +132 -0
- narwhalapi-0.1.0/hatch_build.py +14 -0
- narwhalapi-0.1.0/pyproject.toml +43 -0
- narwhalapi-0.1.0/src/narwhalapi/__init__.py +17 -0
- narwhalapi-0.1.0/src/narwhalapi/_client.py +122 -0
- narwhalapi-0.1.0/src/narwhalapi/_deadline.py +180 -0
- narwhalapi-0.1.0/src/narwhalapi/_decode.py +67 -0
- narwhalapi-0.1.0/src/narwhalapi/_generated/__init__.py +3 -0
- narwhalapi-0.1.0/src/narwhalapi/_generated/models.py +3697 -0
- narwhalapi-0.1.0/src/narwhalapi/_generated/operations.py +6506 -0
- narwhalapi-0.1.0/src/narwhalapi/_meta.py +40 -0
- narwhalapi-0.1.0/src/narwhalapi/_pagination.py +47 -0
- narwhalapi-0.1.0/src/narwhalapi/_transport.py +346 -0
- narwhalapi-0.1.0/src/narwhalapi/_version.py +1 -0
- narwhalapi-0.1.0/src/narwhalapi/codes.py +33 -0
- narwhalapi-0.1.0/src/narwhalapi/errors.py +90 -0
- narwhalapi-0.1.0/src/narwhalapi/models.py +333 -0
- narwhalapi-0.1.0/src/narwhalapi/py.typed +0 -0
- narwhalapi-0.1.0/src/narwhalapi/webhooks.py +60 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-09
|
|
4
|
+
|
|
5
|
+
- Initial typed synchronous and asynchronous clients for the stable API: economics,
|
|
6
|
+
FX, calendars, physical palm oil, spot prices (`commodities.spot_price`), events,
|
|
7
|
+
webhook destinations and calendar subscriptions.
|
|
8
|
+
- `nw.unstable` holds operations not yet in the live API; it is empty in this release.
|
|
9
|
+
- Exact decimals, typed errors, bounded retries, page iterators, response metadata,
|
|
10
|
+
and local webhook signature verification.
|
|
11
|
+
- `codes.PLAN_UPGRADE_REQUIRED` for the 403 returned when a request reaches before the
|
|
12
|
+
plan's history window.
|
|
13
|
+
- The release also covers prediction market reads, FX and commodity bar history, and physical commodity reference price series.
|
|
14
|
+
- Spot `bid` and `ask` are optional because the API may omit them.
|
narwhalapi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Narwhal API Team
|
|
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.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: narwhalapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed clients for the Narwhal API
|
|
5
|
+
Project-URL: Homepage, https://narwhalapi.com
|
|
6
|
+
Author-email: Narwhal API Team <support@narwhalapi.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: api,decimal,economics,exchange-rates,fx,holidays,inflation,palm-oil,sdk
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Narwhal API for Python
|
|
27
|
+
|
|
28
|
+
Typed synchronous and asynchronous clients for economics, foreign exchange, calendars,
|
|
29
|
+
physical palm oil, physical commodity reference prices, spot gold and silver, prediction markets, FX and commodity bar history, and webhook delivery. Python 3.10 or newer. Install with `pip install narwhalapi`.
|
|
30
|
+
|
|
31
|
+
Set `NARWHAL_API_KEY` in the environment. Keep keys on the server. The default base URL is
|
|
32
|
+
`https://api.narwhalapi.com`; override it with `NARWHAL_BASE_URL` or `base_url=`.
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```pycon
|
|
37
|
+
>>> from decimal import Decimal
|
|
38
|
+
>>> from narwhalapi import Narwhal
|
|
39
|
+
>>> with Narwhal() as nw:
|
|
40
|
+
... cpi = nw.economics.cpi("USA")
|
|
41
|
+
... rates = nw.fx.rates("USD", currencies=["EUR", "JPY"])
|
|
42
|
+
... days = list(nw.fx.rate_history_iter(
|
|
43
|
+
... "USD", currencies=["EUR"], start_date="2026-08-20", end_date="2026-08-24"
|
|
44
|
+
... ))
|
|
45
|
+
>>> isinstance(cpi.value, Decimal)
|
|
46
|
+
True
|
|
47
|
+
>>> isinstance(rates.rates[0].rate, Decimal)
|
|
48
|
+
True
|
|
49
|
+
>>> all(isinstance(rate.rate, Decimal) for day in days for rate in day.rates)
|
|
50
|
+
True
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Decimals remain exact `Decimal` values. Use `format(value, "f")` to display fixed-point
|
|
55
|
+
wire text, including very small values. Decimal inputs accept `Decimal` or `str`;
|
|
56
|
+
other numeric types raise `TypeError`. Responses accept only decimal strings matching
|
|
57
|
+
`^-?\d+(\.\d+)?$` with ASCII digits. Dates and timestamps remain strings.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from narwhalapi import AsyncNarwhal, errors
|
|
61
|
+
|
|
62
|
+
async def load_rates():
|
|
63
|
+
async with AsyncNarwhal() as nw:
|
|
64
|
+
try:
|
|
65
|
+
return await nw.fx.rates("USD", currencies=["IDR", "SGD"])
|
|
66
|
+
except errors.RateLimited as error:
|
|
67
|
+
if error.is_quota_exhausted:
|
|
68
|
+
return None
|
|
69
|
+
raise
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Share a synchronous client across threads. Use one async client per event loop.
|
|
73
|
+
Context managers close connections; explicit `close()` and `await aclose()` also work.
|
|
74
|
+
|
|
75
|
+
## Operations and types
|
|
76
|
+
|
|
77
|
+
Namespaces are `economics`, `fx`, `calendars`, `commodities`, `events`, `webhooks`, and
|
|
78
|
+
`calendar_subscriptions`. Operations not yet in the live API appear under `nw.unstable`;
|
|
79
|
+
there are none today.
|
|
80
|
+
Models and known string constants are available through `narwhalapi.models`,
|
|
81
|
+
for example `from narwhalapi.models import Cpi`.
|
|
82
|
+
Unknown enum strings are retained. Union variants are frozen dataclasses with a `kind`;
|
|
83
|
+
its `kind` is the variant model name (for example `PalmoilSchedulePrice`).
|
|
84
|
+
An unknown variant has `kind="unknown"` and keeps its JSON in `raw`.
|
|
85
|
+
New response fields are ignored.
|
|
86
|
+
|
|
87
|
+
Required path arguments may be positional. Query and body arguments are keyword-only:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
nw.fx.convert_currency(from_="USD", to="IDR", amount="100.00")
|
|
91
|
+
nw.fx.rate_history("USD", currencies=["IDR"], start_date="2026-01-01", end_date="2026-01-31")
|
|
92
|
+
spot = nw.commodities.spot_price("XAU", "USD")
|
|
93
|
+
nw.webhooks.update_destination(
|
|
94
|
+
"destination-id", url="https://example.test/webhook",
|
|
95
|
+
countries=["IDN"], families=["prices"], enabled=False,
|
|
96
|
+
)
|
|
97
|
+
feed_url = nw.calendar_subscriptions.feed_url("subscription-token")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Updates require `enabled` explicitly. Creation sends only supplied fields; omitted fields
|
|
101
|
+
and explicit `None` remain distinct in request bodies. `feed_url()` makes no request.
|
|
102
|
+
Lists use the documented repeated or comma-joined encoding. Scalar strings, empty list
|
|
103
|
+
items and items containing commas are rejected for list query parameters.
|
|
104
|
+
|
|
105
|
+
Every paginated operation has an `_iter` method, yielding items lazily (use `async for`
|
|
106
|
+
with an async client). Filters persist across pages; `limit` is the page size. Cursor expiry
|
|
107
|
+
raises `errors.CursorExpired`, including after earlier items were yielded.
|
|
108
|
+
|
|
109
|
+
Top-level `meta` contains optional `request_id`, `quota`, and `short` values from response
|
|
110
|
+
headers. Each rate limit has optional `limit`, `remaining`, and `reset` fields.
|
|
111
|
+
|
|
112
|
+
## Failures and retries
|
|
113
|
+
|
|
114
|
+
All client failures inherit `errors.NarwhalError`. `errors.ApiError` exposes `status`,
|
|
115
|
+
`code`, `title`, `detail`, `request_id`, `instance`, `retry_after`, and `raw_body` bytes.
|
|
116
|
+
Typed subclasses cover common statuses and cursor expiry. Known codes are exported as
|
|
117
|
+
uppercase constants in `narwhalapi.codes`; codes also accept future strings.
|
|
118
|
+
`TransportError.cause` preserves the underlying error. `DecodeError.raw_body` keeps an
|
|
119
|
+
invalid success response as the original bytes. Import exceptions directly, for example
|
|
120
|
+
`from narwhalapi.errors import RateLimited`. API errors display server fields as
|
|
121
|
+
`HTTP <status> <code>: <title> (request_id <id>)`. All SDK errors support pickling.
|
|
122
|
+
Client representations exclude credentials.
|
|
123
|
+
|
|
124
|
+
Defaults are a 30-second attempt timeout, a 10-second connect timeout, two retries, and a
|
|
125
|
+
30-second maximum retry wait. Only short-window 429s retry on any method. A 503 retries
|
|
126
|
+
on GET; a transport failure retries on GET only before any response. Monthly quota
|
|
127
|
+
exhaustion never retries. Redirects are not followed.
|
|
128
|
+
Use `max_retries=0` to disable retries. Both clients honor standard proxy and certificate
|
|
129
|
+
environment variables, including `HTTPS_PROXY` and `SSL_CERT_FILE`; supply `proxy=`
|
|
130
|
+
to select an explicit proxy. `extra_headers` cannot replace Authorization.
|
|
131
|
+
The `transport`, `clock`, `sleep`, and `random_fraction` constructor options support tests;
|
|
132
|
+
async `sleep` must be awaitable.
|
|
133
|
+
|
|
134
|
+
## Webhooks
|
|
135
|
+
|
|
136
|
+
`from narwhalapi.webhooks import verify` verifies **exact raw bytes** before decoding.
|
|
137
|
+
It accepts a nonempty secret string or a nonempty list of nonempty secrets during
|
|
138
|
+
rotation. Empty secrets raise `ValueError` before verification. Timestamp tolerance is
|
|
139
|
+
300 seconds by default. Use the returned `event_id` as the idempotency key and store it
|
|
140
|
+
before applying the event. Future schema versions still decode.
|
|
141
|
+
|
|
142
|
+
FastAPI:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
raw_body = await request.body()
|
|
146
|
+
event = verify(raw_body, request.headers, webhook_secret)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Flask:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
raw_body = request.get_data(cache=True)
|
|
153
|
+
event = verify(raw_body, request.headers, webhook_secret)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Do not parse and re-serialize the body before verification. A verification failure raises
|
|
157
|
+
`errors.WebhookVerificationError` with a `reason`. Neither framework is a package dependency.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Narwhal API for Python
|
|
2
|
+
|
|
3
|
+
Typed synchronous and asynchronous clients for economics, foreign exchange, calendars,
|
|
4
|
+
physical palm oil, physical commodity reference prices, spot gold and silver, prediction markets, FX and commodity bar history, and webhook delivery. Python 3.10 or newer. Install with `pip install narwhalapi`.
|
|
5
|
+
|
|
6
|
+
Set `NARWHAL_API_KEY` in the environment. Keep keys on the server. The default base URL is
|
|
7
|
+
`https://api.narwhalapi.com`; override it with `NARWHAL_BASE_URL` or `base_url=`.
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```pycon
|
|
12
|
+
>>> from decimal import Decimal
|
|
13
|
+
>>> from narwhalapi import Narwhal
|
|
14
|
+
>>> with Narwhal() as nw:
|
|
15
|
+
... cpi = nw.economics.cpi("USA")
|
|
16
|
+
... rates = nw.fx.rates("USD", currencies=["EUR", "JPY"])
|
|
17
|
+
... days = list(nw.fx.rate_history_iter(
|
|
18
|
+
... "USD", currencies=["EUR"], start_date="2026-08-20", end_date="2026-08-24"
|
|
19
|
+
... ))
|
|
20
|
+
>>> isinstance(cpi.value, Decimal)
|
|
21
|
+
True
|
|
22
|
+
>>> isinstance(rates.rates[0].rate, Decimal)
|
|
23
|
+
True
|
|
24
|
+
>>> all(isinstance(rate.rate, Decimal) for day in days for rate in day.rates)
|
|
25
|
+
True
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Decimals remain exact `Decimal` values. Use `format(value, "f")` to display fixed-point
|
|
30
|
+
wire text, including very small values. Decimal inputs accept `Decimal` or `str`;
|
|
31
|
+
other numeric types raise `TypeError`. Responses accept only decimal strings matching
|
|
32
|
+
`^-?\d+(\.\d+)?$` with ASCII digits. Dates and timestamps remain strings.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from narwhalapi import AsyncNarwhal, errors
|
|
36
|
+
|
|
37
|
+
async def load_rates():
|
|
38
|
+
async with AsyncNarwhal() as nw:
|
|
39
|
+
try:
|
|
40
|
+
return await nw.fx.rates("USD", currencies=["IDR", "SGD"])
|
|
41
|
+
except errors.RateLimited as error:
|
|
42
|
+
if error.is_quota_exhausted:
|
|
43
|
+
return None
|
|
44
|
+
raise
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Share a synchronous client across threads. Use one async client per event loop.
|
|
48
|
+
Context managers close connections; explicit `close()` and `await aclose()` also work.
|
|
49
|
+
|
|
50
|
+
## Operations and types
|
|
51
|
+
|
|
52
|
+
Namespaces are `economics`, `fx`, `calendars`, `commodities`, `events`, `webhooks`, and
|
|
53
|
+
`calendar_subscriptions`. Operations not yet in the live API appear under `nw.unstable`;
|
|
54
|
+
there are none today.
|
|
55
|
+
Models and known string constants are available through `narwhalapi.models`,
|
|
56
|
+
for example `from narwhalapi.models import Cpi`.
|
|
57
|
+
Unknown enum strings are retained. Union variants are frozen dataclasses with a `kind`;
|
|
58
|
+
its `kind` is the variant model name (for example `PalmoilSchedulePrice`).
|
|
59
|
+
An unknown variant has `kind="unknown"` and keeps its JSON in `raw`.
|
|
60
|
+
New response fields are ignored.
|
|
61
|
+
|
|
62
|
+
Required path arguments may be positional. Query and body arguments are keyword-only:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
nw.fx.convert_currency(from_="USD", to="IDR", amount="100.00")
|
|
66
|
+
nw.fx.rate_history("USD", currencies=["IDR"], start_date="2026-01-01", end_date="2026-01-31")
|
|
67
|
+
spot = nw.commodities.spot_price("XAU", "USD")
|
|
68
|
+
nw.webhooks.update_destination(
|
|
69
|
+
"destination-id", url="https://example.test/webhook",
|
|
70
|
+
countries=["IDN"], families=["prices"], enabled=False,
|
|
71
|
+
)
|
|
72
|
+
feed_url = nw.calendar_subscriptions.feed_url("subscription-token")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Updates require `enabled` explicitly. Creation sends only supplied fields; omitted fields
|
|
76
|
+
and explicit `None` remain distinct in request bodies. `feed_url()` makes no request.
|
|
77
|
+
Lists use the documented repeated or comma-joined encoding. Scalar strings, empty list
|
|
78
|
+
items and items containing commas are rejected for list query parameters.
|
|
79
|
+
|
|
80
|
+
Every paginated operation has an `_iter` method, yielding items lazily (use `async for`
|
|
81
|
+
with an async client). Filters persist across pages; `limit` is the page size. Cursor expiry
|
|
82
|
+
raises `errors.CursorExpired`, including after earlier items were yielded.
|
|
83
|
+
|
|
84
|
+
Top-level `meta` contains optional `request_id`, `quota`, and `short` values from response
|
|
85
|
+
headers. Each rate limit has optional `limit`, `remaining`, and `reset` fields.
|
|
86
|
+
|
|
87
|
+
## Failures and retries
|
|
88
|
+
|
|
89
|
+
All client failures inherit `errors.NarwhalError`. `errors.ApiError` exposes `status`,
|
|
90
|
+
`code`, `title`, `detail`, `request_id`, `instance`, `retry_after`, and `raw_body` bytes.
|
|
91
|
+
Typed subclasses cover common statuses and cursor expiry. Known codes are exported as
|
|
92
|
+
uppercase constants in `narwhalapi.codes`; codes also accept future strings.
|
|
93
|
+
`TransportError.cause` preserves the underlying error. `DecodeError.raw_body` keeps an
|
|
94
|
+
invalid success response as the original bytes. Import exceptions directly, for example
|
|
95
|
+
`from narwhalapi.errors import RateLimited`. API errors display server fields as
|
|
96
|
+
`HTTP <status> <code>: <title> (request_id <id>)`. All SDK errors support pickling.
|
|
97
|
+
Client representations exclude credentials.
|
|
98
|
+
|
|
99
|
+
Defaults are a 30-second attempt timeout, a 10-second connect timeout, two retries, and a
|
|
100
|
+
30-second maximum retry wait. Only short-window 429s retry on any method. A 503 retries
|
|
101
|
+
on GET; a transport failure retries on GET only before any response. Monthly quota
|
|
102
|
+
exhaustion never retries. Redirects are not followed.
|
|
103
|
+
Use `max_retries=0` to disable retries. Both clients honor standard proxy and certificate
|
|
104
|
+
environment variables, including `HTTPS_PROXY` and `SSL_CERT_FILE`; supply `proxy=`
|
|
105
|
+
to select an explicit proxy. `extra_headers` cannot replace Authorization.
|
|
106
|
+
The `transport`, `clock`, `sleep`, and `random_fraction` constructor options support tests;
|
|
107
|
+
async `sleep` must be awaitable.
|
|
108
|
+
|
|
109
|
+
## Webhooks
|
|
110
|
+
|
|
111
|
+
`from narwhalapi.webhooks import verify` verifies **exact raw bytes** before decoding.
|
|
112
|
+
It accepts a nonempty secret string or a nonempty list of nonempty secrets during
|
|
113
|
+
rotation. Empty secrets raise `ValueError` before verification. Timestamp tolerance is
|
|
114
|
+
300 seconds by default. Use the returned `event_id` as the idempotency key and store it
|
|
115
|
+
before applying the event. Future schema versions still decode.
|
|
116
|
+
|
|
117
|
+
FastAPI:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
raw_body = await request.body()
|
|
121
|
+
event = verify(raw_body, request.headers, webhook_secret)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Flask:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
raw_body = request.get_data(cache=True)
|
|
128
|
+
event = verify(raw_body, request.headers, webhook_secret)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Do not parse and re-serialize the body before verification. A verification failure raises
|
|
132
|
+
`errors.WebhookVerificationError` with a `reason`. Neither framework is a package dependency.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Keep repository ignore files out of the source distribution."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CustomBuildHook(BuildHookInterface):
|
|
9
|
+
def initialize(self, version, build_data):
|
|
10
|
+
if self.target_name == "sdist":
|
|
11
|
+
forced = build_data["force_include"]
|
|
12
|
+
for path in list(forced):
|
|
13
|
+
if Path(path).name in {".gitignore", ".hgignore"}:
|
|
14
|
+
del forced[path]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "narwhalapi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Typed clients for the Narwhal API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [{ name = "Narwhal API Team", email = "support@narwhalapi.com" }]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
15
|
+
keywords = [
|
|
16
|
+
"api", "sdk", "economics", "fx", "exchange-rates", "inflation", "holidays",
|
|
17
|
+
"palm-oil", "decimal",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Topic :: Office/Business :: Financial",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
|
+
"License :: OSI Approved :: MIT License",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://narwhalapi.com"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/narwhalapi"]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.sdist]
|
|
41
|
+
include = ["src/narwhalapi", "README.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.hooks.custom]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Typed clients for the Narwhal API."""
|
|
2
|
+
|
|
3
|
+
from . import codes, errors, models
|
|
4
|
+
from ._client import AsyncNarwhal, Narwhal
|
|
5
|
+
from ._meta import RateLimit, ResponseMeta
|
|
6
|
+
from ._version import __version__
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AsyncNarwhal",
|
|
10
|
+
"Narwhal",
|
|
11
|
+
"RateLimit",
|
|
12
|
+
"ResponseMeta",
|
|
13
|
+
"__version__",
|
|
14
|
+
"codes",
|
|
15
|
+
"errors",
|
|
16
|
+
"models",
|
|
17
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Connection-owning synchronous and asynchronous clients."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
7
|
+
from types import TracebackType
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from ._generated.operations import (
|
|
13
|
+
LIVE_SPEC_SHA,
|
|
14
|
+
AsyncStableOperations,
|
|
15
|
+
StableOperations,
|
|
16
|
+
)
|
|
17
|
+
from ._transport import AsyncTransport, Settings, SyncTransport
|
|
18
|
+
|
|
19
|
+
SyncClient = TypeVar("SyncClient", bound="Narwhal")
|
|
20
|
+
AsyncClient = TypeVar("AsyncClient", bound="AsyncNarwhal")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Narwhal(StableOperations):
|
|
24
|
+
"""Share one client across threads and close it when finished."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
api_key: str | None = None,
|
|
29
|
+
*,
|
|
30
|
+
base_url: str | None = None,
|
|
31
|
+
proxy: str | None = None,
|
|
32
|
+
timeout: float = 30,
|
|
33
|
+
max_retries: int = 2,
|
|
34
|
+
max_retry_wait: float = 30,
|
|
35
|
+
extra_headers: Mapping[str, str] | None = None,
|
|
36
|
+
transport: httpx.BaseTransport | None = None,
|
|
37
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
38
|
+
clock: Callable[[], float] = time.monotonic,
|
|
39
|
+
random_fraction: Callable[[], float] = random.random,
|
|
40
|
+
) -> None:
|
|
41
|
+
settings = Settings(
|
|
42
|
+
api_key=api_key,
|
|
43
|
+
base_url=base_url,
|
|
44
|
+
proxy=proxy,
|
|
45
|
+
timeout=timeout,
|
|
46
|
+
max_retries=max_retries,
|
|
47
|
+
max_retry_wait=max_retry_wait,
|
|
48
|
+
extra_headers=extra_headers,
|
|
49
|
+
clock=clock,
|
|
50
|
+
random_fraction=random_fraction,
|
|
51
|
+
spec_sha=LIVE_SPEC_SHA,
|
|
52
|
+
)
|
|
53
|
+
self._transport = SyncTransport(settings, transport=transport, sleep=sleep)
|
|
54
|
+
super().__init__(self._transport)
|
|
55
|
+
|
|
56
|
+
def __repr__(self) -> str:
|
|
57
|
+
return "Narwhal(api_key=<redacted>)"
|
|
58
|
+
|
|
59
|
+
def close(self) -> None:
|
|
60
|
+
self._transport.http.close()
|
|
61
|
+
|
|
62
|
+
def __enter__(self: SyncClient) -> SyncClient:
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(
|
|
66
|
+
self,
|
|
67
|
+
exc_type: type[BaseException] | None,
|
|
68
|
+
exc: BaseException | None,
|
|
69
|
+
tb: TracebackType | None,
|
|
70
|
+
) -> None:
|
|
71
|
+
self.close()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AsyncNarwhal(AsyncStableOperations):
|
|
75
|
+
"""Use one client per event loop and close it with async with."""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
api_key: str | None = None,
|
|
80
|
+
*,
|
|
81
|
+
base_url: str | None = None,
|
|
82
|
+
proxy: str | None = None,
|
|
83
|
+
timeout: float = 30,
|
|
84
|
+
max_retries: int = 2,
|
|
85
|
+
max_retry_wait: float = 30,
|
|
86
|
+
extra_headers: Mapping[str, str] | None = None,
|
|
87
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
88
|
+
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
|
89
|
+
clock: Callable[[], float] = time.monotonic,
|
|
90
|
+
random_fraction: Callable[[], float] = random.random,
|
|
91
|
+
) -> None:
|
|
92
|
+
settings = Settings(
|
|
93
|
+
api_key=api_key,
|
|
94
|
+
base_url=base_url,
|
|
95
|
+
proxy=proxy,
|
|
96
|
+
timeout=timeout,
|
|
97
|
+
max_retries=max_retries,
|
|
98
|
+
max_retry_wait=max_retry_wait,
|
|
99
|
+
extra_headers=extra_headers,
|
|
100
|
+
clock=clock,
|
|
101
|
+
random_fraction=random_fraction,
|
|
102
|
+
spec_sha=LIVE_SPEC_SHA,
|
|
103
|
+
)
|
|
104
|
+
self._transport = AsyncTransport(settings, transport=transport, sleep=sleep)
|
|
105
|
+
super().__init__(self._transport)
|
|
106
|
+
|
|
107
|
+
def __repr__(self) -> str:
|
|
108
|
+
return "AsyncNarwhal(api_key=<redacted>)"
|
|
109
|
+
|
|
110
|
+
async def aclose(self) -> None:
|
|
111
|
+
await self._transport.http.aclose()
|
|
112
|
+
|
|
113
|
+
async def __aenter__(self: AsyncClient) -> AsyncClient:
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
async def __aexit__(
|
|
117
|
+
self,
|
|
118
|
+
exc_type: type[BaseException] | None,
|
|
119
|
+
exc: BaseException | None,
|
|
120
|
+
tb: TracebackType | None,
|
|
121
|
+
) -> None:
|
|
122
|
+
await self.aclose()
|