Skip to content

fix: accept migration template configuration keys - #674

Merged
cofin merged 5 commits into
mainfrom
fix/migration-templates
Aug 3, 2026
Merged

fix: accept migration template configuration keys#674
cofin merged 5 commits into
mainfrom
fix/migration-templates

Conversation

@cofin

@cofin cofin commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

v0.58.0 started rejecting migration_config keys it does not recognize, but three keys that customize generated migration files were never on the recognized list. Anyone using a custom migration template could no longer build their configuration at all. This restores them, and adds a check so the list cannot fall behind again.

What changed

  • Custom migration templates work again. templates, default_format, and title are accepted. Previously all three raised ImproperConfigurationError the moment a configuration was constructed. default_format was the worst of them: the error suggested default_schema, an unrelated setting that changes which schema migrations run against.
  • Template mistakes are caught when you build the config, not when you generate a migration. A misspelling inside templates.sql or templates.py reports its full path and the closest valid key, so templates.sql.headerr tells you it meant header. An override that is not a mapping is named along with the type you passed.
  • The template override shape is typed. MigrationTemplates, SQLTemplateOverride, and PythonTemplateOverride describe what each fragment accepts, so type checkers cover a surface that previously had none.
  • The recognized-key list is now checked against its readers. The list is built from a TypedDict, so it is only correct while that declaration matches what the code actually reads. A test scans the package for key reads and fails if any is undeclared — which is what went unnoticed here, since the template reader rebinds the mapping to a local name before reading it.
  • Two documentation corrections. version_table_name defaults to ddl_migrations, not sqlspec_migrations; the migrations guide now has a template customization section.

How you use it

Override only the fragments you want to change; everything else keeps its default.

from sqlspec.adapters.duckdb import DuckDBConfig

config = DuckDBConfig(
    connection_config={"database": "/tmp/analytics.db"},
    migration_config={
        "title": "Acme Migration",
        "default_format": "py",
        "templates": {
            "sql": {
                "header": "-- {title} [{adapter}]",
                "metadata": ["-- Version: {version}", "-- Owner: {author}"],
            }
        },
    },
)

sqlspec create-migration -m "add users" then writes:

-- Acme Migration [DuckDBDriver]
-- Version: 0001
-- Owner: Ada Lovelace <ada@example.com>

-- name: migrate-0001-up
...

Every fragment is rendered with str.format. Available placeholders are title, version, message, description, created_at, author, adapter, project_slug, and slug. The SQL template takes header, metadata, body, and description_key; the Python template takes docstring, imports, body, and description_key.

Replacing body replaces the whole body, including the -- name: migrate-{version}-up and -- name: migrate-{version}-down markers.

cofin added 4 commits August 3, 2026 02:14
The migration_config allowlist rejected the three keys the template
renderer reads, so any customized migration template failed at config
construction.

- Declare templates, default_format, and title on MigrationConfig, with
  MigrationTemplates, SQLTemplateOverride, and PythonTemplateOverride
  describing the override shape.
- Validate keys nested under templates.sql and templates.py, reporting
  the dotted path and the closest valid key, and reject non-mapping
  overrides with a clear message rather than a render-time TypeError.
- Correct the version_table_name docstring: the default is
  ddl_migrations.
MIGRATION_CONFIG_KEYS rejects any key absent from the MigrationConfig
declaration, so the declaration has to stay a complete inventory of what
SQLSpec reads. Scan the package for key reads and assert the allowlist
covers them.

The scanner resolves one level of local aliasing, since a search keyed on
the literal name misses the rebinding the template reader uses. Reads
through a lookup result are deliberately not tracked, so nested keys are
not mistaken for top-level ones. Fixture cases prove each read shape is
detected.
- Convert the template tests from a stub config to a real adapter config
  so they exercise the validation path users hit, and cover the Python
  template override alongside the SQL one.
- Add a migrations guide section for default_format, title, and templates,
  listing the placeholders each fragment can use.
- Correct the documented version_table_name default.
- Add the template override types to the configuration reference.
- Record v0.58.1 in the changelog.
## Summary

- bump SQLSpec to `0.58.1`
- refresh prompt-toolkit to 3.0.53 in the lock file
@codecov-commenter

codecov-commenter commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.33%. Comparing base (b287cb7) to head (bc538f6).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #674      +/-   ##
==========================================
+ Coverage   77.30%   77.33%   +0.02%     
==========================================
  Files         476      476              
  Lines       68052    68106      +54     
  Branches     9357     9363       +6     
==========================================
+ Hits        52609    52668      +59     
+ Misses      12035    12029       -6     
- Partials     3408     3409       +1     
Flag Coverage Δ
integration 60.91% <66.12%> (+<0.01%) ⬆️
py3.10 75.64% <100.00%> (+0.02%) ⬆️
py3.11 75.66% <100.00%> (+0.03%) ⬆️
py3.12 75.66% <100.00%> (+0.02%) ⬆️
py3.13 75.65% <100.00%> (+0.01%) ⬆️
py3.14 76.53% <100.00%> (+0.02%) ⬆️
unit 64.89% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sqlspec/config.py 93.86% <100.00%> (+0.46%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cofin
cofin force-pushed the fix/migration-templates branch from 9efc0a0 to c52c8e3 Compare August 3, 2026 13:17
Subscripting the MigrationConfig TypedDict with a parametrized variable
is not a literal key, which mypy rejects.
@cofin
cofin merged commit b475727 into main Aug 3, 2026
27 checks passed
@cofin
cofin deleted the fix/migration-templates branch August 3, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants