brokapi.record
The event log: write the events of a run to a file, read them back, play them again.
This is brokapi_runtime::record of the Rust library, and the file is the same: a header line
({"brokapi_log":1,"envelope_schema":1}) and one JSON line per envelope. A log recorded here reads in Rust and the
other way round. Prices, quantities and money are written as decimal strings, so they come back exactly.
| Need | Item |
|---|---|
| Append envelopes to a file | EnvelopeWriter, or record_events() around a stream |
| Read them back in order | EnvelopeReader |
Feed them to the code that reads client.events, fast or at the recorded pace |
replay(), ReplaySpeed |
What the file keeps and what it does not:
- The monotonic clock of the receive time (
rx_monoin Rust) is not stored, and Python does not show it at all. UseTimestamps.rx_nsandbroker_nsto order and measure replayed data. - A line that cannot be read stops the read with a
~brokapi.LogErrorthat names the line. A reader never skips a bad line silently. - An envelope with a
schemanewer than this build understands is refused (LogErrorof kindschema). - It is a log of a stream, not a catalog: there is no index by instrument or time, so filter while you read.
- Only an envelope that came from a connector or from a log can be written: it carries the line it was made from. An
Envelopethat you build by hand cannot.
>>> import asyncio, os, tempfile
>>> from brokapi import record
>>> from brokapi.testing import mock_bcs
>>> async def demo():
... path = os.path.join(tempfile.mkdtemp(), "run.jsonl")
... async with await mock_bcs() as mock:
... client = await mock.connect()
... with record.EnvelopeWriter(path) as log:
... log.write(await client.events.get(timeout=10))
... await client.close()
... return len([envelope async for envelope in record.replay(path)])
>>> asyncio.run(demo())
1
Reads the envelopes of a log back, in the order they were written: for envelope in EnvelopeReader(path).
Opening the file reads and checks the header: a file that is not a log, an empty one, or one written by a newer
version raises ~brokapi.LogError at once. After the first bad line the iteration raises it and ends: what
follows a bad line cannot be trusted to be in order.
Appends envelopes to a new log file, one line each. Use it as with: the file is flushed and closed at the end.
The file is created and the header written when the writer is made. An existing file is an error
(LogError of kind io) unless overwrite=True: a log is a record of one run, and a second run must not
silently replace the first. Writes are buffered: flush() or close() after the last one, so the tail
is not left in a buffer when the program ends. After a failed write do not append to the same file, start a new
one.
Appends one envelope as one line. Raises ~brokapi.LogError when the write fails.
The version of the file format. It is in the header line of every file.
None is as fast as the consumer reads. Else the speed-up: 1.0 is real time, 10.0 ten times faster.
The longest single pause, so that a night between two sessions does not stop the replay.
Passes the events of a stream on and writes each one to a log first:
async for envelope in record_events(client.events, "run.jsonl").
It records what your code reads, which is the stream as the bot saw it. Each envelope is flushed to the file as
it is written, so a program that stops in the middle of a run (a crash, break) leaves a log that is complete up
to the last envelope it read. The file is closed when the stream ends or the generator is closed
(contextlib.aclosing), not at the break itself.
A ~brokapi.Gap (the stream of a client made by ~brokapi.connect_gateway()) is passed on and not written:
it is not an event of a connector, and the file has the format of those.
Plays a log as a stream of envelopes, in order, like client.events: async for envelope in replay(path).
The envelopes keep their source and sub, so the code that reads them routes them as it did live. The
difference from a live connector is the one that matters for research: a replay waits for the reader, so a
slow reader slows it down and no event is lost. A live connector cannot wait and applies the loss policy of the
subscription instead. Code that handles Overflow therefore needs its own test: a replay never produces one.
buffer is how many envelopes are read ahead. A bad line raises ~brokapi.LogError after the envelopes
before it. The file is opened (and its header checked) at the call, not at the first await.