diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f0c5d56..f040ebe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -53,7 +53,7 @@ jobs: architecture: 'x64' - script: | - pip install -U setuptools --user + pip install -U -r requirements.txt python setup.py sdist displayName: 'Build sdist' @@ -67,7 +67,5 @@ jobs: pip install dist/$SDIST displayName: 'Install from sdist' - - script: | - pip install -U pytest - python -m pytest test_catalogue.py + - script: python -m pytest test_catalogue.py displayName: 'Run tests' diff --git a/catalogue.py b/catalogue.py index 783ff2e..0d78b35 100644 --- a/catalogue.py +++ b/catalogue.py @@ -4,17 +4,24 @@ from collections import OrderedDict import sys +try: # Python 3.8 + import importlib.metadata as importlib_metadata +except ImportError: + import importlib_metadata + if sys.version_info[0] == 2: basestring_ = basestring # noqa: F821 else: basestring_ = str +# Ony ever call this once for performance reasons +AVAILABLE_ENTRY_POINTS = importlib_metadata.entry_points() # This is where functions will be registered REGISTRY = OrderedDict() -def create(*namespace): +def create(*namespace, entry_points=False): """Create a new registry. *namespace (str): The namespace, e.g. "spacy" or "spacy", "architectures". @@ -23,17 +30,22 @@ def create(*namespace): """ if check_exists(*namespace): raise RegistryError("Namespace already exists: {}".format(namespace)) - return Registry(namespace) + return Registry(namespace, entry_points=entry_points) class Registry(object): - def __init__(self, namespace): + def __init__(self, namespace, entry_points=False): """Initialize a new registry. namespace (Tuple[str]): The namespace. + entry_points (bool): Whether to also check for entry points. RETURNS (Registry): The newly created object. """ self.namespace = namespace + self.entry_point_namespace = "_".join(namespace) + if entry_points: + for name, value in self.get_entry_points().items(): + self.register(name, func=value) def register(self, name, **kwargs): """Register a function for a given namespace. @@ -79,6 +91,28 @@ def get_all(self): result[keys[-1]] = value return result + def get_entry_points(self): + """Get registered entry points from other packages for this namespace. + + RETURNS (dict): Entry points, keyed by name. + """ + result = {} + for entry_point in AVAILABLE_ENTRY_POINTS.get(self.entry_point_namespace, []): + result[entry_point.name] = entry_point.load() + return result + + def get_entry_point(self, name, default=None): + """Check if registered entry point is available for a given name in the + namespace and load it. Otherwise, return None. + + name (unicode): Name of entry point to load. + RETURNS: The loaded entry point or None. + """ + for entry_point in AVAILABLE_ENTRY_POINTS.get(self.entry_point_namespace, []): + if entry_point.name == name: + return entry_point.load() + return default + def check_exists(*namespace): """Check if a namespace exists. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..568e84e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +importlib_metadata>=0.20; python_version < "3.8" +setuptools +pytest>=4.6.5 diff --git a/setup.cfg b/setup.cfg index 1150a19..a82b8a0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,8 @@ py_modules = catalogue zip_safe = true include_package_data = true python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* +install_requires = + importlib_metadata>=0.20; python_version < "3.8" [bdist_wheel] universal = true diff --git a/test_catalogue.py b/test_catalogue.py index ee0f5d2..c327f8a 100644 --- a/test_catalogue.py +++ b/test_catalogue.py @@ -87,3 +87,19 @@ def z(): assert items["z"] == z assert catalogue.check_exists("x", "y", "z") assert catalogue._get(("x", "y", "z")) == z + + +def test_entry_points(): + # Create a new EntryPoint object by pretending we have a config.cfg and + # use one of catalogue's util functions as the advertised function + ep_string = "[options.entry_points]test_foo\n bar = catalogue:check_exists" + ep = catalogue.importlib_metadata.EntryPoint._from_text(ep_string) + catalogue.AVAILABLE_ENTRY_POINTS["test_foo"] = ep + assert catalogue.REGISTRY == {} + test_registry = catalogue.create("test", "foo", entry_points=True) + entry_points = test_registry.get_entry_points() + assert "bar" in entry_points + assert entry_points["bar"] == catalogue.check_exists + assert test_registry.get_entry_point("bar") == catalogue.check_exists + assert ("test", "foo", "bar") in catalogue.REGISTRY + assert catalogue._get(("test", "foo", "bar")) == catalogue.check_exists