Skip to content
Snippets Groups Projects
Verified Commit 04f00e53 authored by Michal Hrusecky's avatar Michal Hrusecky :mouse:
Browse files

ubus: Add content that went missing during rebase

parent a2daefc9
No related branches found
No related tags found
No related merge requests found
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment