feat(cli): project hook sqlpush.py — dsn/metadata/chain-dir defaults with flag > hook > env precedence - #8
Merged
Merged
Conversation
Any verb run with a sqlpush.py in the CWD loads it by path (spec_from_file_location, never by module name) and uses it as the source of defaults: get_dsn() for --dsn/--ref-dsn, get_metadata() for the module:attribute positional (now optional on diff/check/push/revision — the object flows straight into the api calls, no import happens), CHAIN_DIR for --dir. Precedence: explicit flag > hook > $DATABASE_URL/current default. Missing or raising members fail as typed HookError (SqlpushError family) naming the file and member — exit 1 via main(), no traceback. The CWD is APPENDED to sys.path on discovery, never inserted at the front: a file named sqlpush.py must not shadow the installed package (pinned by a subprocess test replicating the console-script sys.path shape). Without a hook, behavior is identical to 0.5.1 (pinned). hook.py is pure stdlib + SqlpushError.
Complete sqlpush.py example (get_metadata/get_dsn/CHAIN_DIR), the precedence table (flag > hook > env/default, including which inputs each verb resolves), the typed-error contract, and the sys.path shadowing note (append, never prepend). The knobs table now marks --ref-dsn as required only without a hook.
ruff 0.16 formats Python fences inside Markdown; the Project hook example needed two-space comment spacing and blank lines between top-level defs.
…hadowing Three review nits as test pins: - revision env-isolation: $DATABASE_URL set + no hook + no --ref-dsn must exit 2 with the remedy, and create_engine must never see the env DSN — guards the cli.py ordering (ref_dsn checked None before _engine) against a silent refactor. Mutation-checked: removing the guard so _engine falls back to the env var fails the pin. - shadowing: the subprocess script now drops sys.modules['sqlpush'] and re-imports — the fresh PATH-ORDER resolution (CWD appended last) must still find the installed package, proving the original assert was not a sys.modules cache short-circuit. - laziness: migrate with a hook whose get_metadata RAISES (get_dsn fine, empty chain dir) is a clean idle run — get_metadata() is never called for verbs that do not need it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The 0.6.0 payload: the project hook,
sqlpush.pyin the CWD (theenv.py/conftest.pypattern) delegates metadata, DSN and chain-dir to the consumer's own code. Result:uv run sqlpush revision -m "change"with no--dsn, nomodule:attribute, no credentials on the command line, no PYTHONPATH.The hook contract (user-side code)
Behavior
importlib.util.spec_from_file_location, never by module name); on discovery the CWD is appended tosys.path(never prepended) so a file namedsqlpush.pycan never shadow the installed package. Pinned by a subprocess test that assertsimport sqlpushresolves to the package both from cache and after a fresh re-import.$DATABASE_URL> default.--ref-dsnkeeps its no-env-fallback isolation (pinned); the hook'sget_dsn()beats$DATABASE_URL; without a hook every verb behaves exactly as before (pinned).get_dsn()/get_metadata()are called only at the moment a verb needs the value, a hook whoseget_metadataraises works fine formigrate(pinned).HookError(SqlpushError)family): messages name the file and the member (sqlpush.py: missing get_dsn(),get_dsn() raised: <original>with__cause__preserved); exit 1 via the existing handler, never a traceback.module:attributepositional becomes optional; explicit positionals route through the unchanged loader path.Docs
README gains a Project hook section (complete example, precedence table with the fail-loud fallback note, typed-error contract, shadowing note). CHANGELOG
[Unreleased]Added entry (CLI surface).Verification
181 passed, 1 xfailed(baseline 166 + 15: 13 feature tests, 2 review-nit pins, 1 in-place strengthening) against the live dev DB; DB-free subset skips cleanlyruff check,ruff format --check,ty checkall cleanRelease plan
Feature release: on merge, cut
v0.6.0. Post-publish, the consumer-side verification lands (ansqlpush.pyin the atlas repo exercising the one-command workflow).