Using Alembic with Snowflake SQLAlchemy¶
Alembic support overview¶
Snowflake SQLAlchemy supports Alembic, a lightweight database migration tool for SQLAlchemy. Alembic lets you manage schema changes over time using revision files.
Alembic migrations must use a synchronous engine. There’s no async-aware Alembic runner for the Snowflake dialect.
Installing Alembic¶
Initializing an Alembic repository¶
Configuring Alembic for Snowflake¶
Alembic doesn’t ship a built-in implementation for the snowflake dialect. You must register one in migrations/env.py by subclassing DefaultImpl. Without it, every Alembic command that connects fails with KeyError: 'snowflake'.
Edit migrations/env.py:
Set sqlalchemy.url in alembic.ini to your Snowflake connection URL. For URL formats and parameters, see Connection parameters.
Hand-written migrations¶
Create manual migrations for fine-grained control:
This creates a migration file. Edit it to add your changes:
Use quoted_name for case-sensitive column names. Snowflake stores unquoted names in uppercase, so the table above has the columns ID, NAME, and mycol.
Pass quoted_name everywhere a migration refers to a case-sensitive column, including op.drop_column and op.alter_column. A plain string such as op.drop_column("users", "mycol") targets MYCOL and fails:
Run migrations:
Case-sensitive schema configuration¶
When the migrations or the Alembic version table live in a case-sensitive (quoted, lowercase) schema, pass the schema as quoted_name(..., True):
To make the case-sensitive schema the connection’s default schema, percent-encode the quotes in sqlalchemy.url, for example snowflake://<user_login_name>:<password>@<account_identifier>/mydb/%22myschema%22. For other ways to set it, see Connection parameters.
Keep the following in mind:
- Don’t pass
"%22myschema%22"asversion_table_schema. Percent-encoding applies only to URLs. Here, Snowflake treats the value as a literal schema name and fails with a002003SQL compilation error. - The quoted-string form
version_table_schema='"myschema"'works only when the engine is created withcase_sensitive_identifiers=True. Without that flag the quotes are stripped and Snowflake resolves the name toMYSCHEMA.
Note
With include_schemas=True, Alembic passes each result of inspector.get_schema_names() to include_name. A schema that was created quoted in lowercase ("myschema") is returned as quoted_name("myschema", True). A schema created unquoted (stored as MYSCHEMA) is returned as the plain lowercase string "myschema". Both compare equal to "myschema" because quoted_name is a str subclass, so filter against the lowercase value.
Alembic autogenerate with case-sensitive columns¶
Alembic’s default renderer writes a quoted_name("mycol", True) column as the plain string 'mycol', so the generated migration creates a case-insensitive MYCOL column. To preserve case, register the render_item hook in env.py (as shown in Configuring Alembic for Snowflake):
With the hook, a new case-sensitive column is rendered as:
Keep the following in mind:
- The hook is opt-in for each project. Alembic has no dialect-level rendering hook, so
env.pyis the only place to register it. - The hook only affects column rendering. The generated
downgrade()still containsop.drop_column("users", "extra"), which fails against the case-sensitive column. Change it toop.drop_column("users", quoted_name("extra", True))before you run it. - Autogenerate can report type differences between your model types and the types that Snowflake reflects. For example, an
sa.Integer()column reflects asNUMBER(38,0), which produces anop.alter_column(..., type_=sa.Integer())on every run. Review these operations, or passcompare_type=Falsetocontext.configure()if you don’t need type comparison.
Patterns that do not work¶
Plain unquoted strings as case-sensitive schemas¶
Using a plain lowercase string as a schema name doesn’t preserve case:
A plain lowercase string has no quote attribute, so the dialect converts it to MYSCHEMA before it reaches Snowflake. If a schema named MYSCHEMA exists, the query succeeds silently against the wrong schema.
Instead, use quoted_name("myschema", True):
The quoted-string form schema='"myschema"' gives the same result only when the engine is created with case_sensitive_identifiers=True. For details, see Case-sensitive identifiers.
Best practices¶
- Always register
SnowflakeImpland thesnowflake_render_itemhook inenv.py. - Use
quoted_namefor case-sensitive identifiers in models and in every migration operation; avoid plain lowercase strings. - Review autogenerated migrations before applying them, especially
downgrade()operations on case-sensitive columns and type changes. - Test migrations in a development environment before applying them to production.
- Keep migration files in version control for a full audit trail and rollback capability.
- Snowflake commits the open transaction before each DDL statement, so a failed migration can’t roll back DDL that already ran. For details, see Known limitations.