Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5b5b45f
fix: stop sending the options argument as a query parameter
chipkent Oct 24, 2026
052a9ff
test: stop pook mocks leaking between REST test classes
chipkent Oct 24, 2026
a000902
ci: run unit tests on Python 3.12, 3.13 and 3.14
chipkent Oct 24, 2026
a06ef39
fix: log the topic for an invalid subscription
chipkent Oct 23, 2026
182dd9d
feat: let type checkers see model constructors
chipkent Oct 24, 2026
ce957c7
fix: correct type hints that disagree with the API
chipkent Oct 24, 2026
89df459
fix: include the HTTP status in BadResponse
chipkent Oct 24, 2026
ab20477
ci: run unit tests on free-threaded Python 3.14t
chipkent Oct 24, 2026
0f6a9d7
feat: let type checkers see model constructors
chipkent Oct 24, 2026
81b08a3
fix: match WebSocket wildcard topics on the exact channel
chipkent Oct 24, 2026
8da79e1
feat: ship py.typed so the package is checked as typed
chipkent Oct 24, 2026
52c0ab5
feat: add list_funding_conditions for /fed/v1/funding-conditions
chipkent Oct 23, 2026
6ead873
feat: add get_daily_open_close_crypto for /v1/open-close/crypto/{from…
chipkent Oct 23, 2026
1f2d987
fix: keep pending WebSocket subscriptions across a reconnect
chipkent Oct 24, 2026
90a5fb6
fix: make WebSocket subscribe and unsubscribe thread-safe
chipkent Oct 24, 2026
0c0a16c
feat: send WebSocket subscription changes without waiting for recv
chipkent Oct 24, 2026
6c8a38d
feat: add WebSocketClient.set_subscriptions() and resubscribe()
chipkent Oct 24, 2026
217eec5
feat: add opt-in WebSocket reconnect options
chipkent Oct 24, 2026
9b1226d
feat: add raise_on_status to RESTClient
chipkent Oct 24, 2026
7033403
Merge branch 'feat/py-typed' into feat/raw-overloads
chipkent Oct 24, 2026
cc5cd77
feat: overload REST methods on raw so callers get precise return types
chipkent Oct 24, 2026
b037345
test: type-check a strict user of the public API
chipkent Oct 24, 2026
a80042c
Merge #4 fix: log the topic for an invalid subscription
chipkent Oct 24, 2026
a14c44b
Merge #7 feat: add funding-conditions and crypto daily open/close end…
chipkent Oct 24, 2026
628d925
Merge #8 fix: stop sending the options argument as a query parameter
chipkent Oct 24, 2026
5557065
Merge #9 test: stop pook mocks leaking between REST test classes
chipkent Oct 24, 2026
bfb9e98
Merge #10 ci: run unit tests on Python 3.12, 3.13 and 3.14
chipkent Oct 24, 2026
4abeae7
Merge #11 ci: run unit tests on free-threaded Python 3.14t
chipkent Oct 24, 2026
e0d513d
Merge #12 fix: include the HTTP status in BadResponse
chipkent Oct 24, 2026
e4e0ec5
Merge #13 feat: add raise_on_status to RESTClient
chipkent Oct 24, 2026
a910bf6
Merge #14 fix: correct type hints that disagree with the API
chipkent Oct 24, 2026
bc53f6a
Merge #15 feat: let type checkers see model constructors
chipkent Oct 24, 2026
cbbb584
Merge #16 feat: ship py.typed so the package is checked as typed
chipkent Oct 24, 2026
8e2a47d
Merge #17 feat: overload REST methods on raw so callers get precise r…
chipkent Oct 24, 2026
931833a
Merge #18 fix: match WebSocket wildcard topics on the exact channel
chipkent Oct 24, 2026
56d493b
Merge #19 fix: keep pending WebSocket subscriptions across a reconnect
chipkent Oct 24, 2026
819f5d0
Merge #20 fix: make WebSocket subscribe and unsubscribe thread-safe
chipkent Oct 24, 2026
f344c9c
Merge #21 feat: send WebSocket subscription changes without waiting f…
chipkent Oct 24, 2026
59a191a
Merge #22 feat: add WebSocketClient.set_subscriptions() and resubscri…
chipkent Oct 24, 2026
7f8913d
Merge #23 feat: add opt-in WebSocket reconnect options
chipkent Oct 24, 2026
e818c9c
feat: add WebSocketClient.request_close()
chipkent Oct 24, 2026
fa67617
Merge #24 feat: add WebSocketClient.request_close()
chipkent Oct 24, 2026
46d13cc
Rebuild deephaven-fixes as the merge of the submitted series (#4, #7-…
chipkent Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
tags:
- v*
branches:
- '**'
- v1
pull_request:
permissions:
contents: read
Expand Down
39 changes: 11 additions & 28 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
tags:
- v*
branches:
- '**'
- v1
pull_request:
permissions:
contents: read
Expand Down Expand Up @@ -33,39 +33,22 @@ jobs:
- name: Unit tests
run: make test
test-free-threaded:
strategy:
fail-fast: false
matrix:
python-version: ['3.13t', '3.14t']
runs-on: ubuntu-latest
name: ubuntu-latest Unit test ${{ matrix.python-version }} (PYTHON_GIL=0)
name: ubuntu-latest Unit test 3.14t (PYTHON_GIL=0)
env:
PYTHON_GIL: '0'
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup Poetry
# with the runner's own Python: some of Poetry's dependencies (e.g.
# cryptography) don't build on free-threaded 3.13
run: pipx install poetry
python-version: '3.14t'
- name: Install pypi deps
# orjson, a dev dependency used only by the examples, does not support
# free-threaded Python, so install the package and the test dependency
# (pook) at their locked versions instead of `poetry install`
run: |
poetry env use python${{ matrix.python-version }}
poetry install --only main
poetry run python - > "$RUNNER_TEMP/constraints.txt" <<'EOF'
import tomllib
with open("poetry.lock", "rb") as f:
for p in tomllib.load(f)["package"]:
print(f"{p['name']}=={p['version']}")
EOF
poetry run pip install pook -c "$RUNNER_TEMP/constraints.txt"
# the tests need only the package and pook, not the dev tools
run: pip install -e . pook
- name: Check that the GIL is off
run: python -c "import sys; assert not sys._is_gil_enabled(), sys.version"
- name: Unit tests
env:
PYTHON_GIL: '0'
run: |
poetry run python -c 'import sys; assert not sys._is_gil_enabled(), sys.version'
make test
python -m unittest discover -s test_rest
python -m unittest discover -s test_websocket
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The OpenAPI spec at https://api.massive.com/openapi is the source of truth for p
- Single-object endpoints are named `get_<...>` and call `self._get(...)`.
- Every query param is a keyword argument defaulting to `None`. Write range filters as `x_gt`, `x_gte`, `x_lt`, `x_lte`, `x_any_of`; `_get_params` turns them into `x.gt` and so on.
- Always end the signature with `params: Optional[Dict[str, Any]] = None, raw: bool = False, options: Optional[RequestOptionBuilder] = None`.
- Precede the method with three `@overload`s that copy its signature, so callers get the precise type for each value of `raw`: `raw: Literal[False] = False` returns the model type (`Iterator[Model]`, `List[Model]` or `Model`); `*, raw: Literal[True]` (keyword-only) returns `HTTPResponse`; and `raw: bool = False` returns the full `Union`. Copy an existing method, such as `list_aggs` in `massive/rest/aggs.py`.
4. **New domain only.** Create a `BaseClient` subclass and add it to `RESTClient`'s bases in `massive/rest/__init__.py`.
5. **Tests, examples, docs.** For core endpoints, add a fixture under `test_rest/mocks/<url path>.json`, a test in `test_rest/test_<domain>.py`, an example in `examples/rest/`, and an `automethod` entry in `docs/source/*.rst`.
6. Run `make lint` and `make test`.
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ style:
## Check static types
static:
poetry run mypy massive test_* examples
cd test_typing && poetry run mypy --strict consumer.py
@# From test_rest/, massive resolves to the installed package, as it does
@# for users, so this also fails if the package stops shipping py.typed.
cd test_rest && poetry run mypy --strict typing_consumer.py

## Check code style and static types
lint: style static
Expand Down
4 changes: 2 additions & 2 deletions docs/source/WebSocket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ Set subscriptions
.. automethod:: massive.WebSocketClient.set_subscriptions

============================
Unsubscribe all
Resubscribe
============================
.. automethod:: massive.WebSocketClient.unsubscribe_all
.. automethod:: massive.WebSocketClient.resubscribe

============================
Close
Expand Down
27 changes: 7 additions & 20 deletions massive/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Optional

# the package star-imports this module; keep Optional out of its namespace
__all__ = ["AuthError", "BadResponse"]


class AuthError(Exception):
"""
Expand All @@ -13,26 +16,10 @@ class BadResponse(Exception):
"""
Non-200 response from API

The exception message (``str(exc)`` and ``exc.args[0]``) is the response
body, as before. The HTTP status code and the decoded body are also
available as attributes, so callers can tell, for example, a 403 from a
429 without parsing the message.

:param message: The exception message, normally the response body.
:param status: The HTTP status code of the response, if known.
:param body: The decoded response body. Defaults to ``message``.
:ivar status: The HTTP status code of the response, or ``None`` if the
exception was raised without one.
:ivar body: The decoded response body.
The message is the response body. ``status`` is the HTTP status code, or
None if not given.
"""

def __init__(
self,
message: str,
*,
status: Optional[int] = None,
body: Optional[str] = None,
):
super().__init__(message)
def __init__(self, *args: object, status: Optional[int] = None):
super().__init__(*args)
self.status = status
self.body = message if body is None else body
6 changes: 5 additions & 1 deletion massive/modelclass.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import inspect
import sys
import typing
from dataclasses import dataclass

if typing.TYPE_CHECKING:
from typing_extensions import dataclass_transform
if sys.version_info >= (3, 11):
from typing import dataclass_transform
else:
from typing_extensions import dataclass_transform
else:

def dataclass_transform(**kwargs):
Expand Down
6 changes: 6 additions & 0 deletions massive/rest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(
ca_certs: Optional[str] = None,
proxy: Optional[str] = None,
connection_pool_kw: Optional[Dict[str, Any]] = None,
raise_on_status: bool = True,
):
"""
:param maxsize: Connections kept open per host, for reuse across threads.
Expand All @@ -75,6 +76,9 @@ def __init__(
Environment proxy variables (HTTPS_PROXY etc.) are not used.
:param connection_pool_kw: Other urllib3 PoolManager options. They override
the client's defaults.
:param raise_on_status: When a retried status (413, 429, 499, 500, 502, 503,
504) is still returned after the last retry: if True, urllib3 raises
MaxRetryError; if False, the client raises BadResponse with that status.
"""
# resolve once, so the main and vx clients share it and warn only once
ca_certs = ca_certs or ca_bundle_path(logger)
Expand All @@ -93,6 +97,7 @@ def __init__(
ca_certs=ca_certs,
proxy=proxy,
connection_pool_kw=connection_pool_kw,
raise_on_status=raise_on_status,
)
self.vx = VXClient(
api_key=api_key,
Expand All @@ -109,4 +114,5 @@ def __init__(
ca_certs=ca_certs,
proxy=proxy,
connection_pool_kw=connection_pool_kw,
raise_on_status=raise_on_status,
)
Loading
Loading