Skip to content

save_component carries the component's imports with it - #8

Merged
stefan-jansen merged 1 commit into
mainfrom
fix/carry-imports-into-the-saved-component
Sep 7, 2026
Merged

save_component carries the component's imports with it#8
stefan-jansen merged 1 commit into
mainfrom
fix/carry-imports-into-the-saved-component

Conversation

@stefan-jansen

Copy link
Copy Markdown
Contributor

The defect

A component is validated by re-executing its saved source in an empty namespace with only numpy and pandas in scope. That is the right design - it is what makes a component survive a cold Colab session - but it means a student who writes

from sklearn.linear_model import Ridge      # an import cell

and then uses Ridge inside their component had their correct model recorded as not conformant.

Both remedies the error message suggests fail on an imported class, reproduced before the fix:

Remedy Result
also=[Ridge] ValueError: Your model_linear refers to 'MultiOutputMixin' - it tries to inline sklearn's own source
include={'Ridge': Ridge} SyntaxError - it writes the class's repr into the file

This was the one place the conformance machinery was brittle in a way a student experiences as us being wrong, and it fires on the most ordinary thing they can do.

The fix

save_component works out the imports from the names the component actually mentions and writes them into the saved file as imports. also carries a function, include carries a value, and an import now travels as what it is.

Name resolution walks up to the shallowest ancestor module that still exports the object, so the saved file records the path the student typed:

# Saved by ml4t-coursework. This is your own code, exactly as you wrote it.
import numpy as np
import pandas as pd

from sklearn.linear_model import Ridge

class LinearModel:
    ...

rather than sklearn.linear_model._ridge, which is what the class reports as its own module and which the library is free to rename.

The name collection is deliberately not scope-aware. It over-collects - a local variable's name lands in the candidate set too - and the caller-namespace lookup is what filters, because a local name does not resolve to an importable object. Precise scoping would be more code for the same result.

What is unchanged

The genuine missing-symbol case is still refused, and there is a test asserting it: a value or helper defined in another cell does not resolve to an importable object, so it falls through to the existing message naming the symbol.

Verification

  • 124 passed, 1 skipped. Three new tests: an imported class travels; a module imported under an alias travels; a value from another cell is still refused.
  • ruff check src tests clean.
  • The original failing reproduction now saves conformant, with a reference delta of 1.2e-18 against the shipped ridge.

🤖 Generated with Claude Code

A component is validated by re-executing its source in an empty namespace, so
anything it reads from another cell has to travel with it. `also` carries a
function and `include` carries a value; an import is the third case and neither
fits it. `also=[Ridge]` tries to inline sklearn's own source and dies on
MultiOutputMixin; `include={'Ridge': Ridge}` writes the class's repr into the
file and produces a SyntaxError.

So a student who writes `from sklearn.linear_model import Ridge` in an import
cell - the normal thing - had their correct model rejected, and was handed two
remedies that both fail. That is the one place the conformance machinery was
brittle in a way a student would experience as us being wrong.

Now the imports are worked out from the names the component actually mentions
and written into the saved file as imports. Name resolution walks up to the
shallowest ancestor module that still exports the object, so the file records
`from sklearn.linear_model import Ridge` rather than the private
`sklearn.linear_model._ridge` path the class reports as its own.

The genuine missing-symbol case is unchanged and still refused: a value or a
helper defined in another cell does not resolve to an importable object, so it
falls through to the existing message naming the symbol.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1QS8AeUNLQTvUhxZiVSbT
Copilot AI lite review requested due to automatic review settings September 7, 2026 20:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@stefan-jansen
stefan-jansen merged commit 6e541c8 into main Sep 7, 2026
34 checks passed
@stefan-jansen
stefan-jansen deleted the fix/carry-imports-into-the-saved-component branch September 7, 2026 21:52
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.

2 participants