Features
Database Connections
Defining database connections in the settings and querying them with db_query(), db_execute(), and db_transaction()
Overview
Framework defines database connections once, in the project settings, and opens them by name. The query functions open a connection, run the statement, and close the connection again, so analysis code does not manage connections by hand. Credentials come from the project's .env file and never sit in the settings.
Configuring Connections
Connections live in the connections section, which standard and privacy-sensitive projects keep in settings/connections.yml. Each database is an entry under databases.
connections:
default_database: framework
databases:
framework:
driver: sqlite
database: framework.db
warehouse:
driver: postgres
host: env("POSTGRES_HOST")
port: env("POSTGRES_PORT", "5432")
database: env("POSTGRES_DB")
schema: env("POSTGRES_SCHEMA", "public")
user: env("POSTGRES_USER")
password: env("POSTGRES_PASSWORD")
local:
driver: duckdb
database: inputs/final/warehouse.duckdb
storage_buckets: {}
Every new project defines the framework connection, which points at the project's own framework.db.
env() reads a value from the environment, which Framework loads from .env before it reads the settings. New projects have empty POSTGRES_* entries in .env to fill in.
# .env (ignored by git)
POSTGRES_HOST=db.example.org
POSTGRES_PORT=5432
POSTGRES_DB=analytics
POSTGRES_SCHEMA=public
POSTGRES_USER=analyst
POSTGRES_PASSWORD=change-me
Framework also finds connections written directly under connections with a driver key, and in a top-level databases section, which older R projects use.
Drivers
SQLite is built into Python. The other drivers come from optional extras of framework-py.
| Driver names | Python package | Extra | Required fields |
|---|---|---|---|
sqlite |
sqlite3 (built in) | database |
|
postgres, postgresql |
psycopg | postgres |
host, port, database, user, password |
mysql, mariadb |
PyMySQL | mysql |
host, port, database, user, password |
duckdb |
duckdb | duckdb |
database |
sqlserver, mssql |
pyodbc | sqlserver |
host, database, user, password |
uv add "framework-py[postgres]"
A connection whose package is missing raises an error that names the install command. db_drivers_status() reports which drivers are installed.
import framework as fw
fw.db_drivers_status()
Each driver accepts a few more fields.
- SQLite and DuckDB.
databaseis a file path, relative to the project root. Its folder must exist. DuckDB also takesread_only,memory_limit, andthreads. - PostgreSQL.
schemasets the search path and defaults topublic. - SQL Server.
portdefaults to 1433.odbc_drivernames the ODBC driver and defaults toODBC Driver 17 for SQL Server.trust_server_certificatedefaults totrue, which accepts self-signed certificates. The ODBC driver itself is installed separately.
PostgreSQL, MySQL, and SQL Server connections time out after ten seconds when the server does not answer.
Running Queries
db_query() runs a query and returns the rows as a DataFrame. It takes the SQL and the connection name.
visits = fw.db_query("SELECT * FROM visits WHERE active", "warehouse")
Pass values in params, never by formatting them into the SQL string. The placeholder style is the driver's: ? for SQLite, DuckDB, and SQL Server, and %s for PostgreSQL and MySQL.
fw.db_query("SELECT * FROM visits WHERE site = ?", "local", params=["north"])
fw.db_query("SELECT * FROM visits WHERE site = %s", "warehouse", params=["north"])
db_execute() runs a statement, commits it, and returns the number of rows affected. It returns -1 when the driver does not report a count.
fw.db_execute("CREATE TABLE IF NOT EXISTS visits (id INTEGER, site TEXT)", "local")
fw.db_execute("INSERT INTO visits VALUES (?, ?)", "local", params=[1, "north"])
A failed query raises FrameworkError with the driver's message.
Working with a Connection
db_connect() opens a connection and returns a Connection. It behaves like the driver's own connection object, with cursor(), commit(), and the rest of the DB-API, and it closes when a with block ends.
with fw.db_connect("warehouse") as conn:
cursor = conn.cursor()
cursor.execute("SELECT count(*) FROM visits")
print(cursor.fetchone())
The driver's object is available as conn.raw for features outside the DB-API.
Transactions
db_transaction() groups statements so that they are committed together. When the block raises an exception, every statement in it is rolled back, and Framework raises FrameworkError with the original exception as its cause.
with fw.db_connect("local") as conn, fw.db_transaction(conn):
cursor = conn.cursor()
cursor.execute("INSERT INTO visits VALUES (2, 'south')")
cursor.execute("INSERT INTO visits VALUES (3, 'east')")
A transaction inside another transaction on the same connection joins the outer one.
Listing Connections
db_list() returns the configured databases with their driver, host, port, and database. connections_list() returns databases and storage buckets together and marks the defaults.
fw.db_list()
fw.connections_list()
Storage buckets are defined in the same section and used for publishing. The Publishing guide covers them.
Credentials
Keep every credential in .env and refer to it with env(). Settings files are committed to git, and .env is not.
# Settings: a reference to the environment
password: env("POSTGRES_PASSWORD")
Framework refuses secret-looking values in project specs and in your global defaults, and it makes sure every project's .gitignore excludes .env. The Environment guide covers .env files.