Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a get_additional_deps hook to Plugin to support django-stubs #6598

Merged
merged 3 commits into from Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
msullivan marked this conversation as resolved.
Show resolved Hide resolved

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.
"""
Copy link
Member

Choose a reason for hiding this comment

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

This (+ an example) should be also added to mypy/docs/source/extending_mypy.rst.

@msullivan Will you have time to make a PR? I suppose this will be announced in the release blog post, it would be great to have some docs to link to.

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