diff --git a/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch b/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch
index a23508d2b2f71fe4a975a5d1fd9ce288f62efc49..1a3fc94e6e08b7f48ca6f2038833adeeb81a6358 100644
--- a/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch
+++ b/patches/openwrt/hack/0001-ubus-Hackilly-add-Stepans-python-bindings.patch
@@ -70,6 +70,2454 @@ index 523c362..d39b918 100644
  $(eval $(call BuildPackage,ubus))
  $(eval $(call BuildPackage,ubusd))
 +$(eval $(call BuildPackage,python-ubus))
+diff --git a/package/system/ubus/patches/001-python.patch b/package/system/ubus/patches/001-python.patch
+new file mode 100644
+index 0000000..7338b2f
+--- /dev/null
++++ b/package/system/ubus/patches/001-python.patch
+@@ -0,0 +1,2442 @@
++diff --git a/CMakeLists.txt b/CMakeLists.txt
++index 471b38e..cc6376c 100644
++--- a/CMakeLists.txt
+++++ b/CMakeLists.txt
++@@ -4,6 +4,7 @@ PROJECT(ubus C)
++ ADD_DEFINITIONS(-Os -Wall --std=gnu99 -g3 -Wmissing-declarations)
++ 
++ OPTION(BUILD_LUA "build Lua plugin" ON)
+++OPTION(BUILD_PYTHON "build python plugin" OFF)
++ OPTION(BUILD_EXAMPLES "build examples" ON)
++ 
++ SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
++@@ -37,6 +38,9 @@ SET_TARGET_PROPERTIES(cli PROPERTIES OUTPUT_NAME ubus)
++ TARGET_LINK_LIBRARIES(cli ubus ${ubox_library} ${blob_library} ${json})
++ 
++ ADD_SUBDIRECTORY(lua)
+++if (BUILD_PYTHON)
+++	    ADD_SUBDIRECTORY(python)
+++endif ()
++ ADD_SUBDIRECTORY(examples)
++ 
++ INSTALL(TARGETS ubus cli
++diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
++new file mode 100644
++index 0000000..2e66f2a
++--- /dev/null
+++++ b/python/CMakeLists.txt
++@@ -0,0 +1,58 @@
+++cmake_minimum_required(VERSION 3.0)
+++
+++SET(CURRENT_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+++SET(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
+++SET(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
+++
+++IF(NOT PYTHON_CFLAGS)
+++	FIND_PROGRAM(PKG_CONFIG pkg-config)
+++	IF(PKG_CONFIG)
+++		EXECUTE_PROCESS(
+++			COMMAND pkg-config --silence-errors --cflags python2
+++			OUTPUT_VARIABLE PYTHON_CFLAGS
+++			OUTPUT_STRIP_TRAILING_WHITESPACE
+++		)
+++	ENDIF()
+++ENDIF()
+++
+++IF(NOT PYTHON)
+++	FIND_PROGRAM(PYTHON python)
+++ENDIF()
+++
+++SET(PYTHON_CFLAGS "-Os -Wall --std=gnu99 -g3 -I.. ${PYTHON_CFLAGS}")
+++INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..)
+++LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..)
+++
+++IF(APPLE)
+++	SET(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup")
+++ENDIF(APPLE)
+++
+++IF(NOT PYTHONPATH)
+++	EXECUTE_PROCESS(
+++		COMMAND ${PYTHON} -c "import os; print os.path.dirname(os.__file__)"
+++		OUTPUT_VARIABLE PYTHONPATH
+++		RESULT_VARIABLE PYTHON_CHECK_RES
+++		OUTPUT_STRIP_TRAILING_WHITESPACE
+++	)
+++
+++	IF(NOT ${PYTHON_CHECK_RES} EQUAL 0 OR "${PYTHONPATH}" EQUAL "")
+++		MESSAGE(SEND_ERROR "Python was not found on your system")
+++	ENDIF()
+++ENDIF()
+++
+++CONFIGURE_FILE(${SETUP_PY_IN} ${SETUP_PY})
+++
+++
+++SET(PYTHON_CFLAGS "${PYTHON_CFLAGS} -DUBUS_UNIX_SOCKET=\\\\\\\"${UBUS_UNIX_SOCKET}\\\\\\\"")
+++SET_PROPERTY(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "build")
+++
+++SET(LDSHARED "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1} -shared")
+++
+++ADD_CUSTOM_TARGET(python ALL
+++	COMMAND ${CMAKE_COMMAND} -E env "CC=${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" "LDSHARED=${LDSHARED}" "CFLAGS=${PYTHON_CFLAGS}" ${PYTHON} ${SETUP_PY} build
+++	DEPENDS ubus "${CURRENT_SOURCE_DIR}/ubus_python.c"
+++)
+++
+++INSTALL(
+++	CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E env \"CC=${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}\" \"LDSHARED=${LDSHARED}\" \"CFLAGS=${PYTHON_CFLAGS}\" ${PYTHON} ${SETUP_PY} install --prefix=${CMAKE_INSTALL_PREFIX})"
+++)
++diff --git a/python/README.rst b/python/README.rst
++new file mode 100644
++index 0000000..8fcd7f4
++--- /dev/null
+++++ b/python/README.rst
++@@ -0,0 +1,97 @@
+++Python bindings for ubus
+++========================
+++Code in this directory enables a subset of libubus functions to be used directly from python.
+++
+++Examples
+++########
+++
+++connect and disconnect
+++----------------------
+++To connect you need to::
+++
+++    import ubus
+++    ubus.connect("/var/run/ubus.sock")
+++
+++To disconnect you can simply::
+++
+++    ubus.disconnect()
+++
+++Note that calling connect()/disconnect() on opened/closed connection will throw an exception.
+++
+++add
+++---
+++To add an object to ubus you can (you need to become root first)::
+++
+++    def callback(handler, data):
+++        handler.reply(data)  # this should return exactly the same data to the caller
+++
+++    ubus.add(
+++        "my_object", {
+++            "my_method": {"method": callback, "signature": {
+++                    "first": ubus.BLOBMSG_TYPE_STRING,
+++                    "second": ubus.BLOBMSG_TYPE_BOOL,
+++                    "third": ubus.BLOBMSG_TYPE_INT32,
+++            }},
+++         },
+++    )
+++
+++You need to enter the loop to serve the object methods afterwards::
+++
+++    ubus.loop()
+++
+++
+++Note that it might not be a good idea to call the callback function recursively.
+++
+++
+++objects
+++-------
+++To list the objects which are currently connected to ubus you can call::
+++
+++    ubus.objects()
+++
+++    ->
+++
+++    {u'my_object': {u'my_method': {u'first': 3, u'second': 7, u'third': 5}}}
+++
+++
+++
+++call
+++----
+++To call an actual method on an object you can use::
+++
+++    ubus.call("my_object", "my_method", {"first": "my_string", "second": True, "third": 42})
+++
+++    ->
+++
+++    [{"first": "my_string", "second": True, "third": 42}]
+++
+++
+++listen
+++------
+++To listen for an event you can::
+++
+++    def callback(event, data):
+++        print(event, data)  # just print event name and data to stdout
+++
+++    ubus.listen(("my_event", callback))
+++
+++And you need to enter the loop to start to listen::
+++
+++    ubus.loop()
+++
+++Note that it might not be a good idea to call the callback function recursively.
+++
+++send
+++----
+++This will send an event to ubus::
+++
+++    ubus.send("my_event", {"some": "data"})
+++
+++
+++Notes
+++#####
+++
+++There are some tests present ('tests/' directory). So feel free to check it for some more complex examples.
+++To run the tests you need to have ubus installed and become root::
+++
+++    sudo python setup.py test
++diff --git a/python/setup.cfg b/python/setup.cfg
++new file mode 100644
++index 0000000..d1844e1
++--- /dev/null
+++++ b/python/setup.cfg
++@@ -0,0 +1,6 @@
+++[aliases]
+++test=pytest
+++
+++[tool:pytest]
+++addopts = --verbose -s
+++python_files = tests/*.py
++diff --git a/python/setup.py.in b/python/setup.py.in
++new file mode 100644
++index 0000000..b030d87
++--- /dev/null
+++++ b/python/setup.py.in
++@@ -0,0 +1,18 @@
+++from setuptools import setup, Extension
+++
+++extension = Extension(
+++    'ubus',
+++    ['@CURRENT_SOURCE_DIR@/ubus_python.c'],
+++    libraries=['ubus', 'blobmsg_json', 'ubox'],
+++    include_dirs=['@PROJECT_SOURCE_DIR@'],
+++    library_dirs=['@PROJECT_BINARY_DIR@'],
+++)
+++
+++setup(
+++    name='ubus',
+++    version='@PYTHON_PACKAGE_VERSION@',
+++    description="Python bindings for libubus",
+++    ext_modules=[extension],
+++    provides=['ubus'],
+++    tests_require=['pytest'],
+++)
++diff --git a/python/tests/__init__.py b/python/tests/__init__.py
++new file mode 100644
++index 0000000..e69de29
++diff --git a/python/tests/fixtures.py b/python/tests/fixtures.py
++new file mode 100644
++index 0000000..e9d28b4
++--- /dev/null
+++++ b/python/tests/fixtures.py
++@@ -0,0 +1,226 @@
+++import os
+++from multiprocessing import Process, Value
+++import pytest
+++import subprocess
+++import time
+++
+++
+++UBUSD_TEST_SOCKET_PATH = "/tmp/ubus-test-socket"
+++
+++
+++class Guard(object):
+++
+++    def __init__(self):
+++        self.counter = Value('i', 0)
+++
+++    def __enter__(self):
+++        return self
+++
+++    def __exit__(self, *args):
+++        return True
+++
+++    def touch(self):
+++        self.counter.value += 1
+++
+++    def wait(self):
+++        while not self.counter.value > 0:
+++            time.sleep(0.05)
+++
+++
+++@pytest.fixture(scope="session")
+++def ubusd_test():
+++    ubusd_instance = subprocess.Popen(["ubusd", "-s", UBUSD_TEST_SOCKET_PATH])
+++    while not os.path.exists(UBUSD_TEST_SOCKET_PATH):
+++        time.sleep(0.2)
+++    yield ubusd_instance
+++    ubusd_instance.kill()
+++    os.unlink(UBUSD_TEST_SOCKET_PATH)
+++
+++
+++@pytest.fixture(scope="function")
+++def event_sender():
+++
+++    with Guard() as guard:
+++
+++        def process_function():
+++            import ubus
+++            ubus.connect(UBUSD_TEST_SOCKET_PATH)
+++            while True:
+++                ubus.send("event_sender", dict(a="b", c=3, d=False))
+++                guard.touch()
+++                time.sleep(0.1)
+++
+++        p = Process(target=process_function, name="event_sender")
+++        p.start()
+++        guard.wait()
+++        setattr(p, 'counter', guard.counter)
+++
+++        yield p
+++
+++        p.terminate()
+++        p.join()
+++
+++
+++@pytest.fixture(scope="function")
+++def registered_objects():
+++    with Guard() as guard:
+++
+++        def process_function():
+++
+++            def handler1(*arg):
+++                pass
+++
+++            import ubus
+++            ubus.connect(UBUSD_TEST_SOCKET_PATH)
+++            ubus.add(
+++                "registered_object1",
+++                {
+++                    "method1": {"method": handler1, "signature": {}},
+++                    "method2": {"method": handler1, "signature": {
+++                        "first": ubus.BLOBMSG_TYPE_STRING,
+++                        "second": ubus.BLOBMSG_TYPE_BOOL,
+++                        "third": ubus.BLOBMSG_TYPE_INT32,
+++                    }},
+++                },
+++            )
+++            ubus.add(
+++                "registered_object2", {},
+++            )
+++            ubus.add(
+++                "registered_object3",
+++                {
+++                    "method1": {"method": handler1, "signature": {}},
+++                }
+++            )
+++            guard.touch()
+++            ubus.loop()
+++
+++        p = Process(target=process_function)
+++        p.start()
+++        guard.wait()
+++
+++        yield p
+++
+++        p.terminate()
+++        p.join()
+++
+++
+++@pytest.fixture(scope="function")
+++def responsive_object():
+++    with Guard() as guard:
+++
+++        def process_function():
+++
+++            def handler1(handler, data):
+++                data["passed"] = True
+++                handler.reply(data)
+++
+++            def handler2(handler, data):
+++                data["passed1"] = True
+++                handler.reply(data)
+++                data["passed2"] = True
+++                handler.reply(data)
+++                data["passed3"] = True
+++                handler.reply(data)
+++
+++            def handler_fail(handler, data):
+++                raise Exception("Handler Fails")
+++
+++            import ubus
+++            ubus.connect(UBUSD_TEST_SOCKET_PATH)
+++            ubus.add(
+++                "responsive_object",
+++                {
+++                    "respond": {"method": handler1, "signature": {
+++                        "first": ubus.BLOBMSG_TYPE_STRING,
+++                        "second": ubus.BLOBMSG_TYPE_BOOL,
+++                        "third": ubus.BLOBMSG_TYPE_INT32,
+++                    }},
+++                    "fail": {"method": handler_fail, "signature": {}},
+++                    "multi_respond": {"method": handler2, "signature": {}},
+++                    "number": {"method": handler1, "signature": {
+++                        "number": ubus.BLOBMSG_TYPE_INT32,
+++                    }},
+++                },
+++            )
+++            guard.touch()
+++            ubus.loop()
+++
+++        p = Process(target=process_function)
+++        p.start()
+++        guard.wait()
+++
+++        yield p
+++
+++        p.terminate()
+++        p.join()
+++
+++
+++@pytest.fixture(scope="function")
+++def call_for_object():
+++    with Guard() as guard:
+++
+++        def process_function():
+++
+++            import ubus
+++            ubus.connect(UBUSD_TEST_SOCKET_PATH)
+++            args = [
+++                ('callee_object', 'method1', {'first': 1}),
+++                ('callee_object', 'method2', {'second': 2}),
+++                ('callee_object', 'method3', {}),
+++            ]
+++            while True:
+++                for arg in args:
+++                    try:
+++                        ubus.call(*arg)
+++                    except RuntimeError as e:
+++                        pass
+++                    guard.touch()
+++                    time.sleep(0.05)
+++
+++        p = Process(target=process_function)
+++        p.start()
+++        guard.wait()
+++
+++        yield p
+++
+++        p.terminate()
+++        p.join()
+++
+++
+++@pytest.fixture(scope="function")
+++def calls_extensive():
+++    with Guard() as guard:
+++
+++        def process_function():
+++
+++            import ubus
+++            ubus.connect(UBUSD_TEST_SOCKET_PATH)
+++            while True:
+++                for i in range(20):
+++                    try:
+++                        ubus.call('extensive_object_%d' % i, 'method', {})
+++                        time.sleep(0.1)
+++                    except RuntimeError as e:
+++                        pass
+++                    guard.touch()
+++
+++        p = Process(target=process_function)
+++        p.start()
+++        guard.wait()
+++        setattr(p, 'counter', guard.counter)
+++
+++        yield p
+++
+++        p.terminate()
+++        p.join()
+++
+++
+++@pytest.fixture(scope="function")
+++def disconnect_after():
+++    yield None
+++    import ubus
+++    try:
+++        ubus.disconnect()
+++    except:
+++        pass
++diff --git a/python/tests/test_connection.py b/python/tests/test_connection.py
++new file mode 100644
++index 0000000..d5ee835
++--- /dev/null
+++++ b/python/tests/test_connection.py
++@@ -0,0 +1,646 @@
+++import time
+++import pytest
+++import ubus
+++import sys
+++
+++from fixtures import (
+++    event_sender,
+++    call_for_object,
+++    calls_extensive,
+++    disconnect_after,
+++    ubusd_test,
+++    registered_objects,
+++    responsive_object,
+++    UBUSD_TEST_SOCKET_PATH,
+++)
+++
+++stored_reference_counts = None
+++
+++
+++class CheckRefCount(object):
+++
+++    def __init__(self, *objects):
+++        self.objects = objects
+++
+++    def __enter__(self):
+++        self.stored_reference_counts = [sys.getrefcount(e) for e in self.objects]
+++
+++    def __exit__(self, exc_type, *args):
+++
+++        if exc_type:
+++            # don't count references when an exception occured
+++            return
+++
+++        objects = self.objects
+++        current = [sys.getrefcount(e) for e in objects]
+++        stored = self.stored_reference_counts
+++        for (obj, old, new) in zip(objects, stored, current):
+++            assert old == new, "Reference count for '%s' mismatch (%d!=%d)" % (obj, old, new)
+++
+++
+++def test_socket_missing(ubusd_test):
+++    path = "/non/existing/path"
+++
+++    with CheckRefCount(path):
+++        with pytest.raises(IOError):
+++            ubus.connect(socket_path="/non/existing/path")
+++
+++
+++def test_connect_and_disconnect(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    with CheckRefCount(path):
+++
+++        assert ubus.get_connected() is False
+++        assert ubus.get_socket_path() is None
+++
+++        ubus.connect(socket_path=path)
+++        assert ubus.get_socket_path() == path
+++        assert ubus.get_connected() is True
+++
+++        assert ubus.disconnect() is None
+++        with pytest.raises(RuntimeError):
+++            ubus.disconnect()
+++        assert ubus.get_connected() is False
+++        assert ubus.get_socket_path() is None
+++
+++        ubus.connect(socket_path=path)
+++        with pytest.raises(RuntimeError):
+++            ubus.connect(socket_path=path)
+++        assert ubus.get_socket_path() == path
+++        assert ubus.get_connected() is True
+++
+++        assert ubus.disconnect() is None
+++
+++        assert ubus.get_connected() is False
+++        assert ubus.get_socket_path() is None
+++
+++
+++def test_send_failed(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    with CheckRefCount(path):
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.send("disconnected", {})
+++
+++        ubus.connect(socket_path=path)
+++        with pytest.raises(TypeError):
+++            ubus.send()
+++
+++        with pytest.raises(TypeError):
+++            ubus.send({}, {})
+++
+++        with pytest.raises(TypeError):
+++            ubus.send("", "")
+++
+++        with pytest.raises(TypeError):
+++            ubus.send("", {}, {})
+++
+++        class NonSerializable(object):
+++            pass
+++
+++        with pytest.raises(TypeError):
+++            ubus.send("", NonSerializable())
+++
+++        ubus.disconnect()
+++
+++
+++def test_send_succeeded(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++    test_dict = dict(a=5, b="True", c=False)
+++
+++    with CheckRefCount(path, test_dict):
+++
+++        ubus.connect(socket_path=path)
+++
+++        assert ubus.send("normal", test_dict)
+++        assert ubus.send("*", test_dict)
+++        assert ubus.send("", test_dict)
+++
+++        ubus.disconnect()
+++
+++
+++def test_loop(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    time1 = 200
+++    time2 = 1
+++    time3 = 50
+++    time4 = 2 ** 65
+++    time5 = None
+++    time6 = "5"
+++    time7 = 0
+++
+++    with CheckRefCount(path, time1, time2, time3, time4, time6, time7):
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.loop(time1)
+++
+++        ubus.connect(socket_path=path)
+++        assert ubus.loop(time1) is None
+++        assert ubus.loop(time2) is None
+++        assert ubus.loop(time3) is None
+++
+++        with pytest.raises(OverflowError):
+++            ubus.loop(time4)
+++
+++        with pytest.raises(TypeError):
+++            ubus.loop(time5)
+++
+++        with pytest.raises(TypeError):
+++            ubus.loop(time6)
+++
+++        assert ubus.loop(time7) is None
+++
+++        ubus.disconnect()
+++
+++
+++def test_listen_failed(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    obj1 = {}
+++    obj2 = ("a", lambda x, y: (x, y))
+++    obj3 = ("b", None)
+++    obj4 = ({}, lambda x, y: (x, y))
+++    obj5 = ("b", lambda x, y: (x, y))
+++
+++    with CheckRefCount(path, obj1, obj2, obj3, obj4, obj5):
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.listen(obj1, obj1)
+++
+++        ubus.connect(socket_path=path)
+++
+++        with pytest.raises(TypeError):
+++            ubus.listen(obj1, obj1)
+++
+++        with pytest.raises(TypeError):
+++            ubus.send(obj2, obj3)
+++
+++        with pytest.raises(TypeError):
+++            ubus.send(obj4, obj5)
+++
+++        with pytest.raises(TypeError):
+++            ubus.listen()
+++
+++        ubus.disconnect()
+++
+++
+++def test_listen(ubusd_test, event_sender, disconnect_after):
+++    listen_test = {"passed": False, "passed2": False}
+++
+++    def set_result(event, data):
+++        assert event == "event_sender"
+++        assert data == dict(a="b", c=3, d=False)
+++        listen_test["passed"] = True
+++
+++    def test1(event, data):
+++        assert event == "event_sender"
+++        assert data == dict(a="b", c=3, d=False)
+++        listen_test["passed2"] = True
+++
+++    path = UBUSD_TEST_SOCKET_PATH
+++    timeout = 300
+++    event_name = "event_sender"
+++
+++    with CheckRefCount(path, time, event_name, test1):
+++
+++        ubus.connect(socket_path=path)
+++        ubus.listen((event_name, test1), (event_name, set_result))
+++        ubus.listen((event_name, test1))
+++
+++        del set_result
+++
+++        ubus.loop(timeout)
+++        assert listen_test["passed"]
+++        assert listen_test["passed2"]
+++
+++        listen_test = {"passed": False, "passed2": False}
+++        ubus.loop(timeout)
+++        assert listen_test["passed"]
+++        assert listen_test["passed2"]
+++
+++        ubus.disconnect()
+++
+++
+++def test_add_object_failed(ubusd_test, registered_objects, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    def fake(*args):
+++        pass
+++
+++    type_errors = [
+++        [],
+++        ("name", ),
+++        ("name", {}, False),
+++        ("name", {"test": []}),
+++        ("name", {"test": 5}),
+++        ("name", {"test": {"method": 5, "signature": {}}}),
+++        ("name", {"test": {"method": fake}}),
+++        ("name", {"test": {"method": fake, "signature": {}, "another": 5}}),
+++    ]
+++
+++    runtime_errors = [
+++        ("registered_object1", {"test": {"method": fake, "signature": {}}}),
+++        ("registered_object2", {}),
+++    ]
+++
+++    with CheckRefCount(path, fake, *(type_errors + runtime_errors)):
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.add(*type_errors[0])
+++
+++        ubus.connect(socket_path=path)
+++
+++        for wrong_args in type_errors:
+++            with pytest.raises(TypeError):
+++                ubus.add(*wrong_args)
+++
+++        for wrong_args in runtime_errors:
+++            with pytest.raises(RuntimeError):
+++                ubus.add(*wrong_args)
+++
+++        ubus.disconnect()
+++        del wrong_args
+++
+++
+++def test_add_object(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    def fake(*args):
+++        pass
+++
+++    arguments1 = ("new_object1", {})
+++    arguments2 = ("new_object2", {"test": {"method": fake, "signature": {}}})
+++    arguments3 = ("new_object3", {
+++        "test1": {"method": fake, "signature": dict(arg1=3)},
+++        "test2": {"method": fake, "signature": dict(arg2=2)},
+++    })
+++
+++    with CheckRefCount(path, fake, *(arguments1 + arguments2 + arguments3)):
+++
+++        ubus.connect(socket_path=path)
+++
+++        assert ubus.add(*arguments1) is None
+++        assert ubus.add(*arguments2) is None
+++        assert ubus.add(*arguments3) is None
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.add(*arguments1)
+++
+++        ubus.disconnect()
+++
+++
+++def test_list_objects_failed(ubusd_test, registered_objects, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    type_errors = [
+++        ("more", "than", "one"),
+++        ({}, ),
+++        (True, ),
+++        (3, ),
+++        (None, ),
+++    ]
+++
+++    with CheckRefCount(path, *(type_errors)):
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.objects()
+++
+++        ubus.connect(socket_path=path)
+++
+++        for wrong_args in type_errors:
+++            with pytest.raises(TypeError):
+++                ubus.objects(*wrong_args)
+++
+++        ubus.disconnect()
+++        del wrong_args
+++
+++
+++def test_list_objects(ubusd_test, registered_objects, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    def fake(*args):
+++        pass
+++
+++    new_object = ("new_object", {"new_method": {"method": fake, "signature": {}}})
+++
+++    expected1 = {
+++        u"registered_object1": {
+++            u"method1": {},
+++            u"method2": {
+++                u"first": ubus.BLOBMSG_TYPE_STRING,
+++                u"second": ubus.BLOBMSG_TYPE_BOOL,
+++                u"third": ubus.BLOBMSG_TYPE_INT32,
+++            },
+++        },
+++        u"registered_object2": {},
+++        u"registered_object3": {
+++            u"method1": {},
+++        },
+++    }
+++
+++    expected2 = {
+++        u"registered_object2": {},
+++    }
+++
+++    expected3 = {
+++        u"registered_object1": {
+++            u"method1": {},
+++            u"method2": {
+++                u"first": ubus.BLOBMSG_TYPE_STRING,
+++                u"second": ubus.BLOBMSG_TYPE_BOOL,
+++                u"third": ubus.BLOBMSG_TYPE_INT32,
+++            },
+++        },
+++        u"registered_object2": {},
+++        u"registered_object3": {
+++            u"method1": {},
+++        },
+++        u"new_object": {
+++            u"new_method": {},
+++        },
+++    }
+++
+++    expected4 = {
+++        u"new_object": {"new_method": {}},
+++    }
+++
+++    with CheckRefCount(path, fake, expected1, expected2, expected3, expected4, *new_object):
+++
+++        ubus.connect(socket_path=path)
+++
+++        # All objects
+++        res1 = ubus.objects()
+++        assert res1 == expected1
+++
+++        # Filtered objects
+++        res2 = ubus.objects("registered_object2")
+++        assert res2 == expected2
+++
+++        # Append an object
+++        ubus.add(*new_object)
+++
+++        # All objects + new
+++        res3 = ubus.objects()
+++        assert res3 == expected3
+++
+++        # New object
+++        res4 = ubus.objects("new_object")
+++        assert res4 == expected4
+++
+++        ubus.disconnect()
+++        del res1
+++        del res2
+++        del res3
+++        del res4
+++
+++
+++def test_list_objects_empty(ubusd_test, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    with CheckRefCount(path):
+++        ubus.connect(path)
+++        assert ubus.objects() == {}
+++
+++        ubus.disconnect()
+++
+++
+++def test_reply_out_of_handler():
+++    data = {"this": "should fail"}
+++
+++    with CheckRefCount(data):
+++
+++        # should be called only within call
+++        handler = ubus.__ResponseHandler()
+++        with pytest.raises(RuntimeError):
+++            handler.reply(data)
+++
+++        del handler
+++
+++
+++def test_reply_failed(ubusd_test, call_for_object, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    results = {e: {'data': None, 'exits': False} for e in range(1, 4)}
+++
+++    def handler_fail1(handler, data):
+++        results[1]['data'] = data
+++        handler.reply()
+++        results[1]['exits'] = True
+++
+++    def handler_fail2(handler, data):
+++        results[2]['data'] = data
+++        handler.reply(6)
+++        results[2]['exits'] = True
+++
+++    def handler_fail3(handler, data):
+++        results[3]['data'] = data
+++        handler.reply({'data': 6}, {'fail': 'here'})
+++        results[3]['exits'] = True
+++
+++    with CheckRefCount(path, results, handler_fail1, handler_fail2, handler_fail3):
+++
+++        ubus.connect(path)
+++        ubus.add(
+++            "callee_object",
+++            {
+++                "method1": {"method": handler_fail1, "signature": {
+++                    "first": ubus.BLOBMSG_TYPE_INT32,
+++                }},
+++                "method2": {"method": handler_fail2, "signature": {
+++                    "second": ubus.BLOBMSG_TYPE_INT32,
+++                }},
+++                "method3": {"method": handler_fail3, "signature": {}},
+++            },
+++        )
+++        ubus.loop(500)
+++
+++        assert results == {
+++            1: {'data': {'first': 1}, 'exits': False},
+++            2: {'data': {'second': 2}, 'exits': False},
+++            3: {'data': {}, 'exits': False},
+++        }
+++
+++        ubus.disconnect()
+++
+++
+++def test_reply(ubusd_test, call_for_object, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    results = {e: {'data': None, 'exits': False} for e in range(1, 4)}
+++
+++    def handler1(handler, data):
+++        results[1]['data'] = data
+++        handler.reply(data)
+++        results[1]['exits'] = True
+++
+++    def handler2(handler, data):
+++        results[2]['data'] = data
+++        handler.reply(data)
+++        results[2]['exits'] = True
+++
+++    def handler3(handler, data):
+++        results[3]['data'] = data
+++        handler.reply(data)
+++        results[3]['exits'] = True
+++
+++    with CheckRefCount(path, results, handler1, handler2, handler3):
+++
+++        ubus.connect(path)
+++        ubus.add(
+++            "callee_object",
+++            {
+++                "method1": {"method": handler1, "signature": {
+++                    "first": ubus.BLOBMSG_TYPE_INT32,
+++                }},
+++                "method2": {"method": handler2, "signature": {
+++                    "second": ubus.BLOBMSG_TYPE_INT32,
+++                }},
+++                "method3": {"method": handler3, "signature": {}},
+++            },
+++        )
+++        ubus.loop(500)
+++
+++        assert results == {
+++            1: {'data': {'first': 1}, 'exits': True},
+++            2: {'data': {'second': 2}, 'exits': True},
+++            3: {'data': {}, 'exits': True},
+++        }
+++
+++        ubus.disconnect()
+++
+++
+++def test_call_failed(ubusd_test, responsive_object, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    args1 = (
+++        [],
+++        ("responsive_object", ),
+++        ("responsive_object", "respond", ),
+++        ("responsive_object", "respond", 4, ),
+++        ("responsive_object", "respond", {"first": "test", "second": True, "third": 56}, "x"),
+++        ("responsive_object", "respond", {"first": "test", "second": True, "third": 56}, -1),
+++    )
+++    args2 = (
+++        ("responsive_object", "respond", {"first": 6, "second": True, "third": 56}, ),
+++        ("responsive_object", "respond", {"first": "test", "third": 56}, ),
+++        ("responsive_object", "respond", {"first": "test", "second": True, "third": 56, "x": 1}),
+++        ("responsive_object", "fail", {"first": "test", "second": True, "third": 56, "x": 1}),
+++        ("responsive_object", "fail", {}),
+++    )
+++    args3 = (
+++        ("responsive_object", "respond", {"first": "test", "second": True, "third": 56}, 2 ** 64),
+++    )
+++    with CheckRefCount(path, *(args1 + args2 + args3)):
+++
+++        with pytest.raises(RuntimeError):
+++            ubus.objects(*args1[0])
+++
+++        ubus.connect(path)
+++        for arg in args1:
+++            with pytest.raises(TypeError):
+++                ubus.call(*arg)
+++
+++        for arg in args2:
+++            with pytest.raises(RuntimeError):
+++                ubus.call(*arg)
+++
+++        for arg in args3:
+++            with pytest.raises(OverflowError):
+++                ubus.call(*arg)
+++
+++        ubus.disconnect()
+++        del arg
+++
+++
+++def test_call(ubusd_test, responsive_object, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    ubus_object = "responsive_object"
+++    method1 = "respond"
+++    method2 = "multi_respond"
+++    data = {"first": "1", "second": False, "third": 22}
+++
+++    with CheckRefCount(path, ubus_object, method1, method2):
+++
+++        ubus.connect(socket_path=path)
+++        res = ubus.call(ubus_object, method1, data)
+++        assert len(res) == 1
+++        assert res[0] == {"first": "1", "second": False, "third": 22, "passed": True}
+++
+++        res = ubus.call(ubus_object, method1, data, timeout=200)
+++        assert len(res) == 1
+++        assert res[0] == {"first": "1", "second": False, "third": 22, "passed": True}
+++
+++        res = ubus.call(ubus_object, method2, {})
+++        assert len(res) == 3
+++        assert res[0] == {"passed1": True}
+++        assert res[1] == {"passed1": True, "passed2": True}
+++        assert res[2] == {"passed1": True, "passed2": True, "passed3": True}
+++
+++        del res
+++        ubus.disconnect()
+++
+++
+++def test_call_max_min_number(ubusd_test, responsive_object, disconnect_after):
+++    path = UBUSD_TEST_SOCKET_PATH
+++    data1 = {"number": 2 ** 32}
+++    data2 = {"number": -(2 ** 32)}
+++
+++    with CheckRefCount(path, data1, data2):
+++
+++        ubus.connect(socket_path=path)
+++        res = ubus.call("responsive_object", "number", data1)
+++        assert res[0] == {"number": 2 ** 31 - 1, "passed": True}
+++        res = ubus.call("responsive_object", "number", data2)
+++        assert res[0] == {"number": -(2 ** 31), "passed": True}
+++
+++        del res
+++        ubus.disconnect()
+++
+++
+++def test_multi_objects_listeners(ubusd_test, event_sender, calls_extensive, disconnect_after):
+++    counts = 20
+++    listen_test = {"pass%d" % e: False for e in range(counts)}
+++    object_test = {"pass%d" % e: False for e in range(counts)}
+++    event_name = "event_sender"
+++    timeout = 200
+++
+++    path = UBUSD_TEST_SOCKET_PATH
+++
+++    def passed_listen_gen(index):
+++        def passed(*args):
+++            listen_test["pass%d" % index] = True
+++        return passed
+++
+++    def passed_object_gen(index):
+++        def passed(*args):
+++            object_test["pass%d" % index] = True
+++        return passed
+++
+++    with CheckRefCount(path, time):
+++
+++        for _ in range(5):
+++            ubus.connect(socket_path=path)
+++
+++            for i in range(counts):
+++                ubus.listen((event_name, passed_listen_gen(i)))
+++                ubus.add(
+++                    "extensive_object_%d" % i,
+++                    {"method": {"method": passed_object_gen(i), "signature": {}}}
+++                )
+++
+++            stored_counter = calls_extensive.counter.value
+++            while calls_extensive.counter.value - stored_counter < counts:
+++                ubus.loop(timeout)
+++            ubus.disconnect()
+++
+++            for i in range(counts):
+++                current = "pass%d" % i
+++                assert listen_test[current]
+++                assert object_test[current]
+++
+++            listen_test = {"pass%d" % e: False for e in range(counts)}
+++            object_test = {"pass%d" % e: False for e in range(counts)}
++diff --git a/python/ubus_python.c b/python/ubus_python.c
++new file mode 100644
++index 0000000..d51ce43
++--- /dev/null
+++++ b/python/ubus_python.c
++@@ -0,0 +1,1324 @@
+++
+++#include <Python.h>
+++#include <dlfcn.h>
+++#include <libubox/blobmsg_json.h>
+++#include <libubus.h>
+++#include <stdio.h>
+++
+++#ifndef UBUS_UNIX_SOCKET
+++#define UBUS_UNIX_SOCKET "/var/run/ubus.sock"
+++#endif
+++
+++#define DEFAULT_SOCKET UBUS_UNIX_SOCKET
+++#define RESPONSE_HANDLER_OBJECT_NAME "ubus.__ResponseHandler"
+++
+++#define MSG_ALLOCATION_FAILS "Failed to allocate memory!"
+++#define MSG_LISTEN_TUPLE_EXPECTED "Expected (event, callback) tuple"
+++#define MSG_ADD_SIGNATURE_INVALID \
+++"Incorrect method arguments!\n" \
+++"Expected:\n" \
+++"	(<obj_name>, { " \
+++	"<method_name>: {'signature': <method_signature>, 'method': <callable>}" \
+++", ...})"
+++#define MSG_JSON_TO_UBUS_FAILED "Failed to create json for ubus."
+++#define MSG_JSON_FROM_UBUS_FAILED "Failed to create json from ubus."
+++#define MSG_NOT_CONNECTED "You are not connected to ubus."
+++#define MSG_ALREADY_CONNECTED "You are already connected to ubus."
+++
+++typedef struct {
+++	struct ubus_object object;
+++	PyObject *methods;
+++} ubus_Object;
+++
+++typedef struct {
+++	struct ubus_event_handler handler;
+++	PyObject *callback;
+++}ubus_Listener ;
+++
+++
+++PyObject *prepare_bool(bool yes)
+++{
+++	if (yes) {
+++		Py_INCREF(Py_True);
+++		return Py_True;
+++	} else {
+++		Py_INCREF(Py_False);
+++		return Py_False;
+++	}
+++}
+++
+++/* ubus module objects */
+++static PyMethodDef ubus_methods[];
+++PyObject *python_alloc_list = NULL;
+++char *socket_path = NULL;
+++ubus_Listener **listeners = NULL;
+++size_t listerners_size = 0;
+++ubus_Object **objects = NULL;
+++size_t objects_size = 0;
+++struct blob_buf python_buf;
+++struct ubus_context *ctx = NULL;
+++
+++#define CONNECTED (ctx != NULL)
+++
+++
+++/* json module handlers */
+++PyObject *json_module = NULL;
+++
+++enum json_function {
+++	LOADS,
+++	DUMPS,
+++};
+++
+++const char *json_function_names[2] = {
+++	[LOADS] = "loads",
+++	[DUMPS] = "dumps",
+++};
+++
+++
+++PyObject *perform_json_function(enum json_function json_function, PyObject *input)
+++{
+++	PyObject *function = PyObject_GetAttrString(json_module, json_function_names[json_function]);
+++	if (!function) {
+++		return NULL;
+++	}
+++	PyObject *arglist = Py_BuildValue("(O)", input);
+++	if (!arglist) {
+++		Py_DECREF(function);
+++		return NULL;
+++	}
+++	PyObject *data_object = PyObject_CallObject(function, arglist);
+++	Py_DECREF(function);
+++	Py_DECREF(arglist);
+++
+++	return data_object;  // New reference - should be decreased by the caller
+++}
+++
+++/* ResponseHandler */
+++
+++typedef struct {
+++	PyObject_HEAD
+++	struct ubus_context *ctx;
+++	struct ubus_request_data *req;
+++	struct blob_buf buf;
+++} ubus_ResponseHandler;
+++
+++static void ubus_ResponseHandler_dealloc(ubus_ResponseHandler* self)
+++{
+++	blob_buf_free(&self->buf);
+++	Py_TYPE(self)->tp_free((PyObject*)self);
+++}
+++
+++PyDoc_STRVAR(
+++	ResponseHandler_reply_doc,
+++	"reply(data)\n"
+++	"\n"
+++	":param data: JSON to be send as a response to a ubus call.\n"
+++	":type data: dict\n"
+++);
+++
+++static PyObject *ubus_ResponseHandler_reply(ubus_ResponseHandler *self, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	PyObject *data = NULL;
+++	static char *kwlist[] = {"data",  NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &data)) {
+++		return NULL;
+++	}
+++
+++	// Call python function json.dumps
+++	PyObject *json_str = perform_json_function(DUMPS, data);
+++	if (!json_str) {
+++		return NULL;
+++	}
+++
+++	// put json string into buffer
+++	blob_buf_init(&self->buf, 0);
+++	bool res = blobmsg_add_json_from_string(&self->buf, PyString_AsString(json_str));
+++	Py_DECREF(json_str);
+++	if (!res) {
+++		PyErr_Format(PyExc_TypeError, MSG_JSON_TO_UBUS_FAILED);
+++		return NULL;
+++	}
+++
+++	// handler is not linked to a call response
+++	if (!self->req || !self->ctx) {
+++		PyErr_Format(PyExc_RuntimeError, "Handler is not linked to a call response.");
+++		return NULL;
+++	}
+++
+++	int retval = ubus_send_reply(self->ctx, self->req, self->buf.head);
+++	return prepare_bool(!retval);
+++}
+++
+++PyDoc_STRVAR(
+++	ResponseHandler_doc,
+++	"__ResponseHandler\n"
+++	"\n"
+++	"Object which is used to handle responses to ubus calls.\n"
+++);
+++
+++static PyMethodDef ubus_ResponseHandler_methods[] = {
+++	{"reply", (PyCFunction)ubus_ResponseHandler_reply, METH_VARARGS|METH_KEYWORDS, ResponseHandler_reply_doc},
+++	{NULL},
+++};
+++
+++static int ubus_ResponseHandler_init(ubus_ResponseHandler *self, PyObject *args, PyObject *kwargs)
+++{
+++	static char *kwlist[] = {NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwlist)){
+++		return -1;
+++	}
+++	memset(&self->buf, 0, sizeof(self->buf));
+++	self->ctx = NULL;
+++	self->req = NULL;
+++	return 0;
+++}
+++
+++static PyObject *ubus_ResponseHandler_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+++{
+++	ubus_ResponseHandler *self = (ubus_ResponseHandler *)type->tp_alloc(type, 0);
+++	return (PyObject *)self;
+++}
+++
+++static PyTypeObject ubus_ResponseHandlerType = {
+++	PyVarObject_HEAD_INIT(NULL, 0)
+++	RESPONSE_HANDLER_OBJECT_NAME,				/* tp_name */
+++	sizeof(ubus_ResponseHandler),				/* tp_basicsize */
+++	0,											/* tp_itemsize */
+++	(destructor)ubus_ResponseHandler_dealloc,	/* tp_dealloc */
+++	0,											/* tp_print */
+++	0,											/* tp_getattr */
+++	0,											/* tp_setattr */
+++	0,											/* tp_compare */
+++	0,											/* tp_repr */
+++	0,											/* tp_as_number */
+++	0,											/* tp_as_sequence */
+++	0,											/* tp_as_mapping */
+++	0,											/* tp_hash */
+++	0,											/* tp_call */
+++	0,											/* tp_str */
+++	0,											/* tp_getattro */
+++	0,											/* tp_setattro */
+++	0,											/* tp_as_buffer */
+++	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/* tp_flags */
+++	ResponseHandler_doc,						/* tp_doc */
+++	0,											/* tp_traverse */
+++	0,											/* tp_clear */
+++	0,											/* tp_richcompare */
+++	0,											/* tp_weaklistoffset */
+++	0,											/* tp_iter */
+++	0,											/* tp_iternext */
+++	ubus_ResponseHandler_methods,				/* tp_methods */
+++	0,											/* tp_members */
+++	0,											/* tp_getset */
+++	0,											/* tp_base */
+++	0,											/* tp_dict */
+++	0,											/* tp_descr_get */
+++	0,											/* tp_descr_set */
+++	0,											/* tp_dictoffset */
+++	(initproc)ubus_ResponseHandler_init,		/* tp_init */
+++	0,											/* tp_alloc */
+++	ubus_ResponseHandler_new,					/* tp_new */
+++};
+++
+++typedef struct {
+++	PyObject_HEAD
+++	PyObject *socket_path;
+++	ubus_Listener **listeners;
+++	size_t listerners_size;
+++	ubus_Object **objects;
+++	size_t objects_size;
+++	PyObject *alloc_list;  // Used for easy deallocation
+++	struct blob_buf buf;
+++	struct ubus_context *ctx;
+++} ubus_Connection;
+++
+++void free_ubus_object(ubus_Object *obj)
+++{
+++	if (obj->object.methods) {
+++		for (int i = 0; i < obj->object.n_methods; i++) {
+++			if (&obj->object.methods[i] && obj->object.methods[i].policy) {
+++				free((struct blobmsg_policy *)obj->object.methods[i].policy);
+++			}
+++		}
+++		free((struct ubus_method *)obj->object.methods);
+++	}
+++
+++	if (obj->object.type) {
+++		free(obj->object.type);
+++	}
+++	free(obj);
+++}
+++
+++PyObject *ubus_python_module_init(void)
+++{
+++	PyObject *module = Py_InitModule3("ubus", ubus_methods, "Ubus bindings");
+++	return module;
+++}
+++
+++PyDoc_STRVAR(
+++	disconnect_doc,
+++	"disconnect(deregister=True)\n"
+++	"\n"
+++	":param deregister: Deregisters object and handlers from ubus as well.\n"
+++	":type deregister: bool\n"
+++	"Disconnects from ubus and disposes all connection structures.\n"
+++);
+++
+++void dispose_connection(bool deregister)
+++{
+++	if (ctx != NULL) {
+++		if (deregister) {
+++			// remove objects
+++			for (int i = 0; i < objects_size; i++) {
+++				ubus_remove_object(ctx, &objects[i]->object);
+++			}
+++
+++			// remove listeners
+++			for (int i = 0; i < listerners_size; i++) {
+++				ubus_unregister_event_handler(ctx, &listeners[i]->handler);
+++			}
+++		}
+++
+++		ubus_free(ctx);
+++		ctx = NULL;
+++	}
+++	uloop_done();
+++	blob_buf_free(&python_buf);
+++	if (python_alloc_list) {
+++		Py_DECREF(python_alloc_list);
+++		python_alloc_list = NULL;
+++	}
+++	// clear event listeners
+++	if (listeners) {
+++		for (int i = 0; i < listerners_size; i++) {
+++			free(listeners[i]);
+++		}
+++		free(listeners);
+++		listerners_size = 0;
+++		listeners = NULL;
+++	}
+++	// clear objects
+++	if (objects) {
+++		for (int i = 0; i < objects_size; i++) {
+++			free_ubus_object(objects[i]);
+++		}
+++		free(objects);
+++		objects_size = 0;
+++		objects = NULL;
+++	}
+++
+++	if (socket_path) {
+++		free(socket_path);
+++		socket_path = NULL;
+++	}
+++}
+++
+++static PyObject *ubus_python_disconnect(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	PyObject* deregister = Py_True;
+++	static char *kwlist[] = {"deregister", NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O!", kwlist, &PyBool_Type, &deregister)){
+++		return NULL;
+++	}
+++
+++	dispose_connection(PyObject_IsTrue(deregister));
+++
+++	Py_INCREF(Py_None);
+++	return Py_None;
+++}
+++
+++PyDoc_STRVAR(
+++	connect_doc,
+++	"connect(socket_path='" DEFAULT_SOCKET "')\n"
+++	"\n"
+++	"Establishes a connection to ubus.\n"
+++);
+++
+++static PyObject *ubus_python_connect(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_ALREADY_CONNECTED);
+++		return NULL;
+++	}
+++
+++	static char *kwlist[] = {"socket_path", NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &socket_path)){
+++		return NULL;
+++	}
+++
+++	// Init object list
+++	python_alloc_list = PyList_New(0);
+++	if (!python_alloc_list) {
+++		return NULL;
+++	}
+++
+++	// socket path
+++	if (!socket_path) {
+++		socket_path = strdup(DEFAULT_SOCKET);
+++		if (!socket_path) {
+++			PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++			return NULL;
+++		}
+++	} else {
+++		char *tmp = strdup(socket_path);
+++		if (!tmp) {
+++			PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++			return NULL;
+++		}
+++		socket_path = tmp;
+++	}
+++
+++	// Init event listner array
+++	listeners = NULL;
+++	listerners_size = 0;
+++
+++	// Init objects array
+++	objects = NULL;
+++	objects_size = 0;
+++
+++	// Connect to ubus
+++	ctx = ubus_connect(socket_path);
+++	if (!ctx) {
+++		PyErr_Format(
+++				PyExc_IOError,
+++				"Failed to connect to the ubus socket '%s'\n", socket_path
+++		);
+++		dispose_connection(true);
+++		return NULL;
+++	}
+++	ubus_add_uloop(ctx);
+++	memset(&python_buf, 0, sizeof(python_buf));
+++
+++	return prepare_bool(true);
+++}
+++
+++PyDoc_STRVAR(
+++	get_connected_doc,
+++	"get_connected()\n"
+++	"\n"
+++	"Determines whether we are connected to ubus.\n"
+++	":return: True if connected, False otherwise.\n"
+++	":rtype: bool \n"
+++);
+++
+++static PyObject *ubus_python_get_connected(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	return prepare_bool(CONNECTED);
+++}
+++
+++PyDoc_STRVAR(
+++	get_socket_path_doc,
+++	"get_socket_path()\n"
+++	"\n"
+++	"Gets socket path for the current connection.\n"
+++	":return: path to socket if connected, None otherwise.\n"
+++	":rtype: bool or str \n"
+++);
+++
+++static PyObject *ubus_python_get_socket_path(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (socket_path) {
+++		return PyString_FromString(socket_path);
+++	} else {
+++		Py_INCREF(Py_None);
+++		return Py_None;
+++	}
+++}
+++
+++PyDoc_STRVAR(
+++	connect_send_doc,
+++	"send(event, data)\n"
+++	"\n"
+++	"Send an event via ubus.\n"
+++	"\n"
+++	":param event: ubus event which will be used \n"
+++	":type event: str\n"
+++	":param data: python object which can be serialized to json \n"
+++	":type data: dict or list \n"
+++	":return: True on success, False otherwise \n"
+++	":rtype: bool \n"
+++);
+++
+++static PyObject *ubus_python_send(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	char *event = NULL;
+++	PyObject *data = NULL;
+++	static char *kwlist[] = {"event", "data",  NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO", kwlist, &event, &data)){
+++		return NULL;
+++	}
+++
+++	// Call python function json.dumps
+++	PyObject *json_str = perform_json_function(DUMPS, data);
+++	if (!json_str) {
+++		return NULL;
+++	}
+++
+++	// put json string into buffer
+++	blob_buf_init(&python_buf, 0);
+++	bool res = blobmsg_add_json_from_string(&python_buf, PyString_AsString(json_str));
+++	Py_DECREF(json_str);
+++	if (!res) {
+++		PyErr_Format(PyExc_TypeError, MSG_JSON_TO_UBUS_FAILED);
+++		return NULL;
+++	}
+++
+++	int retval = ubus_send_event(ctx, event, python_buf.head);
+++	return prepare_bool(!retval);
+++}
+++
+++static void ubus_python_event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev,
+++			const char *type, struct blob_attr *msg)
+++{
+++	PyGILState_STATE gstate = PyGILState_Ensure();
+++
+++	// Prepare event
+++	PyObject *event = PyString_FromString(type);
+++	if (!event) {
+++		goto event_handler_cleanup0;
+++	}
+++
+++	// Prepare json data
+++	char *str = blobmsg_format_json(msg, true);
+++	if (!str) {
+++		goto event_handler_cleanup1;
+++	}
+++	PyObject *data = PyString_FromString(str);
+++	free(str);
+++	if (!data) {
+++		goto event_handler_cleanup1;
+++	}
+++
+++	// Call python function json.loads
+++	PyObject *data_object = perform_json_function(LOADS, data);
+++	if (!data_object) {
+++		goto event_handler_cleanup2;
+++	}
+++	
+++	// Get PyObject callback
+++	ubus_Listener *listener = container_of(ev, ubus_Listener, handler);
+++
+++	// Trigger callback
+++	PyObject *callback_arglist = Py_BuildValue("(O, O)", event, data_object);
+++	if (!callback_arglist) {
+++		goto event_handler_cleanup3;
+++	}
+++
+++	PyObject *result = PyObject_CallObject(listener->callback, callback_arglist);
+++	if (result) {
+++		Py_DECREF(result);  // result of the callback is quite useless
+++	} else {
+++		PyErr_Print();
+++	}
+++	Py_DECREF(callback_arglist);
+++
+++event_handler_cleanup3:
+++	Py_DECREF(data_object);
+++event_handler_cleanup2:
+++	Py_DECREF(data);
+++event_handler_cleanup1:
+++	Py_DECREF(event);
+++
+++event_handler_cleanup0:
+++	// Clear python exceptions
+++	PyErr_Clear();
+++
+++	PyGILState_Release(gstate);
+++}
+++
+++PyDoc_STRVAR(
+++	connect_listen_doc,
+++	"listen(event, ...)\n"
+++	"\n"
+++	"Adds a listener on ubus events.\n"
+++	"\n"
+++	":param event: tuple contaning event string and a callback (str, callable) \n"
+++	":type event: tuple\n"
+++);
+++
+++static PyObject *ubus_python_listen(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	args = PySequence_Fast(args, "expected a sequence");
+++	int len = PySequence_Size(args);
+++	if (!len) {
+++		PyErr_Format(PyExc_TypeError, "You need to set at least one event.");
+++		goto listen_error1;
+++	}
+++
+++	if (!PyTuple_Check(args)) {
+++		PyErr_Format(PyExc_TypeError, "Tuple of (event, callback) expected.");
+++		goto listen_error1;
+++	}
+++
+++	// Test whether the arguments are valid
+++	for (int i = 0; i < len; i++) {
+++		PyObject *item = PySequence_Fast_GET_ITEM(args, i);
+++		// Test tuple
+++		if (!PyTuple_Check(item)) {
+++			PyErr_Format(PyExc_TypeError, MSG_LISTEN_TUPLE_EXPECTED);
+++			goto listen_error1;
+++		}
+++		PyObject *item_tuple = PySequence_Fast(item, MSG_LISTEN_TUPLE_EXPECTED);
+++		if (!item_tuple) {
+++			PyErr_Format(PyExc_MemoryError, "Failed to obtain tuple item");
+++			goto listen_error1;
+++		}
+++		if (!PyTuple_Check(item_tuple) || PySequence_Size(item_tuple) != 2) {
+++			PyErr_Format(PyExc_TypeError, MSG_LISTEN_TUPLE_EXPECTED);
+++			Py_DECREF(item_tuple);
+++			Py_DECREF(args);
+++			goto listen_error1;
+++		}
+++
+++		// Test types
+++		if (!PyString_Check(PyTuple_GET_ITEM(item_tuple, 0))
+++					|| !PyCallable_Check(PyTuple_GET_ITEM(item_tuple, 1))) {
+++			PyErr_Format(PyExc_TypeError, MSG_LISTEN_TUPLE_EXPECTED);
+++			Py_DECREF(item_tuple);
+++			goto listen_error1;
+++		}
+++		Py_DECREF(item_tuple);
+++	}
+++
+++	// add callbacks
+++	for (int i = 0; i < len; i++) {
+++		PyObject *item = PySequence_Fast_GET_ITEM(args, i);
+++		PyObject *item_tuple = PySequence_Fast(item, MSG_LISTEN_TUPLE_EXPECTED);
+++		if (!item_tuple) {
+++			PyErr_Format(PyExc_MemoryError, "Failed to obtain tuple item");
+++			goto listen_error1;
+++		}
+++		PyObject *event = PyTuple_GET_ITEM(item_tuple, 0);
+++		PyObject *callback = PyTuple_GET_ITEM(item_tuple, 1);
+++		// Keep event and callback references
+++		if (PyList_Append(python_alloc_list, event) || PyList_Append(python_alloc_list, callback)) {
+++			Py_DECREF(item_tuple);
+++			goto listen_error1;
+++		}
+++		Py_DECREF(item_tuple);
+++
+++		// prepare event listener
+++		ubus_Listener *listener = calloc(1, sizeof(ubus_Listener));
+++		if (!listener) {
+++			PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++			Py_DECREF(item_tuple);
+++			goto listen_error1;
+++		}
+++
+++		listener->handler.cb = ubus_python_event_handler;
+++		listener->callback = callback;
+++
+++		ubus_Listener **new_listeners = realloc(listeners,
+++			(listerners_size + 1) * sizeof(*listeners));
+++		if (!new_listeners) {
+++			free(listener);
+++			goto listen_error1;
+++		}
+++		listeners = new_listeners;
+++		listeners[listerners_size++] = listener;
+++
+++		// register event handler
+++		int retval = ubus_register_event_handler(ctx, &listener->handler, PyString_AsString(event));
+++		if (retval != UBUS_STATUS_OK) {
+++			listerners_size--;
+++			free(listener);
+++		}
+++	}
+++
+++	Py_DECREF(args);
+++
+++	Py_INCREF(Py_None);
+++	return Py_None;
+++
+++listen_error1:
+++	Py_DECREF(args);
+++	return NULL;
+++}
+++
+++static void ubus_python_timeout_handler(struct uloop_timeout *timeout) {
+++	uloop_end();
+++}
+++
+++PyDoc_STRVAR(
+++	connect_loop_doc,
+++	"loop(timeout=-1)\n"
+++	"\n"
+++	"Enters a loop and processes events.\n"
+++	"\n"
+++	":param timeout: loop timeout in ms (if lower than zero then it will run forever) \n"
+++	":type timeout: int\n"
+++);
+++
+++static PyObject *ubus_python_loop(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	int timeout = -1;
+++	static char *kwlist[] = {"timeout", NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout)){
+++		return NULL;
+++	}
+++
+++	Py_BEGIN_ALLOW_THREADS
+++	if (timeout == 0) {
+++		// process events directly without uloop
+++		ubus_handle_event(ctx);
+++	} else {
+++		uloop_init();
+++		struct uloop_timeout u_timeout;
+++		if (timeout > 0) {
+++			// prepare for timeout
+++			memset(&u_timeout, 0, sizeof(u_timeout));
+++			u_timeout.cb = ubus_python_timeout_handler;
+++			uloop_timeout_set(&u_timeout, timeout);  // Timeout to seconds
+++		}
+++		uloop_run();
+++		if (timeout > 0) {
+++			uloop_timeout_cancel(&u_timeout);
+++		}
+++	}
+++	Py_END_ALLOW_THREADS
+++
+++	Py_INCREF(Py_None);
+++	return Py_None;
+++}
+++
+++bool test_policies(const struct blobmsg_policy *policies, int n_policies, struct blob_attr *args)
+++{
+++	struct blob_attr *cur;
+++	int idx = 0, passed_count = 0;
+++
+++	blob_for_each_attr(cur, args, idx) {
+++		const char *name = blobmsg_name(cur);
+++		int type = blobmsg_type(cur);
+++		int pol_idx;
+++
+++		// Iterate through policies
+++		for (pol_idx = 0; pol_idx < n_policies; pol_idx++) {
+++
+++			if (!strcmp(name, policies[pol_idx].name)) {
+++				passed_count += 1;
+++				int pol_type = policies[pol_idx].type;
+++				if (pol_type != BLOBMSG_TYPE_UNSPEC && pol_type != type) {
+++					return false;
+++				}
+++				break;
+++			}
+++		}
+++
+++		// Policy was not found
+++		if (pol_idx >= n_policies) {
+++			return false;
+++		}
+++	}
+++
+++	// All attributes are present and checked
+++	return passed_count == n_policies;
+++}
+++
+++static int ubus_python_method_handler(struct ubus_context *ctx, struct ubus_object *obj,
+++		struct ubus_request_data *req, const char *method,
+++		struct blob_attr *msg)
+++{
+++	// Check whether method signature matches
+++	int method_idx;
+++	for (method_idx = 0; method_idx < obj->n_methods; ++method_idx) {
+++		if (!strcmp(obj->methods[method_idx].name, method)) {
+++			break;
+++		}
+++	}
+++	if (method_idx >= obj->n_methods) {
+++		// Can't find method
+++		return UBUS_STATUS_UNKNOWN_ERROR;
+++	}
+++	if (!test_policies(obj->methods[method_idx].policy, obj->methods[method_idx].n_policy, msg)) {
+++		return UBUS_STATUS_INVALID_ARGUMENT;
+++	}
+++
+++	PyGILState_STATE gstate = PyGILState_Ensure();
+++
+++	int retval = UBUS_STATUS_OK;
+++	// Get python method
+++	PyObject *methods = container_of(obj, ubus_Object, object)->methods;
+++	PyObject *python_method = PyDict_GetItemString(methods, method);
+++	if (!python_method) {
+++		retval = UBUS_STATUS_METHOD_NOT_FOUND;
+++		goto method_handler_exit;
+++	}
+++
+++	// prepare json data
+++	char *str = blobmsg_format_json(msg, true);
+++	if (!str) {
+++		retval = UBUS_STATUS_UNKNOWN_ERROR;
+++		goto method_handler_exit;
+++	}
+++	PyObject *data = PyString_FromString(str);
+++	free(str);
+++	if (!data) {
+++		retval = UBUS_STATUS_UNKNOWN_ERROR;
+++		goto method_handler_exit;
+++	}
+++
+++	// Call python function json.loads
+++	PyObject *data_object = perform_json_function(LOADS, data);
+++	if (!data_object) {
+++		retval = UBUS_STATUS_UNKNOWN_ERROR;
+++		goto method_handler_cleanup1;
+++	}
+++
+++	PyObject *handler = PyObject_CallObject((PyObject *)&ubus_ResponseHandlerType, NULL);
+++	if (!handler) {
+++		PyErr_Print();
+++		goto method_handler_cleanup2;
+++	}
+++	((ubus_ResponseHandler *)handler)->req = req;
+++	((ubus_ResponseHandler *)handler)->ctx = ctx;
+++
+++	// Trigger method
+++	PyObject *callback_arglist = Py_BuildValue("(O, O)", handler, data_object);
+++	if (!callback_arglist) {
+++		retval = UBUS_STATUS_UNKNOWN_ERROR;
+++		goto method_handler_cleanup3;
+++	}
+++	PyObject *callable = PyDict_GetItemString(python_method, "method");
+++	PyObject *result = PyObject_CallObject(callable, callback_arglist);
+++	Py_DECREF(callback_arglist);
+++	if (!result) {
+++		PyErr_Print();
+++		retval = UBUS_STATUS_UNKNOWN_ERROR;
+++	} else {
+++		Py_DECREF(result);  // we don't care about the result
+++	}
+++
+++method_handler_cleanup3:
+++	// NULLify the structures so that using this structure will we useless if a reference
+++	// is left outside the callback code
+++	((ubus_ResponseHandler *)handler)->req = NULL;
+++	((ubus_ResponseHandler *)handler)->ctx = NULL;
+++	Py_DECREF(handler);
+++method_handler_cleanup2:
+++	Py_DECREF(data_object);
+++method_handler_cleanup1:
+++	Py_DECREF(data);
+++method_handler_exit:
+++
+++	// Clear python exceptions
+++	PyErr_Clear();
+++
+++	PyGILState_Release(gstate);
+++
+++	return retval;
+++}
+++
+++static bool test_methods_argument(PyObject *methods)
+++{
+++	if (!methods) {
+++		return false;
+++	}
+++
+++	PyObject *method_name = NULL, *value = NULL;
+++	Py_ssize_t pos = 0;
+++	// Iterate through methods
+++	while(PyDict_Next(methods, &pos, &method_name, &value)) {
+++		// Test name
+++		if (!PyString_Check(method_name)) {
+++			return false;
+++		}
+++		if (!PyDict_Check(value)) {
+++			return false;
+++		}
+++
+++		// Dict should contain only two elemnts - 'signature' and 'method'
+++		if (PyDict_Size(value) != 2) {
+++				return false;
+++		}
+++
+++		// Test signature
+++		PyObject *signature = PyDict_GetItemString(value, "signature");
+++		if (!signature || !PyDict_Check(signature)) {
+++			return false;
+++		}
+++		Py_ssize_t sig_pos = 0;
+++		PyObject *signature_name = NULL, *signature_type = NULL;
+++		while (PyDict_Next(signature, &sig_pos, &signature_name, &signature_type)) {
+++			if (!PyString_Check(signature_name)) {
+++				return false;
+++			}
+++			if (!PyInt_Check(signature_type)) {
+++				return false;
+++			}
+++			int type = PyInt_AsLong(signature_type);
+++			if (type < 0 || type > BLOBMSG_TYPE_LAST) {  // indexed from 0
+++				return false;
+++			}
+++		}
+++
+++		// Test callable
+++		PyObject *method = PyDict_GetItemString(value, "method");
+++		if (!method || !PyCallable_Check(method)) {
+++			return false;
+++		}
+++	}
+++
+++	return true;
+++}
+++
+++PyDoc_STRVAR(
+++	connect_add_doc,
+++	"add(object_name, methods)\n"
+++	"\n"
+++	"Adds an object to ubus.\n"
+++	"methods should look like this: \n"
+++	"{ \n"
+++	"	<method_name>: {'signature': <method_signature>, 'method': <callable>} \n"
+++	"} \n"
+++	"\n"
+++	"{ \n"
+++	"	test: {'signature': {'argument1': BLOBMSG_TYPE_STRING}, 'method': my_callback} \n"
+++	"} \n"
+++	"\n"
+++	":param object_name: the name of the object which will be present on ubus \n"
+++	":type object_name: str\n"
+++	":param methods: {<method_name>: callable} where callable signature is (request, msg) \n"
+++	":type methods: dict\n"
+++);
+++
+++static PyObject *ubus_python_add(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	// arguments
+++	PyObject *object_name = NULL;
+++	PyObject *methods= NULL;
+++	static char *kwlist[] = {"object_name", "methods",  NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &object_name, &methods)){
+++		return NULL;
+++	}
+++
+++	// test arguments
+++	if (!PyString_Check(object_name)) {
+++		PyErr_Format(PyExc_TypeError, MSG_ADD_SIGNATURE_INVALID);
+++		return NULL;
+++	}
+++
+++	if (!test_methods_argument(methods)) {
+++		PyErr_Format(PyExc_TypeError, MSG_ADD_SIGNATURE_INVALID);
+++		return NULL;
+++	}
+++
+++	// allocate the object
+++	ubus_Object *object = calloc(1, sizeof(ubus_Object));
+++	if (!object) {
+++		PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++		return NULL;
+++	}
+++	object->methods = methods;
+++
+++	// set the object
+++	object->object.name = PyString_AsString(object_name);
+++	object->object.n_methods = PyDict_Size(methods);
+++
+++	if (object->object.n_methods > 0) {
+++		struct ubus_method *ubus_methods = calloc(object->object.n_methods, sizeof(struct ubus_method));
+++		if (!ubus_methods) {
+++			free(object);
+++			PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++			return NULL;
+++		}
+++
+++		PyObject *method_name = NULL, *value = NULL;
+++		Py_ssize_t pos = 0;
+++		// Iterate through methods
+++		for (int i = 0; PyDict_Next(methods, &pos, &method_name, &value); i++) {
+++			ubus_methods[i].name = PyString_AsString(method_name);
+++			ubus_methods[i].handler = ubus_python_method_handler;
+++
+++			// alocate and set policy objects
+++			PyObject *signature = PyDict_GetItemString(value, "signature");
+++			Py_ssize_t signature_size = PyDict_Size(signature);
+++			struct blobmsg_policy *policy = calloc(signature_size, sizeof(struct blobmsg_policy));
+++			if (!policy) {
+++				// dealloc allocated data
+++				free_ubus_object(object);
+++				PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++				return NULL;
+++			}
+++			Py_ssize_t sig_pos = 0;
+++			PyObject *signature_name = NULL, *signature_type = NULL;
+++			for (int j = 0; PyDict_Next(signature, &sig_pos, &signature_name, &signature_type); j++) {
+++				policy[j].name = PyString_AsString(signature_name);
+++				policy[j].type = PyInt_AsLong(signature_type);
+++			}
+++			ubus_methods[i].policy = policy;
+++			ubus_methods[i].n_policy = signature_size;
+++		}
+++
+++		// assign methods
+++		object->object.methods = ubus_methods;
+++	}
+++
+++	object->object.type = calloc(1, sizeof(struct ubus_object_type));
+++	if (!object->object.type) {
+++		free_ubus_object(object);
+++		PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++		return NULL;
+++	}
+++	object->object.type->name = PyString_AsString(object_name);
+++	object->object.type->methods = object->object.methods;
+++	object->object.type->n_methods = object->object.n_methods;
+++
+++	// add object to object array to be deallocated later
+++	ubus_Object **new_objects = realloc(objects,
+++			(objects_size + 1) * sizeof(*objects));
+++	if (!new_objects) {
+++		// dealloc the object
+++		free_ubus_object(object);
+++		PyErr_Format(PyExc_MemoryError, MSG_ALLOCATION_FAILS);
+++		return NULL;
+++	}
+++	objects = new_objects;
+++	objects[objects_size++] = object;
+++
+++	int ret = ubus_add_object(ctx, &object->object);
+++	if (ret) {
+++		// deallocate object on failure
+++		objects_size--;  // no need to realloc the whole array
+++		free_ubus_object(object);
+++
+++		PyErr_Format(
+++				PyExc_RuntimeError,
+++				"ubus error occured: %s", ubus_strerror(ret)
+++		);
+++		return NULL;
+++	}
+++
+++	// put arguments into alloc list (used for reference counting)
+++	if (PyList_Append(python_alloc_list, object_name)) {
+++		ubus_remove_object(ctx, &object->object);
+++		free_ubus_object(object);
+++		return NULL;
+++	}
+++
+++	if (PyList_Append(python_alloc_list, methods)) {
+++		ubus_remove_object(ctx, &object->object);
+++		free_ubus_object(object);
+++		PyEval_CallMethod(python_alloc_list, "pop", "");
+++		return NULL;
+++	}
+++
+++	Py_INCREF(Py_None);
+++	return Py_None;
+++}
+++
+++static void ubus_python_objects_handler(struct ubus_context *c, struct ubus_object_data *o, void *p)
+++{
+++	// should be a single instance for all the objects
+++	PyObject *objects = (PyObject *)p;
+++
+++	PyObject *str_signatures = PyString_FromString("{");
+++	if (!str_signatures) {
+++		return;
+++	}
+++
+++	bool first = true;
+++	if (o->signature) {
+++		struct blob_attr *cur;
+++		int rem = 0;
+++		blob_for_each_attr(cur, o->signature, rem) {
+++			char *s = blobmsg_format_json(cur, false);
+++			if (!s) {
+++				goto object_handler_cleanup;
+++			}
+++			PyObject *str_signature = NULL;
+++			if (first) {
+++				first = false;
+++				str_signature = PyString_FromString(s);
+++			} else {
+++				str_signature = PyString_FromFormat(" ,%s", s);
+++			}
+++			free(s);
+++			if (!str_signature) {
+++				goto object_handler_cleanup;
+++			}
+++			PyString_Concat(&str_signatures, str_signature);
+++			Py_DECREF(str_signature);
+++			if (!str_signatures) {
+++				return;  //str_signatures already discarded in PyString_Concat
+++			}
+++		}
+++	}
+++
+++	PyObject *closing_bracket = PyString_FromString("}");
+++	PyString_Concat(&str_signatures, closing_bracket);
+++	Py_DECREF(closing_bracket);
+++	if (!str_signatures) {
+++		return;
+++	}
+++
+++	// convert json string to json
+++	PyObject *json_signatures = perform_json_function(LOADS, str_signatures);
+++	if (!json_signatures) {
+++		goto object_handler_cleanup;
+++	}
+++
+++	// Add it to dict object
+++	PyObject *path = PyUnicode_FromString(o->path);
+++	if (!path) {
+++		goto object_handler_cleanup;
+++	}
+++	PyDict_SetItem(objects, path, json_signatures);  // we don't care about retval here
+++	Py_DECREF(path);
+++
+++object_handler_cleanup:
+++	Py_DECREF(str_signatures);
+++
+++	// Clear python exceptions
+++	PyErr_Clear();
+++}
+++
+++PyDoc_STRVAR(
+++	connect_objects_doc,
+++	"objects(path='*')\n"
+++	"\n"
+++	"Prints all objects present on ubus\n"
+++	"\n"
+++	":param path: only object which match the given path \n"
+++	":type path: str\n"
+++	":return: {<object_path>: {{<function_name>: <function_signature>}, ...}, ...} \n"
+++	":rtype: dict\n"
+++);
+++
+++static PyObject *ubus_python_objects(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	char *ubus_path = NULL;
+++	static char *kwlist[] = {"path", NULL};
+++	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &ubus_path)){
+++		return NULL;
+++	}
+++
+++	ubus_path = ubus_path ? ubus_path : "*";
+++
+++	PyObject *res = PyDict_New();
+++	if (!res) {
+++		return NULL;
+++	}
+++
+++	int retval = ubus_lookup(ctx, ubus_path, ubus_python_objects_handler, res);
+++	switch (retval) {
+++		case UBUS_STATUS_OK:
+++		case UBUS_STATUS_NOT_FOUND:
+++			break;
+++		default:
+++			Py_DECREF(res);
+++			PyErr_Format(
+++					PyExc_RuntimeError,
+++					"ubus error occured: %s", ubus_strerror(retval)
+++			);
+++			return NULL;
+++	}
+++
+++	return res;
+++}
+++
+++static void ubus_python_call_handler(struct ubus_request *req, int type, struct blob_attr *msg)
+++{
+++	assert(type == UBUS_MSG_DATA);
+++
+++	PyObject **results = (PyObject **)req->priv;
+++	if (!*results) {
+++		// error has occured in some previous call -> exit
+++		return;
+++	}
+++
+++	if (!msg) {
+++		PyErr_Format(PyExc_RuntimeError, "No data in call hander");
+++		goto call_handler_cleanup;
+++	}
+++
+++	// convert message do python json object
+++	char *str = blobmsg_format_json(msg, true);
+++	if (!str) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_JSON_FROM_UBUS_FAILED);
+++		goto call_handler_cleanup;
+++	}
+++	PyObject *data = PyString_FromString(str);
+++	free(str);
+++	if (!data) {
+++		goto call_handler_cleanup;
+++	}
+++	PyObject *data_object = perform_json_function(LOADS, data);
+++	Py_DECREF(data);
+++	if (!data_object) {
+++		goto call_handler_cleanup;
+++	}
+++
+++	// append to results
+++	int failed = PyList_Append(*results, data_object);
+++	Py_DECREF(data_object);
+++	if (failed) {
+++		goto call_handler_cleanup;
+++	}
+++
+++	return;
+++
+++	call_handler_cleanup:
+++
+++	// clear the result
+++	Py_DECREF(*results);
+++	results = NULL;
+++}
+++
+++PyDoc_STRVAR(
+++	connect_call_doc,
+++	"call(object, method, arguments, timeout=0)\n"
+++	"\n"
+++	"Calls object's method on ubus.\n"
+++	"\n"
+++	":param object: name of the object\n"
+++	":type object: str\n"
+++	":param method: name of the method\n"
+++	":type method: str\n"
+++	":param arguments: arguments of the method (should be JSON serialisable).\n"
+++	":type argument: dict\n"
+++	":param timeout: timeout in ms (0 = wait forever)\n"
+++	":type timeout: int\n"
+++);
+++
+++static PyObject *ubus_python_call(PyObject *module, PyObject *args, PyObject *kwargs)
+++{
+++	if (!CONNECTED) {
+++		PyErr_Format(PyExc_RuntimeError, MSG_NOT_CONNECTED);
+++		return NULL;
+++	}
+++
+++	char *object = NULL, *method = NULL;
+++	int timeout = 0;
+++	PyObject *arguments = NULL;
+++	static char *kwlist[] = {"object", "method", "arguments", "timeout", NULL};
+++	if (!PyArg_ParseTupleAndKeywords(
+++				args, kwargs, "ssO|i", kwlist, &object, &method, &arguments, &timeout)){
+++		return NULL;
+++	}
+++	if (timeout < 0) {
+++		PyErr_Format(PyExc_TypeError, "timeout can't be lower than 0");
+++		return NULL;
+++	}
+++
+++	uint32_t id = 0;
+++	int retval = ubus_lookup_id(ctx, object, &id);
+++	if (retval != UBUS_STATUS_OK) {
+++		PyErr_Format(PyExc_RuntimeError, "Object '%s' was not found.", object);
+++		return NULL;
+++	}
+++
+++	// Call python function json.dumps
+++	PyObject *json_arguments = perform_json_function(DUMPS, arguments);
+++	if (!json_arguments) {
+++		return NULL;
+++	}
+++
+++	// put data into buffer
+++	blob_buf_init(&python_buf, 0);
+++	bool res = blobmsg_add_json_from_string(&python_buf, PyString_AsString(json_arguments));
+++	Py_DECREF(json_arguments);
+++	if (!res) {
+++		PyErr_Format(PyExc_TypeError, MSG_JSON_TO_UBUS_FAILED);
+++		return NULL;
+++	}
+++
+++	PyObject *results = PyList_New(0);
+++	if (!results) {
+++		return NULL;
+++	}
+++
+++	retval = ubus_invoke(
+++			ctx, id, method, python_buf.head, ubus_python_call_handler, &results, timeout);
+++
+++	if (retval != UBUS_STATUS_OK) {
+++		Py_XDECREF(results);
+++		PyErr_Format(
+++				PyExc_RuntimeError,
+++				"ubus error occured: %s", ubus_strerror(retval)
+++		);
+++		return NULL;
+++	}
+++
+++	// Note that results might be NULL indicating that something went wrong in the handler
+++	return results;
+++}
+++
+++static PyMethodDef ubus_methods[] = {
+++	{"disconnect", (PyCFunction)ubus_python_disconnect, METH_VARARGS|METH_KEYWORDS, disconnect_doc},
+++	{"connect", (PyCFunction)ubus_python_connect, METH_VARARGS|METH_KEYWORDS, connect_doc},
+++	{"get_connected", (PyCFunction)ubus_python_get_connected, METH_NOARGS, get_connected_doc},
+++	{"get_socket_path", (PyCFunction)ubus_python_get_socket_path, METH_NOARGS, get_socket_path_doc},
+++	{"send", (PyCFunction)ubus_python_send, METH_VARARGS|METH_KEYWORDS, connect_send_doc},
+++	{"listen", (PyCFunction)ubus_python_listen, METH_VARARGS, connect_listen_doc},
+++	{"loop", (PyCFunction)ubus_python_loop, METH_VARARGS|METH_KEYWORDS, connect_loop_doc},
+++	{"add", (PyCFunction)ubus_python_add, METH_VARARGS|METH_KEYWORDS, connect_add_doc},
+++	{"objects", (PyCFunction)ubus_python_objects, METH_VARARGS|METH_KEYWORDS, connect_objects_doc},
+++	{"call", (PyCFunction)ubus_python_call, METH_VARARGS|METH_KEYWORDS, connect_call_doc},
+++	{NULL}
+++};
+++
+++PyMODINIT_FUNC initubus(void) {
+++	if (PyType_Ready(&ubus_ResponseHandlerType)) {
+++		return;
+++	}
+++
+++	json_module = PyImport_ImportModule("json");
+++	if (!json_module) {
+++		return;
+++	}
+++
+++	PyObject *module = ubus_python_module_init();
+++	if (!module) {
+++		return;
+++	}
+++
+++	Py_INCREF(&ubus_ResponseHandlerType);
+++	PyModule_AddObject(module, "__ResponseHandler", (PyObject *)&ubus_ResponseHandlerType);
+++
+++	/* export ubus json types */
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_UNSPEC);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_ARRAY);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_TABLE);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_STRING);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT64);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT32);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT16);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_INT8);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_DOUBLE);
+++	PyModule_AddIntMacro(module, BLOBMSG_TYPE_BOOL);
+++}
 -- 
 2.18.0