refactor: one lazy import left, and it is the only real cycle - #110
Merged
FBumann merged 1 commit intoJul 26, 2026
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
FBumann
force-pushed
the
refactor/untangle-language-layer
branch
from
July 26, 2026 16:32
e792234 to
5916635
Compare
FBumann
force-pushed
the
refactor/split-relational-engine
branch
from
July 26, 2026 16:45
f0105b1 to
432b534
Compare
The package had eight in-function `linopy_yaml` imports. One broke a genuine cycle; the other seven broke nothing and made that one impossible to spot. `piecewise.py` and `lowering.py` imported each other lazily, and piecewise reached for a *private* name — `lowering._lower_expr` — using it as a predicate and throwing the result away. The dependency is real and its direction is right: a formulation sits above lowering, and it needs to know whether a link expression is in the streaming subset. So lowering now offers `check_core_subset()` for exactly that, piecewise imports it at module level, and only the reverse edge stays lazy — documented, and now the sole entry in `DELIBERATE_LAZY_IMPORTS`. That guard turned out to be load-bearing and untested. Removing it, `p ** 2` and `p * p` in a `piecewise:` link are still refused — but by lowering, on the *expanded* declarations, so the message names `cost_curve_link0`, which the user never wrote. The guard exists to keep the error pointing at the block and link index in the file. Two tests now pin that. The other six were leftovers: `resolution` imported `errors`, `where_parser` and `helpers` in-function while already importing them at the top, and `dimensions` and `validation` deferred imports of modules with no path back. All hoisted. `test_lazy_intra_package_imports_are_all_declared` fails on any undeclared one from here, and also on a stale declaration — so the list cannot rot in either direction. `tidy_sources` moves to a new `sources.py`. It was in `lowering.py`, where it shared no code and no concept with anything: lowering turns an AST into a plan and touches no data, that function touches only data and knows nothing about expressions. They were together because `api.build` calls them on consecutive lines. 311 tests pass, 3 of them new. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FBumann
force-pushed
the
refactor/untangle-language-layer
branch
from
July 26, 2026 16:47
5916635 to
4076ca6
Compare
This was referenced Jul 26, 2026
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.
The package had eight in-function
linopy_yamlimports. One broke a genuine cycle. The other seven broke nothing — and made that one impossible to spot.The one real cycle went through a private name
piecewise.pyandlowering.pyimported each other lazily, and piecewise reached forlowering._lower_expr, using it as a predicate and discarding the result:The dependency is real and its direction is right: a formulation sits above lowering, and it needs to know whether a link expression is in the streaming subset. So lowering now offers a public name for exactly that —
— piecewise imports it at module level, and only the reverse edge stays lazy. It is now the sole entry in
DELIBERATE_LAZY_IMPORTS, with the reason written down.That guard was load-bearing, and untested
I checked whether it could just go (by removing the call, not by monkeypatching — the first attempt patched
lowering._lower_exprglobally, which also disabled lowering's own use and falsely reported the guard as dead).With it removed,
p ** 2andp * pin apiecewise:link are still refused — by lowering, on the expanded declarations:cost_curve_link0is a declaration the formulation generated. The user wrotecost_curve, link 0. The guard exists to keep the message pointing at the file. Two tests now pin that, so the next person to find this "redundant" call has the reason in front of them.The other six were leftovers
resolution.pyimportederrors,where_parserandhelpersin-function while already importing them at the top;dimensions.pyandvalidation.pydeferred imports of modules with no path back. All hoisted.A new architecture test keeps it that way, and fails in both directions — an undeclared in-function import, or a declaration for an import that no longer exists:
ARCHITECTURE.mdgains this as hard rule 0, since it is the mechanical form of "the layers are ordered".It earned itself on the rebase. #104 landed while this was open and added a ninth in-function import —
piecewise.py→relational.arrow. The test failed on rebase and named it. It turned out to be hoistable (arrow.pyimports pyarrow lazily itself, so module-level costs nothing), so that is a tenth import gone rather than a second declaration.tidy_sources→sources.pyRebased onto #104's Arrow rewrite of that function, so
sources.pycarries the new body, not the pandas one. It was inlowering.py, sharing no code and no concept with anything there: lowering turns an AST into a plan and touches no data; that function touches only data and knows nothing about expressions. They were in one file becauseapi.buildcalls them on consecutive lines, which is not a reason.Verification
uv run pytest— 311 passed, 1 xfailed (3 new) ·ruff checkandruff format --checkclean ·pyrefly check0 errors.🤖 Generated with Claude Code