Repository navigation
fix(feature_utils): bind X and ndf_ on the routes that read them - #2097
Merged
Merged
Conversation
Two functions read a local that an earlier branch may never have assigned, and
both are reachable.
transform() pre-initializes y and T at the top and then binds X only inside its
`nodes`/`edges` branches. `kind` is a plain str and FastEncoder stores it
without validation, so any other value falls through to `if not tX.empty and
not X.empty` and raises UnboundLocalError. Demonstrated against master:
transform(df, DataFrame([]), res, 'bogus', ...)
master: UnboundLocalError: cannot access local variable 'X'
here: X.empty=True y.empty=True
get_numeric_transformers() returns ndf_ unconditionally but assigns it only
under `if ndf is not None`, while the sibling y_ is assigned before its guard.
get_numeric_transformers(None, None) raised UnboundLocalError on master and now
returns ndf_=None.
Two further sites in this file were considered and deliberately left alone,
because neither is a bug. The skrub names are function-locals from a guarded
import, but every use sits under the same has_skrub flag. scale()'s scaler and
scaler_target look unbound for an unrecognized kind, but kind is validated
upstream ("kind must be one of 'nodes' or 'edges'") and a missing encoder
raises AttributeError before the branch chain. Hardening those would have meant
three `# pragma: no cover` lines in a file no lane feeding the changed-line
gate can execute -- a poor trade for changes that fix nothing.
That gap is worth naming: feature_utils.py collects ZERO coverage in the lanes
the changed-line gate combines (core, gfql, polars), because none installs
scikit-learn. The one sklearn-only line here carries a pragma whose claim was
checked -- it is covered when the umap tests run with sklearn present.
Pyright ratchet 255 -> 245 (-10). Changed-line coverage 1/1.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud
3 of 8 tasks
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.
Fifth of the pyright-ratchet cleanup series. Ratchet 255 → 245.
Two functions read a local that an earlier branch may never have assigned, and both are reachable.
transform()yandTare pre-initialized at the top;Xis bound only inside thenodes/edgesbranches.kindis a plainstrthatFastEncoderstores without validation, so any other value falls through toif not tX.empty and not X.empty:get_numeric_transformers()Returns
ndf_unconditionally but assigns it only underif ndf is not None— while the siblingy_ = yis assigned before its guard, which is the shape the author clearly intended.get_numeric_transformers(None, None)raisedUnboundLocalErroron master; it now returnsndf_=None.Two sites deliberately left alone
Both looked like the same defect and neither is a bug:
has_skrubflag — including theisinstanceat line 1025, which I checked individually.scale()'sscaler/scaler_targetlook unbound for an unrecognizedkind, butkindis validated upstream (kind must be one of 'nodes' or 'edges') and a missing encoder raisesAttributeErrorbefore the branch chain. I tried to construct a reaching call and could not.Hardening those would have meant three
# pragma: no coverlines in a file no gate-feeding lane can execute — a poor trade for changes that fix nothing. They stay in the ratchet baseline, visible.A gap worth naming
feature_utils.pycollects zero coverage in the lanes the changed-line gate combines (core, gfql, polars), because none of them installs scikit-learn — the umap/ai lanes that do exercise it produce no coverage artifact at all. Any change to this file is therefore invisible to that gate.The one sklearn-only line here carries a pragma, and I checked its claim rather than asserting it: line 873 is covered when the umap tests run with sklearn present.
Wiring an umap/ai coverage artifact into
changed-line-coveragewould fix this properly and unblock future work in this file. It is a real CI change (new artifact + combine step + aneedsedge, and per #2018 a skipped prerequisite skips the gate), so I have not folded it in here.Verification
bin/lint.sh(ruff, type-hygiene 4448, comment-density 1151) · pyright guard 245 · mypy 351 files · changed-line coverage 1/1 = 100% · tests pass with and without sklearn (2 passed/1 skipped, then 3 passed) · 9 passed in the umap env across feature/umap suites · meta-guards run · the recognized-kind test confirmed discriminating by disabling thenodesbranch.🤖 Generated with Claude Code
https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud