This release is a pre-release and may not be stable for production use.
alchemy-utils
An executable research spike for a subset of the
sqlite-utils Python API backed by
SQLAlchemy Core, built using GPT-5.6 Sol Ultra and Codex.
It demonstrates the same style of table-first API across SQLite, PostgreSQL, and DuckDB:
from alchemy_utils import Database
db = Database("sqlite:///:memory:")
people = db["people"].insert(
{"id": 1, "name": "Ada", "profile": {"language": "Python"}},
pk="id",
)
people.upsert({"id": 1, "name": "Ada Lovelace"})
people.insert_all(
[
{"id": 2, "name": "Grace"},
{"id": 3, "name": "Katherine"},
]
)
people.update(2, {"name": "Grace Hopper"})
assert people.pks == ["id"]
assert people.columns_dict["profile"] is dict
assert people.get(1)["name"] == "Ada Lovelace"
Swap only the URL to use another engine:
postgres = Database("postgresql+psycopg://user:password@localhost/app")
duckdb = Database("duckdb:///analytics.duckdb")
This is a spike, not a published compatibility promise. See RESEARCH.md for the conclusion, design trade-offs, and a rough production estimate.
Implemented API
Database supports:
- construction from a SQLAlchemy
Engine,URL, URL string, or SQLite path; db[name],db.table(), anddb.create_table();table_names(),view_names(),tables, and normalizedschema;- context-manager cleanup and
close().
Table supports:
create()with Python or SQLAlchemy types, single/compound primary keys, partial column ordering,NOT NULL, server defaults, single/compound foreign keys, and existing-table options;insert(),insert_all(),upsert(),upsert_all(), andupdate();- generated integer primary keys on all three engines;
alter=Truefor new nullable columns, plus insertignore=Trueandreplace=Trueconflict modes;exists(),count,rows, andget();columns,columns_dict,pks,foreign_keys,indexes,schema,default_values, anduse_rowid.
All mutation methods return the same Table object for chaining. A one-record
write sets last_pk; bulk writes leave it as None.
Engine-specific architecture
Calling Database(...) selects an independent engine implementation:
Database factory
├── SQLiteDatabase SQLite ON CONFLICT and rowid capability
├── PostgreSQLDatabase PostgreSQL ON CONFLICT
└── DuckDBDatabase DuckDB ON CONFLICT, sequences, catalog fallbacks
│
└── PK / JSON / index / DDL reflection repairs
Table shared API and orchestration only
The shared Table class does not inspect dialect names or build dialect SQL.
It delegates conflict statements, generated-key DDL, table lifecycle, and
reflection to its Database instance.
Install
The base package needs Click and SQLAlchemy and works with Python 3.10 or later. Engine drivers are extras:
pip install alchemy-utils
pip install 'alchemy-utils[postgresql]'
pip install 'alchemy-utils[duckdb]'
For this checkout, uv sync installs the development group, including both
drivers and the test tools.
Command-line interface
Installing the package adds a collision-safe alchemy-utils command.
It follows the relevant sqlite-utils command shapes, but its DATABASE
argument can be either a SQLite filename or any installed SQLAlchemy URL:
# SQLite: a bare path
alchemy-utils create-table data.db people \
id integer name text profile json --pk id --not-null name
# PostgreSQL and DuckDB: SQLAlchemy URLs
alchemy-utils tables \
postgresql+psycopg://user@localhost/app --json
alchemy-utils schema duckdb:///analytics.duckdb
For PostgreSQL credentials, prefer libpq environment variables or a password file instead of putting a password in the command-line URL, where it could be recorded in shell history.
insert and upsert each handle either one record or many records, mapping to
the corresponding single-record or *_all() API. The default input is a JSON
object or array. Use --nl, --csv, or --tsv for other formats; - reads
standard input, and those formats are also detected from file extensions.
CSV and TSV inputs are streamed in batches of 100 records; use --batch-size
to tune the batch size. --alter scans all records first so it can add every
missing column before writing.
echo '{"id": 1, "name": "Ada"}' \
| alchemy-utils insert data.db people -
printf '%s\n' \
'{"id": 1, "name": "Ada Lovelace"}' \
'{"id": 2, "name": "Grace Hopper"}' \
| alchemy-utils upsert data.db people - --nl
echo '{"name": "Amazing Grace", "active": true}' \
| alchemy-utils update data.db people 2 - --alter
Repeat --pk for compound keys. get and update accept a JSON array for a
compound key, for example '["acme", 7]'. Binary JSON values use sqlite-utils'
portable shape, {"$base64": true, "encoded": "AP8="}.
Available inspection and read commands are tables, views, schema,
columns, indexes, foreign-keys, rows, get, and count. Metadata and
rows use normalized JSON (or JSON lines with --nl where offered); schema is
engine-shaped reflected DDL. tables and views use JSON by default, with
--plain for one name per line. Mutations are silent on success, as in
sqlite-utils. Run any command with --help for its options. The same interface
is also available as python -m alchemy_utils.
Test
uv sync
uv run ruff check src tests
uv run pytest
PostgreSQL tests use testing.postgresql to start one disposable server and a
unique database per test. They never use an existing application database. The
fixture finds postgres and initdb from environment variables, PATH, or
pg_config; if it cannot find them, PostgreSQL cases are skipped.
Homebrew example:
PG_BIN="$(brew --prefix postgresql@18)/bin"
POSTGRESQL_PATH="$PG_BIN/postgres" \
INITDB_PATH="$PG_BIN/initdb" \
uv run pytest
The library and CLI suites run on all three engines and have been exercised on both Python 3.10 and 3.14.3, with SQLAlchemy 2.0.52, SQLite 3.50.4, PostgreSQL 18.3, DuckDB 1.5.5, duckdb-engine 0.17.0, and psycopg 3.3.4.
Deliberate spike limitations
- Bulk inputs are materialized in memory;
batch_sizeis accepted but not yet used for streaming chunks. - Bulk input supports mappings, not sqlite-utils' header-plus-sequence mode.
alter=Trueonly adds nullable columns. Full transforms are out of scope.- Exact SQLite DDL text, implicit indexes, triggers, checks, FTS, and STRICT metadata are not portable and are not emulated.
hash_id, extracts, conversions,analyze, and schema transforms are not implemented.- Reflected type names and raw server-default SQL vary by engine. The stable portable fields are names, Python types, nullability, key ordering, foreign key shape, and explicit indexes.
- A hidden
rowidis never synthesized as a primary key.use_rowidreports the SQLite capability, whilepksreturns only declared keys on every engine. - Mixing explicit integer primary keys with later generated keys may require synchronizing the PostgreSQL or DuckDB sequence; this spike does not do that.
- DuckDB expression-index parsing is intentionally best-effort; ordinary column indexes are covered.
Release files for alchemy-utils 0.1a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| alchemy_utils-0.1a1.tar.gz | 23.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| alchemy_utils-0.1a1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.9 kB
Release files / alchemy_utils-0.1a1.tar.gz
| Download URL | alchemy_utils-0.1a1.tar.gz |
|---|---|
| Size | 23.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
78ba8ee725c9d6b7f3621d2355131f32c88d17483595674b4e4f34fd65f9901f
|
|
BLAKE2b-256 checksum How to use checksums |
edc8e89a75392c9926a42a1ad9c023a7c18ebb83d3599f608e863d1547bc9d25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.
Transparency logRelease files / alchemy_utils-0.1a1-py3-none-any.whl
| Download URL | alchemy_utils-0.1a1-py3-none-any.whl |
|---|---|
| Size | 27.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d3239b56d3375ce2270577657ab4938fedd245cef1a5312bfae31be99472d7c3
|
|
BLAKE2b-256 checksum How to use checksums |
8bd4700efee252bac851774e1109a8b2f2d056f7d73684bb6868385231784d0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.
Transparency log