Repository navigation
refactor(python): resolve extensions through the core's plan contract - #1125
Merged
Merged
Conversation
The Python SDK carried its own port of extension resolution: whether an entry names a package or a file, which literal path each entry resolves to against its own config's directory, and which installed file is *the* loadable. None of that is host-specific, and since #1119 it lives once in `dirsql::extension_resolution`. Python now reaches it through three new PyO3 bindings -- `plan_config_extensions`, `select_loadable`, `is_bare_name` -- and keeps only the two halves the core cannot have: reading the config files, and locating an installed package with `importlib.util.find_spec`. Seven internal modules go away (`is_bare_name`, `has_bare_name`, `load_extension_entries`, `resolve_entries`, `platform_patterns`, `resolve_config_extensions`, `toml_module`) against two added (`read_config`, `plan_entry_path`), and with them the `tomli` dependency on Python 3.10 -- the core parses the TOML now. Behavior-preserving: same resolved paths, same error messages (the core's `SelectError` renders the Python wording verbatim), same intervene-or-not decision. One inconsequential normalization: the core treats a backslash as a path separator on every host, where `is_bare_name` used `os.altsep`. Only a value with a backslash *and* no loadable suffix reclassifies, and such a value errors on both the old and the new path. Fixes #1120
8 tasks done
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.
Slice B of #1111: the Python SDK stops carrying its own port of extension resolution and reaches the core contract landed in #1124.
What moved
Python kept three decisions the core already knows how to make:
dirsql._dirsql.is_bare_namedirsql._dirsql.plan_config_extensionsdirsql._dirsql.select_loadableIt keeps only the two halves the core cannot have: reading the config files off disk, and locating an installed package with
importlib.util.find_spec.The PyO3 layer stays pure — Python reads each config and passes
(abs_path, contents | None)pairs; the core parses and plans. Adding an fs-readingplan_config_filesto the core was the obvious alternative and was rejected: a Rust unit test may call onlysuper::and pure std, so an fs-reading function cannot be unit-tested, and diff-scoped cargo-mutants would leave a surviving mutant in the untested reader.Seven internal modules go away (
is_bare_name,has_bare_name,load_extension_entries,resolve_entries,platform_patterns,resolve_config_extensions,toml_module) against two added (read_config,plan_entry_path, split apart to satisfyone-function-per-file).resolve_config_extension_specs— the singular resolver — had no production caller and goes with them. Thetomlidependency on Python 3.10 goes too: the core parses the TOML now.Why there is no pushed-RED commit
This is a behavior-preserving refactor plus removals, both exempt from the pushed-RED CI gate. The existing suites cover every path and stayed green throughout: same resolved paths, same intervene-or-not decision, and the core's
SelectErrorrenders the Python wording verbatim, so the error messages are byte-identical.One normalization, which I judged inconsequential — please push back if you disagree. The core treats a backslash as a path separator on every host, where
is_bare_nameusedos.altsep(Noneon POSIX).is_bare_namechecks the loadable-suffix list first, so the only value that reclassifies is one with a backslash and no loadable suffix — e.g.a\b. On the old path that was a bare name, went toimportlib.util.find_spec, and raisedcould not resolve extension package 'a\\b'; on the new path it is a literal path and fails at load. Both error. No migration fragment on that basis.Platform scoping is preserved:
select_loadableis handed aPLATFORM_SUFFIXESlist (.dylibon macOS,.dll/.pydon Windows,.soelsewhere) that mirrors the deleted_platform_patterns, so a package shipping loadables for several platforms still resolves rather than going ambiguous.is_bare_namekeeps the full four-suffix list, matching the deleted_LOADABLE_SUFFIXES.Manual verification
Built the repo's
tests/fixtures/testextcdylib (registersdirsql_testext_answer() -> 42), staged it asmypkg/testext0.soin a temp dir onPYTHONPATH, and wrote a.dirsql.tomlwhose[[dirsql.extension]].pathis the bare namemypkg.Same through the SDK, both entry points:
The bare name resolved through
plan_config_extensions→select_loadableto the absolute.so, which SQLite then loaded.E2E Verification
packages/tschangepackages/rustchangepackages/python/e2e-attestations/<branch>.jsonreceipt written ifpackages/pythonchanged (just e2e-attest-python)packages/ts/e2e-attestations/<branch>.jsonreceipt written ifpackages/tschanged (just e2e-attest-ts) — N/Ajust e2e-attest-python;cargo test --manifest-path packages/python/Cargo.toml --features extension-module;uv run pytest dirsql testsChangelog / Migrations
<root>/<pkg>/changelog.d/for each changed package (or:skip-changelogtrailer on a commit with reason)<root>/<pkg>/migrations.d/(or: not required — additive/bugfix only) — not required: behavior-preserving refactor, every deleted module is private to the package and none is exported fromdirsql/__init__.py.Parity
Restoring parity, with intentional drift that slice C closes.
PARITY.mdgains a paragraph on where the planning lives. Python now reaches the shared core contract; TypeScript still carries its own port and is tracked by #1121, the next slice.Gates run
one-function-per-file,unit-lint,colocated-test,integration-lint,unit coverage(100%),unit mutation(7 mutants, 0 survivors),declared-deps, plus therust-python-bindinglane'scolocated-testandunit-lint.ruff check,ruff format --check,ty check --error all,cargo fmt, andcargo clippy --workspace --features cli -- -D warningsall clean.Fixes #1120