From ef062e4d8c60b5d4280ea95d0ddd999923996436 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 11 Dec 2018 12:21:30 -0500 Subject: [PATCH 1/2] Add plugin interface for external provider registration This commit adds a simple and standardized interface for external plugins to register providers and have them work natively off of the qiskit namespace. With this anyone can setup providers to be discoverable and automatically loaded into qiskit-terra if they're installed. It leverages setuptools entry points to define an entry point to basically advertise to terra that a python package provides a backend provider. Terra now leverages the stevedore library (which just provides a useful API ontop of entrypoints and plugins) to discover the providers installed. --- doc/providers.rst | 44 +++++++++++++++++++++++++++++++++ qiskit/backends/providers.py | 47 ++++++++++++++++++++++++++++++++++++ qiskit/providers/__init__.py | 5 ++++ requirements.txt | 1 + setup.py | 1 + 5 files changed, 98 insertions(+) create mode 100644 doc/providers.rst create mode 100644 qiskit/backends/providers.py diff --git a/doc/providers.rst b/doc/providers.rst new file mode 100644 index 000000000000..648bb348f6ae --- /dev/null +++ b/doc/providers.rst @@ -0,0 +1,44 @@ +=========================== +Creating External Providers +=========================== + +Terra provides a plugin interface to define external backend providers + + +Creating a Plugin +----------------- + +Creating a plugin is fairly straightforward and doesn't require much additional +effort on top of creating a provider. All external provider classes must be +defined off the abstract class defined at ``qiskit.backends.baseprovider``. + +Entry Point +----------- + +Once you've created your plugin class you need to add a `setuptools`_ entry +point to your provider to enable Qiskit-Terra to find the plugin. The entry +point must be added to the ``qiskit.providers`` namespace. + +.. _setuptools: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins + +For example, in your provider package's setup.py you would add something like:: + + entry_points={'qiskit.providers': + ['MyProvider = my_provider.provider:MyProvider'] + } + +where ``my_provider.provider`` is the import path and ``MyProvider`` is the +provider class. Once this is added to your setup.py then whenever the package is +installed Qiskit-terra will detect the provider is installed and a +``MyProvider`` provider instance to the qiskit namespace. + + +Using Provider Plugins +---------------------- + +When installing provider plugins the name of the entry point becomes the name +of providers off the global instance of ``qiskit.providers`` namespace. So +continuing from the above example after installing the ``my_provider`` package +you can access the backends provided by ``MyProvider`` by using +``qiskit.providers.MyProvider``. Any other provider plugins installed will also +be accessible off of ``qiskit.providers``. diff --git a/qiskit/backends/providers.py b/qiskit/backends/providers.py new file mode 100644 index 000000000000..0852eb027a35 --- /dev/null +++ b/qiskit/backends/providers.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Provider plugin manager.""" + +import logging + +import stevedore + +logger = logging.getLogger(__name__) + + +class ProviderPluginManager: + """Qiskit provider plugin manager class + + This class is used to manage the lifecycle of external backend provider + plugins. It provides functions for getting all backends + """ + def __init__(self): + self.ext_plugins = stevedore.ExtensionManager( + 'qiskit.providers', invoke_on_load=True, + propagate_map_exceptions=True, + on_load_failure_callback=self.failure_hook) + + @staticmethod + def failure_hook(_, err_plugin, err): + """Log errors on import and don't fail.""" + logger.error("Could not load provider plugin %r with error: %s", + err_plugin.name, err) + + def get_providers(self): + """Return dict of all discovered provider plugins.""" + providers = {} + for plug in self.ext_plugins: + providers[plug.name] = plug.obj + return providers + + def get_all_backends(self): + """Return dict of lists of backends for all discovered provider plugins.""" + backends_dict = {} + for plug in self.ext_plugins: + backends_dict[plug.name] = plug.obj.backends() + return backends_dict diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index 261f03512828..c36b99dd5cf7 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -9,6 +9,7 @@ import pkgutil +from qiskit.backends.providers import ProviderPluginManager from .basebackend import BaseBackend from .baseprovider import BaseProvider from .basejob import BaseJob @@ -16,5 +17,9 @@ from .jobstatus import JobStatus +ALL_PROVIDERS = ProviderPluginManager().get_providers() +for prov in ALL_PROVIDERS: + globals()[prov] = ALL_PROVIDERS[prov] + # Allow extending this namespace. __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/requirements.txt b/requirements.txt index 93c10418c962..c6c38a258d7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ requests>=2.19 requests-ntlm>=1.1.0 scipy>=0.19,!=0.19.1 sympy>=1.3 +stevedore>=1.30.0 diff --git a/setup.py b/setup.py index 8740ffd3e2a0..5fe1a89c6761 100755 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ "requests-ntlm>=1.1.0", "scipy>=0.19,!=0.19.1", "sympy>=1.3" + "stevedore>=1.30.0" ] From be36049c6da3fd97581587e8696f32dc5b00cc91 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 2 Apr 2019 11:44:41 -0400 Subject: [PATCH 2/2] Remove docs file --- doc/providers.rst | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 doc/providers.rst diff --git a/doc/providers.rst b/doc/providers.rst deleted file mode 100644 index 648bb348f6ae..000000000000 --- a/doc/providers.rst +++ /dev/null @@ -1,44 +0,0 @@ -=========================== -Creating External Providers -=========================== - -Terra provides a plugin interface to define external backend providers - - -Creating a Plugin ------------------ - -Creating a plugin is fairly straightforward and doesn't require much additional -effort on top of creating a provider. All external provider classes must be -defined off the abstract class defined at ``qiskit.backends.baseprovider``. - -Entry Point ------------ - -Once you've created your plugin class you need to add a `setuptools`_ entry -point to your provider to enable Qiskit-Terra to find the plugin. The entry -point must be added to the ``qiskit.providers`` namespace. - -.. _setuptools: https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins - -For example, in your provider package's setup.py you would add something like:: - - entry_points={'qiskit.providers': - ['MyProvider = my_provider.provider:MyProvider'] - } - -where ``my_provider.provider`` is the import path and ``MyProvider`` is the -provider class. Once this is added to your setup.py then whenever the package is -installed Qiskit-terra will detect the provider is installed and a -``MyProvider`` provider instance to the qiskit namespace. - - -Using Provider Plugins ----------------------- - -When installing provider plugins the name of the entry point becomes the name -of providers off the global instance of ``qiskit.providers`` namespace. So -continuing from the above example after installing the ``my_provider`` package -you can access the backends provided by ``MyProvider`` by using -``qiskit.providers.MyProvider``. Any other provider plugins installed will also -be accessible off of ``qiskit.providers``.