Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spanshift-0.1.1.tar.gz (19.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spanshift-0.1.1-py3-none-any.whl (25.8 kB view details)

Uploaded Python 3

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

Hashes for spanshift-0.1.1.tar.gz
Algorithm Hash digest
SHA256 61f987c6a705e3782463e5cc1025c159b3d84f3fede3f48aa770f83b1382a3ab
MD5 5265dcfc2495de09a1c6445e0e6c0535
BLAKE2b-256 93dbab3fc3de806025a570459a9c173fa23d06ddc5816a2be2f9fda2ca5c4413

See more details on using hashes here.

Provenance

The following attestation bundles were made for spanshift-0.1.1.tar.gz:

Publisher: workflow.yml on last-brain-cell/spanshift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for spanshift-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fca6368561dc0fdc731ab780319a7b1bf1bca35b03bf31ce54f7942374691384
MD5 9ea7b8b3fe2f59645a04c451b01e5fee
BLAKE2b-256 537e32386b45d3f1cd67e52aedde3e96cf390fa89fb6199fc72717ee0a9ddff6

See more details on using hashes here.

Provenance

The following attestation bundles were made for spanshift-0.1.1-py3-none-any.whl:

Publisher: workflow.yml on last-brain-cell/spanshift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page