Skip to content

ci: split packages/python to clean one_function_per_file at 9 - #1059

Merged
thekevinscott merged 2 commits into
mainfrom
claude/1056-py-ofpf9
Sep 5, 2026
Merged

thekevinscott merged 2 commits into
mainfrom
claude/1056-py-ofpf9

Conversation

@thekevinbot

Copy link
Copy Markdown
Collaborator

Fixes #1056. Step under #1053 (census/plan) and #1020 (tracker). Lane P, the rung after #1042 / PR #1051 (16 -> 12).

Makes packages/python/dirsql clean at one_function_per_file max_lines = 9 by splitting files only. This PR touches no testing-conventions.toml — the ceiling drops in a separate config PR once every lane is clean at 9, per invariant 1 in #1053.

Measured work list

Throwaway shallow clone of origin/main, packages/python/testing-conventions.toml rewritten to max_lines = 9, nothing committed:

npx -y testing-conventions@0.0.111 unit one-function-per-file \
  --language python --config packages/python/testing-conventions.toml packages/python/dirsql
resolve_config_extensions.py:70:  `_resolve_entries` runs 12 lines, and `_load_extension_entries` already holds this file
resolve_config_extensions.py:105: `resolve_configs_extension_specs` runs 10 lines, and `_load_extension_entries` already holds this file
error: 2 function(s) sharing a file with another function over the 9-line threshold

Two, matching #1053's ladder (python: 2 at 9). resolve_extension.py is already clean at 9.

Moves

From Function To
dirsql/resolve_config_extensions.py _resolve_entries dirsql/resolve_entries.py
dirsql/resolve_config_extensions.py resolve_configs_extension_specs dirsql/resolve_configs_extension_specs.py

_load_extension_entries is the first over-threshold function in source order, so it holds resolve_config_extensions.py alongside _load_toml_module, _has_bare_name and the single-config resolve_config_extension_specs. Both moved functions keep their names and bodies verbatim.

No import cycle. Nothing is re-exported back into resolve_config_extensions, so the graph stays acyclic: resolve_config_extensions -> resolve_entries, and resolve_configs_extension_specs -> both. Call sites (dirsql/_async.py, dirsql/cli/resolve_config_extensions.py) import the plural resolver from its new home.

Tests move with the code, each new module's test mocking its own collaborator — resolve_extension_path for resolve_entries, _resolve_entries for the two callers — so every unit test still isolates its subject. The two call sites gain an assertion pinning which module the resolver is imported from, which is the only thing that changed in them (and satisfies the colocated-test co-change check honestly rather than with a whitespace edit).

Red/green does not apply: pure moves, no behavior change. Tests move with the code and stay green throughout.

Parity: no PARITY.md change. It names resolve_config_extension_specs / resolve_configs_extension_specs by function, not by module, and both keep their names; neither is in dirsql.__all__. No drift introduced, none restored. The TypeScript mirror of this file (packages/ts/src/resolve-config-extensions.ts) is lane T's to split; the two lanes reorganise mirrored files independently.

Verification

just preflight — 0 failing pairs, 3 skipped (the packaging pairs, which need a built artifact). Notable lines:

==> python-sdk [python] one-function-per-file: ok
==> python-sdk [python] colocated-test: ok
==> python-sdk [python] unit-lint: ok
==> python-sdk [python] unit-coverage: ok
==> python-sdk [python] mutation: no surviving mutants — every mutation was caught (13 mutant(s) tested)

Plus, in packages/python: ruff check / ruff format --check clean, ty check --error all --error-on-warning dirsql/ clean, 115 unit / 26 hermetic integration / 105 binding tests pass. dirsql-checks changelog-gate --base-sha origin/main --head-sha HEAD exits 0.

Gate re-measured on a throwaway clone with the post-split files and max_lines = 9: 0 violations.

Manual exercise. cargo build -p dirsql --features cli, uv run maturin develop, then the e2e suite drives the real CLI through the launcher (with_resolved_extensions -> the relocated resolve_configs_extension_specs) 37 times; all pass. The SDK config= path is exercised the same way by the binding tier.

E2E Verification

  • Ran e2e suites locally for every affected SDK
  • Python SDK e2e: pass
  • TypeScript SDK e2e: N/A
  • Rust core e2e (if applicable): N/A
  • packages/python/e2e-attestations/<branch>.json receipt written if packages/python changed (just e2e-attest-python)
  • packages/ts/e2e-attestations/<branch>.json receipt written if packages/ts changed (just e2e-attest-ts) — N/A, no packages/ts change
  • Command(s) run: just test-e2e, then just e2e-attest-python
  • Result summary: 37 passed in 13.07s; receipt at packages/python/e2e-attestations/claude-1056-py-ofpf9.json. git show --stat on the attest commit confirms it adds only its own receipt and deletes no sibling receipts (e2e attest silently deletes other branches' attestation receipts #1041).

Changelog / Migrations

  • Changelog fragment added under <root>/<pkg>/changelog.d/ for each changed package (or: skip-changelog trailer on a commit with reason)
  • Migration fragment added under <root>/<pkg>/migrations.d/ (or: not required — additive/bugfix only) — not required: pure refactor, no public API change

…-per-file 9

`packages/python/dirsql` carries two violations once the
`one_function_per_file` ceiling drops from 12 to 9, both in
`resolve_config_extensions.py`:

    resolve_config_extensions.py:70:  `_resolve_entries` runs 12 lines
    resolve_config_extensions.py:105: `resolve_configs_extension_specs` runs 10 lines

`_load_extension_entries` is the first over-threshold function in source
order, so it holds the file. Move the other two out:

    `_resolve_entries`                -> dirsql/resolve_entries.py
    `resolve_configs_extension_specs` -> dirsql/resolve_configs_extension_specs.py

Both keep their names and bodies verbatim. Nothing is re-exported back into
`resolve_config_extensions`, so the import graph stays acyclic:
`resolve_config_extensions` -> `resolve_entries`, and the plural module ->
both. `_async.py` and `cli/resolve_config_extensions.py` import the plural
resolver from its new home.

The colocated tests move with the code. Each new module's test mocks its own
collaborator -- `resolve_extension_path` for `resolve_entries`,
`_resolve_entries` for the two callers -- so every unit test still isolates
its subject.

No behavior change, so red/green does not apply.
@thekevinscott
thekevinscott merged commit cb6f99d into main Sep 5, 2026
38 checks passed
@thekevinscott
thekevinscott deleted the claude/1056-py-ofpf9 branch September 5, 2026 01:41
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.

ci: split packages/python to clean one_function_per_file at 9

2 participants