|
159 | 159 | ModuleSearchResult, |
160 | 160 | SearchPaths, |
161 | 161 | compute_search_paths, |
| 162 | + get_search_dirs, |
162 | 163 | ) |
163 | 164 | from mypy.modules_state import modules_state |
164 | 165 | from mypy.nodes import Expression |
@@ -658,72 +659,99 @@ def plugin_error(message: str) -> NoReturn: |
658 | 659 |
|
659 | 660 | custom_plugins: list[Plugin] = [] |
660 | 661 | errors.set_file(options.config_file, None, options) |
661 | | - for plugin_path in options.plugins: |
662 | | - func_name = "plugin" |
663 | | - plugin_dir: str | None = None |
664 | | - if ":" in os.path.basename(plugin_path): |
665 | | - plugin_path, func_name = plugin_path.rsplit(":", 1) |
666 | | - if plugin_path.endswith(".py"): |
667 | | - # Plugin paths can be relative to the config file location. |
668 | | - plugin_path = os_path_join(os.path.dirname(options.config_file), plugin_path) |
669 | | - if not os.path.isfile(plugin_path): |
670 | | - plugin_error(f'Can\'t find plugin "{plugin_path}"') |
671 | | - # Use an absolute path to avoid populating the cache entry |
672 | | - # for 'tmp' during tests, since it will be different in |
673 | | - # different tests. |
674 | | - plugin_dir = os.path.abspath(os.path.dirname(plugin_path)) |
675 | | - fnam = os.path.basename(plugin_path) |
676 | | - module_name = fnam[:-3] |
677 | | - sys.path.insert(0, plugin_dir) |
678 | | - elif re.search(r"[\\/]", plugin_path): |
679 | | - fnam = os.path.basename(plugin_path) |
680 | | - plugin_error(f'Plugin "{fnam}" does not have a .py extension') |
681 | | - else: |
682 | | - module_name = plugin_path |
| 662 | + with plugin_import_path(options): |
| 663 | + for plugin_path in options.plugins: |
| 664 | + func_name = "plugin" |
| 665 | + plugin_dir: str | None = None |
| 666 | + if ":" in os.path.basename(plugin_path): |
| 667 | + plugin_path, func_name = plugin_path.rsplit(":", 1) |
| 668 | + if plugin_path.endswith(".py"): |
| 669 | + # Plugin paths can be relative to the config file location. |
| 670 | + plugin_path = os_path_join(os.path.dirname(options.config_file), plugin_path) |
| 671 | + if not os.path.isfile(plugin_path): |
| 672 | + plugin_error(f'Can\'t find plugin "{plugin_path}"') |
| 673 | + # Use an absolute path to avoid populating the cache entry |
| 674 | + # for 'tmp' during tests, since it will be different in |
| 675 | + # different tests. |
| 676 | + plugin_dir = os.path.abspath(os.path.dirname(plugin_path)) |
| 677 | + fnam = os.path.basename(plugin_path) |
| 678 | + module_name = fnam[:-3] |
| 679 | + sys.path.insert(0, plugin_dir) |
| 680 | + elif re.search(r"[\\/]", plugin_path): |
| 681 | + fnam = os.path.basename(plugin_path) |
| 682 | + plugin_error(f'Plugin "{fnam}" does not have a .py extension') |
| 683 | + else: |
| 684 | + module_name = plugin_path |
683 | 685 |
|
684 | | - try: |
685 | | - module = importlib.import_module(module_name) |
686 | | - except Exception as exc: |
687 | | - plugin_error(f'Error importing plugin "{plugin_path}": {exc}') |
688 | | - finally: |
689 | | - if plugin_dir is not None: |
690 | | - assert sys.path[0] == plugin_dir |
691 | | - del sys.path[0] |
692 | | - |
693 | | - if not hasattr(module, func_name): |
694 | | - plugin_error( |
695 | | - 'Plugin "{}" does not define entry point function "{}"'.format( |
696 | | - plugin_path, func_name |
| 686 | + try: |
| 687 | + module = importlib.import_module(module_name) |
| 688 | + except Exception as exc: |
| 689 | + plugin_error(f'Error importing plugin "{plugin_path}": {exc}') |
| 690 | + finally: |
| 691 | + if plugin_dir is not None: |
| 692 | + assert sys.path[0] == plugin_dir |
| 693 | + del sys.path[0] |
| 694 | + |
| 695 | + if not hasattr(module, func_name): |
| 696 | + plugin_error( |
| 697 | + 'Plugin "{}" does not define entry point function "{}"'.format( |
| 698 | + plugin_path, func_name |
| 699 | + ) |
697 | 700 | ) |
698 | | - ) |
699 | 701 |
|
700 | | - try: |
701 | | - plugin_type = getattr(module, func_name)(__version__) |
702 | | - except Exception: |
703 | | - print(f"Error calling the plugin(version) entry point of {plugin_path}\n", file=stdout) |
704 | | - raise # Propagate to display traceback |
705 | | - |
706 | | - if not isinstance(plugin_type, type): |
707 | | - plugin_error( |
708 | | - 'Type object expected as the return value of "plugin"; got {!r} (in {})'.format( |
709 | | - plugin_type, plugin_path |
| 702 | + try: |
| 703 | + plugin_type = getattr(module, func_name)(__version__) |
| 704 | + except Exception: |
| 705 | + print( |
| 706 | + f"Error calling the plugin(version) entry point of {plugin_path}\n", |
| 707 | + file=stdout, |
710 | 708 | ) |
711 | | - ) |
712 | | - if not issubclass(plugin_type, Plugin): |
713 | | - plugin_error( |
714 | | - 'Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" ' |
715 | | - "(in {})".format(plugin_path) |
716 | | - ) |
717 | | - try: |
718 | | - custom_plugins.append(plugin_type(options)) |
719 | | - snapshot[module_name] = take_module_snapshot(module) |
720 | | - except Exception: |
721 | | - print(f"Error constructing plugin instance of {plugin_type.__name__}\n", file=stdout) |
722 | | - raise # Propagate to display traceback |
| 709 | + raise # Propagate to display traceback |
| 710 | + |
| 711 | + if not isinstance(plugin_type, type): |
| 712 | + plugin_error( |
| 713 | + 'Type object expected as the return value of "plugin"; ' |
| 714 | + "got {!r} (in {})".format(plugin_type, plugin_path) |
| 715 | + ) |
| 716 | + if not issubclass(plugin_type, Plugin): |
| 717 | + plugin_error( |
| 718 | + 'Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" ' |
| 719 | + "(in {})".format(plugin_path) |
| 720 | + ) |
| 721 | + try: |
| 722 | + custom_plugins.append(plugin_type(options)) |
| 723 | + snapshot[module_name] = take_module_snapshot(module) |
| 724 | + except Exception: |
| 725 | + print( |
| 726 | + f"Error constructing plugin instance of {plugin_type.__name__}\n", |
| 727 | + file=stdout, |
| 728 | + ) |
| 729 | + raise # Propagate to display traceback |
723 | 730 |
|
724 | 731 | return custom_plugins, snapshot |
725 | 732 |
|
726 | 733 |
|
| 734 | +@contextlib.contextmanager |
| 735 | +def plugin_import_path(options: Options) -> Iterator[None]: |
| 736 | + """Make module-name plugins importable from --python-executable.""" |
| 737 | + python_executable = options.python_executable |
| 738 | + if python_executable is None or is_current_python_executable(python_executable): |
| 739 | + yield |
| 740 | + return |
| 741 | + |
| 742 | + sys_path, site_packages = get_search_dirs(python_executable) |
| 743 | + original_path = sys.path[:] |
| 744 | + sys.path[:0] = sys_path + site_packages |
| 745 | + try: |
| 746 | + yield |
| 747 | + finally: |
| 748 | + sys.path[:] = original_path |
| 749 | + |
| 750 | + |
| 751 | +def is_current_python_executable(python_executable: str) -> bool: |
| 752 | + return os.path.abspath(python_executable) == os.path.abspath(sys.executable) |
| 753 | + |
| 754 | + |
727 | 755 | def load_plugins( |
728 | 756 | options: Options, errors: Errors, stdout: TextIO, extra_plugins: Sequence[Plugin] |
729 | 757 | ) -> tuple[Plugin, dict[str, str]]: |
|
0 commit comments