Records: SQL for Humans
Records is a simple yet powerful Python library designed for making raw SQL queries to various relational databases with an elegant interface. It aims to reduce boilerplate code associated with common database tasks. The current version, 0.6.0, includes support for SQLAlchemy v2, making it compatible with modern database backends. Releases are infrequent but tend to include significant updates.
Warnings
- breaking Version 0.6.0 introduces support for SQLAlchemy 2+ and drops support for Python 2.7, 3.4, and 3.5. Applications targeting these older Python versions or SQLAlchemy 1.x will encounter compatibility issues. SQLAlchemy 2.0 itself includes significant breaking changes in its API from 1.x, which might indirectly affect custom extensions or very specific usage patterns if Records' abstraction leaks.
- gotcha While Records supports parameterized queries (e.g., `db.query('...', name='value')`), directly concatenating user-supplied input into SQL strings (SQL injection) remains a severe security vulnerability. This applies to any library allowing raw SQL queries. Always use parameterized queries for all dynamic values.
- gotcha Records employs lazy database connection. The connection to the database is not fully established or checked until the first query is executed. This means that errors related to incorrect connection strings or inaccessible databases will only manifest at the time of the first query, not at the `records.Database()` initialization.
- gotcha Without explicitly using `db.transaction()`, each `db.query()` might operate as an auto-committed transaction, depending on the underlying database and its driver's default behavior. This can lead to unexpected partial commits if multiple queries are intended to be part of a single, atomic operation.
Install
-
pip install records -
pip install records[pandas]
Imports
- Database
import records db = records.Database(...)
Quickstart
import records
import os
# Example using SQLite in-memory or a database URL from an environment variable
# For other databases, change the connection string, e.g.,
# "postgres://user:pass@host:port/dbname"
database_url = os.environ.get('DATABASE_URL', 'sqlite:///:memory:')
db = records.Database(database_url)
# Create a table and insert data (example for SQLite)
if 'sqlite' in database_url:
db.query('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)')
db.query('INSERT INTO users (name, age) VALUES (:name, :age)', name='Alice', age=30)
db.query('INSERT INTO users (name, age) VALUES (:name, :age)', name='Bob', age=24)
# Run a query
rows = db.query('SELECT * FROM users WHERE age > :min_age', min_age=25)
# Iterate over results
print('Users over 25:')
for row in rows:
print(f" Name: {row.name}, Age: {row.age}")
# Access columns by name or index
first_row = rows.first()
if first_row:
print(f"First user found: {first_row.name}")
# Export to CSV (requires tablib)
# print(rows.export('csv'))
# Export to Pandas DataFrame (requires pandas)
# df = rows.export('df')
# print(df.head())
# Close the connection (important for some databases)
db.close()