Skip to content

Make every example import the core sitting next to it - #89

Merged
qmohsu merged 2 commits into
mainfrom
claude/examples-bootstrap-syspath
Aug 23, 2026
Merged

Make every example import the core sitting next to it#89
qmohsu merged 2 commits into
mainfrom
claude/examples-bootstrap-syspath

Conversation

@qmohsu

@qmohsu qmohsu commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Closes #86.

python ch4_rf_point_positioning/example_toa_positioning.py puts the chapter directory on sys.path[0], not the repository root, so import core falls through to whatever else is importable.

Reproduced, and the quiet failure is the real one

A probe run as a script from the chapter directory, in this worktree:

sys.path[0] = .../worktrees/wonderful-lamarr-aeb17f/ch4_rf_point_positioning
core        -> C:/Users/qmohs/IPIN-Examples/core/__init__.py     ← a different checkout

versus module form, which resolves core inside the worktree. No error either way — only the answer changes. On a fresh clone with no install it is ModuleNotFoundError instead.

One correction to the issue, and one gap it did not name

The issue's premise, "running an example the way the README shows it", is out of date: the READMEs show python -m throughout (6 occurrences in the top-level README, 16 in ch4's), held by tests/docs/test_documented_commands_use_module_form.py.

But that guard's regex is anchored to ch\d+_ chapter directories, so python scripts/generate_*.py sits outside it and is still documented in script form in several dataset READMEs. I checked those: the three scripts/ files lacking a bootstrap import no core at all, so they need none. (My earlier read that they were an inconsistency was wrong.)

The fix was already the repo's own idiom

ch5_fingerprinting/example_classification has carried sys.path.insert(0, ...) for as long as it has existed, and nine of twelve scripts/ generators do the same. It had simply never reached the other 37 examples. This is not a new pattern to debate — it is an existing one applied to one directory and not the other.

tests/test_examples_import_this_checkout.py holds it and checks order, not presence: a bootstrap below the import changes nothing. Both mutations fire — removing the line, and moving it after the import.

Three bugs in the sweep, all caught by pyflakes rather than review

Each was a plausible way to find an insertion point, and each compiles:

  1. ast.walk to test whether sys was imported counts an import sys inside a function → four files got a module-level sys.path.insert with no sys bound.
  2. Locating the stdlib group by line.startswith(("import ", "from ")) matched a line of module docstring prose beginning "from range measurements ..."import sys written inside the docstring.
  3. Accepting any module-level import of a name ignores position: ch7's pose-graph example imports pathlib.Path three lines below its first core import.

test_the_check_reads_the_shapes_it_has_to_distinguish pins the last two against the guard's own parser, since that is the half most likely to agree with itself.

Lint moved the right way

rule before after
I001 113 84
E402 1 1 (unchanged)

Inserting import sys at the head of a stdlib group is unsorted, so I001 first went to 147; ruff --select I001 --fix over the chapter directories cleared 63, 29 of which predated this change. Baseline lowered. E402 never fires — ruff exempts imports following a sys.path manipulation, which is what makes this idiom viable at all.

Verification

--help captured for all 38 examples before the change and diffed after: byte-identical, twice — once after the insertions, again after ruff reordered imports. --help exits during argument parsing but only after every module-level import has run, so it exercises exactly what this touches.

590 passed across repo conventions, the --help sweep, console encoding, the new guard and the lint ratchet. pyflakes clean.

The README paragraph contrasting the two invocation forms claimed the script form only works once installed. That is no longer true, and it now says what the line is for instead.

🤖 Generated with Claude Code

qmohsu and others added 2 commits August 23, 2026 23:17
`python ch4_rf_point_positioning/example_toa_positioning.py` puts the *chapter*
directory on `sys.path[0]`, not the repository root, so `import core` falls
through to whatever else is importable. On a fresh clone that is
ModuleNotFoundError. On a machine that has ever installed this package it is
worse and quieter: measured in this worktree, a probe run as a script from the
chapter directory resolved `core` to `C:/Users/qmohs/IPIN-Examples/core` -- a
different checkout -- and would have run to completion against it. No error;
only the answer changes.

All 38 examples now insert the repository root before their first `core`
import. This is not a new idiom: `ch5_fingerprinting/example_classification`
has carried the same line for as long as it has existed, and nine of the twelve
generators in `scripts/` do the same. It had simply never been brought to the
other 37. (The three `scripts/` files without it import no `core` at all, so
they need none.)

`tests/test_examples_import_this_checkout.py` holds it, and checks *order*
rather than presence, because a bootstrap below the import changes nothing.
Both mutations fire: removing the line, and moving it after the import.

Three bugs in the sweep script, all found by pyflakes rather than by reading,
each a plausible way to locate an insertion point:

  - `ast.walk` to test whether `sys` was imported counts an `import sys` inside
    a *function*, so four files got a module-level `sys.path.insert` with no
    `sys` bound.
  - Locating the stdlib group by `line.startswith(("import ", "from "))`
    matched a line of module *docstring prose* beginning "from range
    measurements ...", writing `import sys` inside the docstring.
  - Accepting any module-level import of a name ignores position: ch7's
    pose-graph example imports `pathlib.Path` three lines *below* its first
    `core` import, so the name existed but not yet where the bootstrap runs.

All three compile. `test_the_check_reads_the_shapes_it_has_to_distinguish` pins
the last two against the guard's own parser, since that is the half most likely
to agree with itself.

Lint moved the right way. Inserting `import sys` at the head of a stdlib group
is unsorted, so I001 went 113 -> 147; `ruff --select I001 --fix` over the
chapter directories cleared 63 of them, 29 of which predated this change,
leaving 84. Baseline lowered accordingly. E402 does not fire at all -- ruff
exempts imports following a `sys.path` manipulation, which is what makes the
idiom viable.

Verified by capturing `--help` for all 38 examples before the change and
diffing after: byte-identical, twice -- once after the insertions and again
after ruff reordered the imports. `--help` exits during argument parsing but
only after every module-level import has run, so it exercises exactly what this
touches.

The README's paragraph contrasting the two invocation forms said the script
form only works once the package is installed. That is no longer true, and it
now says what the line is for instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qmohsu
qmohsu merged commit d1ffb22 into main Aug 23, 2026
1 check passed
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.

Examples do not put the repo root on sys.path, so a fresh clone fails on 'import core'

1 participant