From 223523cae164855fa92ec230a887400e2cb6ba95 Mon Sep 17 00:00:00 2001 From: Nick Macholl Date: Wed, 19 Nov 2025 18:51:03 -0800 Subject: [PATCH 1/6] FIX: Fix missing Ruff configuration extension --- databento/common/types.py | 15 ++++++++------- databento/live/gateway.py | 8 +++----- examples/live_smoke_test.py | 9 +++++---- examples/reference_adjustment_factors.py | 1 + examples/reference_corporate_actions.py | 1 + examples/reference_security_master_get_last.py | 1 + examples/reference_security_master_get_range.py | 1 + pyproject.toml | 1 + tests/conftest.py | 6 +++--- tests/data/generator.py | 4 ++-- tests/mockliveserver/__main__.py | 2 +- tests/mockliveserver/controller.py | 4 ++-- tests/mockliveserver/fixture.py | 4 ++-- tests/mockliveserver/server.py | 3 ++- tests/mockliveserver/source.py | 3 ++- tests/test_bento_compression.py | 1 + tests/test_bento_data_source.py | 3 ++- tests/test_common_cram.py | 1 + tests/test_common_enums.py | 11 ++++++----- tests/test_common_iterator.py | 1 + tests/test_common_parsing.py | 3 ++- tests/test_common_symbology.py | 9 +++++---- tests/test_common_validation.py | 3 ++- tests/test_historical_batch.py | 5 +++-- tests/test_historical_bento.py | 13 +++++++------ tests/test_historical_client.py | 5 +++-- tests/test_historical_data.py | 3 ++- tests/test_historical_error.py | 1 + tests/test_historical_metadata.py | 3 ++- tests/test_historical_timeseries.py | 5 +++-- tests/test_historical_warnings.py | 3 ++- tests/test_live_client.py | 8 ++++---- tests/test_live_client_reconnect.py | 2 +- tests/test_live_gateway_messages.py | 7 ++++--- tests/test_live_protocol.py | 4 ++-- tests/test_live_session.py | 1 + tests/test_reference_adjustment.py | 4 ++-- tests/test_reference_corporate.py | 4 ++-- tests/test_reference_security.py | 4 ++-- tests/test_release.py | 2 +- 40 files changed, 97 insertions(+), 72 deletions(-) diff --git a/databento/common/types.py b/databento/common/types.py index 0241661f..6727aac6 100644 --- a/databento/common/types.py +++ b/databento/common/types.py @@ -1,19 +1,20 @@ import datetime as dt import logging +import pathlib +import warnings from collections.abc import Callable from os import PathLike -import pathlib -from typing import Generic from typing import IO +from typing import Generic from typing import TypedDict from typing import TypeVar -import warnings import databento_dbn import pandas as pd from databento.common.error import BentoWarning + logger = logging.getLogger(__name__) DBNRecord = ( @@ -188,14 +189,14 @@ def write(self, data: bytes) -> None: except Exception as exc: if self._exc_fn is None: self._warn( - f"stream '{self.stream_name}' encountered an exception without an exception handler: {repr(exc)}", + f"stream '{self.stream_name}' encountered an exception without an exception handler: {exc!r}", ) else: try: self._exc_fn(exc) except Exception as inner_exc: self._warn( - f"exception callback '{self.exc_callback_name}' encountered an exception: {repr(inner_exc)}", + f"exception callback '{self.exc_callback_name}' encountered an exception: {inner_exc!r}", ) raise inner_exc from exc raise exc @@ -258,14 +259,14 @@ def call(self, record: DBNRecord) -> None: except Exception as exc: if self._exc_fn is None: self._warn( - f"callback '{self.callback_name}' encountered an exception without an exception callback: {repr(exc)}", + f"callback '{self.callback_name}' encountered an exception without an exception callback: {exc!r}", ) else: try: self._exc_fn(exc) except Exception as inner_exc: self._warn( - f"exception callback '{self.exc_callback_name}' encountered an exception: {repr(inner_exc)}", + f"exception callback '{self.exc_callback_name}' encountered an exception: {inner_exc!r}", ) raise inner_exc from exc raise exc diff --git a/databento/live/gateway.py b/databento/live/gateway.py index 414683bb..8c98e603 100644 --- a/databento/live/gateway.py +++ b/databento/live/gateway.py @@ -5,11 +5,11 @@ from io import BytesIO from operator import attrgetter from typing import SupportsBytes -from typing import TypeVar from databento_dbn import Encoding from databento_dbn import Schema from databento_dbn import SType +from typing_extensions import Self from databento.common.publishers import Dataset from databento.common.system import USER_AGENT @@ -17,8 +17,6 @@ logger = logging.getLogger(__name__) -T = TypeVar("T", bound="GatewayControl") - @dataclasses.dataclass class GatewayControl(SupportsBytes): @@ -27,9 +25,9 @@ class GatewayControl(SupportsBytes): """ @classmethod - def parse(cls: type[T], line: str | bytes) -> T: + def parse(cls: type[Self], line: str | bytes) -> Self: """ - Parse a message of type `T` from a string. + Parse a `GatewayControl` message from a string. Parameters ---------- diff --git a/examples/live_smoke_test.py b/examples/live_smoke_test.py index 5a53ef60..f4650a14 100755 --- a/examples/live_smoke_test.py +++ b/examples/live_smoke_test.py @@ -4,15 +4,16 @@ import os import typing +from databento_dbn import ErrorMsg +from databento_dbn import MBOMsg +from databento_dbn import RType +from databento_dbn import SymbolMappingMsg + from databento import Dataset from databento import Live from databento import RecordFlags from databento import Schema from databento import SType -from databento_dbn import ErrorMsg -from databento_dbn import MBOMsg -from databento_dbn import RType -from databento_dbn import SymbolMappingMsg def parse_args() -> argparse.Namespace: diff --git a/examples/reference_adjustment_factors.py b/examples/reference_adjustment_factors.py index fb2b5878..0141d325 100644 --- a/examples/reference_adjustment_factors.py +++ b/examples/reference_adjustment_factors.py @@ -1,6 +1,7 @@ from pprint import pprint import pandas as pd + from databento import Reference diff --git a/examples/reference_corporate_actions.py b/examples/reference_corporate_actions.py index 0651579a..ce265ed3 100644 --- a/examples/reference_corporate_actions.py +++ b/examples/reference_corporate_actions.py @@ -1,6 +1,7 @@ from pprint import pprint import pandas as pd + from databento import Reference diff --git a/examples/reference_security_master_get_last.py b/examples/reference_security_master_get_last.py index 324f113d..20a9d292 100644 --- a/examples/reference_security_master_get_last.py +++ b/examples/reference_security_master_get_last.py @@ -1,6 +1,7 @@ from pprint import pprint import pandas as pd + from databento import Reference diff --git a/examples/reference_security_master_get_range.py b/examples/reference_security_master_get_range.py index 2ed2b442..79660065 100644 --- a/examples/reference_security_master_get_range.py +++ b/examples/reference_security_master_get_range.py @@ -1,6 +1,7 @@ from pprint import pprint import pandas as pd + from databento import Reference diff --git a/pyproject.toml b/pyproject.toml index 72cadfdb..498d0046 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,4 +81,5 @@ testpaths = ["tests"] asyncio_mode = "auto" [tool.ruff] +extend = "../ruff.toml" target-version = "py310" diff --git a/tests/conftest.py b/tests/conftest.py index 94fb5d27..0f189143 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,14 +12,14 @@ from collections.abc import Generator from collections.abc import Iterable -import databento.live.session import pytest +from databento_dbn import Schema + +import databento.live.session from databento import historical from databento import live from databento import reference from databento.common.publishers import Dataset -from databento_dbn import Schema - from tests import TESTS_ROOT from tests.mockliveserver.fixture import MockLiveServerInterface from tests.mockliveserver.fixture import fixture_mock_live_server # noqa diff --git a/tests/data/generator.py b/tests/data/generator.py index a0c232e5..a59940fa 100644 --- a/tests/data/generator.py +++ b/tests/data/generator.py @@ -11,10 +11,10 @@ import warnings from typing import Final -import databento as db -from databento.common.publishers import Dataset from databento_dbn import Schema +import databento as db +from databento.common.publishers import Dataset from tests import TESTS_ROOT diff --git a/tests/mockliveserver/__main__.py b/tests/mockliveserver/__main__.py index ea54d595..a0873478 100644 --- a/tests/mockliveserver/__main__.py +++ b/tests/mockliveserver/__main__.py @@ -6,9 +6,9 @@ from collections import defaultdict from socket import AF_INET -from databento.common.publishers import Dataset from databento_dbn import Schema +from databento.common.publishers import Dataset from tests.mockliveserver.controller import Controller from tests.mockliveserver.source import ReplayProtocol diff --git a/tests/mockliveserver/controller.py b/tests/mockliveserver/controller.py index 437c3f6a..e9e8ab81 100644 --- a/tests/mockliveserver/controller.py +++ b/tests/mockliveserver/controller.py @@ -8,10 +8,10 @@ from collections.abc import MutableMapping from pathlib import Path -from databento.common.cram import BUCKET_ID_LENGTH -from databento.common.publishers import Dataset from databento_dbn import Schema +from databento.common.cram import BUCKET_ID_LENGTH +from databento.common.publishers import Dataset from tests.mockliveserver.server import MockLiveServerProtocol from tests.mockliveserver.source import FileReplay from tests.mockliveserver.source import ReplayProtocol diff --git a/tests/mockliveserver/fixture.py b/tests/mockliveserver/fixture.py index 802591a0..4ae4ef33 100644 --- a/tests/mockliveserver/fixture.py +++ b/tests/mockliveserver/fixture.py @@ -11,10 +11,10 @@ import pytest import pytest_asyncio -from databento.common.publishers import Dataset -from databento.live.gateway import GatewayControl from databento_dbn import Schema +from databento.common.publishers import Dataset +from databento.live.gateway import GatewayControl from tests import TESTS_ROOT diff --git a/tests/mockliveserver/server.py b/tests/mockliveserver/server.py index 36a31de2..334e0efc 100644 --- a/tests/mockliveserver/server.py +++ b/tests/mockliveserver/server.py @@ -13,6 +13,8 @@ from typing import Any from typing import Final +from databento_dbn import Schema + from databento.common import cram from databento.common.publishers import Dataset from databento.live.gateway import AuthenticationRequest @@ -23,7 +25,6 @@ from databento.live.gateway import SessionStart from databento.live.gateway import SubscriptionRequest from databento.live.gateway import parse_gateway_message -from databento_dbn import Schema from .source import ReplayProtocol diff --git a/tests/mockliveserver/source.py b/tests/mockliveserver/source.py index 9dd0cec2..1305b769 100644 --- a/tests/mockliveserver/source.py +++ b/tests/mockliveserver/source.py @@ -6,9 +6,10 @@ from typing import Protocol import zstandard -from databento.common.dbnstore import is_zstandard from databento_dbn import Compression +from databento.common.dbnstore import is_zstandard + FILE_READ_SIZE: Final = 2**10 diff --git a/tests/test_bento_compression.py b/tests/test_bento_compression.py index 21f360ae..9dd62a65 100644 --- a/tests/test_bento_compression.py +++ b/tests/test_bento_compression.py @@ -5,6 +5,7 @@ from io import BytesIO import pytest + from databento.common.dbnstore import is_dbn from databento.common.dbnstore import is_zstandard diff --git a/tests/test_bento_data_source.py b/tests/test_bento_data_source.py index c758b129..3693f6dc 100644 --- a/tests/test_bento_data_source.py +++ b/tests/test_bento_data_source.py @@ -2,10 +2,11 @@ from collections.abc import Callable import pytest +from databento_dbn import Schema + from databento.common.dbnstore import FileDataSource from databento.common.dbnstore import MemoryDataSource from databento.common.publishers import Dataset -from databento_dbn import Schema @pytest.mark.parametrize( diff --git a/tests/test_common_cram.py b/tests/test_common_cram.py index e58e16b4..22450926 100644 --- a/tests/test_common_cram.py +++ b/tests/test_common_cram.py @@ -3,6 +3,7 @@ """ import pytest + from databento.common import cram diff --git a/tests/test_common_enums.py b/tests/test_common_enums.py index 45003708..01fed07b 100644 --- a/tests/test_common_enums.py +++ b/tests/test_common_enums.py @@ -8,6 +8,12 @@ from typing import Final import pytest +from databento_dbn import Compression +from databento_dbn import DBNError +from databento_dbn import Encoding +from databento_dbn import Schema +from databento_dbn import SType + from databento.common.enums import Delivery from databento.common.enums import FeedMode from databento.common.enums import HistoricalGateway @@ -18,11 +24,6 @@ from databento.common.enums import StringyMixin from databento.common.enums import SymbologyResolution from databento.common.publishers import Dataset -from databento_dbn import Compression -from databento_dbn import DBNError -from databento_dbn import Encoding -from databento_dbn import Schema -from databento_dbn import SType NATIVE_ENUMS: Final = ( diff --git a/tests/test_common_iterator.py b/tests/test_common_iterator.py index 4d919434..e730ee85 100644 --- a/tests/test_common_iterator.py +++ b/tests/test_common_iterator.py @@ -1,6 +1,7 @@ from collections.abc import Iterable import pytest + from databento.common import iterator diff --git a/tests/test_common_parsing.py b/tests/test_common_parsing.py index aefd0761..9c463da5 100644 --- a/tests/test_common_parsing.py +++ b/tests/test_common_parsing.py @@ -7,6 +7,8 @@ import numpy as np import pandas as pd import pytest +from databento_dbn import SType + from databento.common.constants import ALL_SYMBOLS from databento.common.parsing import optional_date_to_string from databento.common.parsing import optional_datetime_to_string @@ -14,7 +16,6 @@ from databento.common.parsing import optional_symbols_list_to_list from databento.common.parsing import optional_values_list_to_string from databento.common.parsing import symbols_list_to_list -from databento_dbn import SType # Set the type to `Any` to disable mypy type checking. Used to test if functions diff --git a/tests/test_common_symbology.py b/tests/test_common_symbology.py index 8cd0264e..49467c54 100644 --- a/tests/test_common_symbology.py +++ b/tests/test_common_symbology.py @@ -10,10 +10,6 @@ import databento_dbn import pandas as pd import pytest -from databento.common.dbnstore import DBNStore -from databento.common.publishers import Dataset -from databento.common.symbology import InstrumentMap -from databento.common.symbology import MappingInterval from databento_dbn import UNDEF_TIMESTAMP from databento_dbn import Metadata from databento_dbn import Schema @@ -21,6 +17,11 @@ from databento_dbn import SymbolMappingMsg from databento_dbn import SymbolMappingMsgV1 +from databento.common.dbnstore import DBNStore +from databento.common.publishers import Dataset +from databento.common.symbology import InstrumentMap +from databento.common.symbology import MappingInterval + class SymbolMapping(NamedTuple): """ diff --git a/tests/test_common_validation.py b/tests/test_common_validation.py index fb63b03d..0b8bb9fe 100644 --- a/tests/test_common_validation.py +++ b/tests/test_common_validation.py @@ -5,6 +5,8 @@ from typing import Any import pytest +from databento_dbn import Encoding + from databento.common.validation import validate_enum from databento.common.validation import validate_file_write_path from databento.common.validation import validate_gateway @@ -12,7 +14,6 @@ from databento.common.validation import validate_path from databento.common.validation import validate_semantic_string from databento.common.validation import validate_smart_symbol -from databento_dbn import Encoding @pytest.mark.parametrize( diff --git a/tests/test_historical_batch.py b/tests/test_historical_batch.py index f0d89ab1..fe9729bd 100644 --- a/tests/test_historical_batch.py +++ b/tests/test_historical_batch.py @@ -4,12 +4,13 @@ from unittest.mock import MagicMock from zipfile import ZipFile -import databento as db import pytest import requests +from databento_dbn import Schema + +import databento as db from databento.common.publishers import Dataset from databento.historical.client import Historical -from databento_dbn import Schema def test_batch_submit_job_given_invalid_schema_raises_error( diff --git a/tests/test_historical_bento.py b/tests/test_historical_bento.py index 674d2984..6620d18d 100644 --- a/tests/test_historical_bento.py +++ b/tests/test_historical_bento.py @@ -9,23 +9,24 @@ from typing import Literal from unittest.mock import MagicMock -import databento -import databento.common.dbnstore import numpy as np import pandas as pd import pytest import pytz import zstandard +from databento_dbn import Compression +from databento_dbn import MBOMsg +from databento_dbn import Schema +from databento_dbn import SType + +import databento +import databento.common.dbnstore from databento.common.constants import SCHEMA_STRUCT_MAP from databento.common.dbnstore import DBNStore from databento.common.error import BentoError from databento.common.error import BentoWarning from databento.common.publishers import Dataset from databento.common.types import DBNRecord -from databento_dbn import Compression -from databento_dbn import MBOMsg -from databento_dbn import Schema -from databento_dbn import SType def test_from_file_when_not_exists_raises_expected_exception() -> None: diff --git a/tests/test_historical_client.py b/tests/test_historical_client.py index 6e75bc27..90418129 100644 --- a/tests/test_historical_client.py +++ b/tests/test_historical_client.py @@ -4,14 +4,15 @@ from collections.abc import Callable from unittest.mock import MagicMock -import databento as db import pytest import requests +from databento_dbn import Schema + +import databento as db from databento import DBNStore from databento import Historical from databento.common.enums import HistoricalGateway from databento.common.publishers import Dataset -from databento_dbn import Schema def test_key_returns_expected() -> None: diff --git a/tests/test_historical_data.py b/tests/test_historical_data.py index c015e51e..b535c089 100644 --- a/tests/test_historical_data.py +++ b/tests/test_historical_data.py @@ -1,5 +1,6 @@ -import databento import pytest + +import databento from databento.common.constants import SCHEMA_STRUCT_MAP diff --git a/tests/test_historical_error.py b/tests/test_historical_error.py index 1c35b5e6..7e130023 100644 --- a/tests/test_historical_error.py +++ b/tests/test_historical_error.py @@ -4,6 +4,7 @@ import aiohttp import pytest import requests + from databento.common.error import BentoClientError from databento.common.error import BentoServerError from databento.common.http import check_http_error diff --git a/tests/test_historical_metadata.py b/tests/test_historical_metadata.py index b7c60d7c..9d51220b 100644 --- a/tests/test_historical_metadata.py +++ b/tests/test_historical_metadata.py @@ -2,9 +2,10 @@ from unittest.mock import MagicMock -import databento as db import pytest import requests + +import databento as db from databento.common.publishers import Dataset from databento.historical.client import Historical diff --git a/tests/test_historical_timeseries.py b/tests/test_historical_timeseries.py index 6f992da1..04c43a64 100644 --- a/tests/test_historical_timeseries.py +++ b/tests/test_historical_timeseries.py @@ -2,14 +2,15 @@ from pathlib import Path from unittest.mock import MagicMock -import databento as db import pytest import requests +from databento_dbn import Schema + +import databento as db from databento import DBNStore from databento.common.error import BentoServerError from databento.common.publishers import Dataset from databento.historical.client import Historical -from databento_dbn import Schema def test_get_range_given_invalid_schema_raises_error( diff --git a/tests/test_historical_warnings.py b/tests/test_historical_warnings.py index 6073558d..0785f640 100644 --- a/tests/test_historical_warnings.py +++ b/tests/test_historical_warnings.py @@ -1,9 +1,10 @@ import json import pytest -from databento.common.http import check_backend_warnings from requests import Response +from databento.common.http import check_backend_warnings + @pytest.mark.parametrize( "header_field", diff --git a/tests/test_live_client.py b/tests/test_live_client.py index db500e0b..d7862231 100644 --- a/tests/test_live_client.py +++ b/tests/test_live_client.py @@ -16,6 +16,10 @@ import databento_dbn import pytest import zstandard +from databento_dbn import Encoding +from databento_dbn import Schema +from databento_dbn import SType + from databento.common.constants import ALL_SYMBOLS from databento.common.constants import SCHEMA_STRUCT_MAP from databento.common.cram import BUCKET_ID_LENGTH @@ -27,10 +31,6 @@ from databento.live import gateway from databento.live import protocol from databento.live import session -from databento_dbn import Encoding -from databento_dbn import Schema -from databento_dbn import SType - from tests.mockliveserver.fixture import MockLiveServerInterface diff --git a/tests/test_live_client_reconnect.py b/tests/test_live_client_reconnect.py index ee671a71..9ff10d13 100644 --- a/tests/test_live_client_reconnect.py +++ b/tests/test_live_client_reconnect.py @@ -6,6 +6,7 @@ import pandas as pd import pytest + from databento import Dataset from databento import Schema from databento import SType @@ -15,7 +16,6 @@ from databento.live.gateway import AuthenticationRequest from databento.live.gateway import SessionStart from databento.live.gateway import SubscriptionRequest - from tests.mockliveserver.fixture import MockLiveServerInterface diff --git a/tests/test_live_gateway_messages.py b/tests/test_live_gateway_messages.py index 6183520a..0c2befbe 100644 --- a/tests/test_live_gateway_messages.py +++ b/tests/test_live_gateway_messages.py @@ -1,6 +1,10 @@ from __future__ import annotations import pytest +from databento_dbn import Encoding +from databento_dbn import Schema +from databento_dbn import SType + from databento.common.publishers import Dataset from databento.live.gateway import AuthenticationRequest from databento.live.gateway import AuthenticationResponse @@ -9,9 +13,6 @@ from databento.live.gateway import Greeting from databento.live.gateway import SessionStart from databento.live.gateway import SubscriptionRequest -from databento_dbn import Encoding -from databento_dbn import Schema -from databento_dbn import SType ALL_MESSAGES = ( diff --git a/tests/test_live_protocol.py b/tests/test_live_protocol.py index 8478664e..4d451ed8 100644 --- a/tests/test_live_protocol.py +++ b/tests/test_live_protocol.py @@ -3,11 +3,11 @@ from unittest.mock import MagicMock import pytest -from databento.common.publishers import Dataset -from databento.live.protocol import DatabentoLiveProtocol from databento_dbn import Schema from databento_dbn import SType +from databento.common.publishers import Dataset +from databento.live.protocol import DatabentoLiveProtocol from tests.mockliveserver.fixture import MockLiveServerInterface diff --git a/tests/test_live_session.py b/tests/test_live_session.py index c3b164a6..a02413d4 100644 --- a/tests/test_live_session.py +++ b/tests/test_live_session.py @@ -1,4 +1,5 @@ import pytest + from databento.common.error import BentoError from databento.live.session import DBNQueue diff --git a/tests/test_reference_adjustment.py b/tests/test_reference_adjustment.py index 746cbbd0..e0989918 100644 --- a/tests/test_reference_adjustment.py +++ b/tests/test_reference_adjustment.py @@ -4,12 +4,12 @@ from pathlib import Path from unittest.mock import MagicMock -import databento as db import pytest import requests import zstandard -from databento.reference.client import Reference +import databento as db +from databento.reference.client import Reference from tests import TESTS_ROOT diff --git a/tests/test_reference_corporate.py b/tests/test_reference_corporate.py index a9779414..a53fa94a 100644 --- a/tests/test_reference_corporate.py +++ b/tests/test_reference_corporate.py @@ -4,13 +4,13 @@ from pathlib import Path from unittest.mock import MagicMock -import databento as db import pandas as pd import pytest import requests import zstandard -from databento.reference.client import Reference +import databento as db +from databento.reference.client import Reference from tests import TESTS_ROOT diff --git a/tests/test_reference_security.py b/tests/test_reference_security.py index 19af12e2..0b4ed6a2 100644 --- a/tests/test_reference_security.py +++ b/tests/test_reference_security.py @@ -4,12 +4,12 @@ from pathlib import Path from unittest.mock import MagicMock -import databento as db import pytest import requests import zstandard -from databento.reference.client import Reference +import databento as db +from databento.reference.client import Reference from tests import TESTS_ROOT diff --git a/tests/test_release.py b/tests/test_release.py index ceb1a571..7191ed42 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -6,10 +6,10 @@ import re from datetime import date -import databento import pytest import tomli +import databento from tests import PROJECT_ROOT From 60fe12c3526f2e0fcc83e5ca339a7c9e4d066a2b Mon Sep 17 00:00:00 2001 From: Nick Macholl Date: Wed, 19 Nov 2025 12:56:57 -0800 Subject: [PATCH 2/6] ADD: Add getter for Live subscription requests --- CHANGELOG.md | 9 ++++++ databento/live/client.py | 44 +++++++++++++++++++++++++++-- databento/live/protocol.py | 58 ++++++++++++++++++++++---------------- databento/live/session.py | 35 ++++++++++++----------- tests/test_live_client.py | 9 ++++-- 5 files changed, 109 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 053dee50..3b679740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.67.0 - TBD + +#### Enhancements +- Added a property `Live.subscription_requests` which returns a list of tuples containing every `SubscriptionRequest` for the live session +- Changed the return value of `Live.subscribe()` to `int`, the value of the subscription ID, which can be used to index into the `Live.subscription_requests` property + +#### Breaking changes +- Several log messages have been reformatted to improve clarity and reduce redundancy, especially at debug levels + ## 0.66.0 - 2025-11-18 #### Enhancements diff --git a/databento/live/client.py b/databento/live/client.py index 8513fa2a..0ea52448 100644 --- a/databento/live/client.py +++ b/databento/live/client.py @@ -31,6 +31,7 @@ from databento.common.types import RecordCallback from databento.common.validation import validate_enum from databento.common.validation import validate_semantic_string +from databento.live.gateway import SubscriptionRequest from databento.live.session import DEFAULT_REMOTE_PORT from databento.live.session import LiveSession from databento.live.session import SessionMetadata @@ -228,6 +229,38 @@ def session_id(self) -> str | None: """ return self._session.session_id + @property + def subscription_requests( + self, + ) -> list[tuple[SubscriptionRequest, ...]]: + """ + Return a list of tuples containing every `SubscriptionRequest` message + sent for the session. The list is in order of the subscriptions made + and can be indexed using the value returned by each call to + `Live.subscribe()`. + + Subscriptions which contain a large + list of symbols are batched. Because of this, a single `subscription_id` may have + more than one associated `SubscriptionRequest`. + + Returns + ------- + list[tuple[SubscriptionRequest, ...]] + A list of tuples containing every subscription request. + Each entry in the list corresponds to a single subscription. + + Raises + ------ + IndexError + If the subscription ID is invalid. + + See Also + -------- + Live.subscribe() + + """ + return self._session._subscriptions + @property def symbology_map(self) -> dict[int, str | int]: """ @@ -446,7 +479,7 @@ def subscribe( stype_in: SType | str = SType.RAW_SYMBOL, start: pd.Timestamp | datetime | date | str | int | None = None, snapshot: bool = False, - ) -> None: + ) -> int: """ Add a new subscription to the session. @@ -476,6 +509,11 @@ def subscribe( Request subscription with snapshot. The `start` parameter must be `None`. Only supported with `mbo` schema. + Returns + ------- + int + The numeric identifier for this subscription request. + Raises ------ ValueError @@ -494,7 +532,7 @@ def subscribe( """ logger.info( - "subscribing to %s:%s %s start=%s snapshot=%s", + "subscribing to schema=%s stype_in=%s symbols='%s' start=%s snapshot=%s", schema, stype_in, symbols, @@ -509,7 +547,7 @@ def subscribe( if snapshot and start is not None: raise ValueError("Subscription with snapshot expects start=None") - self._session.subscribe( + return self._session.subscribe( dataset=dataset, schema=schema, stype_in=stype_in, diff --git a/databento/live/protocol.py b/databento/live/protocol.py index 372729e2..4c33deb3 100644 --- a/databento/live/protocol.py +++ b/databento/live/protocol.py @@ -7,11 +7,9 @@ from typing import Final import databento_dbn -from databento_dbn import DBNError from databento_dbn import Metadata from databento_dbn import Schema from databento_dbn import SType -from databento_dbn import SystemCode from databento_dbn import VersionUpgradePolicy from databento.common import cram @@ -313,13 +311,14 @@ def subscribe( list[SubscriptionRequest] """ - logger.info( - "sending subscription to %s:%s %s start=%s snapshot=%s", + logger.debug( + "sending subscription request schema=%s stype_in=%s symbols='%s' start='%s' snapshot=%s id=%s", schema, stype_in, symbols, start if start is not None else "now", snapshot, + subscription_id, ) stype_in_valid = validate_enum(stype_in, SType, "stype_in") @@ -341,6 +340,12 @@ def subscribe( ) subscriptions.append(message) + if len(subscriptions) > 1: + logger.debug( + "batched subscription into %d requests id=%s", + len(subscriptions), + subscription_id, + ) self.transport.writelines(map(bytes, subscriptions)) return subscriptions @@ -374,7 +379,8 @@ def _process_dbn(self, data: bytes) -> None: continue if isinstance(record, databento_dbn.ErrorMsg): logger.error( - "gateway error: %s", + "gateway error code=%s err='%s'", + record.code, record.err, ) self._error_msgs.append(record.err) @@ -382,19 +388,11 @@ def _process_dbn(self, data: bytes) -> None: if record.is_heartbeat(): logger.debug("gateway heartbeat") else: - try: - msg_code = record.code - except DBNError: - msg_code = None - if msg_code == SystemCode.SLOW_READER_WARNING: - logger.warning( - record.msg, - ) - else: - logger.debug( - "gateway message: %s", - record.msg, - ) + logger.info( + "system message code=%s msg='%s'", + record.code, + record.msg, + ) self.received_record(record) def _process_gateway(self, data: bytes) -> None: @@ -423,11 +421,14 @@ def _handle_gateway_message(self, message: GatewayControl) -> None: @_handle_gateway_message.register(Greeting) def _(self, message: Greeting) -> None: - logger.debug("greeting received by remote gateway v%s", message.lsg_version) + logger.debug( + "greeting received by remote gateway version='%s'", + message.lsg_version, + ) @_handle_gateway_message.register(ChallengeRequest) def _(self, message: ChallengeRequest) -> None: - logger.debug("received CRAM challenge: %s", message.cram) + logger.debug("received CRAM challenge cram='%s'", message.cram) response = cram.get_challenge_response(message.cram, self.__api_key) auth_request = AuthenticationRequest( auth=response, @@ -435,22 +436,29 @@ def _(self, message: ChallengeRequest) -> None: ts_out=str(int(self._ts_out)), heartbeat_interval_s=self._heartbeat_interval_s, ) - logger.debug("sending CRAM challenge response: %s", str(auth_request).strip()) + logger.debug( + "sending CRAM challenge response auth='%s' dataset=%s encoding=%s ts_out=%s heartbeat_interval_s=%s client='%s'", + auth_request.auth, + auth_request.dataset, + auth_request.encoding, + auth_request.ts_out, + auth_request.heartbeat_interval_s, + auth_request.client, + ) self.transport.write(bytes(auth_request)) @_handle_gateway_message.register(AuthenticationResponse) def _(self, message: AuthenticationResponse) -> None: if message.success == "0": - logger.error("CRAM authentication failed: %s", message.error) + logger.error("CRAM authentication error: %s", message.error) self.authenticated.set_exception( - BentoError(f"User authentication failed: {message.error}"), + BentoError(message.error), ) self.transport.close() else: session_id = message.session_id logger.debug( - "CRAM authenticated session id assigned `%s`", - session_id, + "CRAM authentication successful", ) self.authenticated.set_result(session_id) diff --git a/databento/live/session.py b/databento/live/session.py index 454c3719..ee056e83 100644 --- a/databento/live/session.py +++ b/databento/live/session.py @@ -2,6 +2,7 @@ import asyncio import dataclasses +import itertools import logging import queue import struct @@ -329,8 +330,7 @@ def __init__( self._transport: asyncio.Transport | None = None self._session_id: str | None = None - self._subscription_counter = 0 - self._subscriptions: list[SubscriptionRequest] = [] + self._subscriptions: list[tuple[SubscriptionRequest, ...]] = [] self._reconnect_policy = ReconnectPolicy(reconnect_policy) self._reconnect_task: asyncio.Task[None] | None = None @@ -463,7 +463,7 @@ def subscribe( stype_in: SType | str = SType.RAW_SYMBOL, start: str | int | None = None, snapshot: bool = False, - ) -> None: + ) -> int: """ Send a subscription request on the current connection. This will create a new connection if there is no active connection to the gateway. @@ -498,17 +498,20 @@ def subscribe( self._session_id = None self._connect(dataset=dataset) - self._subscription_counter += 1 - self._subscriptions.extend( - self._protocol.subscribe( - schema=schema, - symbols=symbols, - stype_in=stype_in, - start=start, - snapshot=snapshot, - subscription_id=self._subscription_counter, + subscription_id = len(self._subscriptions) + self._subscriptions.append( + tuple( + self._protocol.subscribe( + schema=schema, + symbols=symbols, + stype_in=stype_in, + start=start, + snapshot=snapshot, + subscription_id=subscription_id, + ), ), ) + return subscription_id def terminate(self) -> None: with self._lock: @@ -542,7 +545,7 @@ async def wait_for_close(self) -> None: self._cleanup() def _cleanup(self) -> None: - logger.debug("cleaning up session_id=%s", self.session_id) + logger.debug("cleaning up session_id='%s'", self.session_id) self._user_callbacks.clear() for stream in self._user_streams: if not stream.is_closed: @@ -596,7 +599,7 @@ async def _connect_task( logger.debug("using default gateway for dataset %s", dataset) else: gateway = self._user_gateway - logger.debug("using user specified gateway: %s", gateway) + logger.debug("user gateway override gateway='%s'", gateway) logger.info("connecting to remote gateway") try: @@ -638,7 +641,7 @@ async def _connect_task( self._session_id = session_id logger.info( - "authenticated session %s", + "authenticated session_id='%s'", self.session_id, ) @@ -669,7 +672,7 @@ async def _reconnect(self) -> None: dataset=self._protocol._dataset, ) - for sub in self._subscriptions: + for sub in itertools.chain(*self._subscriptions): self._protocol.subscribe( schema=sub.schema, symbols=sub.symbols, diff --git a/tests/test_live_client.py b/tests/test_live_client.py index d7862231..da647af9 100644 --- a/tests/test_live_client.py +++ b/tests/test_live_client.py @@ -163,7 +163,7 @@ def test_live_connection_cram_failure( ) # Ensure this was an authentication error - exc.match(r"User authentication failed:") + exc.match(r"Authentication failed.") @pytest.mark.parametrize( @@ -554,6 +554,8 @@ async def test_live_subscribe( assert message.symbols == symbols assert message.start == start assert message.snapshot == "0" + assert len(live_client.subscription_requests[0]) == 1 + assert live_client.subscription_requests[0][0].id == int(message.id) @pytest.mark.parametrize( @@ -645,6 +647,7 @@ async def test_live_subscribe_large_symbol_list( symbols=large_symbol_list, ) + batched = [] reconstructed: list[str] = [] for i in range(8): message = await mock_live_server.wait_for_message_of_type( @@ -652,9 +655,11 @@ async def test_live_subscribe_large_symbol_list( ) assert int(message.is_last) == int(i == 7) reconstructed.extend(message.symbols.split(",")) + batched.append(message) # Assert assert reconstructed == large_symbol_list + assert len(live_client.subscription_requests[0]) == len(batched) async def test_live_subscribe_from_callback( @@ -1663,7 +1668,7 @@ async def test_live_connection_reuse_cram_failure( ) # Ensure this was an authentication error - exc.match(r"User authentication failed:") + exc.match(r"Authentication failed.") async with mock_live_server.api_key_context(test_api_key): live_client.subscribe( From c18a6a618376c4dde94c1635257cb83dcdcbe280 Mon Sep 17 00:00:00 2001 From: Nick Macholl Date: Fri, 21 Nov 2025 15:21:44 -0800 Subject: [PATCH 3/6] ADD: Actively monitor for hung Live sessions --- CHANGELOG.md | 2 ++ databento/live/session.py | 29 ++++++++++++++++++++++++++--- tests/test_live_client.py | 3 +-- tests/test_live_client_reconnect.py | 14 +++++++------- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b679740..b55c852b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ #### Enhancements - Added a property `Live.subscription_requests` which returns a list of tuples containing every `SubscriptionRequest` for the live session - Changed the return value of `Live.subscribe()` to `int`, the value of the subscription ID, which can be used to index into the `Live.subscription_requests` property +- Added feature to automatically monitor for hung connections in the `Live` client + - Hung connections will be disconnected client side with a `BentoError` #### Breaking changes - Several log messages have been reformatted to improve clarity and reduce redundancy, especially at debug levels diff --git a/databento/live/session.py b/databento/live/session.py index ee056e83..c56848cd 100644 --- a/databento/live/session.py +++ b/databento/live/session.py @@ -4,6 +4,7 @@ import dataclasses import itertools import logging +import math import queue import struct import threading @@ -35,6 +36,7 @@ CONNECT_TIMEOUT_SECONDS: Final = 10.0 DBN_QUEUE_CAPACITY: Final = 2**20 DEFAULT_REMOTE_PORT: Final = 13000 +CLIENT_TIMEOUT_MARGIN_SECONDS: Final = 10 class DBNQueue(queue.SimpleQueue): # type: ignore [type-arg] @@ -212,6 +214,7 @@ def __init__( self._user_callbacks = user_callbacks self._user_streams = user_streams self._last_ts_event: int | None = None + self._last_msg_loop_time: float = math.inf def received_metadata(self, metadata: databento_dbn.Metadata) -> None: if self._metadata: @@ -235,6 +238,7 @@ def received_record(self, record: DBNRecord) -> None: if self._dbn_queue.is_enabled(): self._queue_for_iteration(record) self._last_ts_event = record.ts_event + self._last_msg_loop_time = self._loop.time() return super().received_record(record) @@ -324,7 +328,7 @@ def __init__( self._api_key = api_key self._ts_out = ts_out - self._heartbeat_interval_s = heartbeat_interval_s + self._heartbeat_interval_s = heartbeat_interval_s or 30 self._protocol: _SessionProtocol | None = None self._transport: asyncio.Transport | None = None @@ -333,6 +337,7 @@ def __init__( self._subscriptions: list[tuple[SubscriptionRequest, ...]] = [] self._reconnect_policy = ReconnectPolicy(reconnect_policy) self._reconnect_task: asyncio.Task[None] | None = None + self._heartbeat_monitor_task: asyncio.Task[None] | None = None self._dataset = "" @@ -436,8 +441,6 @@ def stop(self) -> None: with self._lock: if self._transport is None: return - 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: @@ -454,6 +457,9 @@ def start(self) -> None: if self._protocol is None: raise ValueError("session is not connected") self._protocol.start() + self._heartbeat_monitor_task = self._loop.create_task( + self._heartbeat_monitor(), + ) def subscribe( self, @@ -553,6 +559,8 @@ def _cleanup(self) -> None: if stream.is_managed: stream.close() + if self._heartbeat_monitor_task is not None: + self._heartbeat_monitor_task.cancel() self._user_callbacks.clear() self._user_streams.clear() self._user_reconnect_callbacks.clear() @@ -647,6 +655,21 @@ async def _connect_task( return transport, protocol + async def _heartbeat_monitor(self) -> None: + while not self._protocol.disconnected.done(): + await asyncio.sleep(1) + gap = self._loop.time() - self._protocol._last_msg_loop_time + if gap > (self._heartbeat_interval_s + CLIENT_TIMEOUT_MARGIN_SECONDS): + logger.error( + "disconnecting client due to timeout, no data received for %d second(s)", + int(gap), + ) + self._protocol.disconnected.set_exception( + BentoError( + f"Gateway timeout: {gap:.0f} second(s) since last message", + ), + ) + async def _reconnect(self) -> None: while True: try: diff --git a/tests/test_live_client.py b/tests/test_live_client.py index da647af9..492bac6d 100644 --- a/tests/test_live_client.py +++ b/tests/test_live_client.py @@ -4,7 +4,6 @@ from __future__ import annotations -import asyncio import pathlib import platform import random @@ -279,7 +278,7 @@ async def test_live_client_reuse( live_client.stop() assert live_client.session_id == first_session_id - await asyncio.sleep(1) + await live_client.wait_for_close() live_client.subscribe( dataset=Dataset.GLBX_MDP3, diff --git a/tests/test_live_client_reconnect.py b/tests/test_live_client_reconnect.py index 9ff10d13..99e92869 100644 --- a/tests/test_live_client_reconnect.py +++ b/tests/test_live_client_reconnect.py @@ -65,9 +65,9 @@ async def test_reconnect_before_start( reconnect_policy: ReconnectPolicy = ReconnectPolicy.RECONNECT, ) -> None: """ - Test that a reconnect policy of "reconnect_do_not_replay" reconnects a - client but does not send the session start command if the session was not - streaming previously. + Test that a reconnect policy of "reconnect" reconnects a client but does + not send the session start command if the session was not streaming + previously. """ # Arrange live_client = client.Live( @@ -138,8 +138,8 @@ async def test_reconnect_subscriptions( reconnect_policy: ReconnectPolicy = ReconnectPolicy.RECONNECT, ) -> None: """ - Test that a reconnect policy of "reconnect_do_not_replay" re-sends the - subscription requests with a start of `None`. + Test that a reconnect policy of "reconnect" re-sends the subscription + requests with a start of `None`. """ # Arrange live_client = client.Live( @@ -192,8 +192,8 @@ async def test_reconnect_callback( reconnect_policy: ReconnectPolicy = ReconnectPolicy.RECONNECT, ) -> None: """ - Test that a reconnect policy of "reconnect_do_not_replay" will cause a user - supplied reconnection callback to be executed when a reconnection occurs. + Test that a reconnect policy of "reconnect" will cause a user supplied + reconnection callback to be executed when a reconnection occurs. """ # Arrange live_client = client.Live( From 4069cdc308f5853ae1e45777dfd2dca34008f216 Mon Sep 17 00:00:00 2001 From: Nick Macholl Date: Fri, 21 Nov 2025 11:04:47 -0800 Subject: [PATCH 4/6] MOD: Enable map_symbols by default for clients --- CHANGELOG.md | 1 + databento/historical/api/batch.py | 35 ++++++++++++++++++++++--------- tests/test_historical_batch.py | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55c852b..807c3032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Breaking changes - Several log messages have been reformatted to improve clarity and reduce redundancy, especially at debug levels +- The `map_symbols` parameter for `Historical.batch.submit_job()` now defaults to `True` for JSON and CSV encodings ## 0.66.0 - 2025-11-18 diff --git a/databento/historical/api/batch.py b/databento/historical/api/batch.py index 5928168f..5143b881 100644 --- a/databento/historical/api/batch.py +++ b/databento/historical/api/batch.py @@ -71,7 +71,7 @@ def submit_job( compression: Compression | str = "zstd", pretty_px: bool = False, pretty_ts: bool = False, - map_symbols: bool = False, + map_symbols: bool | None = None, split_symbols: bool = False, split_duration: SplitDuration | str = "day", split_size: int | None = None, @@ -116,9 +116,10 @@ def submit_job( pretty_ts : bool, default False If timestamps should be formatted as ISO 8601 strings. Only applicable for 'csv' or 'json' encodings. - map_symbols : bool, default False - If the requested symbol should be appended to every text encoded record. - Only applicable for 'csv' or 'json' encodings. + map_symbols : bool, optional + If a symbol field should be included with every text encoded record. + If `None`, will default to `True` for `csv` and `json` encodings and `False` for + `dbn`. split_symbols : bool, default False If files should be split by raw symbol. Cannot be requested with `'ALL_SYMBOLS'`. split_duration : SplitDuration or str {'day', 'week', 'month', 'none'}, default 'day' @@ -149,6 +150,10 @@ def submit_job( """ stype_in_valid = validate_enum(stype_in, SType, "stype_in") symbols_list = symbols_list_to_list(symbols, stype_in_valid) + encoding_valid = validate_enum(encoding, Encoding, "encoding") + + if map_symbols is None: + map_symbols = encoding_valid != Encoding.DBN data: dict[str, object | None] = { "dataset": validate_semantic_string(dataset, "dataset"), @@ -158,7 +163,7 @@ def submit_job( "schema": str(validate_enum(schema, Schema, "schema")), "stype_in": str(stype_in_valid), "stype_out": str(validate_enum(stype_out, SType, "stype_out")), - "encoding": str(validate_enum(encoding, Encoding, "encoding")), + "encoding": str(encoding_valid), "compression": ( str(validate_enum(compression, Compression, "compression")) if compression else None ), @@ -292,7 +297,9 @@ def download( """ if keep_zip and filename_to_download: - raise ValueError("Cannot specify an individual file to download when `keep_zip=True`") + raise ValueError( + "Cannot specify an individual file to download when `keep_zip=True`", + ) batch_download = _BatchJob( self, @@ -369,7 +376,9 @@ async def download_async( """ if keep_zip and filename_to_download: - raise ValueError("Cannot specify an individual file to download when `keep_zip=True`") + raise ValueError( + "Cannot specify an individual file to download when `keep_zip=True`", + ) batch_download = _BatchJob( self, @@ -458,7 +467,9 @@ def _download_batch_file( ) as response: check_http_error(response) with open(output_path, mode=mode) as f: - for chunk in response.iter_content(chunk_size=HTTP_STREAMING_READ_SIZE): + for chunk in response.iter_content( + chunk_size=HTTP_STREAMING_READ_SIZE, + ): f.write(chunk) # Successfully wrote some data, reset attempts counter @@ -548,7 +559,9 @@ def _download_batch_zip( ) as response: check_http_error(response) with open(output_path, mode="wb") as f: - for chunk in response.iter_content(chunk_size=HTTP_STREAMING_READ_SIZE): + for chunk in response.iter_content( + chunk_size=HTTP_STREAMING_READ_SIZE, + ): f.write(chunk) except BentoHttpError as exc: if exc.http_status == 429: @@ -615,7 +628,9 @@ def __init__( urls = file_detail["urls"] except KeyError as exc: missing_key = exc.args[0] - raise BentoError(f"Batch job manifest missing key '{missing_key}'") from None + raise BentoError( + f"Batch job manifest missing key '{missing_key}'", + ) from None except TypeError: raise BentoError("Error parsing job manifest") from None diff --git a/tests/test_historical_batch.py b/tests/test_historical_batch.py index fe9729bd..907f448e 100644 --- a/tests/test_historical_batch.py +++ b/tests/test_historical_batch.py @@ -99,7 +99,7 @@ def test_batch_submit_job_sends_expected_request( "compression": "zstd", "pretty_px": False, "pretty_ts": False, - "map_symbols": False, + "map_symbols": True, "split_symbols": False, "split_duration": "day", "delivery": "download", From 6354d94eddfd4c2ac5f97c2734869226dd0f109d Mon Sep 17 00:00:00 2001 From: Jack Culhane Date: Tue, 2 Dec 2025 18:13:20 +0000 Subject: [PATCH 5/6] ADD: Add publisher enums for Cboe Futures Exchange --- CHANGELOG.md | 1 + databento/common/publishers.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 807c3032..840c50ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Changed the return value of `Live.subscribe()` to `int`, the value of the subscription ID, which can be used to index into the `Live.subscription_requests` property - Added feature to automatically monitor for hung connections in the `Live` client - Hung connections will be disconnected client side with a `BentoError` +- Added new venue, dataset, and publisher for Cboe Futures Exchange (`XCBF.PITCH`) #### Breaking changes - Several log messages have been reformatted to improve clarity and reduce redundancy, especially at debug levels diff --git a/databento/common/publishers.py b/databento/common/publishers.py index dc009e7e..dbe27a85 100644 --- a/databento/common/publishers.py +++ b/databento/common/publishers.py @@ -118,6 +118,8 @@ class Venue(StringyMixin, str, Enum): Eurex Exchange. XEEE European Energy Exchange. + XCBF + Cboe Futures Exchange. """ @@ -172,6 +174,7 @@ class Venue(StringyMixin, str, Enum): IFLL = "IFLL" XEUR = "XEUR" XEEE = "XEEE" + XCBF = "XCBF" @classmethod def from_int(cls, value: int) -> Venue: @@ -280,6 +283,8 @@ def from_int(cls, value: int) -> Venue: return Venue.XEUR if value == 51: return Venue.XEEE + if value == 52: + return Venue.XCBF raise ValueError(f"Integer value {value} does not correspond with any Venue variant") def to_int(self) -> int: @@ -388,6 +393,8 @@ def to_int(self) -> int: return 50 if self == Venue.XEEE: return 51 + if self == Venue.XCBF: + return 52 raise ValueError("Invalid Venue") @property @@ -497,6 +504,8 @@ def description(self) -> str: return "Eurex Exchange" if self == Venue.XEEE: return "European Energy Exchange" + if self == Venue.XCBF: + return "Cboe Futures Exchange" raise ValueError("Unexpected Venue value") @@ -584,6 +593,8 @@ class Dataset(StringyMixin, str, Enum): Eurex EOBI. XEEE_EOBI European Energy Exchange EOBI. + XCBF_PITCH + Cboe Futures Exchange PITCH. """ @@ -626,6 +637,7 @@ class Dataset(StringyMixin, str, Enum): IFLL_IMPACT = "IFLL.IMPACT" XEUR_EOBI = "XEUR.EOBI" XEEE_EOBI = "XEEE.EOBI" + XCBF_PITCH = "XCBF.PITCH" @classmethod def from_int(cls, value: int) -> Dataset: @@ -710,6 +722,8 @@ def from_int(cls, value: int) -> Dataset: return Dataset.XEUR_EOBI if value == 39: return Dataset.XEEE_EOBI + if value == 40: + return Dataset.XCBF_PITCH raise ValueError(f"Integer value {value} does not correspond with any Dataset variant") def to_int(self) -> int: @@ -794,6 +808,8 @@ def to_int(self) -> int: return 38 if self == Dataset.XEEE_EOBI: return 39 + if self == Dataset.XCBF_PITCH: + return 40 raise ValueError("Invalid Dataset") @property @@ -879,6 +895,8 @@ def description(self) -> str: return "Eurex EOBI" if self == Dataset.XEEE_EOBI: return "European Energy Exchange EOBI" + if self == Dataset.XCBF_PITCH: + return "Cboe Futures Exchange PITCH" raise ValueError("Unexpected Dataset value") @@ -1096,6 +1114,8 @@ class Publisher(StringyMixin, str, Enum): Eurex EOBI - Off-Market Trades. XEEE_EOBI_XOFF European Energy Exchange EOBI - Off-Market Trades. + XCBF_PITCH_XCBF + Cboe Futures Exchange. """ @@ -1203,6 +1223,7 @@ class Publisher(StringyMixin, str, Enum): XEEE_EOBI_XEEE = "XEEE.EOBI.XEEE" XEUR_EOBI_XOFF = "XEUR.EOBI.XOFF" XEEE_EOBI_XOFF = "XEEE.EOBI.XOFF" + XCBF_PITCH_XCBF = "XCBF.PITCH.XCBF" @classmethod def from_int(cls, value: int) -> Publisher: @@ -1417,6 +1438,8 @@ def from_int(cls, value: int) -> Publisher: return Publisher.XEUR_EOBI_XOFF if value == 104: return Publisher.XEEE_EOBI_XOFF + if value == 105: + return Publisher.XCBF_PITCH_XCBF raise ValueError(f"Integer value {value} does not correspond with any Publisher variant") def to_int(self) -> int: @@ -1631,6 +1654,8 @@ def to_int(self) -> int: return 103 if self == Publisher.XEEE_EOBI_XOFF: return 104 + if self == Publisher.XCBF_PITCH_XCBF: + return 105 raise ValueError("Invalid Publisher") @property @@ -1846,6 +1871,8 @@ def venue(self) -> Venue: return Venue.XOFF if self == Publisher.XEEE_EOBI_XOFF: return Venue.XOFF + if self == Publisher.XCBF_PITCH_XCBF: + return Venue.XCBF raise ValueError("Unexpected Publisher value") @property @@ -2061,6 +2088,8 @@ def dataset(self) -> Dataset: return Dataset.XEUR_EOBI if self == Publisher.XEEE_EOBI_XOFF: return Dataset.XEEE_EOBI + if self == Publisher.XCBF_PITCH_XCBF: + return Dataset.XCBF_PITCH raise ValueError("Unexpected Publisher value") @property @@ -2276,4 +2305,6 @@ def description(self) -> str: return "Eurex EOBI - Off-Market Trades" if self == Publisher.XEEE_EOBI_XOFF: return "European Energy Exchange EOBI - Off-Market Trades" + if self == Publisher.XCBF_PITCH_XCBF: + return "Cboe Futures Exchange" raise ValueError("Unexpected Publisher value") From c00e308243c589dd797c6cd7eb6ffc4e519d2df1 Mon Sep 17 00:00:00 2001 From: Nick Macholl Date: Tue, 2 Dec 2025 13:32:53 -0800 Subject: [PATCH 6/6] VER: Release 0.67.0 --- CHANGELOG.md | 2 +- databento/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 840c50ae..27141791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.67.0 - TBD +## 0.67.0 - 2025-12-02 #### Enhancements - Added a property `Live.subscription_requests` which returns a list of tuples containing every `SubscriptionRequest` for the live session diff --git a/databento/version.py b/databento/version.py index 8833468a..a2f665b9 100644 --- a/databento/version.py +++ b/databento/version.py @@ -1 +1 @@ -__version__ = "0.66.0" +__version__ = "0.67.0" diff --git a/pyproject.toml b/pyproject.toml index 498d0046..65ee4caf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "databento" -version = "0.66.0" +version = "0.67.0" description = "Official Python client library for Databento" readme = "README.md" requires-python = ">=3.10"