Merge branch 'importlib_metadata' into 'master'

Switch from entrypoints to importlib_metadata

Closes #569

See merge request pycqa/flake8!388
This commit is contained in:
Anthony Sottile 2019-11-29 17:40:55 +00:00
commit 5d7eeaa9b3
12 changed files with 71 additions and 76 deletions

View file

@ -41,7 +41,6 @@ install_requires=
# http://flake8.pycqa.org/en/latest/faq.html#why-does-flake8-use-ranges-for-its-dependencies
# And in which releases we will update those ranges here:
# http://flake8.pycqa.org/en/latest/internal/releases.html#releasing-flake8
entrypoints >= 0.3.0, < 0.4.0
pyflakes >= 2.1.0, < 2.2.0
pycodestyle >= 2.5.0, < 2.6.0
mccabe >= 0.6.0, < 0.7.0
@ -49,6 +48,7 @@ install_requires=
typing; python_version<"3.5"
configparser; python_version<"3.2"
functools32; python_version<"3.2"
importlib-metadata; python_version<"3.8"
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*

14
src/flake8/_compat.py Normal file
View file

@ -0,0 +1,14 @@
"""Expose backports in a single place."""
import sys
if sys.version_info >= (3,): # pragma: no cover (PY3+)
from functools import lru_cache
else: # pragma: no cover (<PY3)
from functools32 import lru_cache
if sys.version_info >= (3, 8): # pragma: no cover (PY38+)
import importlib.metadata as importlib_metadata
else: # pragma: no cover (<PY38)
import importlib_metadata
__all__ = ("lru_cache", "importlib_metadata")

View file

@ -1,10 +1,6 @@
"""Exception classes for all of Flake8."""
from typing import Dict
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from flake8.plugins.manager import Plugin
class Flake8Exception(Exception):
"""Plain Flake8 exception."""
@ -23,18 +19,17 @@ class FailedToLoadPlugin(Flake8Exception):
FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.'
def __init__(self, plugin, exception):
# type: (Plugin, Exception) -> None
def __init__(self, plugin_name, exception):
# type: (str, Exception) -> None
"""Initialize our FailedToLoadPlugin exception."""
self.plugin = plugin
self.ep_name = self.plugin.name
self.plugin_name = plugin_name
self.original_exception = exception
super(FailedToLoadPlugin, self).__init__(plugin, exception)
super(FailedToLoadPlugin, self).__init__(plugin_name, exception)
def __str__(self): # type: () -> str
"""Format our exception message."""
return self.FORMAT % {
"name": self.ep_name,
"name": self.plugin_name,
"exc": self.original_exception,
}

View file

@ -168,9 +168,7 @@ class Application(object):
sys.path.extend(local_plugins.paths)
self.check_plugins = plugin_manager.Checkers(
local_plugins.extension
)
self.check_plugins = plugin_manager.Checkers(local_plugins.extension)
self.formatting_plugins = plugin_manager.ReportFormatters(
local_plugins.report

View file

@ -4,8 +4,7 @@ from __future__ import print_function
import argparse
import json
import platform
import entrypoints
from typing import Dict, List
class DebugAction(argparse.Action):
@ -61,6 +60,6 @@ def plugins_from(option_manager):
]
def dependencies():
def dependencies(): # type: () -> List[Dict[str, str]]
"""Generate the list of dependencies we care about."""
return [{"dependency": "entrypoints", "version": entrypoints.__version__}]
return []

View file

@ -2,10 +2,9 @@
import logging
from typing import Any, Dict, List, Optional, Set
import entrypoints
from flake8 import exceptions
from flake8 import utils
from flake8._compat import importlib_metadata
LOG = logging.getLogger(__name__)
@ -159,7 +158,7 @@ class Plugin(object):
except Exception as load_exception:
LOG.exception(load_exception)
failed_to_load = exceptions.FailedToLoadPlugin(
plugin=self, exception=load_exception
plugin_name=self.name, exception=load_exception
)
LOG.critical(str(failed_to_load))
raise failed_to_load
@ -224,6 +223,7 @@ class PluginManager(object): # pylint: disable=too-few-public-methods
"""Find and manage plugins consistently."""
def __init__(self, namespace, local_plugins=None):
# type: (str, Optional[List[str]]) -> None
"""Initialize the manager.
:param str namespace:
@ -246,12 +246,16 @@ class PluginManager(object): # pylint: disable=too-few-public-methods
for plugin_str in local_plugins:
name, _, entry_str = plugin_str.partition("=")
name, entry_str = name.strip(), entry_str.strip()
entry_point = entrypoints.EntryPoint.from_string(entry_str, name)
entry_point = importlib_metadata.EntryPoint(name, entry_str, None)
self._load_plugin_from_entrypoint(entry_point, local=True)
def _load_entrypoint_plugins(self):
LOG.info('Loading entry-points for "%s".', self.namespace)
for entry_point in entrypoints.get_group_all(self.namespace):
eps = importlib_metadata.entry_points().get(self.namespace, ())
# python2.7 occasionally gives duplicate results due to redundant
# `local/lib` -> `../lib` symlink on linux in virtualenvs so we
# eliminate duplicates here
for entry_point in sorted(frozenset(eps)):
if entry_point.name == "per-file-ignores":
LOG.warning(
"flake8-per-file-ignores plugin is incompatible with "

View file

@ -7,13 +7,13 @@ import enum
import itertools
import linecache
import logging
import sys
from typing import Dict, Generator, List, Match, Optional, Sequence, Set
from typing import Tuple, Union
from flake8 import defaults
from flake8 import statistics
from flake8 import utils
from flake8._compat import lru_cache
from flake8.formatting import base as base_formatter
__all__ = ("StyleGuide",)
@ -21,12 +21,6 @@ __all__ = ("StyleGuide",)
LOG = logging.getLogger(__name__)
if sys.version_info < (3, 2):
from functools32 import lru_cache
else:
from functools import lru_cache
class Selected(enum.Enum):
"""Enum representing an explicitly or implicitly selected code."""

View file

@ -3,6 +3,7 @@ import mock
import pytest
from flake8 import checker
from flake8._compat import importlib_metadata
from flake8.plugins import manager
PHYSICAL_LINE = "# Physical line content"
@ -100,7 +101,11 @@ def mock_file_checker_with_plugin(plugin_target):
entry_point.load.return_value = plugin_target
# Load the checker plugins using the entry point mock
with mock.patch('entrypoints.get_group_all', return_value=[entry_point]):
with mock.patch.object(
importlib_metadata,
'entry_points',
return_value={'flake8.extension': [entry_point]},
):
checks = manager.Checkers()
# Prevent it from reading lines from stdin or somewhere else

View file

@ -1,5 +1,4 @@
"""Tests for our debugging module."""
import entrypoints
import mock
import pytest
@ -9,9 +8,7 @@ from flake8.options import manager
def test_dependencies():
"""Verify that we format our dependencies appropriately."""
expected = [{'dependency': 'entrypoints',
'version': entrypoints.__version__}]
assert expected == debug.dependencies()
assert [] == debug.dependencies()
@pytest.mark.parametrize('plugins, expected', [
@ -46,8 +43,7 @@ def test_information(system, pyversion, pyimpl):
'is_local': False},
{'plugin': 'pycodestyle', 'version': '2.0.0',
'is_local': False}],
'dependencies': [{'dependency': 'entrypoints',
'version': entrypoints.__version__}],
'dependencies': [],
'platform': {
'python_implementation': 'CPython',
'python_version': '3.5.3',

View file

@ -1,10 +1,7 @@
"""Tests for the flake8.exceptions module."""
import pickle
import entrypoints
from flake8 import exceptions
from flake8.plugins import manager as plugins_manager
class _ExceptionTest:
@ -22,10 +19,7 @@ class TestFailedToLoadPlugin(_ExceptionTest):
"""Tests for the FailedToLoadPlugin exception."""
err = exceptions.FailedToLoadPlugin(
plugin=plugins_manager.Plugin(
'plugin_name',
entrypoints.EntryPoint('plugin_name', 'os.path', None),
),
plugin_name='plugin_name',
exception=ValueError('boom!'),
)

View file

@ -1,62 +1,58 @@
"""Tests for flake8.plugins.manager.PluginManager."""
import mock
from flake8._compat import importlib_metadata
from flake8.plugins import manager
def create_entry_point_mock(name):
"""Create a mocked EntryPoint."""
ep = mock.Mock(spec=['name'])
ep.name = name
return ep
@mock.patch('entrypoints.get_group_all')
def test_calls_entrypoints_on_instantiation(get_group_all):
"""Verify that we call get_group_all when we create a manager."""
get_group_all.return_value = []
@mock.patch.object(importlib_metadata, 'entry_points')
def test_calls_entrypoints_on_instantiation(entry_points_mck):
"""Verify that we call entry_points() when we create a manager."""
entry_points_mck.return_value = {}
manager.PluginManager(namespace='testing.entrypoints')
get_group_all.assert_called_once_with('testing.entrypoints')
entry_points_mck.assert_called_once_with()
@mock.patch('entrypoints.get_group_all')
def test_calls_entrypoints_creates_plugins_automaticaly(get_group_all):
@mock.patch.object(importlib_metadata, 'entry_points')
def test_calls_entrypoints_creates_plugins_automaticaly(entry_points_mck):
"""Verify that we create Plugins on instantiation."""
get_group_all.return_value = [
create_entry_point_mock('T100'),
create_entry_point_mock('T200'),
]
entry_points_mck.return_value = {
'testing.entrypoints': [
importlib_metadata.EntryPoint('T100', '', None),
importlib_metadata.EntryPoint('T200', '', None),
],
}
plugin_mgr = manager.PluginManager(namespace='testing.entrypoints')
get_group_all.assert_called_once_with('testing.entrypoints')
entry_points_mck.assert_called_once_with()
assert 'T100' in plugin_mgr.plugins
assert 'T200' in plugin_mgr.plugins
assert isinstance(plugin_mgr.plugins['T100'], manager.Plugin)
assert isinstance(plugin_mgr.plugins['T200'], manager.Plugin)
@mock.patch('entrypoints.get_group_all')
def test_handles_mapping_functions_across_plugins(get_group_all):
@mock.patch.object(importlib_metadata, 'entry_points')
def test_handles_mapping_functions_across_plugins(entry_points_mck):
"""Verify we can use the PluginManager call functions on all plugins."""
entry_point_mocks = [
create_entry_point_mock('T100'),
create_entry_point_mock('T200'),
]
get_group_all.return_value = entry_point_mocks
entry_points_mck.return_value = {
'testing.entrypoints': [
importlib_metadata.EntryPoint('T100', '', None),
importlib_metadata.EntryPoint('T200', '', None),
],
}
plugin_mgr = manager.PluginManager(namespace='testing.entrypoints')
plugins = [plugin_mgr.plugins[name] for name in plugin_mgr.names]
assert list(plugin_mgr.map(lambda x: x)) == plugins
@mock.patch('entrypoints.get_group_all')
def test_local_plugins(get_group_all):
@mock.patch.object(importlib_metadata, 'entry_points')
def test_local_plugins(entry_points_mck):
"""Verify PluginManager can load given local plugins."""
get_group_all.return_value = []
entry_points_mck.return_value = {}
plugin_mgr = manager.PluginManager(
namespace='testing.entrypoints',
local_plugins=['X = path.to:Plugin']
)
assert plugin_mgr.plugins['X'].entry_point.module_name == 'path.to'
assert plugin_mgr.plugins['X'].entry_point.value == 'path.to:Plugin'

View file

@ -13,7 +13,7 @@ def create_plugin_mock(raise_exception=False):
plugin = mock.create_autospec(manager.Plugin, instance=True)
if raise_exception:
plugin.load_plugin.side_effect = exceptions.FailedToLoadPlugin(
plugin=mock.Mock(name='T101'),
plugin_name='T101',
exception=ValueError('Test failure'),
)
return plugin