Skip to content

Commit

Permalink
Add a get_additional_deps hook to Plugin to support django-stubs (#6598)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
msullivan committed Mar 29, 2019
1 parent a213ccb commit 465521b
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 4 deletions.
4 changes: 3 additions & 1 deletion mypy/build.py
Expand Up @@ -1967,7 +1967,9 @@ def compute_dependencies(self) -> None:
dependencies = []
priorities = {} # type: Dict[str, int] # id -> priority
dep_line_map = {} # type: Dict[str, int] # id -> line
for pri, id, line in manager.all_imported_modules_in_file(self.tree):
dep_entries = (manager.all_imported_modules_in_file(self.tree) +
self.manager.plugin.get_additional_deps(self.tree))
for pri, id, line in dep_entries:
priorities[id] = min(pri, priorities.get(id, PRI_ALL))
if id == self.id:
continue
Expand Down
5 changes: 4 additions & 1 deletion mypy/interpreted_plugin.py
@@ -1,6 +1,6 @@
"""Hack for handling non-mypyc compiled plugins with a mypyc-compiled mypy"""

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

def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
return []

def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[['mypy.plugin.AnalyzeTypeContext'], Type]]:
return None
Expand Down
28 changes: 27 additions & 1 deletion mypy/plugin.py
Expand Up @@ -371,6 +371,23 @@ def lookup_fully_qualified(self, fullname: str) -> Optional[SymbolTableNode]:
assert self._modules is not None
return lookup_fully_qualified(fullname, self._modules)

def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
"""Customize dependencies for a module.
This hook allows adding in new dependencies for a module. It
is called after parsing a file but before analysis. This can
be useful if a library has dependencies that are dynamic based
on configuration information, for example.
Returns a list of (priority, module name, line number) tuples.
The line number can be -1 when there is not a known real line number.
Priorities are defined in mypy.build (but maybe shouldn't be).
10 is a good choice for priority.
"""
return []

def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
"""Customize behaviour of the type analyzer for given full names.
Expand All @@ -395,7 +412,7 @@ def get_function_hook(self, fullname: str
"""Adjust the return type of a function call.
This method is called after type checking a call. Plugin may adjust the return
type inferred by mypy, and/or emmit some error messages. Note, this hook is also
type inferred by mypy, and/or emit some error messages. Note, this hook is also
called for class instantiation calls, so that in this example:
from lib import Class, do_stuff
Expand Down Expand Up @@ -561,6 +578,9 @@ def set_modules(self, modules: Dict[str, MypyFile]) -> None:
def lookup_fully_qualified(self, fullname: str) -> Optional[SymbolTableNode]:
return self.plugin.lookup_fully_qualified(fullname)

def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
return self.plugin.get_additional_deps(file)

def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
return self.plugin.get_type_analyze_hook(fullname)
Expand Down Expand Up @@ -626,6 +646,12 @@ def set_modules(self, modules: Dict[str, MypyFile]) -> None:
for plugin in self._plugins:
plugin.set_modules(modules)

def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
deps = []
for plugin in self._plugins:
deps.extend(plugin.get_additional_deps(file))
return deps

def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
return self._find_hook(lambda plugin: plugin.get_type_analyze_hook(fullname))
Expand Down
14 changes: 13 additions & 1 deletion test-data/unit/check-custom-plugin.test
Expand Up @@ -71,7 +71,6 @@ tmp/mypy.ini:2: error: Can't find plugin 'tmp/missing.py'
plugins=missing
[out]
tmp/mypy.ini:2: error: Error importing plugin 'missing'
--' (work around syntax highlighting)

[case testMultipleSectionsDefinePlugin]
# flags: --config-file tmp/mypy.ini
Expand Down Expand Up @@ -585,3 +584,16 @@ reveal_type(instance(2)) # E: Revealed type is 'builtins.float*'
[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/callable_instance.py

[case testPluginDependencies]
# flags: --config-file tmp/mypy.ini

# The top level file here doesn't do anything, but the plugin should add
# a dependency on err that will cause err to be processed and an error reported.

[file err.py]
1 + 'lol' # E: Unsupported operand types for + ("int" and "str")

[file mypy.ini]
[[mypy]
plugins=<ROOT>/test-data/unit/plugins/depshook.py
15 changes: 15 additions & 0 deletions test-data/unit/plugins/depshook.py
@@ -0,0 +1,15 @@
from typing import Optional, Callable, List, Tuple

from mypy.plugin import Plugin
from mypy.nodes import MypyFile


class DepsPlugin(Plugin):
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
if file.fullname() == '__main__':
return [(10, 'err', -1)]
return []


def plugin(version):
return DepsPlugin

0 comments on commit 465521b

Please sign in to comment.