Skip to content

Commit bbf5103

Browse files
committed
ADD: Add loop parameter to Live client
1 parent 07b0181 commit bbf5103

2 files changed

Lines changed: 30 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 0.82.0 - TBD
4+
5+
#### Enhancements
6+
- Added a `loop` parameter to the `Live` client to provide user level control
7+
of which event loop the client's connection uses
8+
- The `Live` class will no longer create a background thread and event loop
9+
upon import
10+
311
## 0.81.0 - 2026-07-07
412

513
#### Enhancements

databento/live/client.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,16 @@ class Live:
7272
compression : Compression or str, default "none"
7373
The compression format for live data. Set to "zstd" for
7474
Zstandard-compressed data from the gateway.
75+
loop : asyncio.AbstractEventLoop, optional
76+
The event loop to run the client connection in. The loop must already be
77+
running on another thread than the caller's. If unspecified, a shared
78+
event loop running on a background thread will be used.
7579
7680
"""
7781

78-
_loop = asyncio.new_event_loop()
82+
_shared_loop: asyncio.AbstractEventLoop | None = None
7983
_lock = threading.Lock()
80-
_thread = threading.Thread(
81-
target=_loop.run_forever,
82-
name="databento_live",
83-
daemon=True,
84-
)
84+
_thread: threading.Thread | None = None
8585

8686
def __init__(
8787
self,
@@ -93,6 +93,7 @@ def __init__(
9393
reconnect_policy: ReconnectPolicy | str = ReconnectPolicy.NONE,
9494
slow_reader_behavior: SlowReaderBehavior | str | None = None,
9595
compression: Compression = Compression.NONE,
96+
loop: asyncio.AbstractEventLoop | None = None,
9697
) -> None:
9798
if key is None:
9899
key = os.environ.get("DATABENTO_API_KEY")
@@ -112,6 +113,7 @@ def __init__(
112113
self._ts_out = ts_out
113114
self._compression = compression
114115
self._heartbeat_interval_s = heartbeat_interval_s
116+
self._loop = loop if loop is not None else Live._get_shared_loop()
115117

116118
self._metadata: SessionMetadata = SessionMetadata()
117119
self._symbology_map: dict[int, str | int] = {}
@@ -130,9 +132,18 @@ def __init__(
130132

131133
self._session._user_callbacks.append(ClientRecordCallback(self._map_symbol))
132134

133-
with Live._lock:
134-
if not Live._thread.is_alive():
135-
Live._thread.start()
135+
@classmethod
136+
def _get_shared_loop(cls) -> asyncio.AbstractEventLoop:
137+
with cls._lock:
138+
if cls._shared_loop is None:
139+
cls._shared_loop = asyncio.new_event_loop()
140+
cls._thread = threading.Thread(
141+
target=cls._shared_loop.run_forever,
142+
name="databento_live",
143+
daemon=True,
144+
)
145+
cls._thread.start()
146+
return cls._shared_loop
136147

137148
def __del__(self) -> None:
138149
try:
@@ -649,7 +660,7 @@ def block_for_close(
649660
try:
650661
asyncio.run_coroutine_threadsafe(
651662
self._session.wait_for_close(),
652-
loop=Live._loop,
663+
loop=self._loop,
653664
).result(timeout=timeout)
654665
except (futures.TimeoutError, KeyboardInterrupt) as exc:
655666
logger.info("closing session due to %s", type(exc).__name__)
@@ -697,7 +708,7 @@ async def wait_for_close(
697708
waiter = asyncio.wrap_future(
698709
asyncio.run_coroutine_threadsafe(
699710
self._session.wait_for_close(),
700-
loop=Live._loop,
711+
loop=self._loop,
701712
),
702713
)
703714

0 commit comments

Comments
 (0)