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"