def test_resolve_without_attr(self): ep = EntryPoint( name='ep', value='importlib.metadata', group='grp', ) assert ep.load() is importlib.metadata
def test_sortable(self): """ EntryPoint objects are sortable, but result is undefined. """ sorted([ EntryPoint(name='b', value='val', group='group'), EntryPoint(name='a', value='val', group='group'), ])
def test_sortable(self): """ EntryPoint objects are sortable, but result is undefined. """ sorted([ EntryPoint('b', 'val', 'group'), EntryPoint('a', 'val', 'group'), ])
def test_load_from_entry_point__old_api(): # The following module/class exists but uses an old version of the extensions API # therefore, we should have a meaningful error when trying to load it. entry = f"{test_extensions_pkg}.incompatible_v3_api_fake_extension:FakeExtension" fake = EntryPoint("fake", entry, "pyscaffold.cli") with pytest.raises(ErrorLoadingExtension): extensions.load_from_entry_point(fake)
def test_load_from_entry_point__error(): # This module does not exist, so Python will have some trouble loading it # EntryPoint(name, value, group) fake = EntryPoint("fake", "pyscaffoldext.SOOOOO___fake___:Fake", "pyscaffold.cli") with pytest.raises(ErrorLoadingExtension): extensions.load_from_entry_point(fake)
def dummy_duplicated_entrypoints(): specs = [ ["engine1", "xarray.tests.test_plugins:backend_1", "xarray.backends"], ["engine1", "xarray.tests.test_plugins:backend_2", "xarray.backends"], ["engine2", "xarray.tests.test_plugins:backend_1", "xarray.backends"], ["engine2", "xarray.tests.test_plugins:backend_2", "xarray.backends"], ] eps = [EntryPoint(name, value, group) for name, value, group in specs] return eps
def test_backends_dict_from_pkg() -> None: specs = [ ["engine1", "xarray.tests.test_plugins:backend_1", "xarray.backends"], ["engine2", "xarray.tests.test_plugins:backend_2", "xarray.backends"], ] entrypoints = [EntryPoint(name, value, group) for name, value, group in specs] engines = plugins.backends_dict_from_pkg(entrypoints) assert len(engines) == 2 assert engines.keys() == set(("engine1", "engine2"))
def test_build_engines_sorted() -> None: dummy_pkg_entrypoints = [ EntryPoint("dummy2", "xarray.tests.test_plugins:backend_1", "xarray.backends"), EntryPoint("dummy1", "xarray.tests.test_plugins:backend_1", "xarray.backends"), ] backend_entrypoints = plugins.build_engines(dummy_pkg_entrypoints) backend_entrypoints = list(backend_entrypoints) indices = [] for be in plugins.STANDARD_BACKENDS_ORDER: try: index = backend_entrypoints.index(be) backend_entrypoints.pop(index) indices.append(index) except ValueError: pass assert set(indices) < {0, -1} assert list(backend_entrypoints) == sorted(backend_entrypoints)
def test_build_engines() -> None: dummy_pkg_entrypoint = EntryPoint( "cfgrib", "xarray.tests.test_plugins:backend_1", "xarray_backends" ) backend_entrypoints = plugins.build_engines([dummy_pkg_entrypoint]) assert isinstance(backend_entrypoints["cfgrib"], DummyBackendEntrypoint1) assert backend_entrypoints["cfgrib"].open_dataset_parameters == ( "filename_or_obj", "decoder", )
def test_broken_plugin() -> None: broken_backend = EntryPoint( "broken_backend", "xarray.tests.test_plugins:backend_1", "xarray.backends", ) with pytest.warns(RuntimeWarning) as record: _ = plugins.build_engines([broken_backend]) assert len(record) == 1 message = str(record[0].message) assert "Engine 'broken_backend'" in message
class Discovery(PluginLoader): _ENTRY_POINTS = { "virtualenv.discovery": [ EntryPoint( name=name, value=f"virtualenv.discovery.{dst}", group="virtualenv.discovery" ) for name, dst in [ ("builtin", "builtin:Builtin") ] ] }
def test_error_loading_external_extension(): # Assert the error message displays some meaningful text extension = "pyscaffoldext.fake.extension" # Extension name is given directly ex = str(ErrorLoadingExtension(extension)) assert "an error loading" in ex assert "fake" in ex # Entrypoint is given fake = EntryPoint("fake", f"{extension}:Fake", "pyscaffold.cli") ex = str(ErrorLoadingExtension(entry_point=fake)) assert "an error loading" in ex assert "fake" in ex
def test_wrong_extension(monkeypatch, tmpfolder): # Given an entry point with some problems is registered in the pyscaffold.cli group # (e.g. failing implementation, wrong dependencies that cause the python file to # fail to evaluate) fake = EntryPoint("fake", "pyscaffoldext.SOOO__fake__:Fake", "pyscaffold.cli") entry_points_mock = Mock(return_value={"pyscaffold.cli": [fake]}) monkeypatch.setattr("pyscaffold.extensions.entry_points", entry_points_mock) with pytest.raises(ErrorLoadingExtension, match=r".*error loading.*fake.*"): # When putup is called with the corresponding flag args = ["my-project"] cli.main(args) entry_points_mock.assert_called()
def test_auto_loading_xontribs(xession, shell, mocker): from importlib.metadata import EntryPoint group = "xonsh.xontribs" mocker.patch( "importlib.metadata.entry_points", autospec=True, return_value={ group: [EntryPoint(name="test", group=group, value="test.module")] }, ) xontribs_load = mocker.patch("xonsh.xontribs.xontribs_load") xonsh.main.premain([]) assert xession.builtins.autoloaded_xontribs == {"test": "test.module"} xontribs_load.assert_called()
class SeederSelector(ComponentBuilder): _ENTRY_POINTS = { "virtualenv.seed": [ EntryPoint(name=name, value=f"virtualenv.seed.embed.{dst}", group="virtualenv.seed") for name, dst in [("pip", "pip_invoke:PipInvoke"), ("app-data", "via_app_data.via_app_data:FromAppData")] ] } def __init__(self, interpreter, parser): possible = self.options("virtualenv.seed") super(SeederSelector, self).__init__(interpreter, parser, "seeder", possible) def add_selector_arg_parse(self, name, choices): self.parser.add_argument( "--{}".format(name), choices=choices, default=self._get_default(), required=False, help="seed packages install method", ) self.parser.add_argument( "--no-seed", "--without-pip", help="do not install seed packages", action="store_true", dest="no_seed", ) @staticmethod def _get_default(): return "app-data" def handle_selected_arg_parse(self, options): return super(SeederSelector, self).handle_selected_arg_parse(options) def create(self, options): return self._impl_class(options)
def _load_local_plugins(self, local_plugins: Mapping[str, str]) -> None: """Load local plugins from config. Args: local_plugins: x. """ for name, entry_str in local_plugins.items(): """ I've set mypy to ignore an error on the line below: - Unexpected keyword argument "group" for "EntryPoint" As the EntryPoint definition I see is a NamedTuple with "group" being an attribute (Python 3.8 importlib): class EntryPoint( collections.namedtuple('EntryPointBase', 'name value group')): """ entry_point = EntryPoint( # type: ignore name=name, value=entry_str, group=self.namespace) self._load_plugin_from_entrypoint(entry_point, local=True)
class CreatorSelector(ComponentBuilder): _ENTRY_POINTS = { "virtualenv.create": [ EntryPoint( name=name, value=f"virtualenv.create.via_global_ref.{dst}", group="virtualenv.create" ) for name, dst in [ ("venv", "venv:Venv"), ("cpython3-posix", "builtin.cpython.cpython3:CPython3Posix"), ("cpython3-win", "builtin.cpython.cpython3:CPython3Windows"), ("cpython2-posix", "builtin.cpython.cpython2:CPython2Posix"), ("cpython2-mac-framework", "builtin.cpython.mac_os:CPython2macOsFramework"), ("cpython3-mac-framework", "builtin.cpython.mac_os:CPython3macOsFramework"), ("cpython2-win", "builtin.cpython.cpython2:CPython2Windows"), ("pypy2-posix", "builtin.pypy.pypy2:PyPy2Posix"), ("pypy2-win", "builtin.pypy.pypy2:Pypy2Windows"), ("pypy3-posix", "builtin.pypy.pypy3:PyPy3Posix"), ("pypy3-win", "builtin.pypy.pypy3:Pypy3Windows") ] ] } def __init__(self, interpreter, parser): creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter) super(CreatorSelector, self).__init__(interpreter, parser, "creator", creators) @classmethod def for_interpreter(cls, interpreter): key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None errors = defaultdict(list) for key, creator_class in cls.options("virtualenv.create").items(): if key == "builtin": raise RuntimeError("builtin creator is a reserved name") meta = creator_class.can_create(interpreter) if meta: if meta.error: errors[meta.error].append(creator_class) else: if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin): builtin_key = key key_to_class["builtin"] = creator_class key_to_meta["builtin"] = meta key_to_class[key] = creator_class key_to_meta[key] = meta if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter): describe = creator_class if not key_to_meta: if errors: rows = ["{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errors.items()] raise RuntimeError("\n".join(rows)) else: raise RuntimeError("No virtualenv implementation for {}".format(interpreter)) return CreatorInfo( key_to_class=key_to_class, key_to_meta=key_to_meta, describe=describe, builtin_key=builtin_key, ) def add_selector_arg_parse(self, name, choices): # prefer the built-in venv if present, otherwise fallback to first defined type choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1) default_value = self._get_default(choices) self.parser.add_argument( "--{}".format(name), choices=choices, default=default_value, required=False, help="create environment via{}".format( "" if self.builtin_key is None else " (builtin = {})".format(self.builtin_key), ), ) @staticmethod def _get_default(choices): return next(iter(choices)) def populate_selected_argparse(self, selected, app_data): self.parser.description = "options for {} {}".format(self.name, selected) self._impl_class.add_parser_arguments(self.parser, self.interpreter, self.key_to_meta[selected], app_data) def create(self, options): options.meta = self.key_to_meta[getattr(options, self.name)] if not issubclass(self._impl_class, Describe): options.describe = self.describe(options, self.interpreter) return super(CreatorSelector, self).create(options)
def test_positional_args(self): """ Capture legacy (namedtuple) construction, discouraged. """ EntryPoint('name', 'value', 'group')
def load_from_entry_point(entry_point: EntryPoint) -> Extension: """Carefully load the extension, raising a meaningful message in case of errors""" try: return entry_point.load()(entry_point.name) except Exception as ex: raise ErrorLoadingExtension(entry_point=entry_point) from ex
class ActivationSelector(ComponentBuilder): _ENTRY_POINTS = { "virtualenv.activate": [ EntryPoint(name=name, value=f"virtualenv.activation.{dst}", group="virtualenv.activate") for name, dst in [("bash", "bash:BashActivator"), ("cshell", "cshell:CShellActivator"), ("batch", "batch:BatchActivator"), ( "fish", "fish:FishActivator"), ("nushell", "nushell:NushellActivator"), ("powershell", "powershell:PowerShellActivator" ), ("python", "python:PythonActivator")] ] } def __init__(self, interpreter, parser): self.default = None possible = OrderedDict( (k, v) for k, v in self.options("virtualenv.activate").items() if v.supports(interpreter)) super(ActivationSelector, self).__init__(interpreter, parser, "activators", possible) self.parser.description = "options for activation scripts" self.active = None def add_selector_arg_parse(self, name, choices): self.default = ",".join(choices) self.parser.add_argument( "--{}".format(name), default=self.default, metavar="comma_sep_list", required=False, help="activators to generate - default is all supported", type=self._extract_activators, ) def _extract_activators(self, entered_str): elements = [e.strip() for e in entered_str.split(",") if e.strip()] missing = [e for e in elements if e not in self.possible] if missing: raise ArgumentTypeError( "the following activators are not available {}".format( ",".join(missing))) return elements def handle_selected_arg_parse(self, options): selected_activators = (self._extract_activators(self.default) if options.activators is self.default else options.activators) self.active = { k: v for k, v in self.possible.items() if k in selected_activators } self.parser.add_argument( "--prompt", dest="prompt", metavar="prompt", help=("provides an alternative prompt prefix for this environment " "(value of . means name of the current working directory)"), default=None, ) for activator in self.active.values(): activator.add_parser_arguments(self.parser, self.interpreter) def create(self, options): return [ activator_class(options) for activator_class in self.active.values() ]