Skip to content

fix(python): export ScanFailure from the barrel and sync the type stub - #1141

Merged
thekevinscott merged 3 commits into
mainfrom
claude/1140-scan-failure-export
Sep 18, 2026
Merged

thekevinscott merged 3 commits into
mainfrom
claude/1140-scan-failure-export

Conversation

@thekevinbot

@thekevinbot thekevinbot commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1140

PARITY.md claimed scan-failure reporting was at parity across all three SDKs, but the Python half had no public name: the binding defines ScanFailure and DirSQL.scan_failures returns it, yet the barrel did not export it and the bundled type stub declared neither. A type checker saw DirSQL with no scan_failures at all.

This is additive and changes no runtime behavior: a re-export, a stub correction, and a doc amendment.

What changed

  • packages/python/dirsql/__init__.py re-exports ScanFailure and lists it in __all__, so from dirsql import ScanFailure works the way from dirsql import RowEvent does.
  • packages/python/dirsql/_dirsql.pyi gains ScanFailure (path / message / __repr__), DirSQL.scan_failures() -> list[ScanFailure], and run_cli(argv: list[str]) -> int.
  • Beyond the issue's list: the stub was also missing Table.on_file. PyTable declares #[pyo3(get)] on_file: Py<PyAny> (packages/python/src/lib.rs:36), so the attribute is readable at runtime but was invisible to a type checker. Added as Callable[[str], list[Row]], matching the constructor parameter the stub already declared. Every other registered binding (config_paths_from_argv, plan_config_extensions, select_loadable, plan_extension_path, ExtensionPlanEntry, __version__) was verified against py_dirsql_module and is already present and correctly typed.
  • PARITY.md — the scan-failure bullet now names where each binding's type is importable from, and the Python type-information bullet lists ScanFailure.

Parity

Restores parity. Python's scan-failure surface now matches the TypeScript barrel export and Rust's pub OnFileFailure. No drift introduced; the TypeScript and Rust surfaces are untouched.

Red/green

The barrel surface test (packages/python/dirsql/__init___test.py) went in first as its own commit (333d87f) and CI went red on exactly its assertion — run 35371156085, Unit tests (3.10 / 3.12 / 3.13):

ERROR collecting dirsql/__init___test.py
    from . import DirSQL, RowEvent, ScanFailure, Table, __all__, __version__
E   ImportError: cannot import name 'ScanFailure' from 'dirsql' (.../packages/python/dirsql/__init__.py)

The implementation landed separately in 14a68b8.

Manual exercise

Built the binding (uv run maturin develop --uv in packages/python), then drove a real scan failure through the SDK against a real temp directory — a table whose on_file hook raises on one of two files:

$ uv run python /tmp/dirsql-1140-exercise.py
ScanFailure: <class 'builtins.ScanFailure'>
failure: ScanFailure(path="bad.txt", message="ValueError: hook refused this file") | isinstance: True | path: bad.txt | message: ValueError: hook refused this file
rows: [{'path': '/tmp/dirsql-1140-lnd7qgpc/good.txt'}]

The failing file is skipped, the good one is indexed, and the object imported from the package root is the same class the binding returns.

Type checker, on a file annotating the binding's return:

$ uv run ty check /tmp/dirsql-1140-types.py
All checks passed!
from dirsql import ScanFailure
from dirsql._dirsql import DirSQL

def f(db: DirSQL) -> list[ScanFailure]:
    return db.scan_failures()

def g(failure: ScanFailure) -> tuple[str, str]:
    return failure.path, failure.message

Negative control, to show ty is actually resolving attributes off the stub rather than falling back to Any:

$ uv run ty check /tmp/dirsql-1140-types-neg.py
error[unresolved-attribute]: Object of type `DirSQL` has no attribute `no_such_method`

Also just preflight: preflight: 0 failing pair(s), 3 skipped.

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) — not owed; packages/rust/src and packages/ts are untouched, confirmed by the typescript-sdk [typescript] e2e-verify gate passing in just preflight rather than by assumption
  • Command(s) run: cargo build -p dirsql --features cli, then just e2e-attest-python (which runs just test-e2e = cd packages/python && uv run python -m pytest tests/e2e/ -x -q)
  • Result summary: 37 passed in 12.58s; receipt recorded at packages/python/e2e-attestations/claude-1140-scan-failure-export.json

Changelog / Migrations

  • Changelog fragment added under <root>/<pkg>/changelog.d/ for each changed package (or: skip-changelog trailer on a commit with reason) — packages/python/changelog.d/2026-09-18-export-scan-failure.md (Added)
  • Migration fragment added under <root>/<pkg>/migrations.d/ (or: not required -- additive/bugfix only) — not required, purely additive

skip-changelog: test-only red commit; the behavior fragment lands with the implementation
The barrel now re-exports ScanFailure so callers can annotate what
scan_failures() returns without reaching into dirsql._dirsql. The bundled
stub gains ScanFailure, DirSQL.scan_failures, run_cli, and Table.on_file,
all of which the bindings already define. PARITY.md's scan-failure claim now
names where the Python type is importable from.
@thekevinscott
thekevinscott merged commit 409461b into main Sep 18, 2026
38 checks passed
@thekevinscott
thekevinscott deleted the claude/1140-scan-failure-export branch September 18, 2026 17:06
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.

python: ScanFailure has no public import and is missing from the type stub

2 participants