fix: classify user-package import failures in load_python_modules - #1473
Open
EngHabu wants to merge 2 commits into
Open
fix: classify user-package import failures in load_python_modules#1473EngHabu wants to merge 2 commits into
EngHabu wants to merge 2 commits into
Conversation
`load_python_modules` has three `importlib.import_module(<user-module>)` call sites. #1168 gave two of them the broad "anything raised inside a user module is a user error" treatment, but the third — the branch that imports a *directory* as a package when it contains no top-level `.py` files — kept its original narrow handler: except (ValueError, ModuleNotFoundError): pass That leaks in both directions: 1. Any other exception from the user's `__init__.py` (`TypeError`, `OSError`, `SyntaxError`, `PydanticUserError`, yaml `ScannerError`, ...) escapes `load_python_modules` raw and crash-reports to Sentry, even though it is by definition a bug in the user's code or one of its third-party imports. This is the same class the Sentry corpus shows for the other two sites (FLYTE-SDK-39 and friends), which #1168 already closed. 2. A `ModuleNotFoundError` raised from *inside* the package — a dependency the user has not installed — is swallowed by the bare `pass`. Deploy then proceeds with zero environments and no explanation. The silent half is arguably worse than the crash. The narrow catch was not wrong when it was written: it is control flow for "this directory is not an importable package, skip it". This keeps that, and only that: - `ValueError` is now scoped to the `relative_to` call that actually raises it, so a `ValueError` from user code at import time is no longer mistaken for a path-outside-the-root. - `ModuleNotFoundError` is skipped only when it names the target module (or a parent package of it) — i.e. the directory genuinely is not importable here. `ModuleNotFoundError.name` distinguishes that from a missing inner import. - Everything else is recorded in `failed_paths`, consistent with the sibling directory branch, so `flyte deploy` surfaces a clean `ClickException` and honours `--ignore-load-errors` instead of crash-reporting. No Sentry issue is currently firing on this branch, so nothing is claimed as fixed — this closes the site #1168 missed, plus the silent-skip bug next to it. Six tests; the two that pin the leak and the silent skip fail on main, and the four that pin the preserved skip-and-continue behaviour pass on both sides by design. Signed-off-by: Haytham Abuelfutuh <haytham@afutuh.com>
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.
What
load_python_moduleshas threeimportlib.import_module(<user-module>)call sites. #1168 gave two of them the broad "anything raised inside a user module is a user error" treatment. The third — the branch that imports a directory as a package when it holds no top-level.pyfiles — kept its original narrow handler:That leaks in both directions.
1. Loud half — raw exception to Sentry. Anything else raised by the user's
__init__.py(TypeError,OSError,SyntaxError,PydanticUserError, yamlScannerError, …) escapesload_python_modulesunclassified and crash-reports, even though it is by definition a bug in the user's code or one of its third-party imports. This is the same class the Sentry corpus shows for the other two sites (FLYTE-SDK-39 and friends) and that #1168 already closed there.2. Silent half — swallowed dependency error. A
ModuleNotFoundErrorraised from inside the package (a dependency the user has not installed) is eaten by the barepass.flyte deploythen proceeds with zero environments and no explanation. Arguably worse than the crash, since the user gets no signal at all.Verified on clean
main:and on this branch:
How
The narrow catch was not wrong when it was written — it is control flow for "this directory is not an importable package, skip it". This PR keeps that, and only that:
ValueErroris scoped to therelative_tocall that actually raises it, so aValueErrorfrom user code at import time is no longer mistaken for a path-outside-the-root.ModuleNotFoundErroris skipped only when it names the target module (or a parent package of it) — i.e. the directory genuinely is not importable here.ModuleNotFoundError.namedistinguishes that from a missing inner import, which is now surfaced.failed_paths, consistent with the sibling directory branch, so deploy raises a cleanclick.ClickException(already on the_sentry.pyuser-error allow-list) and still honours--ignore-load-errors.Behaviour change worth a reviewer's eye
A package that fails to import now records a
failed_path, soflyte deployaborts with a clean error instead of silently deploying nothing.--ignore-load-errorsrestores the old permissive behaviour. A package that is simply not importable here is still skipped silently, exactly as before.Sentry status
No
fixes FLYTE-SDK-XXclaimed. No issue in the current corpus lands on this branch — the module-loader issues in Sentry (66, 6D, 56, 5Q, 4X, …) map to the other two call sites on releases predating #1168. I checkedv2.5.1:module_loader.py:117directly to confirm 66 is the dir-of-files loop, not this one. This is the completeness fix for the site #1168 missed, plus the silent-skip bug sitting next to it.Tests
Six new tests. The two that pin the leak and the silent skip fail on
main:The four that pin the preserved skip-and-continue behaviour (target missing, parent package missing, path outside root, healthy package) pass on both sides by design — they are regression guards for the behaviour this PR must not change, not repros.
Each test uses a distinct package name so a cached
sys.modulesentry from one cannot change what another imports.Local run:
tests/flyte+tests/cli= 4057 passed / 7 failed, byte-identical failure set to cleanmain(4051 passed / 7 failed — same 7, all pre-existing in this sandbox).ruff format,ruff check,check_docstring_style.py(492 files clean) andmypyall clean.