Skip to main content

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

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.2.tar.gz (19.6 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.2-py3-none-any.whl (25.8 kB view details)

Uploaded Python 3

File details

Details for the file spanshift-0.1.2.tar.gz.

File metadata

  • Download URL: spanshift-0.1.2.tar.gz
  • Upload date:
  • Size: 19.6 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.2.tar.gz
Algorithm Hash digest
SHA256 d63db5b06ed854d99f6e8928bdcf03ba05c9e2ee8f319ba95e4b60bc07ee3242
MD5 ee7638baefd02f4077ead937ab207e0c
BLAKE2b-256 9c7444a4bc4886376a3035b90e224e3652c4ba74b694b73936caf45c0a988015

See more details on using hashes here.

Provenance

The following attestation bundles were made for spanshift-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: spanshift-0.1.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 46eca2ca94a5fb1a15b59e52fb18414fc19c0384451767fa58aff69b392e7186
MD5 a9e60208ef9c21b7948969dffaf89636
BLAKE2b-256 e03893a51303b3a2fc6b8862682443ce77a69d71b5a96a520769a171163c4ddf

See more details on using hashes here.

Provenance

The following attestation bundles were made for spanshift-0.1.2-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.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

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