cexy 0.1.0.dev2__tar.gz → 0.1.0.dev3__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.
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/CHANGELOG.md +12 -1
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/PKG-INFO +5 -2
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/README.md +4 -1
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_async/transport.py +14 -1
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_sync/transport.py +14 -1
- cexy-0.1.0.dev3/cexy/_version.py +1 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/pyproject.toml +1 -1
- cexy-0.1.0.dev2/cexy/_version.py +0 -1
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/.gitignore +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/LICENSE +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/SECURITY.md +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/__init__.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_async/__init__.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_async/client.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_async/pagination.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_async/resources.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_common.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_decimal.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_enum.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_generated/__init__.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_generated/models.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_generated/operations.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_opmap.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_ratelimit.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_retry.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_sync/__init__.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_sync/client.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_sync/pagination.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/_sync/resources.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/auth.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/errors.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/models.py +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/py.typed +0 -0
- {cexy-0.1.0.dev2 → cexy-0.1.0.dev3}/cexy/ws.py +0 -0
|
@@ -3,7 +3,18 @@
|
|
|
3
3
|
All notable changes to this project are documented here. The SDK stays at 0.x until API
|
|
4
4
|
request signing (HMAC) ships; see "Versioning" in README.md.
|
|
5
5
|
|
|
6
|
-
## 0.1.0.
|
|
6
|
+
## 0.1.0.dev3 (unreleased)
|
|
7
|
+
|
|
8
|
+
### Security
|
|
9
|
+
- **Redirects are never followed**, including on a caller-supplied `http_client` created with
|
|
10
|
+
`follow_redirects=True`. Earlier versions set `follow_redirects=False` only on the client they
|
|
11
|
+
created themselves, so a caller's following client re-sent `X-API-Key` and `X-API-Secret` to a
|
|
12
|
+
redirect target (httpx strips only `Authorization`), and a 307/308 re-posted an order. A 3xx
|
|
13
|
+
was also treated as success. Every request now passes `follow_redirects=False`, and a 3xx
|
|
14
|
+
raises `CexyApiError` with code `UNEXPECTED_REDIRECT`, which is not retried. Upgrade from
|
|
15
|
+
0.1.0.dev2 or earlier if you pass your own `http_client`.
|
|
16
|
+
|
|
17
|
+
## 0.1.0.dev2 (2026-09-27)
|
|
7
18
|
|
|
8
19
|
- `JurisdictionBlockedError` (a `ForbiddenError` subclass) for `JURISDICTION_BLOCKED` / HTTP 451.
|
|
9
20
|
- README: pre-releases install with `pip install --pre cexy` (the PyPI page for 0.1.0.dev1 still says `pip install cexy`, which does not install a pre-release).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cexy
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev3
|
|
4
4
|
Summary: Python SDK for the CEXY.io exchange REST and WebSocket API
|
|
5
5
|
Project-URL: Homepage, https://cexy.io
|
|
6
6
|
Project-URL: Source, https://github.com/cexyio/cexy-python
|
|
@@ -44,7 +44,7 @@ Typed Python client for the [CEXY.io](https://cexy.io) exchange REST and WebSock
|
|
|
44
44
|
- Client-side rate limiting, cursor pagination
|
|
45
45
|
- WebSocket client with heartbeat, reconnect and a self-syncing order book
|
|
46
46
|
|
|
47
|
-
> Status: **0.1.0.
|
|
47
|
+
> Status: **0.1.0.dev3, pre-release.** The API may change before 1.0 (see [Versioning](#versioning)).
|
|
48
48
|
> Pre-releases need `--pre`: `pip install --pre cexy`.
|
|
49
49
|
|
|
50
50
|
## Install
|
|
@@ -285,6 +285,9 @@ client emits `auth_lost`; the socket stays open for public channels.
|
|
|
285
285
|
- The SDK redacts keys from `repr()`, logs and exception messages, and never sends an
|
|
286
286
|
`Authorization` header. If a server response echoes the key or secret, it is replaced
|
|
287
287
|
with `***` in the exception's message, details, fields and request id.
|
|
288
|
+
- The SDK **never follows HTTP redirects**, even on an `http_client` you created with
|
|
289
|
+
`follow_redirects=True`. A 3xx answer raises `CexyApiError` with code `UNEXPECTED_REDIRECT`
|
|
290
|
+
(not retried), so keys are never re-sent to another host and an order is never re-posted.
|
|
288
291
|
- Connections use `https://` and `wss://` only. `allow_insecure=True` permits `http://` or
|
|
289
292
|
`ws://` for a loopback host (localhost, 127.0.0.1, ::1) only, for local testing.
|
|
290
293
|
- Report vulnerabilities as described in [SECURITY.md](SECURITY.md).
|
|
@@ -8,7 +8,7 @@ Typed Python client for the [CEXY.io](https://cexy.io) exchange REST and WebSock
|
|
|
8
8
|
- Client-side rate limiting, cursor pagination
|
|
9
9
|
- WebSocket client with heartbeat, reconnect and a self-syncing order book
|
|
10
10
|
|
|
11
|
-
> Status: **0.1.0.
|
|
11
|
+
> Status: **0.1.0.dev3, pre-release.** The API may change before 1.0 (see [Versioning](#versioning)).
|
|
12
12
|
> Pre-releases need `--pre`: `pip install --pre cexy`.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
@@ -249,6 +249,9 @@ client emits `auth_lost`; the socket stays open for public channels.
|
|
|
249
249
|
- The SDK redacts keys from `repr()`, logs and exception messages, and never sends an
|
|
250
250
|
`Authorization` header. If a server response echoes the key or secret, it is replaced
|
|
251
251
|
with `***` in the exception's message, details, fields and request id.
|
|
252
|
+
- The SDK **never follows HTTP redirects**, even on an `http_client` you created with
|
|
253
|
+
`follow_redirects=True`. A 3xx answer raises `CexyApiError` with code `UNEXPECTED_REDIRECT`
|
|
254
|
+
(not retried), so keys are never re-sent to another host and an order is never re-posted.
|
|
252
255
|
- Connections use `https://` and `wss://` only. `allow_insecure=True` permits `http://` or
|
|
253
256
|
`ws://` for a loopback host (localhost, 127.0.0.1, ::1) only, for local testing.
|
|
254
257
|
- Report vulnerabilities as described in [SECURITY.md](SECURITY.md).
|
|
@@ -114,7 +114,12 @@ class AsyncTransport:
|
|
|
114
114
|
if op.auth == "api_key" and self.auth is not None:
|
|
115
115
|
self.auth.apply(op.method, url, send_headers, content)
|
|
116
116
|
try:
|
|
117
|
-
|
|
117
|
+
# Never follow redirects, even on a caller-supplied client created with
|
|
118
|
+
# follow_redirects=True: httpx would re-send X-API-Key/X-API-Secret to the target
|
|
119
|
+
# (it strips only Authorization), and a 307/308 would re-POST an order.
|
|
120
|
+
resp = await self.http.request(
|
|
121
|
+
op.method, url, params=params, content=content, headers=send_headers, follow_redirects=False
|
|
122
|
+
)
|
|
118
123
|
except httpx.TransportError as exc:
|
|
119
124
|
ambiguous = not isinstance(exc, _NOT_SENT)
|
|
120
125
|
reason = redact_text(f"{type(exc).__name__}: {exc}", self._secrets())
|
|
@@ -132,6 +137,14 @@ class AsyncTransport:
|
|
|
132
137
|
resp_headers = lower_headers(resp.headers)
|
|
133
138
|
self.limiter.update_from_headers(resp_headers)
|
|
134
139
|
logger.debug("cexy: %s %s -> %d (attempt %d)", op.method, op.path, resp.status_code, attempt)
|
|
140
|
+
if 300 <= resp.status_code < 400:
|
|
141
|
+
raise CexyApiError(
|
|
142
|
+
"UNEXPECTED_REDIRECT",
|
|
143
|
+
f"{op.method} {op.path}: the server answered with a redirect (HTTP {resp.status_code}); "
|
|
144
|
+
"the SDK does not follow redirects. Check base_url.",
|
|
145
|
+
status=resp.status_code,
|
|
146
|
+
retryable=False,
|
|
147
|
+
)
|
|
135
148
|
if resp.status_code < 400:
|
|
136
149
|
if raw:
|
|
137
150
|
return resp.content
|
|
@@ -116,7 +116,12 @@ class SyncTransport:
|
|
|
116
116
|
if op.auth == "api_key" and self.auth is not None:
|
|
117
117
|
self.auth.apply(op.method, url, send_headers, content)
|
|
118
118
|
try:
|
|
119
|
-
|
|
119
|
+
# Never follow redirects, even on a caller-supplied client created with
|
|
120
|
+
# follow_redirects=True: httpx would re-send X-API-Key/X-API-Secret to the target
|
|
121
|
+
# (it strips only Authorization), and a 307/308 would re-POST an order.
|
|
122
|
+
resp = self.http.request(
|
|
123
|
+
op.method, url, params=params, content=content, headers=send_headers, follow_redirects=False
|
|
124
|
+
)
|
|
120
125
|
except httpx.TransportError as exc:
|
|
121
126
|
ambiguous = not isinstance(exc, _NOT_SENT)
|
|
122
127
|
reason = redact_text(f"{type(exc).__name__}: {exc}", self._secrets())
|
|
@@ -134,6 +139,14 @@ class SyncTransport:
|
|
|
134
139
|
resp_headers = lower_headers(resp.headers)
|
|
135
140
|
self.limiter.update_from_headers(resp_headers)
|
|
136
141
|
logger.debug("cexy: %s %s -> %d (attempt %d)", op.method, op.path, resp.status_code, attempt)
|
|
142
|
+
if 300 <= resp.status_code < 400:
|
|
143
|
+
raise CexyApiError(
|
|
144
|
+
"UNEXPECTED_REDIRECT",
|
|
145
|
+
f"{op.method} {op.path}: the server answered with a redirect (HTTP {resp.status_code}); "
|
|
146
|
+
"the SDK does not follow redirects. Check base_url.",
|
|
147
|
+
status=resp.status_code,
|
|
148
|
+
retryable=False,
|
|
149
|
+
)
|
|
137
150
|
if resp.status_code < 400:
|
|
138
151
|
if raw:
|
|
139
152
|
return resp.content
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0.dev3"
|
cexy-0.1.0.dev2/cexy/_version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0.dev2"
|
|
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
|