From 2bf557c465d9baaac076b87c60aa2dd42131a5e2 Mon Sep 17 00:00:00 2001 From: Edoardo Putti Date: Fri, 18 May 2018 14:44:11 +0200 Subject: [PATCH] added external backend querying at runtime this will use the entry_points feature of pkg_resources to query other installed packages for additional backends. - [doc] added page create_your_backend - prevent import errors from crashing the cli during load the module will be imported but if an ImportError is raised the cli crash. This will prevent the cli from crashing but will not expose the backend to the user. An error message will be logger to console - exclude except form from flake qa this form is the only one compatible between python2 and python3 but flake is also worried about the e variable which is not used --- bin/netjsonconfig | 13 +-- docs/source/backends/create_your_backend.rst | 96 ++++++++++++++++++++ docs/source/index.rst | 1 + netjsonconfig/__init__.py | 19 ++++ netjsonconfig/backends/openwrt/schema.py | 1 - 5 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 docs/source/backends/create_your_backend.rst diff --git a/bin/netjsonconfig b/bin/netjsonconfig index 317da7b54..b65a41ea3 100644 --- a/bin/netjsonconfig +++ b/bin/netjsonconfig @@ -1,9 +1,11 @@ #!/usr/bin/env python +import argparse import os import sys + import six -import argparse + import netjsonconfig description = """ @@ -56,7 +58,7 @@ output = parser.add_argument_group('output') output.add_argument('--backend', '-b', required=True, - choices=['openwrt', 'openwisp', 'openvpn'], + choices=netjsonconfig.get_backends().keys(), action='store', type=str, help='Configuration backend') @@ -166,13 +168,8 @@ context = dict(os.environ) method = args.method method_arguments = parse_method_arguments(args.args) -backends = { - 'openwrt': netjsonconfig.OpenWrt, - 'openwisp': netjsonconfig.OpenWisp, - 'openvpn': netjsonconfig.OpenVpn -} -backend_class = backends[args.backend] +backend_class = netjsonconfig.get_backends()[args.backend] try: options = dict(templates=templates, context=context) if args.config: diff --git a/docs/source/backends/create_your_backend.rst b/docs/source/backends/create_your_backend.rst new file mode 100644 index 000000000..a6719889e --- /dev/null +++ b/docs/source/backends/create_your_backend.rst @@ -0,0 +1,96 @@ + +=================== +Create your backend +=================== + +.. include:: ../_github.rst + +Every backend is based on the common ground of some elements provided by the +netjsonconfig library. The `BaseBackend`, `BaseConverter`, `BaseParser` and +`BaseRenderer` are a battle proven set of tools that can be extended when +creating you backend. + +But the netjsonconfig package is not a playground to experiment, your contributions +to a new backend should start elsewhere, a different package, where you are in control +and can make errors and experiment more. + +Netjsonconfig can now discover packages that provides a custom backend using +a feature available in the Python packaging ecosystem which is called `entry_points`. + +To create a new backend start from scratch with a new folder and add this file to your +project root directory. + +.. code-block:: python + + # setup.py + from setuptools import setup, find_packages + + setup( + name='example_backend', + version='0.0.0', + description='an example to illustrate a netjsonconfig backend as an external module', + packages=find_packages(), + entry_points={ + 'netjsonconfig.backends': [ + 'example=example_backend.__init__:ExampleBackend', + ] + } + ) + +this file can be used to create a package that can be installed using pip or other tools +in the python ecosystem. You can find more information about Python packaging +`at packaging.python.org `_ +and `at the hitchhikers guide to packaging `_. + +The most important part is to give your package a good name, a well thought description and +to add the `entry_points` keyword argument with the following code + +.. code-block:: python + + { + # this is used by netjsonconfig + # to find your backend + 'netjsonconfig.backends': [ + ... + ] + } + +Now your package will be in the list of backends that netjsonconfig can use! + +But we still have to give us a name to be unique! Netjsonconfig already +defined the names `openwisp`, `openwrt` and `openvpn` but you can choose +whatever you like most. + +The name `netjsonconfig.backends` will be associated with a list of classes +from your package that will be presented to netjconfig at runtime. To specify +which classes you want to expose write the triple `name`, `path` and `class_name` +using the format `name=path:class_name` as in the example below. + +The `path` part is simply the path to the file that contains the class +you want to expose and the `class_name` is the name of the class. + +.. code-block:: python + + { + 'netjsonconfig.backends': [ + # name=path:class_name + 'example=example_backend.__init__:ExampleBackend', + ] + } + +The previous example can be used with the following class definition + +.. code-block:: python + + # example_backend/__init__.py + from netjsonconfig.backends.base.backend import BaseBackend + from netjsonconfig.backends.base.renderer import BaseRenderer + from netjsonconfig.backends.base.parser import BaseParser + + from netjsonconfig.schema import schema as default_schema + + class ExampleBackend(BaseBackend): + schema = default_schema + converter = [] + parser = BaseParser + renderer = BaseRenderer diff --git a/docs/source/index.rst b/docs/source/index.rst index 24b5b4269..bc40f2371 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -55,6 +55,7 @@ Contents: /backends/openwrt /backends/openwisp /backends/openvpn + /backends/create_your_backend /general/commandline_utility /general/running_tests /general/contributing diff --git a/netjsonconfig/__init__.py b/netjsonconfig/__init__.py index 7e5e3c872..feac19d4f 100644 --- a/netjsonconfig/__init__.py +++ b/netjsonconfig/__init__.py @@ -1,5 +1,24 @@ +from pkg_resources import iter_entry_points +import logging + from .version import VERSION, __version__, get_version # noqa from .backends.openwrt.openwrt import OpenWrt # noqa from .backends.openwisp.openwisp import OpenWisp # noqa from .backends.openvpn.openvpn import OpenVpn # noqa + + +def get_backends(): + default = { + 'openwrt': OpenWrt, + 'openwisp': OpenWisp, + 'openvpn': OpenVpn, + } + logger = logging.getLogger(__name__) + + for entry_point in iter_entry_points('netjsonconfig.backends'): + try: + default.update({entry_point.name.lower(): entry_point.load()}) + except ImportError as e: # noqa + logger.error("Error loading backend {}".format(entry_point.name.lower())) + return default diff --git a/netjsonconfig/backends/openwrt/schema.py b/netjsonconfig/backends/openwrt/schema.py index 084370caa..7aeaadcb9 100644 --- a/netjsonconfig/backends/openwrt/schema.py +++ b/netjsonconfig/backends/openwrt/schema.py @@ -6,7 +6,6 @@ from ..openvpn.schema import base_openvpn_schema from .timezones import timezones - default_radio_driver = "mac80211"