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..a57d5e06a 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:
+ 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"