diff --git a/python/libknot/__init__.py.in b/python/libknot/__init__.py.in index 59843404f99b79635f3f6908ad505be560556fff..6faa5f5e91d22504f264215cf5cba1a11d2b5c80 100644 --- a/python/libknot/__init__.py.in +++ b/python/libknot/__init__.py.in @@ -1,3 +1,80 @@ """Python libknot interface.""" -LIBKNOT_VERSION = "@libknot_SOVERSION@" +import ctypes +import sys + + +class KnotLookup(ctypes.Structure): + """Libknot lookup return structure.""" + + _fields_ = [('id', ctypes.c_int), ('name', ctypes.c_char_p)] + + +class KnotRdataDescriptor(ctypes.Structure): + """Rdata descriptor structure.""" + + _fields_ = [('block_types', ctypes.c_int * 8), ('name', ctypes.c_char_p)] + + +class Knot(object): + """Basic libknot interface.""" + + LIBKNOT = None + LIBKNOT_VERSION = "@libknot_SOVERSION@" + + RCODE_NAMES = None + + STRERROR = None + RDATA_DESC = None + + @classmethod + def __init__(cls, path: str = None) -> None: + """Loads shared libknot library. + An explicit library path can be specified. + """ + + if cls.LIBKNOT: + return + + if path is None: + version = "" + try: + version = ".%u" % int(cls.LIBKNOT_VERSION) + except Exception: + pass + + if sys.platform == "darwin": + path = "libknot%s.dylib" % version + else: + path = "libknot.so%s" % version + + cls.LIBKNOT = ctypes.cdll.LoadLibrary(path) + + cls.RCODE_NAMES = (KnotLookup * 32).in_dll(cls.LIBKNOT, "knot_rcode_names") + + cls.STRERROR = cls.LIBKNOT.knot_strerror + cls.STRERROR.restype = ctypes.c_char_p + cls.STRERROR.argtypes = [ctypes.c_int] + + cls.RDATA_DESC = cls.LIBKNOT.knot_get_rdata_descriptor + cls.RDATA_DESC.restype = ctypes.POINTER(KnotRdataDescriptor) + cls.RDATA_DESC.argtypes = [ctypes.c_ushort] + + @classmethod + def rtype_str(cls, rtype: int) -> str: + """Returns RRTYPE in text form.""" + + descr = cls.RDATA_DESC(rtype).contents.name + if descr: + return descr.decode() + else: + return "TYPE%i" % rtype + + @classmethod + def rcode_str(cls, rcode: int) -> str: + """Returns RCODE in text form.""" + + for item in cls.RCODE_NAMES: + if item.name and item.id == rcode: + return item.name.decode() + return "RCODE%i" % rcode diff --git a/python/libknot/control.py b/python/libknot/control.py index ced1be62896e767054788b64b9066f7a38f17c79..b618bca1f8641cd85e4986ff1a72f3df5f55de32 100644 --- a/python/libknot/control.py +++ b/python/libknot/control.py @@ -1,117 +1,49 @@ """Libknot server control interface wrapper. -Example: - import json - from libknot.control import * +## Example ## - #load_lib("/usr/lib/libknot.so") +import json +import libknot.control - ctl = KnotCtl() - ctl.connect("/var/run/knot/knot.sock") +#import libknot +#libknot.Knot("/usr/lib/libknot.so") - try: - ctl.send_block(cmd="conf-begin") - resp = ctl.receive_block() +ctl = libknot.control.KnotCtl() +ctl.connect("/var/run/knot/knot.sock") - ctl.send_block(cmd="conf-set", section="zone", item="domain", data="test") - resp = ctl.receive_block() +try: + ctl.send_block(cmd="conf-begin") + resp = ctl.receive_block() - ctl.send_block(cmd="conf-commit") - resp = ctl.receive_block() + ctl.send_block(cmd="conf-set", section="zone", item="domain", data="test") + resp = ctl.receive_block() - ctl.send_block(cmd="conf-read", section="zone", item="domain") - resp = ctl.receive_block() - print(json.dumps(resp, indent=4)) - finally: - ctl.send(KnotCtlType.END) - ctl.close() -""" - -from ctypes import cdll, c_void_p, c_int, c_char_p, c_uint, byref -from enum import IntEnum -import sys - -CTL_ALLOC = None -CTL_FREE = None -CTL_SET_TIMEOUT = None -CTL_CONNECT = None -CTL_CLOSE = None -CTL_SEND = None -CTL_RECEIVE = None -CTL_ERROR = None - - -def load_lib(path=None): - """Loads the libknot library.""" - - if path is None: - version = "" - try: - from libknot import LIBKNOT_VERSION - version = ".%u" % int(LIBKNOT_VERSION) - except: - pass - - if sys.platform == "darwin": - path = "libknot%s.dylib" % version - else: - path = "libknot.so%s" % version - LIB = cdll.LoadLibrary(path) - - global CTL_ALLOC - CTL_ALLOC = LIB.knot_ctl_alloc - CTL_ALLOC.restype = c_void_p - - global CTL_FREE - CTL_FREE = LIB.knot_ctl_free - CTL_FREE.argtypes = [c_void_p] - - global CTL_SET_TIMEOUT - CTL_SET_TIMEOUT = LIB.knot_ctl_set_timeout - CTL_SET_TIMEOUT.argtypes = [c_void_p, c_int] - - global CTL_CONNECT - CTL_CONNECT = LIB.knot_ctl_connect - CTL_CONNECT.restype = c_int - CTL_CONNECT.argtypes = [c_void_p, c_char_p] - - global CTL_CLOSE - CTL_CLOSE = LIB.knot_ctl_close - CTL_CLOSE.argtypes = [c_void_p] - - global CTL_SEND - CTL_SEND = LIB.knot_ctl_send - CTL_SEND.restype = c_int - CTL_SEND.argtypes = [c_void_p, c_uint, c_void_p] - - global CTL_RECEIVE - CTL_RECEIVE = LIB.knot_ctl_receive - CTL_RECEIVE.restype = c_int - CTL_RECEIVE.argtypes = [c_void_p, c_void_p, c_void_p] - - global CTL_ERROR - CTL_ERROR = LIB.knot_strerror - CTL_ERROR.restype = c_char_p - CTL_ERROR.argtypes = [c_int] + ctl.send_block(cmd="conf-commit") + resp = ctl.receive_block() + ctl.send_block(cmd="conf-read", section="zone", item="domain") + resp = ctl.receive_block() + print(json.dumps(resp, indent=4)) +finally: + ctl.send(libknot.control.KnotCtlType.END) + ctl.close() +""" -class KnotCtlError(Exception): - """Libknot server control error.""" +import ctypes +import enum +import warnings +import libknot - def __init__(self, message, data=None): - """ - @type message: str - @type data: KnotCtlData - """ - self.message = message - self.data = data +def load_lib(path: str = None) -> None: + """Compatibility wrapper.""" - def __str__(self): - return "%s (data: %s)" % (self.message, self.data) + libknot.Knot(path) + warnings.warn("libknot.control.load_lib() is deprecated, use libknot.Knot() instead", \ + category=Warning, stacklevel=2) -class KnotCtlType(IntEnum): +class KnotCtlType(enum.IntEnum): """Libknot server control data unit types.""" END = 0 @@ -120,7 +52,7 @@ class KnotCtlType(IntEnum): BLOCK = 3 -class KnotCtlDataIdx(IntEnum): +class KnotCtlDataIdx(enum.IntEnum): """Libknot server control data unit indices.""" COMMAND = 0 @@ -140,12 +72,14 @@ class KnotCtlDataIdx(IntEnum): class KnotCtlData(object): """Libknot server control data unit.""" - DataArray = c_char_p * len(KnotCtlDataIdx) + DataArray = ctypes.c_char_p * len(KnotCtlDataIdx) - def __init__(self): + def __init__(self) -> None: self.data = self.DataArray() - def __str__(self): + def __str__(self) -> str: + """Returns data unit in text form.""" + string = str() for idx in KnotCtlDataIdx: @@ -156,106 +90,123 @@ class KnotCtlData(object): return string - def __getitem__(self, index): - """Data unit item getter. - - @type index: KnotCtlDataIdx - @rtype: str - """ + def __getitem__(self, index: KnotCtlDataIdx) -> str: + """Data unit item getter.""" value = self.data[index] if not value: value = str() return value if isinstance(value, str) else value.decode() - def __setitem__(self, index, value): - """Data unit item setter. + def __setitem__(self, index: KnotCtlDataIdx, value: str) -> None: + """Data unit item setter.""" - @type index: KnotCtlDataIdx - @type value: str - """ + self.data[index] = ctypes.c_char_p(value.encode()) if value else ctypes.c_char_p() + + +class KnotCtlError(Exception): + """Libknot server control error.""" + + def __init__(self, message: str, data: KnotCtlData = None) -> None: + super().__init__() + self.message = message + self.data = data + + def __str__(self) -> str: + return "%s (data: %s)" % (self.message, self.data) - self.data[index] = c_char_p(value.encode()) if value else c_char_p() class KnotCtl(object): """Libknot server control interface.""" - def __init__(self): - if not CTL_ALLOC: - load_lib() - self.obj = CTL_ALLOC() + ALLOC = None + FREE = None + SET_TIMEOUT = None + CONNECT = None + CLOSE = None + SEND = None + RECEIVE = None + + def __init__(self) -> None: + """Initializes a control interface instance.""" + + if not KnotCtl.ALLOC: + libknot.Knot() + + KnotCtl.ALLOC = libknot.Knot.LIBKNOT.knot_ctl_alloc + KnotCtl.ALLOC.restype = ctypes.c_void_p - def __del__(self): - CTL_FREE(self.obj) + KnotCtl.FREE = libknot.Knot.LIBKNOT.knot_ctl_free + KnotCtl.FREE.argtypes = [ctypes.c_void_p] - def set_timeout(self, timeout): - """Sets control socket operations timeout in seconds. + KnotCtl.SET_TIMEOUT = libknot.Knot.LIBKNOT.knot_ctl_set_timeout + KnotCtl.SET_TIMEOUT.argtypes = [ctypes.c_void_p, ctypes.c_int] - @type timeout: int - """ + KnotCtl.CONNECT = libknot.Knot.LIBKNOT.knot_ctl_connect + KnotCtl.CONNECT.restype = ctypes.c_int + KnotCtl.CONNECT.argtypes = [ctypes.c_void_p, ctypes.c_char_p] - CTL_SET_TIMEOUT(self.obj, timeout * 1000) + KnotCtl.CLOSE = libknot.Knot.LIBKNOT.knot_ctl_close + KnotCtl.CLOSE.argtypes = [ctypes.c_void_p] - def connect(self, path): - """Connect to a specified control UNIX socket. + KnotCtl.SEND = libknot.Knot.LIBKNOT.knot_ctl_send + KnotCtl.SEND.restype = ctypes.c_int + KnotCtl.SEND.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p] - @type path: str - """ + KnotCtl.RECEIVE = libknot.Knot.LIBKNOT.knot_ctl_receive + KnotCtl.RECEIVE.restype = ctypes.c_int + KnotCtl.RECEIVE.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - ret = CTL_CONNECT(self.obj, path.encode()) + self.obj = KnotCtl.ALLOC() + + def __del__(self) -> None: + """Deallocates control interface instance.""" + + KnotCtl.FREE(self.obj) + + def set_timeout(self, timeout: int) -> None: + """Sets control socket operations timeout in seconds.""" + + KnotCtl.SET_TIMEOUT(self.obj, timeout * 1000) + + def connect(self, path: str) -> None: + """Connect to a specified control UNIX socket.""" + + ret = KnotCtl.CONNECT(self.obj, path.encode()) if ret != 0: - err = CTL_ERROR(ret) + err = libknot.Knot.STRERROR(ret) raise KnotCtlError(err if isinstance(err, str) else err.decode()) - def close(self): + def close(self) -> None: """Disconnects from the current control socket.""" - CTL_CLOSE(self.obj) - - def send(self, data_type, data=None): - """Sends a data unit to the connected control socket. + KnotCtl.CLOSE(self.obj) - @type data_type: KnotCtlType - @type data: KnotCtlData - """ + def send(self, data_type: KnotCtlType, data: KnotCtlData = None) -> None: + """Sends a data unit to the connected control socket.""" - ret = CTL_SEND(self.obj, data_type, - data.data if data else c_char_p()) + ret = KnotCtl.SEND(self.obj, data_type, + data.data if data else ctypes.c_char_p()) if ret != 0: - err = CTL_ERROR(ret) + err = libknot.Knot.STRERROR(ret) raise KnotCtlError(err if isinstance(err, str) else err.decode()) - def receive(self, data=None): - """Receives a data unit from the connected control socket. - - @type data: KnotCtlData - @rtype: KnotCtlType - """ + def receive(self, data: KnotCtlData = None) -> KnotCtlType: + """Receives a data unit from the connected control socket.""" - data_type = c_uint() - ret = CTL_RECEIVE(self.obj, byref(data_type), - data.data if data else c_char_p()) + data_type = ctypes.c_uint() + ret = KnotCtl.RECEIVE(self.obj, ctypes.byref(data_type), + data.data if data else ctypes.c_char_p()) if ret != 0: - err = CTL_ERROR(ret) + err = libknot.Knot.STRERROR(ret) raise KnotCtlError(err if isinstance(err, str) else err.decode()) return KnotCtlType(data_type.value) - def send_block(self, cmd, section=None, item=None, identifier=None, zone=None, - owner=None, ttl=None, rtype=None, data=None, flags=None, - filter=None): - """Sends a control query block. - - @type cmd: str - @type section: str - @type item: str - @type identifier: str - @type zone: str - @type owner: str - @type ttl: str - @type rtype: str - @type data: str - @type filter: str - """ + def send_block(self, cmd: str, section: str = None, item: str = None, + identifier: str = None, zone: str = None, owner: str = None, + ttl: str = None, rtype: str = None, data: str = None, + flags: str = None, filters: str = None) -> None: + """Sends a control query block.""" query = KnotCtlData() query[KnotCtlDataIdx.COMMAND] = cmd @@ -268,7 +219,7 @@ class KnotCtl(object): query[KnotCtlDataIdx.TYPE] = rtype query[KnotCtlDataIdx.DATA] = data query[KnotCtlDataIdx.FLAGS] = flags - query[KnotCtlDataIdx.FILTER] = filter + query[KnotCtlDataIdx.FILTER] = filters self.send(KnotCtlType.DATA, query) self.send(KnotCtlType.BLOCK) @@ -372,11 +323,8 @@ class KnotCtl(object): else: section_level[section][item] = data - def receive_stats(self): - """Receives statistics answer and returns it as a structured dictionary. - - @rtype: dict - """ + def receive_stats(self) -> dict: + """Receives statistics answer and returns it as a structured dictionary.""" out = dict() err_reply = None @@ -401,11 +349,8 @@ class KnotCtl(object): return out - def receive_block(self): - """Receives a control answer and returns it as a structured dictionary. - - @rtype: dict - """ + def receive_block(self) -> dict: + """Receives a control answer and returns it as a structured dictionary.""" out = dict() err_reply = None diff --git a/python/setup.py.in b/python/setup.py.in index a5e69c8a3a9fc1794d165f1e000203b52f6b6a66..d40702e6971d041ca22b0c221754a25abc0a9025 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -21,13 +21,9 @@ setuptools.setup( 'Intended Audience :: System Administrators', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: Name Service (DNS)', 'Topic :: Software Development :: Libraries', 'Topic :: System :: Systems Administration', - ] + ], + python_requires='>=3.5', )