Skip to content

Commit b7d168d

Browse files
authored
VER: Release 0.36.0
See release notes.
2 parents 2f2078e + 1fac92c commit b7d168d

File tree

81 files changed

+1090
-1049
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1090
-1049
lines changed

CHANGELOG.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## 0.36.0 - 2024-06-11
4+
5+
#### Enhancements
6+
- Upgraded `databento-dbn` to 0.18.1
7+
8+
#### Bug fixes
9+
- Fixed an issue where `heartbeat_interval_s` was not being sent to the gateway
10+
- Fixed an issue where a truncated DBN stream could be written by the `Live` client in the event of an ungraceful disconnect
11+
12+
#### Breaking changes
13+
- Output streams of the `Live` client added with `Live.add_stream` will now upgrade to the latest DBN version before being written
14+
315
## 0.35.0 - 2024-06-04
416

517
#### Enhancements
@@ -9,7 +21,7 @@
921
- Added new off-market publisher values for `IFEU.IMPACT` and `NDEX.IMPACT`
1022

1123
#### Breaking changes
12-
- Renamed `CbboMsg` to `CBBOMsg`.
24+
- Renamed `CbboMsg` to `CBBOMsg`
1325
- Renamed `use_snapshot` parameter in `Live.subscribe` function to `snapshot`
1426
- All Python exceptions raised by `databento-dbn` have been changed to use the `DBNError` type
1527

@@ -244,7 +256,7 @@ In some cases, DBN v1 records will be converted to their v2 counterparts:
244256
- Fixed an issue where `DBNStore.from_bytes` did not rewind seekable buffers
245257
- Fixed an issue where the `DBNStore` would not map symbols with input symbology of `SType.INSTRUMENT_ID`
246258
- Fixed an issue with `DBNStore.request_symbology` when the DBN metadata's start date and end date were the same
247-
- Fixed an issue where closed streams were not removed from a `Live` client on shutdown.
259+
- Fixed an issue where closed streams were not removed from a `Live` client on shutdown
248260

249261
## 0.20.0 - 2023-09-21
250262

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The library is fully compatible with the latest distribution of Anaconda 3.8 and
3232
The minimum dependencies as found in the `pyproject.toml` are also listed below:
3333
- python = "^3.8"
3434
- aiohttp = "^3.8.3"
35-
- databento-dbn = "0.18.0"
35+
- databento-dbn = "0.18.1"
3636
- numpy= ">=1.23.5"
3737
- pandas = ">=1.5.3"
3838
- pip-system-certs = ">=4.0" (Windows only)

databento/common/cram.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import hashlib
77
import os
88
import sys
9+
from typing import Final
910

1011

11-
BUCKET_ID_LENGTH = 5
12+
BUCKET_ID_LENGTH: Final = 5
1213

1314

1415
def get_challenge_response(challenge: str, key: str) -> str:

databento/live/gateway.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
from io import BytesIO
66
from operator import attrgetter
7+
from typing import SupportsBytes
78
from typing import TypeVar
89

910
from databento_dbn import Encoding
@@ -20,16 +21,21 @@
2021

2122

2223
@dataclasses.dataclass
23-
class GatewayControl:
24+
class GatewayControl(SupportsBytes):
2425
"""
2526
Base class for gateway control messages.
2627
"""
2728

2829
@classmethod
29-
def parse(cls: type[T], line: str) -> T:
30+
def parse(cls: type[T], line: str | bytes) -> T:
3031
"""
3132
Parse a message of type `T` from a string.
3233
34+
Parameters
35+
----------
36+
line : str | bytes
37+
The data to parse into a GatewayControl message.
38+
3339
Returns
3440
-------
3541
T
@@ -40,17 +46,20 @@ def parse(cls: type[T], line: str) -> T:
4046
If the line fails to parse.
4147
4248
"""
49+
if isinstance(line, bytes):
50+
line = line.decode("utf-8")
51+
4352
if not line.endswith("\n"):
44-
raise ValueError(f"`{line.strip()}` does not end with a newline")
53+
raise ValueError(f"'{line!r}' does not end with a newline")
4554

46-
split_tokens = [t.partition("=") for t in line[:-1].split("|")]
55+
split_tokens = [t.partition("=") for t in line.strip().split("|")]
4756
data_dict = {k: v for k, _, v in split_tokens}
4857

4958
try:
5059
return cls(**data_dict)
5160
except TypeError:
5261
raise ValueError(
53-
f"`{line.strip()} is not a parsible {cls.__name__}",
62+
f"'{line!r}'is not a parsible {cls.__name__}",
5463
) from None
5564

5665
def __str__(self) -> str:
@@ -154,7 +163,7 @@ def parse_gateway_message(line: str) -> GatewayControl:
154163
return message_cls.parse(line)
155164
except ValueError:
156165
continue
157-
raise ValueError(f"`{line.strip()}` is not a parsible gateway message")
166+
raise ValueError(f"'{line.strip()}' is not a parsible gateway message")
158167

159168

160169
class GatewayDecoder:

databento/live/session.py

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import dataclasses
55
import logging
66
import queue
7+
import struct
78
import threading
89
from collections.abc import Iterable
910
from typing import IO
1011
from typing import Callable
1112
from typing import Final
1213

1314
import databento_dbn
14-
from databento_dbn import Metadata
1515
from databento_dbn import Schema
1616
from databento_dbn import SType
1717

@@ -188,41 +188,45 @@ def __init__(
188188
ts_out: bool = False,
189189
heartbeat_interval_s: int | None = None,
190190
):
191-
super().__init__(api_key, dataset, ts_out)
191+
super().__init__(api_key, dataset, ts_out, heartbeat_interval_s)
192192

193193
self._dbn_queue = dbn_queue
194194
self._loop = loop
195195
self._metadata: SessionMetadata = metadata
196196
self._user_callbacks = user_callbacks
197197
self._user_streams = user_streams
198198

199-
def _process_dbn(self, data: bytes) -> None:
200-
start_index = 0
201-
if data.startswith(b"DBN") and self._metadata:
202-
# We have already received metata for the stream
203-
# Set start index to metadata length
204-
start_index = int.from_bytes(data[4:8], byteorder="little") + 8
205-
self._metadata.check(Metadata.decode(bytes(data[:start_index])))
206-
for stream, exc_callback in self._user_streams.items():
207-
try:
208-
stream.write(data[start_index:])
209-
except Exception as exc:
210-
stream_name = getattr(stream, "name", str(stream))
211-
logger.error(
212-
"error writing %d bytes to `%s` stream",
213-
len(data[start_index:]),
214-
stream_name,
215-
exc_info=exc,
216-
)
217-
if exc_callback is not None:
218-
exc_callback(exc)
219-
return super()._process_dbn(data)
220-
221199
def received_metadata(self, metadata: databento_dbn.Metadata) -> None:
222-
self._metadata.data = metadata
200+
if self._metadata:
201+
self._metadata.check(metadata)
202+
else:
203+
metadata_bytes = metadata.encode()
204+
for stream, exc_callback in self._user_streams.items():
205+
try:
206+
stream.write(metadata_bytes)
207+
except Exception as exc:
208+
stream_name = getattr(stream, "name", str(stream))
209+
logger.error(
210+
"error writing %d bytes to `%s` stream",
211+
len(metadata_bytes),
212+
stream_name,
213+
exc_info=exc,
214+
)
215+
if exc_callback is not None:
216+
exc_callback(exc)
217+
218+
self._metadata.data = metadata
223219
return super().received_metadata(metadata)
224220

225221
def received_record(self, record: DBNRecord) -> None:
222+
self._dispatch_writes(record)
223+
self._dispatch_callbacks(record)
224+
if self._dbn_queue.is_enabled():
225+
self._queue_for_iteration(record)
226+
227+
return super().received_record(record)
228+
229+
def _dispatch_callbacks(self, record: DBNRecord) -> None:
226230
for callback, exc_callback in self._user_callbacks.items():
227231
try:
228232
callback(record)
@@ -236,18 +240,37 @@ def received_record(self, record: DBNRecord) -> None:
236240
if exc_callback is not None:
237241
exc_callback(exc)
238242

239-
if self._dbn_queue.is_enabled():
240-
self._dbn_queue.put(record)
243+
def _dispatch_writes(self, record: DBNRecord) -> None:
244+
if hasattr(record, "ts_out"):
245+
ts_out_bytes = struct.pack("Q", record.ts_out)
246+
else:
247+
ts_out_bytes = b""
241248

242-
# DBNQueue has no max size; so check if it's above capacity, and if so, pause reading
243-
if self._dbn_queue.is_full():
244-
logger.warning(
245-
"record queue is full; %d record(s) to be processed",
246-
self._dbn_queue.qsize(),
249+
record_bytes = bytes(record) + ts_out_bytes
250+
251+
for stream, exc_callback in self._user_streams.items():
252+
try:
253+
stream.write(record_bytes)
254+
except Exception as exc:
255+
stream_name = getattr(stream, "name", str(stream))
256+
logger.error(
257+
"error writing %d bytes to `%s` stream",
258+
len(record_bytes),
259+
stream_name,
260+
exc_info=exc,
247261
)
248-
self.transport.pause_reading()
262+
if exc_callback is not None:
263+
exc_callback(exc)
249264

250-
return super().received_record(record)
265+
def _queue_for_iteration(self, record: DBNRecord) -> None:
266+
self._dbn_queue.put(record)
267+
# DBNQueue has no max size; so check if it's above capacity, and if so, pause reading
268+
if self._dbn_queue.is_full():
269+
logger.warning(
270+
"record queue is full; %d record(s) to be processed",
271+
self._dbn_queue.qsize(),
272+
)
273+
self.transport.pause_reading()
251274

252275

253276
class Session:

databento/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.35.0"
1+
__version__ = "0.36.0"

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "databento"
3-
version = "0.35.0"
3+
version = "0.36.0"
44
description = "Official Python client library for Databento"
55
authors = [
66
"Databento <support@databento.com>",
@@ -32,7 +32,7 @@ aiohttp = [
3232
{version = "^3.8.3", python = "<3.12"},
3333
{version = "^3.9.0", python = "^3.12"}
3434
]
35-
databento-dbn = "0.18.0"
35+
databento-dbn = "0.18.1"
3636
numpy = [
3737
{version = ">=1.23.5", python = "<3.12"},
3838
{version = "^1.26.0", python = "^3.12"}
@@ -47,12 +47,13 @@ zstandard = ">=0.21.0"
4747
black = "^23.9.1"
4848
mypy = "1.5.1"
4949
pytest = "^7.4.2"
50-
pytest-asyncio = ">=0.21.0"
50+
pytest-asyncio = "==0.21.1"
5151
ruff = "^0.0.291"
5252
types-requests = "^2.30.0.0"
5353
tomli = "^2.0.1"
5454
teamcity-messages = "^1.32"
5555
types-pytz = "^2024.1.0.20240203"
56+
types-aiofiles = "^23.2.0.20240403"
5657

5758
[build-system]
5859
requires = ["poetry-core"]
@@ -75,4 +76,4 @@ warn_unused_ignores = true
7576

7677
[tool.pytest.ini_options]
7778
testpaths = ["tests"]
78-
addopts = "--asyncio-mode auto"
79+
asyncio_mode = "auto"

0 commit comments

Comments
 (0)