Changelog¶
5.0.0 (2026-07-27)¶
Highlights¶
One QWP/WebSocket handle for everything:
questdb.connect()returns a pooled, thread-safeQuestDBthat streams rows, bulk-loads DataFrames, and runs SQL queries.Acknowledged, durable ingestion: rows publish into a store-and-forward queue with automatic reconnect and replay; opt into disk persistence with
sf_dirto survive client restarts.Server rejections are never silent: every rejection is pushed to a handler (or the
questdblogger), with terminal failures also raised from the affected sender.Query egress: SQL with bind parameters streamed back as Arrow record batches into pandas, polars, or pyarrow — pyarrow-free by default.
Columnar DataFrame ingestion for pandas / polars / pyarrow and any Arrow C Data Interface source.
Requires QuestDB server version 10 or later
Breaking changes¶
The public API now lives at the top level of the
questdbpackage:from questdb import Sender, QuestDB, QueryResult, ...(the compiled module is now the privatequestdb._client).questdb.ingressremains as a deprecated compatibility shim re-exporting the 4.x ILP/HTTP and ILP/TCP surface (Sender,Buffer,Protocol,IngressError,VERSION, …) with a single import-timeDeprecationWarning; new code imports fromquestdb.The QWP/WebSocket entry point is
questdb.connect(), returning aQuestDBhandle. The handle lends row-building senders (QuestDB.sender), bulk-loads DataFrames (QuestDB.dataframe), runs queries (QuestDB.query) and lends reader leases (QuestDB.reader). Besides the configuration string,questdb.connect()also accepts the equivalent keywords (host=,port=,tls=plus any further settings).The sender borrowed from the pool is a lean pooled row sender: it exposes
row(),dataframe(),flush(),wait(), the FSN delivery receipts (flush_and_get_fsn,flush_and_keep_and_get_fsn,published_fsn/acked_fsn,await_acked_fsn), lease-scoped rejection polling (poll_error,error_events_dropped) andclose(), and is not aSendersubclass — the standalone-only API such asestablish(), manual progress or explicit buffers simply does not exist on it. One concurrency rule applies everywhere: borrow one sender per thread. Pooled row senders auto-flush by default at 1,000 rows, 100 milliseconds, or a byte threshold derived from the QWP frame cap;auto_flush=offrestores fully explicit publishing. Auto-flush publishes without waiting for an acknowledgement; useflush(wait=True)orwait()as the acknowledgement barrier. The lease types are exported asquestdb.PooledSenderandquestdb.PooledReaderforisinstancechecks and type annotations; instances are only ever constructed byQuestDB.senderandQuestDB.reader.Bufferis no longer part of the top-level API. Buffers are managed internally by senders; for concurrent serialization borrow more senders. Legacy explicit-buffer code keeps working through thequestdb.ingressshim.Over
ws::/wss::, DataFrame bulk loads use the direct columnar path — a database operation, independent ofsf_dir, committed on return, with precisein_doubtfailure semantics. Available asQuestDB.dataframeon the handle, asdataframe()on a pooled sender fromQuestDB.sender(which borrows a direct connection from the pool for that call), and asdataframe()on any standaloneSender(which opens a poolless direct connection from the sender’s own configuration for that call, carrying its auth/TLS regardless of how the sender was built). In all cases the direct load has no ordering relationship with rows buffered viarow()and does not flush them. Over ILP/HTTP, ILP/TCP and QWP/UDP,Sender.dataframe()remains fully supported and is no longer deprecated (over UDP it serializes row by row into fire-and-forget datagrams, with the same delivery caveats asrow()).IngressErroris renamed toQuestDBErrorandIngressErrorCodetoQuestDBErrorCode(the names now also cover the query egress path, which raises the same types).The error model is unified across ingestion and queries. Query/reader failures now surface their own
QuestDBErrorCodemembers (HandshakeError,UnsupportedServer,ProtocolError,InvalidBind,ServerSchemaMismatch,ServerParseError,ServerInternalError,ServerSecurityError,LimitExceeded,ServerLimitExceeded,SchemaDrift,NoSchema,ArrowExport) rather than being bucketed underServerFlushError. Existing member names are unchanged and the numeric codes 0–13 are identical to 4.x. The only shifted 4.x member isBadDataFrame, whose.valuemoved from 14 to0x10000(it is a Python-only sentinel with no backing FFI code); raw value 14 now belongs to the newServerRejection. Compare by member identity, e.g.err.code is QuestDBErrorCode.BadDataFrame— that is the stable contract, not the integer value.Catching errors from the
questdb.ingressshim is broader than in 4.x:IngressErroris an alias of the unifiedQuestDBError, soexcept IngressErrornow also catches query, pool and egress failures raised through the same handle.Pre-epoch
datetimevalues passed as scalars (at=orrow()column values) now floor toward negative infinity when converted to an epoch timestamp. 4.1.0 truncated toward zero, encoding pre-epoch instants with a fractional second one second too high; such values now serialize lower than before, at the correct instant.TimestampNanos.from_datetimenow raisesValueErrorfor datetimes outside the signed 64-bit nanosecond range (1677-09-21 to 2262-04-11 UTC); 4.x silently overflowed.Millisecond-duration parameters given as a negative sub-second
datetime.timedelta(e.g.timedelta(milliseconds=-500)) now raiseValueError; 4.x silently mis-accepted them as positive durations.Naive (timezone-unaware) timestamps are interpreted as UTC everywhere: DataFrame column cells, scalar
at=values,datetimevalues passed torow(),TimestampNanos.from_datetime/TimestampMicros.from_datetime, and query bind parameters. 4.x interpreted naive scalars in the process-local timezone (datetime.timestamp()semantics) — the first naive conversion on such a path now emits a one-per-processUserWarning. Note thatdatetime.now()is your local wall clock: for “now”, useTimestampNanos.now()ordatetime.now(timezone.utc).
Features¶
QWP Ingestion Protocol¶
Adds support for the QuestDB Wire Protocol (QWP) alongside the existing ILP transports.
QWP/UDP (
udp::): fire-and-forget datagram ingestion, defaulting to port 9007. New configuration keysmax_datagram_sizeandmulticast_ttl;protocol_versiondoes not apply.QWP/WebSocket (
ws::/wss::): acknowledged streaming ingestion with frame-sequence-number (FSN) tracking. NewSendermethodsflush_and_get_fsn,flush_and_keep_and_get_fsn,published_fsn,acked_fsn,await_acked_fsn,drive_once,poll_error,error_events_droppedandclose_drain. Server diagnostics are reported through anerror_handlercallback or polled asSenderErrorvalues (classified by theSenderErrorCategory,SenderErrorPolicyandQwpWsProgressenums); terminal server rejections raiseQuestDBServerRejectionError.
Additional configuration keys tls_roots_password,
retry_max_backoff_millis and qwp_ws_progress are also accepted.
Every new key is equally available as a Sender / Sender.from_conf /
Sender.from_env keyword argument (max_datagram_size,
multicast_ttl, tls_roots_password, retry_max_backoff,
qwp_ws_progress and error_handler).
The pooled QuestDB handle exposes QuestDB.sender, returning
a context-managed row sender for row-at-a-time QWP/WebSocket ingestion.
Row builders, whole-dataframe ingestion, and queries share one
questdb.connect("ws::...") configuration without exposing separate row
and column sender pools. The pooled sender’s dataframe() routes to the
same direct columnar path as QuestDB.dataframe, borrowing a direct
connection from the pool for that call only (prefer calling
QuestDB.dataframe directly).
Store-and-forward delivery¶
Pooled row senders publish into a local store-and-forward queue: flush()
returns on local acceptance and a background runner delivers the frames,
reconnecting and replaying across transient failures within the
reconnect_* budget. With the opt-in sf_dir directory the queue is
disk-backed per sender slot and unacknowledged frames are replayed after a
client restart (sender_id names the slots; sf_max_segment_bytes /
sf_max_total_bytes / sf_append_deadline_millis /
close_flush_timeout_millis bound it — see the configuration reference).
flush(wait=True) / wait() observe the server acknowledgement;
request_durable_ack=on upgrades acknowledgements to durable ones on
servers that support them.
Server rejections are pushed to the error_handler callable
passed to questdb.connect() (one questdb.SenderError per
rejection, on a dedicated dispatcher thread; totals via
QuestDB.error_events_delivered /
QuestDB.error_events_dropped). Without a handler every
rejection is logged through the questdb logger, so rejections are
never silent. PooledSender.flush(wait=True) / wait() are pure ack
barriers scoped to the lease’s own publications: only a terminal
connection failure raises there.
Query connections take their own keys — result-set compression
(Zstandard) and the mid-query failover_* / target policy — all
documented in the configuration reference.
Query Egress¶
Adds QuestDB.query, returning a
QueryResult that streams rows as Arrow record batches over the
QWP/WebSocket read endpoint. Queries take positional bind parameters
matching $1..``$N`` placeholders —
db.query('SELECT * FROM trades WHERE ts > $1 AND sym = $2', [ts, 'BTC-USD'])
— covering None (SQL NULL), bool, int, float, str,
datetime.datetime, TimestampMicros, TimestampNanos
and uuid.UUID; always prefer binds over interpolating values into the
SQL text. Results can be consumed via to_arrow,
to_pandas, to_polars, iter_arrow, iter_pandas,
iter_polars or the Arrow C stream PyCapsule protocol
(__arrow_c_stream__). to_polars / iter_polars use pyarrow to
buffer failover-safe batches; __arrow_c_stream__ (consumed as
polars.from_arrow(result)) is the pyarrow-free polars path. SYMBOL
columns are dictionary-encoded on the wire and map to a pandas
Categorical (to_pandas / iter_pandas) or a polars
Categorical (to_polars / iter_polars), the latter sharing one
persistent Categories identity across streamed batches so
polars.concat stitches them without a categories-mismatch error.
to_pandas / iter_pandas default to a native (no-pyarrow) build
straight from the QWP column buffers: a nullable integer column becomes a
pandas nullable Int* when it contains nulls and plain numpy otherwise,
double stays numpy with NaN, TIMESTAMP → datetime64, and
the QuestDB column kinds are recorded in df.attrs['questdb'] for a type
round-trip through QuestDB.dataframe. Pass
dtype_backend="pyarrow" / "numpy_nullable" (or types_mapper=)
to select the pyarrow-backed conversion instead.
QuestDB is a context manager and exposes QuestDB.close and
QuestDB.reap_idle for pooled-connection lifecycle management.
Pool sizing follows the Java client’s configuration keys:
sender_pool_min / sender_pool_max (ingestion, defaults 1/4),
query_pool_min / query_pool_max (readers, defaults 1/4),
acquire_timeout_ms (default 5000 — how long a borrow waits at the cap
before failing; 0 fails fast), idle_timeout_ms (default 60000 —
after which above-minimum idle connections are reaped) and pool_reap
(auto | manual, default auto — whether a background thread
reaps idle connections, or the application calls QuestDB.reap_idle
on its own cadence).
Closing or abandoning a
QueryResultnow releases its pooled connection immediately instead of at garbage collection; abandoning a result without closing it emits aResourceWarning.QuestDB.readerreturns a reader lease (the read-side twin ofQuestDB.sender) that borrows one pooled reader connection for its lifetime and runs queries on it sequentially —with db.reader() as r: r.query(sql)— optionally keeping the connection’s SYMBOL dictionary warm across queries viareset_symbol_dict=False.QuestDB.execute(also on the reader lease) runs a statement, drains whatever it returns, and hands the pooled connection back in one call —db.execute('CREATE TABLE ...')— replacing thewith db.query(ddl) as r: r.to_pandas()drain ceremony for DDL and DML. Statement output is discarded andNoneis returned; useQuestDB.querywhen you want the result.
Columnar DataFrame Ingestion¶
Adds QuestDB.dataframe, ingesting pandas / polars / pyarrow and
any Arrow C Data Interface object over QWP/WebSocket. A
schema_overrides keyword reclassifies columns as symbol,
ipv4, char or geohash (e.g. {'addr': 'ipv4', 'loc':
('geohash', 20)}); it requires fully Arrow-backed input — on input that
falls back to the NumPy planner it raises
UnsupportedDataFrameShapeError rather than being silently
ignored. A max_rows_per_batch keyword (default 16384) bounds the
rows sent per columnar batch.
The designated-timestamp argument at is the timestamp column itself,
given by name (str) or position (int), a fixed
TimestampNanos / datetime shared by every row, or the explicit
ServerTimestamp sentinel. A frame
produced by QueryResult.to_pandas round-trips back to the same
QuestDB column types automatically: the kinds recorded in
df.attrs['questdb'] and pandas nullable extension dtypes are honoured.
Connection Observability¶
A connection_listener callable (accepted by questdb.connect(),
QuestDB.from_conf and the Sender constructors, alongside a
connection_event_inbox_capacity bound) receives one
ConnectionEvent per connection-state transition — initial
connect, per-endpoint attempt failures, failover, terminal auth
rejection — classified by ConnectionEventKind. The listener
runs on a dedicated dispatcher thread fed by a bounded drop-oldest
inbox; delivery totals are exposed as connection_events_delivered /
connection_events_dropped on both QuestDB and
Sender. QuestDB.server_info snapshots the server’s
SERVER_INFO handshake as a ServerInfo (role as
ServerRole, failover epoch, capabilities and cluster / node /
zone ids).
Errors¶
Adds UnsupportedDataFrameShapeError (raised when a DataFrame
cannot be expressed on the QWP columnar path) and the
QuestDBErrorCode members ServerRejection, RoleMismatch,
ArrowUnsupportedColumnKind, ArrowIngest, FailoverRetry,
ConnectTimeout, FailoverWouldDuplicate, Cancelled,
BatchTooLarge and StoreResendRequired.
QuestDBError gains a sender_error property exposing the
structured SenderError view on a server-side QWP/WebSocket
rejection, plus an in_doubt property indicating that failed ingress input
may already have been delivered and must not be blindly replayed without an
appropriate table-level deduplication guarantee.
Build & dependencies¶
The bundled
c-questdb-clientnative library is upgraded to the 7.0.0 development series (pinned at6.1.0-441-g2158029), providing the QWP transports, the columnarcolumn_senderAPI and theline_readerquery API that back the new features above.numpy>=1.21.0is now a hard runtime dependency (previously it was pulled in only via thedataframeextra).pyarrow is now optional. It is imported lazily, only when actually needed (
pd.ArrowDtypecolumns, pyarrow sources,schema_overrides,to_polars/iter_polars, and theto_arrow/iter_arrow/dtype_backendhelpers). The__arrow_c_stream__egress path (consumed aspolars.from_arrow(result)) and the defaultto_pandas/iter_pandaswork without pyarrow.The
dataframeextra now pinspandas>=1.3.5andpyarrow>=10.0.1.
Deprecations¶
The
questdb.ingressmodule is deprecated (kept as a compatibility shim for 4.x ILP/HTTP and ILP/TCP code); it emits oneDeprecationWarningat import. New code imports fromquestdb.
4.1.0 (2025-11-28)¶
Features¶
Decimal Data Type Support¶
This release adds support for ingesting data into QuestDB’s native
DECIMAL(precision, scale) column type introduced in QuestDB 9.2.0.
Use decimals when you need exact-precision values (e.g. prices, balances)
without floating-point rounding issues.
Decimal values can be sent using Python decimal.Decimal objects in both
row() and dataframe() methods. When sending dataframes, you can also use
PyArrow decimal types for better performance.
Sending decimals requires protocol version 3. When using HTTP, this protocol
version is auto-negotiated. For TCP connections, you must explicitly specify
protocol_version=3 in the configuration string:
# HTTP - protocol version 3 is auto-negotiated
conf = 'http::addr=localhost:9000;'
# TCP - must specify protocol_version=3 explicitly
conf = 'tcp::addr=localhost:9009;protocol_version=3;'
Important
Server Requirement: This feature requires QuestDB server version 9.2.0 or higher.
Unlike other column types, DECIMAL columns must be created in advance via SQL before ingesting data. Auto-creation is not supported for DECIMAL columns.
For details on creating DECIMAL columns and working with this data type, see the QuestDB Decimal documentation.
Usage with Python Decimal objects:
from decimal import Decimal
from questdb import Sender, TimestampNanos
# First, create the table with DECIMAL column via SQL:
# CREATE TABLE trades (
# symbol SYMBOL,
# price DECIMAL(18,4),
# amount DECIMAL(18,8),
# timestamp TIMESTAMP
# ) TIMESTAMP(timestamp);
conf = 'http::addr=localhost:9000;'
with Sender.from_conf(conf) as sender:
sender.row(
'trades',
symbols={'symbol': 'ETH-USD'},
columns={
'price': Decimal('2615.5400'),
'amount': Decimal('0.00044000')},
at=TimestampNanos.now())
Usage with Python Decimal objects in Pandas dataframes:
import pandas as pd
from decimal import Decimal
# Create DataFrame with Python Decimal objects
df = pd.DataFrame({
'symbol': ['ETH-USD', 'BTC-USD'],
'price': [Decimal('2615.5400'), Decimal('43210.1234')],
'volume': [Decimal('1234.56789012'), Decimal('98.76543210')]
})
with Sender.from_conf(conf) as sender:
sender.dataframe(
df,
table_name='trades',
symbols='symbol',
at=TimestampNanos.now())
Usage with PyArrow Decimal types in Pandas dataframes:
import pandas as pd
import pyarrow as pa
from decimal import Decimal
# Create DataFrame with Arrow decimal types
df = pd.DataFrame({
'prices': pd.array(
[Decimal('-99999.99'), Decimal('-678.00')],
dtype=pd.ArrowDtype(pa.decimal128(18, 2))
)
})
with Sender.from_conf(conf) as sender:
sender.dataframe(df, table_name='prices', at=TimestampNanos.now())
Additional Arrow Data Type Support¶
Added support for additional PyArrow column types commonly encountered when deserializing from Parquet files or converting Polars dataframes to Pandas:
int16,int32,float32(float),boolstring,large_string(including as symbol types)timestamp[us]with timezone support for microsecond-precision timestamps
Microsecond Timestamp Precision¶
Microsecond-precision timestamp columns (datetime64[us] in NumPy and
timestamp[us] in PyArrow) are now fully supported. When using
protocol_version 2 or higher, microsecond timestamps are sent with full
precision, including for the designated timestamp column.
Bug fixes¶
Updated type hints to allow
Noneas a valid column value inSender.row(). This brings the type annotations in line with the actual behavior, whereNonevalues have always been supported to represent NULL values.
Python Version Support¶
Added support for Python 3.14 on all platforms and 3.14t (free threaded) on all platforms except Windows.
Dropped support for Python 3.9 (end of life).
4.0.0 (2025-10-17)¶
New Breaking Change Feature¶
From QuestDB 9.1.0 onwards you can use CREATE TABLE SQL statements with
TIMESTAMP_NS column types, or rely on column auto-creation.
This client release adds support for sending nanoseconds timestamps to the server without loss of precision.
This release does not introduce new APIs, instead enhancing the sender/buffer’s
.row() API to additionally accept nanosecond precision.
conf = 'http::addr=localhost:9000;'
# or `conf = 'tcp::addr=localhost:9009;protocol_version=2;'`
with Sender.from_conf(conf) as sender:
sender.row(
'trade_executions',
symbols={
'product': 'VOD.L',
'parent_order': '65d1ba36-390e-49a2-93e3-a05ef004b5ff',
'side': 'buy'},
columns={
'order_sent': TimestampNanos(1759246702031355012)},
at=TimestampNanos(1759246702909423071))
If you’re using dataframes, nanosecond timestamps are now also transferred with full precision.
The change is backwards compatible with older QuestDB releases which will simply
continue using the TIMESTAMP column, even when nanoseconds are specified in
the client.
This is a breaking change because it introduces new breaking timestamp column auto-creation behaviour. For full details and upgrade advice, see the nanosecond PR on GitHub.
3.0.0 (2025-07-07)¶
Features¶
This is the first major release of the QuestDB Python client library which supports n-dimensional arrays of doubles for QuestDB servers 9.0.0 and up.
import numpy as np
# Create 2D numpy array
array_2d = np.array([
[1.1, 2.2, 3.3],
[4.4, 5.5, 6.6]], dtype=np.float64)
sender.row(
'table',
columns={'array_2d': array_2d},
at=timestamp)
The array data is sent over a new protocol version (2) that is auto-negotiated
when using HTTP(s), or can be specified explicitly via the protocol_version=2
parameter when using TCP(s).
We recommend using HTTP(s), but here is an TCP example, should you need it:
tcp::addr=localhost:9009;protocol_version=2;
When using protocol_version=2 (with either TCP(s) or HTTP(s)), the sender
will now also serialize float (double-precision) columns as binary.
You might see a performance uplift if this is a dominant data type in your
ingestion workload.
When compared to 2.0.4, this release includes all the changes from 3.0.0rc1 and additionally:
Has optimised ingestion performance from C-style contiguous NumPy arrays.
Warns at most every 10 minutes when burst of reconnections are detected. This is to warn about code patterns that may lead to performance issues, such as
# Don't do this! Sender objects should be reused. for row_fields in data: with Sender.from_conf(conf) as sender: sender.row(**row_fields)
This feature can be disabled in code by setting:
import questdb as qi qi.WARN_HIGH_RECONNECTS = False
Fixed ILP/TCP connection shutdown on Windows where some rows could be lost when closing the
Sender, even if explicitly flushed.Added a “Good Practices” section to the “Sending Data over ILP” section of the documentation.
Breaking Changes¶
Refer to the release notes for 3.0.0rc1 for the breaking changes introduced in this release compared to 2.x.x.
3.0.0rc1 (2025-06-02)¶
This is the pre-release of a major release introducing array ingestion and some minor breaking changes.
Features¶
Array Data Type Support. Adds native support for NumPy arrays (currently only for
np.float64element type and up to 32 dimensions).
Note
Server Requirement: This feature requires QuestDB server version 9.0.0 or higher. Ensure your server is upgraded before ingesting array types, otherwise data ingestion will fail.
import numpy as np
# Create 2D numpy array
array_2d = np.array([
[1.1, 2.2, 3.3],
[4.4, 5.5, 6.6]], dtype=np.float64)
sender.row(
'table',
columns={'array_2d': array_2d},
at=timestamp)
Implements binary protocol for columns of
float(double-precision) andnumpy.ndarray[np.float64], with performance improvements for these two datatypes.
Breaking Changes¶
Buffer Constructor Changes. The
Bufferconstructor now requires theprotocol_versionparameter. You can create buffer through the sender for automaticprotocol_versionmanagement:
buf = sender.new_buffer() # protocol_version determined automatically
buf.row(
'table',
columns={'arr': np.array([1.5, 3.0], dtype=np.float64)},
at=timestamp)
To access the raw payload, call
bytes(sender)orbytes(buffer)( rather than calling thestrfunction on the same objects as in version 2.x.x of the questdb library) method.NumPy Dependency
Array functionality mandates NumPy installation.
Sender/Buffer String Conversion Removal
The legacy string conversion via str(sender) is removed. Access raw binary payloads through the bytes(sender) method:
# for debugging payload = bytes(sender)
Python 3.8 support is dropped.
The minimum supported Python version is now 3.9.
2.0.4 (2025-04-02)¶
Building for Python 3.13.
2.0.3 (2024-06-06)¶
Patch release with bug fixes. No breaking changes.
Bug fixes¶
HTTP timeout wasn’t always being correctly applied in the downstream
c-questdb-clientdependency.request_timeout > 0will now be enforced. This was always required, but would not error.Fixed the source distribution “sdist” package: This allows the package to be installed from source via “pip install” on previously unsupported platforms (YMMV).
2.0.2 (2024-04-11)¶
Patch release with a performance bug fix. No breaking changes.
Bug fixes¶
Fixed the defaulting logic for
auto_flush_rowsparameter for HTTPS. It is now correctly set to 75000 rows by default. The old incorrect default of 600 rows was causing the sender to flush too often, impacting performance. Note that TCP, TCPS and HTTP were not affected.
Features¶
The sender now exposes the
auto_flushsettings as read-only properties. You can inspect the values in use with.auto_flush,.auto_flush_rows,.auto_flush_intervaland.auto_flush_bytes.
2.0.1 (2024-04-03)¶
Patch release with bug fixes, no API changes and some documentation tweaks.
Bug fixes¶
Fixed a bug where an internal “last flushed” timestamp used by
auto_flush_intervalwasn’t updated correctly causing the auto-flush logic to trigger after each row.Removed two unnecessary debugging
print()statements that were accidentally left in the code inSender.from_conf()andSender.from_env().
Documentation¶
Introduced the ability to optionally install
pandasandpyarrowviapython3 -m pip install -U questdb[dataframe]and updated the documentation to reflect this.
2.0.0 (2024-03-19)¶
This is a major release with new features and breaking changes.
Features¶
Support for ILP over HTTP. The sender can now send data to QuestDB via HTTP instead of TCP. This provides error feedback from the server and new features.
conf = 'http::addr=localhost:9000;' with Sender.from_conf(conf) as sender: sender.row(...) sender.dataframe(...) # Will raise `QuestDBError` if there is an error from the server. sender.flush()
New configuration string construction. The sender can now be also constructed from a configuration string in addition to the constructor arguments. This allows for more flexible configuration and is the recommended way to construct a sender. The same string can also be loaded from the
QDB_CLIENT_CONFenvironment variable. The constructor arguments have been updated and some options have changed.Explicit transaction support over HTTP. A set of rows for a single table can now be committed via the sender transactionally. You can do this using a
with sender.transaction('table_name') as txn:block.conf = 'http::addr=localhost:9000;' with Sender.from_conf(conf) as sender: with sender.transaction('test_table') as txn: # Same arguments as the sender methods, minus the table name. txn.row(...) txn.dataframe(...)
A number of documentation improvements.
Breaking Changes¶
New
protocolparameter in the Sender constructor.In previous version the protocol was always TCP. In this new version you must specify the protocol explicitly.
New auto-flush defaults. In previous versions auto-flushing was enabled by default and triggered by a maximum buffer size. In this new version auto-flushing is enabled by row count (600 rows by default) and interval (1 second by default), while auto-flushing by buffer size is disabled by default.
The old behaviour can be still be achieved by tweaking the auto-flush settings.
Setting
Old default
New default
auto_flush_rows
off
600
auto_flush_interval
off
1000
auto_flush_bytes
64512
off
The
at=..argument ofrowanddataframemethods is now mandatory. Omitting it would previously use a server-generated timestamp for the row. Now if you want a server generated timestamp, you can pass the ServerTimestamp singleton to this parameter. _TheServerTimestampbehaviour is considered legacy._The
auth=(u, t, x, y)argument of theSenderconstructor has now been broken up into multiple arguments:username,token,token_x,token_y.The
tlsargument of theSenderconstructor has been removed and replaced with theprotocolargument. UseProtocol.Tcps(orProtocol.Https) to enable TLS. Thetlsvalues have been moved to newtls_caandtls_rootsconfiguration settings.The
net_interfaceargument of theSenderconstructor has been renamed tobind_interfaceand is now only available for TCP connections.
The following example shows how to migrate to the new API.
Old questdb 1.x code
from questdb import Sender
auth = (
'testUser1',
'5UjEMuA0Pj5pjK8a-fa24dyIf-Es5mYny3oE_Wmus48',
'token_x=fLKYEaoEb9lrn3nkwLDA-M_xnuFOdSt9y0Z7_vWSHLU',
'token_y=Dt5tbS1dEDMSYfym3fgMv0B99szno-dFc1rYF9t0aac')
with Sender('localhost', 9009, auth=auth, tls=True) as sender:
sender.row(
'test_table',
symbols={'sym': 'AAPL'},
columns={'price': 100.0}) # `at=None` was defaulted for server time
Equivalent questdb 2.x code
from questdb import Sender, Protocol, ServerTimestamp
sender = Sender(
Protocol.Tcps,
'localhost',
9009,
username='testUser1',
token='5UjEMuA0Pj5pjK8a-fa24dyIf-Es5mYny3oE_Wmus48',
token_x='token_x=fLKYEaoEb9lrn3nkwLDA-M_xnuFOdSt9y0Z7_vWSHLU',
token_y='token_y=Dt5tbS1dEDMSYfym3fgMv0B99szno-dFc1rYF9t0aac',
auto_flush_rows='off',
auto_flush_interval='off',
auto_flush_bytes=64512)
with sender:
sender.row(
'test_table',
symbols={'sym': 'AAPL'},
columns={'price': 100.0},
at=ServerTimestamp)
Equivalent questdb 2.x code with configuration string
from questdb import Sender
conf = (
'tcp::addr=localhost:9009;' +
'username=testUser1;' +
'token=5UjEMuA0Pj5pjK8a-fa24dyIf-Es5mYny3oE_Wmus48;' +
'token_x=token_x=fLKYEaoEb9lrn3nkwLDA-M_xnuFOdSt9y0Z7_vWSHLU;' +
'token_y=token_y=Dt5tbS1dEDMSYfym3fgMv0B99szno-dFc1rYF9t0aac;' +
'auto_flush_rows=off;' +
'auto_flush_interval=off;' +
'auto_flush_bytes=64512;')
with Sender.from_conf(conf) as sender:
sender.row(
'test_table',
symbols={'sym': 'AAPL'},
columns={'price': 100.0},
at=ServerTimestamp)
1.2.0 (2023-11-23)¶
This is a minor release bringing in minor new features and a few bug fixes, without any breaking changes.
Most changes are inherited by internally upgrading to version 3.1.0 of
the c-questdb-client.
Features¶
Sender(..., tls=True)now also uses the OS-provided certificate store. The tls argument can now also be set totls='os_roots'(to only use the OS-provided certs) ortls='webpki_roots'(to only use the certs provided by thewebpki-roots, i.e. the old behaviour prior to this release). The new default behaviour fortls=Trueis equivalent to settingtls='webpki_and_os_roots'.Upgraded dependencies to newer library versions. This also includes the latest webpki-roots crate providing updated TLS CA certificate roots.
Various example code and documentation improvements.
Bug fixes¶
Fixed a bug where timestamp columns could not accept values before Jan 1st 1970 UTC.
TCP connections now enable
SO_KEEPALIVE: This should ensure that connections don’t drop after a period of inactivity.
1.1.0 (2023-01-04)¶
Features¶
High-performance ingestion of Pandas dataframes into QuestDB via ILP. We now support most Pandas column types. The logic is implemented in native code and is orders of magnitude faster than iterating the dataframe in Python and calling the
Buffer.row()orSender.row()methods: TheBuffercan be written from Pandas at hundreds of MiB/s per CPU core. The newdataframe()method continues working with theauto_flushfeature. See API documentation and examples for the newdataframe()method available on both theSenderandBufferclasses.New
TimestampNanos.now()andTimestampMicros.now()methods. These are the new recommended way of getting the current timestamp.The Python GIL is now released during calls to
Sender.flush()and whenauto_flushis triggered. This should improve throughput when using theSenderfrom multiple threads.
Errata¶
In previous releases the documentation for the
from_datetime()methods of theTimestampNanosandTimestampMicrostypes recommended callingdatetime.datetime.utcnow()to get the current timestamp. This is incorrect as it will (confusingly) return an object with the local timezone instead of UTC. This documentation has been corrected and now recommends callingdatetime.datetime.now(tz=datetime.timezone.utc)or (more efficiently) the newTimestampNanos.now()andTimestampMicros.now()methods.
1.0.2 (2022-10-31)¶
Features¶
Support for Python 3.11.
Updated to version 2.1.1 of the
c-questdb-clientlibrary:Setting
SO_REUSEADDRon outbound socket. This is helpful to users with large number of connections who previously ran out of outbound network ports.
1.0.1 (2022-08-16)¶
Features¶
As a matter of convenience, the
Buffer.rowmethod can now takeNonecolumn values. This has the same semantics as skipping the column altogether. Closes #3.
Bug fixes¶
1.0.0 (2022-07-15)¶
Features¶
First stable release.
Insert data into QuestDB via ILP.
Sender and Buffer APIs.
Authentication and TLS support.
Auto-flushing of buffers.
0.0.3 (2022-07-14)¶
Features¶
Initial set of features to connect to the database.
BufferandSenderclasses.First release where
pip install questdbshould work.
0.0.1 (2022-07-08)¶
Features¶
First release on PyPI.