◐ Shell
clean mode source ↗

gh-144957: Add test for lazy imports with __getattr__ by gourijain029-del · Pull Request #145330 · python/cpython

@gourijain029-del

Adds a regression test for lazy imports working with modules that use __getattr__.

The issue reported that lazy from typing import Match would fail since Match is provided by typing.__getattr__ rather than being in the module dict. Testing shows this works correctly in current main - the existing code in register_lazy_on_parent() already checks for __getattr__ and skips adding lazy import objects to those modules.

This test documents the expected behavior and prevents future regressions.

@bedevere-app

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Adds regression test to verify lazy imports work correctly with
modules that use __getattr__ for dynamic attributes (e.g. typing.Match).

The issue appears to be already fixed in current main branch.

@johnslavik

Testing shows this works correctly in current main - the existing code in register_lazy_on_parent() already checks for __getattr__ and skips adding lazy import objects to those modules.

Your test doesn't pass locally for me:

❯ ./python.exe -m unittest Lib.test.test_import.test_lazy_imports
....................................................F............BAR_MODULE_LOADED
...........................
======================================================================
FAIL: test_lazy_import_with_getattr (Lib.test.test_import.test_lazy_imports.LazyImportTests.test_lazy_import_with_getattr)
Lazy imports work with module __getattr__ (gh-144957).
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/bartosz.slawecki/Python/cpython/Lib/test/test_import/test_lazy_imports.py", line 101, in test_lazy_import_with_getattr
    self.assertEqual(result.returncode, 0, result.stderr)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 != 0 : Traceback (most recent call last):
  File "<string>", line 4, in <module>
ImportError: deferred import of 'typing.Match' raised an exception during resolution

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<string>", line 5, in <module>
    print(Match)
          ^^^^^
ImportError: cannot import name 'Match' from 'typing' (/Users/bartosz.slawecki/Python/cpython/Lib/typing.py)

Presumably there's something with CI, needs investigation.
EDIT: See #145334.

When resolving lazy imports, check if a lazy import object was found
and the module has __getattr__. If so, try calling __getattr__ first
before using the lazy import object.

johnslavik

Comment on lines +90 to +103

code = textwrap.dedent("""
import sys
sys.set_lazy_imports("normal")
lazy from test.test_import.data.lazy_imports.module_with_getattr import dynamic_attr
assert dynamic_attr == "from_getattr"
print("OK")
""")
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("OK", result.stdout)

Choose a reason for hiding this comment

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

Less prone to false positives:

code = textwrap.dedent("""
import sys
sys.set_lazy_imports("normal")
lazy from test.test_import.data.lazy_imports.module_with_getattr import dynamic_attr
assert dynamic_attr == "from_getattr"
print("OK")
""")
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("OK", result.stdout)
code = textwrap.dedent("""
import sys
sys.set_lazy_imports("normal")
lazy from test.test_lazy_import.data.module_with_getattr import dynamic_attr
print(repr(dynamic_attr))
""")
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("'from_getattr'", result.stdout)

@pablogsal

This user is creating a lot of AI generated PRs see #145276