diff --git a/CHANGELOG.md b/CHANGELOG.md index 48bccf1..9996c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.63.0 - 2025-09-02 + +#### Enhancements +- Upgraded `databento-dbn` to 0.41.0 + +#### Bug fixes +- Fixed an issue where calling `Live.stop()` would not clean up the client state once the socket is closed + ## 0.62.0 - 2025-08-19 This release delivers a number of breaking changes to the Python interface for DBN records to provide a cleaner and more consistent API. diff --git a/databento/historical/api/batch.py b/databento/historical/api/batch.py index a74ed8d..5928168 100644 --- a/databento/historical/api/batch.py +++ b/databento/historical/api/batch.py @@ -96,12 +96,12 @@ def submit_job( schema : Schema or str {'mbo', 'mbp-1', 'mbp-10', 'trades', 'tbbo', 'ohlcv-1s', 'ohlcv-1m', 'ohlcv-1h', 'ohlcv-1d', 'definition', 'statistics', 'status'}, default 'trades' # noqa The data record schema for the request. start : pd.Timestamp, datetime, date, str, or int - The start of the request time range (inclusive). + The inclusive start of the request range. Filters on `ts_recv` if it exists in the schema, otherwise `ts_event`. Assumes UTC as timezone unless passed a tz-aware object. If an integer is passed, then this represents nanoseconds since the UNIX epoch. end : pd.Timestamp, datetime, date, str, or int, optional - The end of the request time range (exclusive). + The exclusive end of the request range. Filters on `ts_recv` if it exists in the schema, otherwise `ts_event`. Assumes UTC as timezone unless passed a tz-aware object. If an integer is passed, then this represents nanoseconds since the UNIX epoch. diff --git a/databento/historical/api/metadata.py b/databento/historical/api/metadata.py index 3869170..4fcf6f4 100644 --- a/databento/historical/api/metadata.py +++ b/databento/historical/api/metadata.py @@ -67,10 +67,10 @@ def list_datasets( Parameters ---------- start_date : date or str, optional - The start date (UTC) for the request range. + The inclusive UTC start date of the request range. If `None` then first date available. end_date : date or str, optional - The end date (UTC) for the request range. + The exclusive UTC end date of the request range. If `None` then last date available. Returns @@ -202,10 +202,10 @@ def get_dataset_condition( dataset : Dataset or str The dataset code (string identifier) for the request. start_date : date or str, optional - The start date (UTC) for the request range. + The inclusive UTC start date of the request range. If `None` then first date available. end_date : date or str, optional - The end date (UTC) for the request range. + The inclusive UTC end date of the request range. If `None` then last date available. Returns @@ -279,11 +279,11 @@ def get_record_count( dataset : Dataset or str The dataset code for the request. start : pd.Timestamp, datetime, date, str, or int - The start datetime for the request range (inclusive). + The inclusive start of the request range. Assumes UTC as timezone unless otherwise specified. If an integer is passed, then this represents nanoseconds since the UNIX epoch. end : pd.Timestamp, datetime, date, str, or int, optional - The end datetime for the request range (exclusive). + The exclusive end of the request range. Assumes UTC as timezone unless otherwise specified. If an integer is passed, then this represents nanoseconds since the UNIX epoch. Defaults to the forward filled value of `start` based on the resolution provided. @@ -347,11 +347,11 @@ def get_billable_size( dataset : Dataset or str The dataset code for the request. start : pd.Timestamp, datetime, date, str, or int - The start datetime for the request range (inclusive). + The inclusive start of the request range. Assumes UTC as timezone unless otherwise specified. If an integer is passed, then this represents nanoseconds since the UNIX epoch. end : pd.Timestamp, datetime, date, str, or int, optional - The end datetime for the request range (exclusive). + The exclusive end of the request range. Assumes UTC as timezone unless otherwise specified. If an integer is passed, then this represents nanoseconds since the UNIX epoch. Defaults to the forward filled value of `start` based on the resolution provided. @@ -417,11 +417,11 @@ def get_cost( dataset : Dataset or str The dataset code for the request. start : pd.Timestamp, datetime, date, str, or int - The start datetime for the request range (inclusive). + The inclusive start of the request range. Assumes UTC as timezone unless otherwise specified. If an integer is passed, then this represents nanoseconds since the UNIX epoch. end : pd.Timestamp, datetime, date, str, or int, optional - The end datetime for the request range (exclusive). + The exclusive end of the request range. Assumes UTC as timezone unless otherwise specified. If an integer is passed, then this represents nanoseconds since the UNIX epoch. Defaults to the forward filled value of `start` based on the resolution provided. diff --git a/databento/historical/api/symbology.py b/databento/historical/api/symbology.py index 9b59309..11001b0 100644 --- a/databento/historical/api/symbology.py +++ b/databento/historical/api/symbology.py @@ -51,9 +51,10 @@ def resolve( stype_out : SType or str, default 'instrument_id' The output symbology type to resolve to. start_date : date or str - The start date (UTC) of the request time range (inclusive). + The inclusive UTC start date of the request range. end_date : date or str, optional - The end date (UTC) of the request time range (exclusive). + The exclusive UTC end date of the request range. + Defaults to the forward filled value of `start` based on the resolution provided. Returns ------- diff --git a/databento/historical/api/timeseries.py b/databento/historical/api/timeseries.py index 67a9b80..a4b763d 100644 --- a/databento/historical/api/timeseries.py +++ b/databento/historical/api/timeseries.py @@ -60,12 +60,12 @@ def get_range( dataset : Dataset or str The dataset code (string identifier) for the request. start : pd.Timestamp, datetime, date, str, or int - The start datetime of the request time range (inclusive). + The inclusive start of the request range. Filters on `ts_recv` if it exists in the schema, otherwise `ts_event`. Assumes UTC as timezone unless passed a tz-aware object. If an integer is passed, then this represents nanoseconds since the UNIX epoch. end : pd.Timestamp, datetime, date, str, or int, optional - The end datetime of the request time range (exclusive). + The exclusive end of the request range. Filters on `ts_recv` if it exists in the schema, otherwise `ts_event`. Assumes UTC as timezone unless passed a tz-aware object. If an integer is passed, then this represents nanoseconds since the UNIX epoch. @@ -158,12 +158,12 @@ async def get_range_async( dataset : Dataset or str The dataset code (string identifier) for the request. start : pd.Timestamp, datetime, date, str, or int - The start of the request time range (inclusive). + The inclusive start of the request range. Filters on `ts_recv` if it exists in the schema, otherwise `ts_event`. Assumes UTC as timezone unless passed a tz-aware object. If an integer is passed, then this represents nanoseconds since the UNIX epoch. end : pd.Timestamp, datetime, date, str, or int, optional - The end of the request time range (exclusive). + The exclusive end of the request range. Filters on `ts_recv` if it exists in the schema, otherwise `ts_event`. Assumes UTC as timezone unless passed a tz-aware object. If an integer is passed, then this represents nanoseconds since the UNIX epoch. diff --git a/databento/live/session.py b/databento/live/session.py index abcedfb..06852e2 100644 --- a/databento/live/session.py +++ b/databento/live/session.py @@ -439,9 +439,9 @@ def stop(self) -> None: with self._lock: if self._transport is None: return - if self._transport.can_write_eof(): - self._transport.write_eof() - self._transport.close() + if self._protocol is not None: + self._protocol.disconnected.add_done_callback(lambda _: self._cleanup()) + self._loop.call_soon_threadsafe(self._transport.close) def start(self) -> None: """ @@ -516,8 +516,6 @@ def terminate(self) -> None: with self._lock: if self._transport is None: return - if self._transport.can_write_eof(): - self._transport.write_eof() self._transport.abort() self._cleanup() diff --git a/databento/version.py b/databento/version.py index 647040d..c60bb1b 100644 --- a/databento/version.py +++ b/databento/version.py @@ -1 +1 @@ -__version__ = "0.62.0" +__version__ = "0.63.0" diff --git a/pyproject.toml b/pyproject.toml index d51bb5b..c1e3b30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "databento" -version = "0.62.0" +version = "0.63.0" description = "Official Python client library for Databento" authors = [ "Databento ", @@ -32,7 +32,7 @@ aiohttp = [ {version = "^3.8.3", python = "<3.12"}, {version = "^3.9.0", python = "^3.12"} ] -databento-dbn = "~=0.40.0" +databento-dbn = "~=0.41.0" numpy = [ {version = ">=1.23.5", python = "<3.12"}, {version = ">=1.26.0", python = "^3.12"} diff --git a/tests/test_live_client.py b/tests/test_live_client.py index ba46ae2..a6df5c2 100644 --- a/tests/test_live_client.py +++ b/tests/test_live_client.py @@ -4,6 +4,7 @@ from __future__ import annotations +import asyncio import pathlib import platform import random @@ -255,6 +256,42 @@ async def test_live_connect_auth( assert message.encoding == Encoding.DBN +async def test_live_client_reuse( + mock_live_server: MockLiveServerInterface, + live_client: client.Live, +) -> None: + """ + Test that calling stop will *eventually* close a the connection and trigger + a cleanup of the client state. + """ + live_client.subscribe( + dataset=Dataset.GLBX_MDP3, + schema=Schema.MBO, + ) + + await mock_live_server.wait_for_message_of_type( + message_type=gateway.AuthenticationRequest, + ) + + live_client.start() + live_client.stop() + + await asyncio.sleep(1) + + live_client.subscribe( + dataset=Dataset.GLBX_MDP3, + schema=Schema.MBP_1, + ) + + await mock_live_server.wait_for_message_of_type( + message_type=gateway.AuthenticationRequest, + ) + + live_client.start() + live_client.stop() + await live_client.wait_for_close() + + async def test_live_connect_auth_with_heartbeat_interval( mock_live_server: MockLiveServerInterface, test_live_api_key: str,