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 01:32:57 +00:00
commit 65ed8df311
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 # 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: # And in which releases we will update those ranges here:
# http://flake8.pycqa.org/en/latest/internal/releases.html#releasing-flake8 # 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 pyflakes >= 2.1.0, < 2.2.0
pycodestyle >= 2.5.0, < 2.6.0 pycodestyle >= 2.5.0, < 2.6.0
mccabe >= 0.6.0, < 0.7.0 mccabe >= 0.6.0, < 0.7.0
@ -49,6 +48,7 @@ install_requires=
typing; python_version<"3.5" typing; python_version<"3.5"
configparser; python_version<"3.2" configparser; python_version<"3.2"
functools32; 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.* 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.""" """Exception classes for all of Flake8."""
from typing import Dict from typing import Dict
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from flake8.plugins.manager import Plugin
class Flake8Exception(Exception): class Flake8Exception(Exception):
"""Plain Flake8 exception.""" """Plain Flake8 exception."""
@ -23,18 +19,17 @@ class FailedToLoadPlugin(Flake8Exception):
FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.' FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.'
def __init__(self, plugin, exception): def __init__(self, plugin_name, exception):
# type: (Plugin, Exception) -> None # type: (str, Exception) -> None
"""Initialize our FailedToLoadPlugin exception.""" """Initialize our FailedToLoadPlugin exception."""
self.plugin = plugin self.plugin_name = plugin_name
self.ep_name = self.plugin.name
self.original_exception = exception self.original_exception = exception
super(FailedToLoadPlugin, self).__init__(plugin, exception) super(FailedToLoadPlugin, self).__init__(plugin_name, exception)
def __str__(self): # type: () -> str def __str__(self): # type: () -> str
"""Format our exception message.""" """Format our exception message."""
return self.FORMAT % { return self.FORMAT % {
"name": self.ep_name, "name": self.plugin_name,
"exc": self.original_exception, "exc": self.original_exception,
} }

View file

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

View file

@ -4,8 +4,7 @@ from __future__ import print_function
import argparse import argparse
import json import json
import platform import platform
from typing import Dict, List
import entrypoints
class DebugAction(argparse.Action): 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.""" """Generate the list of dependencies we care about."""
return [{"dependency": "entrypoints", "version": entrypoints.__version__}] return []

View file

@ -2,10 +2,9 @@
import logging import logging
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set
import entrypoints
from flake8 import exceptions from flake8 import exceptions
from flake8 import utils from flake8 import utils
from flake8._compat import importlib_metadata
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -159,7 +158,7 @@ class Plugin(object):
except Exception as load_exception: except Exception as load_exception:
LOG.exception(load_exception) LOG.exception(load_exception)
failed_to_load = exceptions.FailedToLoadPlugin( failed_to_load = exceptions.FailedToLoadPlugin(
plugin=self, exception=load_exception plugin_name=self.name, exception=load_exception
) )
LOG.critical(str(failed_to_load)) LOG.critical(str(failed_to_load))
raise 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.""" """Find and manage plugins consistently."""
def __init__(self, namespace, local_plugins=None): def __init__(self, namespace, local_plugins=None):
# type: (str, Optional[List[str]]) -> None
"""Initialize the manager. """Initialize the manager.
:param str namespace: :param str namespace:
@ -246,12 +246,16 @@ class PluginManager(object): # pylint: disable=too-few-public-methods
for plugin_str in local_plugins: for plugin_str in local_plugins:
name, _, entry_str = plugin_str.partition("=") name, _, entry_str = plugin_str.partition("=")
name, entry_str = name.strip(), entry_str.strip() 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) self._load_plugin_from_entrypoint(entry_point, local=True)
def _load_entrypoint_plugins(self): def _load_entrypoint_plugins(self):
LOG.info('Loading entry-points for "%s".', self.namespace) 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": if entry_point.name == "per-file-ignores":
LOG.warning( LOG.warning(
"flake8-per-file-ignores plugin is incompatible with " "flake8-per-file-ignores plugin is incompatible with "

View file

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

View file

@ -3,6 +3,7 @@ import mock
import pytest import pytest
from flake8 import checker from flake8 import checker
from flake8._compat import importlib_metadata
from flake8.plugins import manager from flake8.plugins import manager
PHYSICAL_LINE = "# Physical line content" PHYSICAL_LINE = "# Physical line content"
@ -100,7 +101,11 @@ def mock_file_checker_with_plugin(plugin_target):
entry_point.load.return_value = plugin_target entry_point.load.return_value = plugin_target
# Load the checker plugins using the entry point mock # 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() checks = manager.Checkers()
# Prevent it from reading lines from stdin or somewhere else # Prevent it from reading lines from stdin or somewhere else

View file

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

View file

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

View file

@ -1,62 +1,58 @@
"""Tests for flake8.plugins.manager.PluginManager.""" """Tests for flake8.plugins.manager.PluginManager."""
import mock import mock
from flake8._compat import importlib_metadata
from flake8.plugins import manager from flake8.plugins import manager
def create_entry_point_mock(name): @mock.patch.object(importlib_metadata, 'entry_points')
"""Create a mocked EntryPoint.""" def test_calls_entrypoints_on_instantiation(entry_points_mck):
ep = mock.Mock(spec=['name']) """Verify that we call entry_points() when we create a manager."""
ep.name = name entry_points_mck.return_value = {}
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 = []
manager.PluginManager(namespace='testing.entrypoints') manager.PluginManager(namespace='testing.entrypoints')
entry_points_mck.assert_called_once_with()
get_group_all.assert_called_once_with('testing.entrypoints')
@mock.patch('entrypoints.get_group_all') @mock.patch.object(importlib_metadata, 'entry_points')
def test_calls_entrypoints_creates_plugins_automaticaly(get_group_all): def test_calls_entrypoints_creates_plugins_automaticaly(entry_points_mck):
"""Verify that we create Plugins on instantiation.""" """Verify that we create Plugins on instantiation."""
get_group_all.return_value = [ entry_points_mck.return_value = {
create_entry_point_mock('T100'), 'testing.entrypoints': [
create_entry_point_mock('T200'), importlib_metadata.EntryPoint('T100', '', None),
] importlib_metadata.EntryPoint('T200', '', None),
],
}
plugin_mgr = manager.PluginManager(namespace='testing.entrypoints') 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 'T100' in plugin_mgr.plugins
assert 'T200' in plugin_mgr.plugins assert 'T200' in plugin_mgr.plugins
assert isinstance(plugin_mgr.plugins['T100'], manager.Plugin) assert isinstance(plugin_mgr.plugins['T100'], manager.Plugin)
assert isinstance(plugin_mgr.plugins['T200'], manager.Plugin) assert isinstance(plugin_mgr.plugins['T200'], manager.Plugin)
@mock.patch('entrypoints.get_group_all') @mock.patch.object(importlib_metadata, 'entry_points')
def test_handles_mapping_functions_across_plugins(get_group_all): def test_handles_mapping_functions_across_plugins(entry_points_mck):
"""Verify we can use the PluginManager call functions on all plugins.""" """Verify we can use the PluginManager call functions on all plugins."""
entry_point_mocks = [ entry_points_mck.return_value = {
create_entry_point_mock('T100'), 'testing.entrypoints': [
create_entry_point_mock('T200'), importlib_metadata.EntryPoint('T100', '', None),
] importlib_metadata.EntryPoint('T200', '', None),
get_group_all.return_value = entry_point_mocks ],
}
plugin_mgr = manager.PluginManager(namespace='testing.entrypoints') plugin_mgr = manager.PluginManager(namespace='testing.entrypoints')
plugins = [plugin_mgr.plugins[name] for name in plugin_mgr.names] plugins = [plugin_mgr.plugins[name] for name in plugin_mgr.names]
assert list(plugin_mgr.map(lambda x: x)) == plugins assert list(plugin_mgr.map(lambda x: x)) == plugins
@mock.patch('entrypoints.get_group_all') @mock.patch.object(importlib_metadata, 'entry_points')
def test_local_plugins(get_group_all): def test_local_plugins(entry_points_mck):
"""Verify PluginManager can load given local plugins.""" """Verify PluginManager can load given local plugins."""
get_group_all.return_value = [] entry_points_mck.return_value = {}
plugin_mgr = manager.PluginManager( plugin_mgr = manager.PluginManager(
namespace='testing.entrypoints', namespace='testing.entrypoints',
local_plugins=['X = path.to:Plugin'] 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) plugin = mock.create_autospec(manager.Plugin, instance=True)
if raise_exception: if raise_exception:
plugin.load_plugin.side_effect = exceptions.FailedToLoadPlugin( plugin.load_plugin.side_effect = exceptions.FailedToLoadPlugin(
plugin=mock.Mock(name='T101'), plugin_name='T101',
exception=ValueError('Test failure'), exception=ValueError('Test failure'),
) )
return plugin return plugin