Skip to main content

Pure-Python PEP-249 (DB-API 2.0) driver for NusaDB (Nusa Wire Protocol)

Project description

nusadb — Python driver for NusaDB

A pure-Python, dependency-free PEP-249 / DB-API 2.0 driver that speaks the Nusa Wire Protocol (PROTOCOL_VERSION 1.1) directly over a socket. No C extension; standard library only. cursor.description[i][1] (type_code) carries each column's NusaDB type name (protocol 1.1).

Install

pip install ./drivers/python      # from the repo, or
pip install nusadb                 # once published

Usage

import nusadb

conn = nusadb.connect(host="127.0.0.1", port=5678, user="nusa-root", database="nusadb")
cur = conn.cursor()

cur.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
cur.execute("INSERT INTO t VALUES ($1, $2)", [1, "alice"])

cur.execute("SELECT id, name FROM t WHERE id = $1", [1])
for row in cur:
    print(row)            # (1, 'alice')   — INT decodes to int, TEXT to str

conn.close()

Value types

Each column's wire type tag (protocol 1.1) drives decoding, so values come back as the natural Python type — full typed DB-API fidelity rather than everything-as-string:

NusaDB type Python type
BOOL bool
INT int
FLOAT float
NUMERIC decimal.Decimal
DATE / TIME / TIMESTAMP (and …TZ) datetime.date / time / datetime
UUID uuid.UUID
JSON parsed dict / list / scalar
ARRAY list (elements stay str — the wire array tag carries no element type)
BYTEA bytes
TEXT / VARCHAR / everything else str

A value that does not parse as its declared tag falls back to the raw text, so an unexpected wire form never raises mid-fetch. cursor.description[i][1] (type_code) still carries the type name.

Bulk insert (executemany)

cursor.executemany(sql, seq_of_parameters) runs one statement once per parameter set (DB-API 2.0); cursor.rowcount is the total affected. The wire protocol has no batch pipeline, so this is one round-trip per set.

cur.executemany("INSERT INTO t VALUES ($1, $2)", [[1, "a"], [2, "b"], [3, "c"]])

Bulk load / export (COPY)

For high-throughput load/export, conn.copy_in / conn.copy_out drive the COPY sub-protocol — one round-trip for the whole dataset. Move bytes in the server's text format (tab-delimited fields, \N for SQL NULL, one row per line); you write the COPY statement with any WITH (...) options.

import io

# Bulk load from any binary file-like (read(size) -> bytes).
loaded = conn.copy_in("COPY t (id, name) FROM STDIN", io.BytesIO(b"1\talice\n2\t\\N\n"))

# Bulk export into any binary file-like (write(bytes)).
sink = io.BytesIO()
exported = conn.copy_out("COPY t TO STDOUT", sink)

A COPY the server refuses (bad SQL, an RLS-protected table) raises; the connection stays usable.

Parameters

Placeholders are positional $1, $2, … (the server's native marker). paramstyle is reported as "numeric". Pass the bound values as a sequence to execute / executemany; None is SQL NULL. Values are sent in the wire text format — bind with an explicit CAST(... AS type) when a numeric-looking string must land in a non-numeric column.

TLS

Pass an ssl.SSLContext to connect(ssl=...). The server uses implicit TLS 1.3, so the TLS session is established before any protocol frame.

import ssl, nusadb
ctx = ssl.create_default_context(cafile="ca.pem")
conn = nusadb.connect(host="db.example", port=5678, user="u", database="nusadb",
                      password="…", ssl=ctx)

Authentication

If the server runs with --auth-user USER:PASSWORD, pass password=; the driver performs the SCRAM-SHA-256 handshake and verifies the server's signature (mutual auth).

Connection pool

from nusadb import Pool

pool = Pool(max_size=10, host="127.0.0.1", port=5678, user="nusa-root", database="nusadb")
with pool.connection() as conn:
    conn.cursor().execute("SELECT 1")

Transactions

By default the connection is in autocommit mode (each statement is its own transaction). Pass autocommit=False for the standard transactional model: the connection opens a transaction lazily before the first statement, and commit()/rollback() send COMMIT/ROLLBACK.

Inside a transaction, savepoint(name) marks a point you can later undo to with rollback_to(name) (the transaction stays open) or forget with release_savepoint(name):

cur.execute("INSERT INTO t VALUES (1)")
conn.savepoint("sp1")
cur.execute("INSERT INTO t VALUES (2)")
conn.rollback_to("sp1")   # undoes (2), keeps (1); the transaction continues
conn.commit()
conn = nusadb.connect(port=5678, user="nusa-root", database="nusadb", autocommit=False)
cur = conn.cursor()
cur.execute("INSERT INTO t VALUES (1)")
conn.rollback()   # discards the insert
cur.execute("INSERT INTO t VALUES (2)")
conn.commit()     # persists it

Notifications (LISTEN/NOTIFY)

listen(channel) subscribes the connection; a notify(channel, payload) from any connection on the same database is then delivered asynchronously. Collect delivered notifications with poll(timeout) (seconds; 0 polls without blocking, None blocks) or drain the ones buffered during other queries with notifications():

conn.listen("orders")
# ... elsewhere: other.notify("orders", "42")
note = conn.poll(5.0)          # -> Notification(pid, channel, payload), or None on timeout
print(note.channel, note.payload)
conn.unlisten("orders")

SQLAlchemy

A SQLAlchemy dialect ships in nusadb.sqlalchemy. Once this package is installed its sqlalchemy.dialects entry point registers it, so create_engine("nusadb://…") works directly:

from sqlalchemy import create_engine
engine = create_engine("nusadb://nusa-root@127.0.0.1:5678/nusadb")

Without installing, register it explicitly:

from sqlalchemy.dialects import registry
registry.register("nusadb", "nusadb.sqlalchemy", "NusaDialect")

It supports full ORM use — declarative models, metadata.create_all, insert/query/update/delete, pagination (.limit()/.offset()), joins, aggregates, and real transactions — over the transactional connection, rewriting SQLAlchemy's :1 markers to the server's $1. LIMIT/OFFSET are inlined as constants (the server rejects a bound row count). Reflection (inspect(engine).get_columns(...)) reports each column's server-side default and infers autoincrement from a nextval(...) default, so Alembic autogenerate sees them. Install the extra with pip install nusadb[sqlalchemy].

Tests

cargo build -p nusadb-server          # the tests boot this binary
python -m unittest discover -s drivers/python/tests

The tests start a real nusadb-server on an ephemeral port and exercise simple and parameterised queries, executemany, errors, the pool, cancellation, and SCRAM auth.

License

Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nusadb-0.1.1.tar.gz (32.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nusadb-0.1.1-py3-none-any.whl (25.4 kB view details)

Uploaded Python 3

File details

Details for the file nusadb-0.1.1.tar.gz.

File metadata

  • Download URL: nusadb-0.1.1.tar.gz
  • Upload date:
  • Size: 32.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nusadb-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3012f904a4610633ce4f023decaa0273ae918c868de767df7b0fee3ef7883b25
MD5 e143343a0778bc2b46f4bc0fedefdcab
BLAKE2b-256 9090296926bcf6c9f3d8f535bd8f69290254be6a14fd3089673056c84c8c1d21

See more details on using hashes here.

File details

Details for the file nusadb-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: nusadb-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 25.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nusadb-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 eb844f48bfa87ee19a49f88d53995ddfc1ccc88ba884797c3c775833d02ac2ad
MD5 629f5f17b270e6e987868f8f676d4a27
BLAKE2b-256 18f4cb336c04088b6b218ffe82920d7d854f0ffbcde60c9305c6869cc02e11f7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page