Adds absolute named import support to lazy_export#5078
Adds absolute named import support to lazy_export#5078ooctipus merged 2 commits intoisaac-sim:developfrom
Conversation
Greptile SummaryThis PR extends Key points:
Confidence Score: 4/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["lazy_export() called"] --> B{has .pyi stub?}
B -- No --> C["__all__ = []"]
B -- Yes --> D["_parse_stub(stub_file)"]
D --> E["AST walk over stub body"]
E --> F{node is ImportFrom?}
F -- No --> G["add to filtered_body"]
F -- Yes --> H{classify import}
H -- "level==1, not star" --> I["add to filtered_body\n(relative named → lazy_loader)"]
H -- "level==0, star" --> J["fallback_packages.append(module)"]
H -- "level==1, star" --> K["relative_wildcards.append(module)"]
H -- "level==0, not star" --> L["absolute_named[module].extend(names)"]
H -- "level>1 or no module" --> M["silently dropped, needs_filter=True"]
I --> N["write filtered .pyi\nfor lazy_loader"]
J --> N
K --> N
L --> N
N --> O["lazy.attach_stub → stub_getattr, stub_dir, __all__"]
C --> P["mod = sys.modules[package_name]"]
O --> P
P --> Q["Resolve absolute_named\n(from pkg import a,b)"]
Q --> R["importlib.import_module(abs_pkg)\ngetattr → mod.__dict__[name]\n__all__.append(name)"]
R --> S["Resolve relative_wildcards\n(from .X import *)"]
S --> T["importlib.import_module(fq_name)\nfor each public name → mod.__dict__"]
T --> U{fallback_packages?}
U -- Yes --> V["Build lazy _pkg_getattr\n__getattr__ chains stub → pkg fallback"]
U -- No --> W["__getattr__ = stub_getattr"]
V --> X["setattr mod: __getattr__, __dir__, __all__"]
W --> X
Last reviewed commit: "Add absolute named i..." |
| names = [alias.name for alias in node.names] | ||
| absolute_named.setdefault(node.module, []).extend(names) |
There was a problem hiding this comment.
Import aliases silently dropped
alias.asname is never consulted, so from pkg import foo as bar in a stub will eagerly register the attribute under the original name foo (not the alias bar). The correct exported key should be alias.asname if alias.asname else alias.name, and the value should always be fetched as alias.name from the source module.
In practice, stubs in this project may not currently use as-aliasing for absolute named imports, but the silent misbehaviour is a latent bug: the wrong name ends up in mod.__dict__ and __all__, and the intended alias is never exported.
| names = [alias.name for alias in node.names] | |
| absolute_named.setdefault(node.module, []).extend(names) | |
| names = [(alias.name, alias.asname or alias.name) for alias in node.names] | |
| for src_name, exported_name in names: | |
| absolute_named.setdefault(node.module, []).append((src_name, exported_name)) |
And correspondingly in lazy_export:
for abs_pkg, name_pairs in absolute_named.items():
pkg_mod = importlib.import_module(abs_pkg)
for src_name, exported_name in name_pairs:
mod.__dict__[exported_name] = getattr(pkg_mod, src_name)
if exported_name not in __all__:
__all__.append(exported_name)| for abs_pkg, names in absolute_named.items(): | ||
| pkg_mod = importlib.import_module(abs_pkg) | ||
| for name in names: | ||
| mod.__dict__[name] = getattr(pkg_mod, name) | ||
| if name not in __all__: | ||
| __all__.append(name) |
There was a problem hiding this comment.
No error context on failed absolute named import
Both importlib.import_module(abs_pkg) and getattr(pkg_mod, name) can raise without any pointer back to the stub that declared the import. Compare with the analogous fallback_packages block (lines 176–184) which wraps import_module in a try/except and re-raises an ImportError with a descriptive message.
Consider wrapping this block similarly:
for abs_pkg, names in absolute_named.items():
try:
pkg_mod = importlib.import_module(abs_pkg)
except ImportError as e:
raise ImportError(
f"lazy_export() in {package_name!r}: .pyi stub declares "
f"'from {abs_pkg} import ...' but the package is not installed."
) from e
for name in names:
try:
mod.__dict__[name] = getattr(pkg_mod, name)
except AttributeError as e:
raise AttributeError(
f"lazy_export() in {package_name!r}: .pyi stub declares "
f"'from {abs_pkg} import {name}' but the attribute does not exist."
) from e
if name not in __all__:
__all__.append(name)f495020 to
64eb552
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR extends _parse_stub and lazy_export to handle absolute named imports (from pkg import a, b) in .pyi stubs, eagerly resolving and re-exporting them at lazy_export() time. The implementation is correct — parsing logic is sound, filtered stubs correctly exclude absolute named imports, and the eager resolution order (absolute named → relative wildcards → lazy fallbacks) is well-designed. One code quality improvement to address.
Architecture Impact
_parse_stub is private, called only from lazy_export. The return type change from 3-tuple to 4-tuple is contained within the same file. No existing .pyi stubs use absolute named imports yet (all use from pkg import *), so this is purely additive with zero blast radius on existing code paths — absolute_named will be an empty dict for all current stubs.
Implementation Verdict
Minor fixes needed — Correct approach, 1 issue to address.
CI Status
Core checks passing ✅ (pre-commit, license-check, docs build). Test suite jobs are skipping because the Docker base image build was cancelled — this is infrastructure-related, not caused by this PR.
Findings
🔵 Improvement: source/isaaclab/isaaclab/utils/module.py:153-158 — Missing error wrapping for absolute named imports, inconsistent with the established pattern for fallback packages.
| for abs_pkg, names in absolute_named.items(): | ||
| pkg_mod = importlib.import_module(abs_pkg) | ||
| for name in names: | ||
| mod.__dict__[name] = getattr(pkg_mod, name) |
There was a problem hiding this comment.
The fallback packages path (line 172-177) wraps importlib.import_module with a try/except that produces a user-friendly error message pointing at the .pyi stub declaration. The absolute named imports path does not, so a typo like from mistyped.pkg import foo in a stub would produce a raw ModuleNotFoundError pointing at lazy_export internals. Similarly, getattr(pkg_mod, name) with a nonexistent name would produce a raw AttributeError.
Wrap both calls for consistency with the established pattern:
| mod.__dict__[name] = getattr(pkg_mod, name) | |
| for abs_pkg, names in absolute_named.items(): | |
| try: | |
| pkg_mod = importlib.import_module(abs_pkg) | |
| except (ImportError, ModuleNotFoundError) as e: | |
| raise ImportError( | |
| f"lazy_export() in {package_name!r}: .pyi stub declares " | |
| f"'from {abs_pkg} import ...' but the package is not installed." | |
| ) from e | |
| for name in names: | |
| try: | |
| mod.__dict__[name] = getattr(pkg_mod, name) | |
| except AttributeError as e: | |
| raise ImportError( | |
| f"lazy_export() in {package_name!r}: .pyi stub declares " | |
| f"'from {abs_pkg} import {name}' but {abs_pkg!r} has no attribute {name!r}." | |
| ) from e | |
| if name not in __all__: | |
| __all__.append(name) |
Extend _parse_stub and lazy_export to handle absolute named imports (e.g. `from isaaclab.envs.mdp import foo, bar`) in .pyi stubs. These are eagerly resolved and re-exported alongside the existing relative and wildcard import paths. Add unit tests for _parse_stub's new absolute_named return value covering single/multiple packages, accumulation across lines, mutual exclusion with wildcards and relative imports, and filtered stub integrity.
64eb552 to
927f116
Compare
Extend `_parse_stub` and `lazy_export` to handle absolute named imports (e.g. `from isaaclab.envs.mdp import foo, bar`) in `.pyi` stubs. Previously only relative imports and absolute wildcard fallbacks were supported. Explicit names from absolute packages are now eagerly resolved and re-exported at `lazy_export()` time. - Bug Fix - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
# Description Extend `_parse_stub` and `lazy_export` to handle absolute named imports (e.g. `from isaaclab.envs.mdp import foo, bar`) in `.pyi` stubs. Previously only relative imports and absolute wildcard fallbacks were supported. Explicit names from absolute packages are now eagerly resolved and re-exported at `lazy_export()` time. ## Type of change - Bug Fix ## Checklist - [x] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file - [x] I have added my name to the `CONTRIBUTORS.md` or my name already exists there
Description
Extend
_parse_stubandlazy_exportto handle absolute named imports(e.g.
from isaaclab.envs.mdp import foo, bar) in.pyistubs. Previously onlyrelative imports and absolute wildcard fallbacks were supported. Explicit names from
absolute packages are now eagerly resolved and re-exported at
lazy_export()time.Type of change
Checklist
pre-commitchecks with./isaaclab.sh --formatconfig/extension.tomlfileCONTRIBUTORS.mdor my name already exists there