SQLAlchemy dialect integrated into Cloud Spanner database
sqlalchemy-spanner is a Python SQLAlchemy dialect that enables applications to connect to and interact with Google Cloud Spanner databases. It leverages Cloud Spanner's scale, strong consistency, and high availability. The library is actively maintained by Google and sees regular releases with new features and bug fixes.
Warnings
- gotcha The database URL format for `sqlalchemy-spanner` differs between SQLAlchemy 1.3 and 1.4+/2.0. For SQLAlchemy 1.3, use `spanner:///...`. For SQLAlchemy 1.4+ and 2.0, use `spanner+spanner:///...`.
- gotcha Cloud Spanner does not support DDL (Data Definition Language) statements within transactions. Any DDL operations performed will not be rolled back upon transaction failure.
- gotcha The dialect and the underlying DB API driver do not support Spanner mutations directly; only DML (Data Manipulation Language) statements are supported for updates.
- gotcha When using Alembic for migrations, setting `version_table_pk` to `False` in your Alembic environment configuration is highly recommended to avoid issues with Spanner's primary key restrictions on the `alembic_versions` table.
- gotcha The `sqlalchemy-spanner` dialect is currently synchronous only. There is no official asynchronous dialect available, which can be a blocker for projects requiring an async engine with SQLAlchemy 2.0+'s async features.
- gotcha Using `autoload=True` with `MetaData` in a highly concurrent environment can lead to `sqlalchemy.exc.CompileError: Unconsumed column names` due to race conditions during table reflection. It's an anti-pattern to reflect tables on every statement.
- gotcha The dialect does not currently propagate the `timeout` execution option to the underlying gRPC client, causing all queries to use the default gRPC timeout of 3600 seconds regardless of `execution_options(timeout=...)`.
Install
-
pip install sqlalchemy-spanner
Imports
- create_engine
from sqlalchemy import create_engine
Quickstart
import os
from sqlalchemy import create_engine, text, MetaData, Table, Column, String, Integer
# Set these environment variables or replace directly
project_id = os.environ.get('SPANNER_PROJECT_ID', 'your-gcp-project-id')
instance_id = os.environ.get('SPANNER_INSTANCE_ID', 'your-spanner-instance-id')
database_id = os.environ.get('SPANNER_DATABASE_ID', 'your-spanner-database-id')
# For SQLAlchemy 1.4+ and 2.0, use 'spanner+spanner:///'
# For SQLAlchemy 1.3, use 'spanner:///'
db_url = f"spanner+spanner:///projects/{project_id}/instances/{instance_id}/databases/{database_id}"
try:
engine = create_engine(db_url)
# Example: Define a table for demonstration
metadata = MetaData()
users = Table(
'users',
metadata,
Column('user_id', String(36), primary_key=True),
Column('name', String(255), nullable=False),
Column('age', Integer)
)
# Create tables (DDL operations are not transactional in Spanner)
with engine.connect() as connection:
connection.execute(text("CREATE TABLE IF NOT EXISTS users (user_id STRING(36) NOT NULL, name STRING(255) NOT NULL, age INT64) PRIMARY KEY (user_id)"))
connection.commit()
print("Table 'users' ensured.")
# Insert data
with engine.begin() as connection:
connection.execute(users.insert(), {"user_id": "user1", "name": "Alice", "age": 30})
connection.execute(users.insert(), {"user_id": "user2", "name": "Bob", "age": 24})
print("Data inserted.")
# Query data
with engine.connect() as connection:
result = connection.execute(users.select().where(users.c.age > 25)).fetchall()
print("Users older than 25:", result)
except Exception as e:
print(f"An error occurred: {e}")
print("Ensure Cloud Spanner project, instance, and database are correctly configured and `GOOGLE_APPLICATION_CREDENTIALS` is set or default credentials are available.")