Skip to content

Commit 465521b

Browse files
authored
Add a get_additional_deps hook to Plugin to support django-stubs (#6598)
django-stubs needs to be able to add in additional dependencies to modules and is currently using monkeypatching to do it. That is 1) not great and 2) breaks under mypyc. Add a hook to support generating additional dependencies from a plugin.
1 parent a213ccb commit 465521b

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

mypy/build.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1967,7 +1967,9 @@ def compute_dependencies(self) -> None:
19671967
dependencies = []
19681968
priorities = {} # type: Dict[str, int] # id -> priority
19691969
dep_line_map = {} # type: Dict[str, int] # id -> line
1970-
for pri, id, line in manager.all_imported_modules_in_file(self.tree):
1970+
dep_entries = (manager.all_imported_modules_in_file(self.tree) +
1971+
self.manager.plugin.get_additional_deps(self.tree))
1972+
for pri, id, line in dep_entries:
19711973
priorities[id] = min(pri, priorities.get(id, PRI_ALL))
19721974
if id == self.id:
19731975
continue

mypy/interpreted_plugin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Hack for handling non-mypyc compiled plugins with a mypyc-compiled mypy"""
22

3-
from typing import Optional, Callable, Any, Dict
3+
from typing import Optional, Callable, Any, Dict, List, Tuple
44
from mypy.options import Options
55
from mypy.types import Type, CallableType
66
from mypy.nodes import SymbolTableNode, MypyFile
@@ -39,6 +39,9 @@ def lookup_fully_qualified(self, fullname: str) -> Optional[SymbolTableNode]:
3939
assert self._modules is not None
4040
return lookup_fully_qualified(fullname, self._modules)
4141

42+
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
43+
return []
44+
4245
def get_type_analyze_hook(self, fullname: str
4346
) -> Optional[Callable[['mypy.plugin.AnalyzeTypeContext'], Type]]:
4447
return None

mypy/plugin.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,23 @@ def lookup_fully_qualified(self, fullname: str) -> Optional[SymbolTableNode]:
371371
assert self._modules is not None
372372
return lookup_fully_qualified(fullname, self._modules)
373373

374+
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
375+
"""Customize dependencies for a module.
376+
377+
This hook allows adding in new dependencies for a module. It
378+
is called after parsing a file but before analysis. This can
379+
be useful if a library has dependencies that are dynamic based
380+
on configuration information, for example.
381+
382+
Returns a list of (priority, module name, line number) tuples.
383+
384+
The line number can be -1 when there is not a known real line number.
385+
386+
Priorities are defined in mypy.build (but maybe shouldn't be).
387+
10 is a good choice for priority.
388+
"""
389+
return []
390+
374391
def get_type_analyze_hook(self, fullname: str
375392
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
376393
"""Customize behaviour of the type analyzer for given full names.
@@ -395,7 +412,7 @@ def get_function_hook(self, fullname: str
395412
"""Adjust the return type of a function call.
396413
397414
This method is called after type checking a call. Plugin may adjust the return
398-
type inferred by mypy, and/or emmit some error messages. Note, this hook is also
415+
type inferred by mypy, and/or emit some error messages. Note, this hook is also
399416
called for class instantiation calls, so that in this example:
400417
401418
from lib import Class, do_stuff
@@ -561,6 +578,9 @@ def set_modules(self, modules: Dict[str, MypyFile]) -> None:
561578
def lookup_fully_qualified(self, fullname: str) -> Optional[SymbolTableNode]:
562579
return self.plugin.lookup_fully_qualified(fullname)
563580

581+
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
582+
return self.plugin.get_additional_deps(file)
583+
564584
def get_type_analyze_hook(self, fullname: str
565585
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
566586
return self.plugin.get_type_analyze_hook(fullname)
@@ -626,6 +646,12 @@ def set_modules(self, modules: Dict[str, MypyFile]) -> None:
626646
for plugin in self._plugins:
627647
plugin.set_modules(modules)
628648

649+
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
650+
deps = []
651+
for plugin in self._plugins:
652+
deps.extend(plugin.get_additional_deps(file))
653+
return deps
654+
629655
def get_type_analyze_hook(self, fullname: str
630656
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
631657
return self._find_hook(lambda plugin: plugin.get_type_analyze_hook(fullname))

test-data/unit/check-custom-plugin.test

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ tmp/mypy.ini:2: error: Can't find plugin 'tmp/missing.py'
7171
plugins=missing
7272
[out]
7373
tmp/mypy.ini:2: error: Error importing plugin 'missing'
74-
--' (work around syntax highlighting)
7574

7675
[case testMultipleSectionsDefinePlugin]
7776
# flags: --config-file tmp/mypy.ini
@@ -585,3 +584,16 @@ reveal_type(instance(2)) # E: Revealed type is 'builtins.float*'
585584
[file mypy.ini]
586585
[[mypy]
587586
plugins=<ROOT>/test-data/unit/plugins/callable_instance.py
587+
588+
[case testPluginDependencies]
589+
# flags: --config-file tmp/mypy.ini
590+
591+
# The top level file here doesn't do anything, but the plugin should add
592+
# a dependency on err that will cause err to be processed and an error reported.
593+
594+
[file err.py]
595+
1 + 'lol' # E: Unsupported operand types for + ("int" and "str")
596+
597+
[file mypy.ini]
598+
[[mypy]
599+
plugins=<ROOT>/test-data/unit/plugins/depshook.py

test-data/unit/plugins/depshook.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Optional, Callable, List, Tuple
2+
3+
from mypy.plugin import Plugin
4+
from mypy.nodes import MypyFile
5+
6+
7+
class DepsPlugin(Plugin):
8+
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
9+
if file.fullname() == '__main__':
10+
return [(10, 'err', -1)]
11+
return []
12+
13+
14+
def plugin(version):
15+
return DepsPlugin

0 commit comments

Comments
 (0)