Schema migration tool for Google Cloud Spanner
Project description
Spanshift
Schema migration tool for Google Cloud Spanner.
- Chain validation -- migrations form a linked list; broken chains are caught before execution
- Distributed locking -- prevents concurrent migration runs across multiple machines
- Dry-run mode -- preview DDL changes without touching your database
- Checksum verification -- detects modified migration files after they've been applied
- Multi-environment config -- manage dev, staging, and production from one
spanshift.toml
Installation
pip install spanshift
Requires Python 3.11+ and Application Default Credentials configured for GCP access.
Quick Start
1. Initialize your project
spanshift init \
--project-id my-gcp-project \
--instance-id my-instance \
--database-id my-database
This creates a spanshift.toml config file, a migrations/ directory, and tracking tables in Spanner.
2. Create a migration
spanshift new "create users table"
This generates a timestamped migration file in migrations/:
"""create users table.
Revision: 20260225_143012
Down-revision: None
Created: 2026-02-25T14:30:12+00:00
"""
revision = "20260225_143012"
down_revision = None
description = "create users table"
def upgrade(ctx):
ctx.execute_ddl([
"""CREATE TABLE Users (
UserId STRING(36) NOT NULL,
Email STRING(320) NOT NULL,
CreatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
) PRIMARY KEY (UserId)""",
])
def downgrade(ctx):
ctx.execute_ddl([
"DROP TABLE Users",
])
3. Apply the migration
spanshift upgrade
4. Check status
spanshift status
Configuration
Spanshift reads from spanshift.toml in the project root:
[spanshift]
migrations_dir = "migrations"
naming = "timestamp" # "timestamp" or "sequential"
tracking_table = "spanshift_migrations"
lock_table = "spanshift_lock"
lock_timeout_seconds = 300
ddl_timeout_seconds = 600
[environments.default]
project_id = "my-gcp-project"
instance_id = "my-instance"
database_id = "my-database"
[environments.staging]
project_id = "my-gcp-project"
instance_id = "my-instance"
database_id = "my-database-staging"
Environment variable overrides
Environment variables take precedence over spanshift.toml values:
| Variable | Overrides |
|---|---|
SPANSHIFT_PROJECT_ID |
project_id |
SPANSHIFT_INSTANCE_ID |
instance_id |
SPANSHIFT_DATABASE_ID |
database_id |
SPANSHIFT_CREDENTIALS_PATH |
credentials_path |
Resolution order: CLI flags > environment variables > spanshift.toml > defaults.
CLI Reference
| Command | Description | Key flags |
|---|---|---|
spanshift init |
Initialize config, migrations dir, and tracking tables | --project-id, --instance-id, --database-id, --skip-db |
spanshift new <description> |
Generate a new migration file | --env |
spanshift upgrade [target] |
Apply pending migrations (optionally up to a target revision) | --env, --dry-run, --yes |
spanshift downgrade [steps] |
Revert the last N migrations (default: 1, 0 = all) | --env, --dry-run, --yes |
spanshift status |
Show applied/pending status for all migrations | --env |
spanshift current |
Show the latest applied revision | --env |
spanshift history |
Show applied migration history with timestamps and durations | --env |
spanshift schema |
Dump current database DDL from Spanner | --env |
Use --env to target a specific environment (defaults to default).
Migration Context API
Migration functions receive a MigrationContext object:
def upgrade(ctx):
# DDL -- schema changes (CREATE TABLE, ALTER TABLE, CREATE INDEX, etc.)
ctx.execute_ddl([
"CREATE INDEX UsersByEmail ON Users(Email)",
])
# DML -- single-transaction data changes
ctx.execute_dml(
"UPDATE Users SET Active = @active WHERE CreatedAt < @cutoff",
params={"active": False, "cutoff": "2025-01-01T00:00:00Z"},
param_types={"active": spanner.param_types.BOOL, "cutoff": spanner.param_types.TIMESTAMP},
)
# Partitioned DML -- large-scale data changes across splits
ctx.execute_partitioned_dml(
"DELETE FROM Logs WHERE Timestamp < @cutoff",
params={"cutoff": "2024-01-01T00:00:00Z"},
param_types={"cutoff": spanner.param_types.TIMESTAMP},
)
# Check if running in dry-run mode
if ctx.dry_run:
return
# Direct database access for advanced use cases
ctx.database.run_in_transaction(...)
| Method | Purpose |
|---|---|
execute_ddl(statements) |
Batch DDL via update_ddl(). Blocks until complete. |
execute_dml(dml, params, param_types) |
Run DML in a read-write transaction. Returns row count. |
execute_partitioned_dml(dml, params, param_types) |
Partitioned DML for large-scale changes. Returns row count. |
dry_run |
bool property -- True when running with --dry-run. |
database |
Direct access to the Spanner Database object. |
All methods are no-ops during dry-run (DDL is collected, DML returns 0).
Safety Features
Distributed locking -- A lock table in Spanner ensures only one migration process runs at a time, even across multiple machines. Lock automatically expires after the configured timeout (default: 300s).
Checksum verification -- Each migration file's content is checksummed at apply time. spanshift status flags any files that have been modified after being applied.
Chain validation -- Migrations form a singly-linked list via down_revision. Broken links, duplicates, or forks are detected before execution.
Confirmation prompts -- upgrade and downgrade require interactive confirmation unless --yes or --dry-run is passed.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spanshift-0.1.1.tar.gz.
File metadata
- Download URL: spanshift-0.1.1.tar.gz
- Upload date:
- Size: 19.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61f987c6a705e3782463e5cc1025c159b3d84f3fede3f48aa770f83b1382a3ab
|
|
| MD5 |
5265dcfc2495de09a1c6445e0e6c0535
|
|
| BLAKE2b-256 |
93dbab3fc3de806025a570459a9c173fa23d06ddc5816a2be2f9fda2ca5c4413
|
Provenance
The following attestation bundles were made for spanshift-0.1.1.tar.gz:
Publisher:
workflow.yml on last-brain-cell/spanshift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spanshift-0.1.1.tar.gz -
Subject digest:
61f987c6a705e3782463e5cc1025c159b3d84f3fede3f48aa770f83b1382a3ab - Sigstore transparency entry: 987514454
- Sigstore integration time:
-
Permalink:
last-brain-cell/spanshift@31d8d37c49f9db27f1d8fd6fd8cb08467dd525dc -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/last-brain-cell
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@31d8d37c49f9db27f1d8fd6fd8cb08467dd525dc -
Trigger Event:
release
-
Statement type:
File details
Details for the file spanshift-0.1.1-py3-none-any.whl.
File metadata
- Download URL: spanshift-0.1.1-py3-none-any.whl
- Upload date:
- Size: 25.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fca6368561dc0fdc731ab780319a7b1bf1bca35b03bf31ce54f7942374691384
|
|
| MD5 |
9ea7b8b3fe2f59645a04c451b01e5fee
|
|
| BLAKE2b-256 |
537e32386b45d3f1cd67e52aedde3e96cf390fa89fb6199fc72717ee0a9ddff6
|
Provenance
The following attestation bundles were made for spanshift-0.1.1-py3-none-any.whl:
Publisher:
workflow.yml on last-brain-cell/spanshift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spanshift-0.1.1-py3-none-any.whl -
Subject digest:
fca6368561dc0fdc731ab780319a7b1bf1bca35b03bf31ce54f7942374691384 - Sigstore transparency entry: 987514549
- Sigstore integration time:
-
Permalink:
last-brain-cell/spanshift@31d8d37c49f9db27f1d8fd6fd8cb08467dd525dc -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/last-brain-cell
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@31d8d37c49f9db27f1d8fd6fd8cb08467dd525dc -
Trigger Event:
release
-
Statement type: