Skip to content

feat(hook): migrations/ preferred location + --hook/SQLPUSH_HOOK override (defaults + override, the alembic pattern) - #9

Merged
juanmicl merged 5 commits into
mainfrom
fix/hook-migrations-location
Sep 3, 2026
Merged

feat(hook): migrations/ preferred location + --hook/SQLPUSH_HOOK override (defaults + override, the alembic pattern)#9
juanmicl merged 5 commits into
mainfrom
fix/hook-migrations-location

Conversation

@juanmicl

@juanmicl juanmicl commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Hook location flexibility, following the pattern alembic set with its ini file and its -c flag: sensible defaults, plus an explicit way to point anywhere. This came out of dogfooding the 0.6.0 hook in a consumer repo.

Discovery now checks migrations/sqlpush.py first (the file lives next to the chain, the way alembic's env.py lives inside its script location), then the repo root as the backwards-compatible fallback. First match wins, and error messages name the file that actually loaded: migrations/sqlpush.py: missing get_dsn(). The CWD is what gets appended to sys.path, whichever location the hook came from. If you only have a root sqlpush.py, nothing changes for you.

Anyone who wants the hook somewhere else can say so: --hook PATH on every verb, or the SQLPUSH_HOOK environment variable. Precedence is flag, then env, then discovery. An explicit path that does not exist fails loud with a typed error naming that path; it never falls back to the candidates (there is a test with a loadable candidate planted as temptation). An empty SQLPUSH_HOOK counts as unset, which is what CI systems that export empty variables need.

Commits: 41ad2aa and 62dc7dc are the preferred location; 544b2fc and a65006e are the override; 8ec2814 adds the review pins (empty-env behavior, flag-beats-candidates tested directly instead of by transitivity) and a wording fix.

Verification: 194 passed + 1 xfailed (181 baseline plus 13 new), ruff/format/ty clean, and every behavior test was observed red before its implementation. The review approved the branch after checking the precedence in the code itself, probing the label registry against symlinks and path oddities, and confirming that the no-hook and root-hook paths are byte-identical to 0.6.0.

The CHANGELOG carries the two Added bullets and the README documents both locations and the precedence. On merge this is a minor by the repo's convention (a new flag and a new discovery location are new functionality): v0.7.0, though 0.6.1 is defensible since it extends the hook published yesterday. Owner's call at cut time.

…rrors)

Discovery now checks migrations/sqlpush.py first (preferred: lives
next to the chain, no root clutter) then the repo-root sqlpush.py
(backwards compat) — first match wins; root-only setups behave
exactly as in 0.6.0 (the existing root-location tests now double as
the fallback pin, unchanged).

Two correctness requirements beyond the owner's snippet:

- typed errors name the file that ACTUALLY loaded, in the candidate
  spelling (load-time errors use the matched candidate directly;
  accessors map the loaded module's __file__ back to its candidate —
  posix-formatted so messages and tests read migrations/sqlpush.py).

- sys.path still appends the CWD regardless of which candidate
  loaded, never the hook's own directory (migrations/ on sys.path
  would be wrong for consumer package imports) — pinned by test.

Filed under Added rather than Changed: the only behavior change vs
0.6.0 is the exotic both-files-present case (migrations/ wins); root
hook users see nothing move.
Project hook section now states the two candidate locations and the
first-match order (migrations/sqlpush.py preferred — next to the
chain; repo-root sqlpush.py as the backwards-compat fallback), moves
the example header to the preferred location, and updates the error
contract sentence (the message names the file that actually loaded)
and the shadowing note (append-the-CWD applies whatever location
loaded; the migrations candidate has no shadowing concern but the
same load-by-path mechanics).
Explicit override on top of candidate discovery (the alembic -c
equivalent): --hook PATH on every verb, or $SQLPUSH_HOOK, loads the
hook from any location. Precedence: flag > env > candidates; an
explicit path that does not exist fails loud as a typed HookError
naming that exact path — never a silent fallback. Errors from an
overridden hook name the loaded file in the given spelling
(load-time label registry), and sys.path still appends the CWD,
never the loaded file's directory.
@juanmicl
juanmicl merged commit 7a1bd24 into main Sep 3, 2026
7 checks passed
@juanmicl
juanmicl deleted the fix/hook-migrations-location branch September 3, 2026 13:23
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.

1 participant