Skip to content

Commit 14aa557

Browse files
committed
[3.15] Detect PEP 810 lazy imports
1 parent 64177cd commit 14aa557

4 files changed

Lines changed: 60 additions & 3 deletions

File tree

README.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,10 @@ generalized unpacking, ellipsis literal (``...``) out of slices, dictionary unio
154154
(``list[str]``), function decorators, class decorators, relaxed decorators, ``metaclass`` class
155155
keyword, pattern matching with ``match``, union types written as ``X | Y``, type alias statements
156156
(``type X = SomeType``), type alias statements with lambdas/comprehensions in class scopes, generic
157-
classes (``class C[T]: ...``), and template string literals (``t'{var}'``). It tries to detect and
158-
ignore user-defined functions, classes, arguments, and variables with names that clash with
159-
library-defined symbols.
157+
classes (``class C[T]: ...``), template string literals (``t'{var}'``), lazy imports (``lazy
158+
import``, ``lazy from ... import``), and special module attributes like ``__lazy_modules__``. It
159+
tries to detect and ignore user-defined functions, classes, arguments, and variables with names that
160+
clash with library-defined symbols.
160161

161162
Caveats
162163
=======

tests/lang.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,26 @@ def test_unpacking_in_comprehension(self):
11861186
visitor = self.visit("[x for x in range(10)]")
11871187
self.assertFalse(visitor.unpacking_in_comprehension())
11881188

1189+
@VerminTest.skipUnlessVersion(3, 15)
1190+
def test_lazy_imports(self):
1191+
visitor = self.visit("lazy import foo")
1192+
self.assertTrue(visitor.lazy_imports())
1193+
self.assertOnlyIn((3, 15), visitor.minimum_versions())
1194+
1195+
visitor = self.visit("lazy from bar import foo")
1196+
self.assertTrue(visitor.lazy_imports())
1197+
self.assertOnlyIn((3, 15), visitor.minimum_versions())
1198+
1199+
visitor = self.visit("import foo")
1200+
self.assertFalse(visitor.lazy_imports())
1201+
1202+
def test_lazy_modules(self):
1203+
visitor = self.visit("__lazy_modules__ = {}")
1204+
self.assertTrue(visitor.lazy_modules())
1205+
self.assertOnlyIn((3, 15), visitor.minimum_versions())
1206+
1207+
visitor = self.visit("__all__ = []")
1208+
self.assertFalse(visitor.lazy_modules())
11891209
@VerminTest.skipUnlessVersion(3, 5)
11901210
def test_bytes_format(self):
11911211
visitor = self.visit("b'%x' % 10")

vermin/source_state.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ def __init__(self, config, path=None, source=None):
133133
# `*x` unpacking in comprehension value expressions (PEP 798).
134134
self.unpacking_in_comprehension = False
135135

136+
# `lazy import` statement (PEP 810).
137+
self.lazy_imports = False
138+
139+
# `__lazy_modules__` module attribute.
140+
self.lazy_modules = False
136141
# Imported members of modules, like "exc_clear" of "sys".
137142
self.import_mem_mod = {}
138143

vermin/source_visitor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ def generic_class(self):
279279
def unpacking_in_comprehension(self):
280280
return self.__s.unpacking_in_comprehension
281281

282+
def lazy_imports(self):
283+
return self.__s.lazy_imports
284+
285+
def lazy_modules(self):
286+
return self.__s.lazy_modules
287+
282288
def __get_source_line(self, line, col=0):
283289
if self.__s.source is None:
284290
return None # pragma: no cover
@@ -535,6 +541,15 @@ def minimum_versions(self):
535541
if self.unpacking_in_comprehension():
536542
mins = self.__add_versions_entity(mins, (None, (3, 15)),
537543
"unpacking in comprehension (PEP 798)")
544+
545+
if self.lazy_imports():
546+
mins = self.__add_versions_entity(mins, (None, (3, 15)),
547+
"lazy imports (PEP 810)")
548+
549+
if self.lazy_modules():
550+
mins = self.__add_versions_entity(mins, (None, (3, 15)),
551+
"`__lazy_modules__`")
552+
538553
for directive in self.strftime_directives():
539554
if directive in STRFTIME_REQS:
540555
vers = STRFTIME_REQS[directive]
@@ -1021,6 +1036,12 @@ def visit_Import(self, node):
10211036
if self.__is_no_line(node.lineno):
10221037
return
10231038

1039+
# PEP 810: `lazy import` (3.15+).
1040+
if hasattr(node, "is_lazy") and node.is_lazy:
1041+
self.__s.lazy_imports = True
1042+
self.__vvprint("lazy imports (PEP 810)", line=node.lineno,
1043+
versions=[None, (3, 15)])
1044+
10241045
for name in node.names:
10251046
line = node.lineno
10261047
col = node.col_offset + 7 # "import" = 6 + 1
@@ -1042,6 +1063,12 @@ def visit_ImportFrom(self, node):
10421063
if self.__is_no_line(node.lineno):
10431064
return
10441065

1066+
# PEP 810: `lazy from ... import` (3.15+).
1067+
if hasattr(node, "is_lazy") and node.is_lazy:
1068+
self.__s.lazy_imports = True
1069+
self.__vvprint("lazy imports (PEP 810)", line=node.lineno,
1070+
versions=[None, (3, 15)])
1071+
10451072
from_col = 5 # "from" = 4 + 1
10461073
self.__add_module(node.module, node.lineno, node.col_offset + from_col)
10471074

@@ -1075,6 +1102,10 @@ def visit_Name(self, node):
10751102
elif node.id in ("True", "False"):
10761103
self.__s.bool_const = True
10771104
self.__vvvprint("True/False constant", versions=[(2, 3), (3, 0)])
1105+
elif node.id == "__lazy_modules__":
1106+
self.__s.lazy_modules = True
1107+
self.__vvprint("`__lazy_modules__`", line=node.lineno,
1108+
versions=[None, (3, 15)])
10781109

10791110
def visit_Print(self, node): # pragma: no cover
10801111
self.__s.printv2 = True

0 commit comments

Comments
 (0)