Skip to content

Commit

Permalink
Fixed compatibility with the importlib-metadata library
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Mar 23, 2023
1 parent 42a7f46 commit ddfa802
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 10 deletions.
5 changes: 5 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ Version history

This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.

**UNRELEASED**

- Worked around the presence of the ``importlib_metadata`` module on Python 3.10 and
later causing ``PluginContainer.create_component()`` to fail with ``AttributeError``

**4.11.0** (2022-12-03)

- Added the ``--set`` option for ``asphalt run`` for overriding specific values in
Expand Down
21 changes: 12 additions & 9 deletions src/asphalt/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
from typeguard import check_argument_types

if sys.version_info >= (3, 10):
from importlib.metadata import EntryPoint, entry_points
from importlib.metadata import entry_points
else:
from importlib_metadata import EntryPoint, entry_points
from importlib_metadata import entry_points

T_Object = TypeVar("T_Object")

Expand Down Expand Up @@ -124,13 +124,14 @@ class PluginContainer:
don't point to classes)
"""

__slots__ = "namespace", "base_class", "_entrypoints"
__slots__ = "namespace", "base_class", "_entrypoints", "_resolved"

def __init__(self, namespace: str, base_class: type | None = None) -> None:
self.namespace = namespace
self.base_class = base_class
group = entry_points().select(group=namespace) # type: ignore[attr-defined]
self._entrypoints = {ep.name: ep for ep in group}
self._resolved: dict[str, Any] = {}

@overload
def resolve(self, obj: str) -> Any:
Expand All @@ -157,14 +158,14 @@ def resolve(self, obj: Any) -> Any:
return obj
if ":" in obj:
return resolve_reference(obj)
if obj in self._resolved:
return self._resolved[obj]

value = self._entrypoints.get(obj)
if value is None:
raise LookupError(f"no such entry point in {self.namespace}: {obj}")

if isinstance(value, EntryPoint):
value = self._entrypoints[obj] = value.load()

value = self._resolved[obj] = value.load()
return value

def create_object(self, type: Union[Type, str], **constructor_kwargs) -> Any:
Expand Down Expand Up @@ -204,9 +205,11 @@ def all(self) -> List[Any]:
"""
values = []
for name, value in self._entrypoints.items():
if isinstance(value, EntryPoint):
value = self._entrypoints[name] = value.load()
for name, ep in self._entrypoints.items():
if name in self._resolved:
value = self._resolved[name]
else:
value = self._resolved[name] = ep.load()

values.append(value)

Expand Down
11 changes: 10 additions & 1 deletion tests/test_component.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import asyncio
import sys
from asyncio import AbstractEventLoop
from typing import NoReturn
from unittest.mock import Mock

import pytest

Expand All @@ -15,6 +17,11 @@
)
from asphalt.core.context import Context

if sys.version_info >= (3, 10):
from importlib.metadata import EntryPoint
else:
from importlib_metadata import EntryPoint


class DummyComponent(Component):
def __init__(self, **kwargs):
Expand All @@ -28,7 +35,9 @@ async def start(self, ctx):

@pytest.fixture(autouse=True)
def monkeypatch_plugins(monkeypatch):
monkeypatch.setattr(component_types, "_entrypoints", {"dummy": DummyComponent})
entrypoint = Mock(EntryPoint)
entrypoint.load.configure_mock(return_value=DummyComponent)
monkeypatch.setattr(component_types, "_entrypoints", {"dummy": entrypoint})


class TestContainerComponent:
Expand Down

0 comments on commit ddfa802

Please sign in to comment.