Skip to content

Commit

Permalink
extension: add ExtensionPlugin class
Browse files Browse the repository at this point in the history
This patch introduces new class ExtensionPlugin, which is wrapper around
libyang extension plugin, which allows user to define custom action for
parsing, compiling, and freeing parsed or compiled extensions.

Custom actions can also raise a new type of exception
LibyangExtensionError, which allows proper translation of exception to
libyang error codes and logging of error message

Signed-off-by: Stefan Gula <steweg@gmail.com>
  • Loading branch information
steweg committed Aug 5, 2024
1 parent 2cdb2f6 commit acf7ee0
Show file tree
Hide file tree
Showing 10 changed files with 517 additions and 8 deletions.
57 changes: 57 additions & 0 deletions cffi/cdefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ enum ly_stmt {
LY_STMT_ARG_VALUE
};

#define LY_STMT_OP_MASK ...
#define LY_STMT_DATA_NODE_MASK ...
#define LY_STMT_NODE_MASK ...

#define LY_LOLOG ...
#define LY_LOSTORE ...
#define LY_LOSTORE_LAST ...
Expand Down Expand Up @@ -354,6 +358,7 @@ LY_ERR lys_print_module(struct ly_out *, const struct lys_module *, LYS_OUTFORMA
#define LYS_PRINT_SHRINK ...

struct lys_module {
struct ly_ctx *ctx;
const char *name;
const char *revision;
const char *ns;
Expand Down Expand Up @@ -423,6 +428,22 @@ struct lysc_node_container {
struct lysc_node_notif *notifs;
};

struct lysp_stmt {
const char *stmt;
const char *arg;
LY_VALUE_FORMAT format;
void *prefix_data;
struct lysp_stmt *next;
struct lysp_stmt *child;
uint16_t flags;
enum ly_stmt kw;
};

struct lysp_ext_substmt {
enum ly_stmt stmt;
...;
};

struct lysp_ext_instance {
const char *name;
const char *argument;
Expand Down Expand Up @@ -1261,6 +1282,42 @@ struct lyd_leafref_links_rec {

LY_ERR lyd_leafref_get_links(const struct lyd_node_term *e, const struct lyd_leafref_links_rec **);
LY_ERR lyd_leafref_link_node_tree(struct lyd_node *);
const char *lyplg_ext_stmt2str(enum ly_stmt stmt);
const struct lysp_module *lyplg_ext_parse_get_cur_pmod(const struct lysp_ctx *);
struct ly_ctx *lyplg_ext_compile_get_ctx(const struct lysc_ctx *);
void lyplg_ext_parse_log(const struct lysp_ctx *, const struct lysp_ext_instance *, LY_LOG_LEVEL, LY_ERR, const char *, ...);
void lyplg_ext_compile_log(const struct lysc_ctx *, const struct lysc_ext_instance *, LY_LOG_LEVEL, LY_ERR, const char *, ...);
LY_ERR lyplg_ext_parse_extension_instance(struct lysp_ctx *, struct lysp_ext_instance *);
LY_ERR lyplg_ext_compile_extension_instance(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
void lyplg_ext_pfree_instance_substatements(const struct ly_ctx *ctx, struct lysp_ext_substmt *substmts);
void lyplg_ext_cfree_instance_substatements(const struct ly_ctx *ctx, struct lysc_ext_substmt *substmts);
typedef LY_ERR (*lyplg_ext_parse_clb)(struct lysp_ctx *, struct lysp_ext_instance *);
typedef LY_ERR (*lyplg_ext_compile_clb)(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
typedef void (*lyplg_ext_parse_free_clb)(const struct ly_ctx *, struct lysp_ext_instance *);
typedef void (*lyplg_ext_compile_free_clb)(const struct ly_ctx *, struct lysc_ext_instance *);
struct lyplg_ext {
const char *id;
lyplg_ext_parse_clb parse;
lyplg_ext_compile_clb compile;
lyplg_ext_parse_free_clb pfree;
lyplg_ext_compile_free_clb cfree;
...;
};

struct lyplg_ext_record {
const char *module;
const char *revision;
const char *name;
struct lyplg_ext plugin;
...;
};

#define LYPLG_EXT_API_VERSION ...
LY_ERR lyplg_add_extension_plugin(struct ly_ctx *, uint32_t, const struct lyplg_ext_record *);
extern "Python" LY_ERR lypy_lyplg_ext_parse_clb(struct lysp_ctx *, struct lysp_ext_instance *);
extern "Python" LY_ERR lypy_lyplg_ext_compile_clb(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
extern "Python" void lypy_lyplg_ext_parse_free_clb(const struct ly_ctx *, struct lysp_ext_instance *);
extern "Python" void lypy_lyplg_ext_compile_free_clb(const struct ly_ctx *, struct lysc_ext_instance *);

/* from libc, needed to free allocated strings */
void free(void *);
6 changes: 6 additions & 0 deletions libyang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,13 @@
UnitsRemoved,
schema_diff,
)
from .extension import ExtensionPlugin, LibyangExtensionError
from .keyed_list import KeyedList
from .log import configure_logging
from .schema import (
Extension,
ExtensionCompiled,
ExtensionParsed,
Feature,
IfAndFeatures,
IfFeature,
Expand Down Expand Up @@ -143,6 +146,9 @@
"EnumRemoved",
"Extension",
"ExtensionAdded",
"ExtensionCompiled",
"ExtensionParsed",
"ExtensionPlugin",
"ExtensionRemoved",
"Feature",
"IfAndFeatures",
Expand Down
4 changes: 1 addition & 3 deletions libyang/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ def get_module_data(
"""
if self._module_data_clb is None:
return "", None
ret = self._module_data_clb(
mod_name, mod_rev, submod_name, submod_rev
)
ret = self._module_data_clb(mod_name, mod_rev, submod_name, submod_rev)
if ret is None:
return None
fmt_str, module_data = ret
Expand Down
216 changes: 216 additions & 0 deletions libyang/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Copyright (c) 2018-2019 Robin Jarry
# Copyright (c) 2020 6WIND S.A.
# Copyright (c) 2021 RACOM s.r.o.
# SPDX-License-Identifier: MIT

from typing import Callable, Optional

from _libyang import ffi, lib
from .context import Context
from .log import get_libyang_level
from .schema import ExtensionCompiled, ExtensionParsed, Module
from .util import LibyangError, c2str, str2c


# -------------------------------------------------------------------------------------
extensions_plugins = {}


class LibyangExtensionError(LibyangError):
def __init__(self, message: str, ret: int, log_level: int) -> None:
super().__init__(message)
self.ret = ret
self.log_level = log_level


@ffi.def_extern(name="lypy_lyplg_ext_parse_clb")
def libyang_c_lyplg_ext_parse_clb(pctx, pext):
plugin = extensions_plugins[pext.record.plugin]
module_cdata = lib.lyplg_ext_parse_get_cur_pmod(pctx).mod
context = Context(cdata=module_cdata.ctx)
module = Module(context, module_cdata)
parsed_ext = ExtensionParsed(context, pext, module)
plugin.set_parse_ctx(pctx)
try:
plugin.parse_clb(module, parsed_ext)
return lib.LY_SUCCESS
except LibyangExtensionError as e:
ly_level = get_libyang_level(e.log_level)
if ly_level is None:
ly_level = lib.LY_EPLUGIN
e_str = str(e)
plugin.add_error_message(e_str)
lib.lyplg_ext_parse_log(pctx, pext, ly_level, e.ret, str2c(e_str))
return e.ret


@ffi.def_extern(name="lypy_lyplg_ext_compile_clb")
def libyang_c_lyplg_ext_compile_clb(cctx, pext, cext):
plugin = extensions_plugins[pext.record.plugin]
context = Context(cdata=lib.lyplg_ext_compile_get_ctx(cctx))
module = Module(context, cext.module)
parsed_ext = ExtensionParsed(context, pext, module)
compiled_ext = ExtensionCompiled(context, cext)
plugin.set_compile_ctx(cctx)
try:
plugin.compile_clb(parsed_ext, compiled_ext)
return lib.LY_SUCCESS
except LibyangExtensionError as e:
ly_level = get_libyang_level(e.log_level)
if ly_level is None:
ly_level = lib.LY_EPLUGIN
e_str = str(e)
plugin.add_error_message(e_str)
lib.lyplg_ext_compile_log(cctx, cext, ly_level, e.ret, str2c(e_str))
return e.ret


@ffi.def_extern(name="lypy_lyplg_ext_parse_free_clb")
def libyang_c_lyplg_ext_parse_free_clb(ctx, pext):
plugin = extensions_plugins[pext.record.plugin]
context = Context(cdata=ctx)
parsed_ext = ExtensionParsed(context, pext, None)
plugin.parse_free_clb(parsed_ext)


@ffi.def_extern(name="lypy_lyplg_ext_compile_free_clb")
def libyang_c_lyplg_ext_compile_free_clb(ctx, cext):
plugin = extensions_plugins[getattr(cext, "def").plugin]
context = Context(cdata=ctx)
compiled_ext = ExtensionCompiled(context, cext)
plugin.compile_free_clb(compiled_ext)


class ExtensionPlugin:
ERROR_SUCCESS = lib.LY_SUCCESS
ERROR_MEM = lib.LY_EMEM
ERROR_INVALID_INPUT = lib.LY_EINVAL
ERROR_NOT_VALID = lib.LY_EVALID
ERROR_DENIED = lib.LY_EDENIED
ERROR_NOT = lib.LY_ENOT

def __init__(
self,
module_name: str,
name: str,
id_str: str,
context: Optional[Context] = None,
parse_clb: Optional[Callable[[Module, ExtensionParsed], None]] = None,
compile_clb: Optional[
Callable[[ExtensionParsed, ExtensionCompiled], None]
] = None,
parse_free_clb: Optional[Callable[[ExtensionParsed], None]] = None,
compile_free_clb: Optional[Callable[[ExtensionCompiled], None]] = None,
) -> None:
"""
Set the callback functions, which will be called if libyang will be processing
given extension defined by name from module defined by module_name.
:arg self:
This instance of extension plugin
:arg module_name:
The name of module in which the extension is defined
:arg name:
The name of extension itself
:arg id_str:
The unique ID of extension plugin within the libyang context
:arg context:
The context in which the extension plugin will be used. If set to None,
the plugin will be used for all existing and even future contexts
:arg parse_clb:
The optional callback function of which will be called during extension parsing
Expected arguments are:
module: The module which is being parsed
extension: The exact extension instance
Expected raises:
LibyangExtensionError in case of processing error
:arg compile_clb:
The optional callback function of which will be called during extension compiling
Expected arguments are:
extension_parsed: The parsed extension instance
extension_compiled: The compiled extension instance
Expected raises:
LibyangExtensionError in case of processing error
:arg parse_free_clb
The optional callback function of which will be called during freeing of parsed extension
Expected arguments are:
extension: The parsed extension instance to be freed
:arg compile_free_clb
The optional callback function of which will be called during freeing of compiled extension
Expected arguments are:
extension: The compiled extension instance to be freed
"""
self.context = context
self.module_name = module_name
self.module_name_cstr = str2c(self.module_name)
self.name = name
self.name_cstr = str2c(self.name)
self.id_str = id_str
self.id_cstr = str2c(self.id_str)
self.parse_clb = parse_clb
self.compile_clb = compile_clb
self.parse_free_clb = parse_free_clb
self.compile_free_clb = compile_free_clb
self._error_messages = []
self._pctx = ffi.NULL
self._cctx = ffi.NULL

self.cdata = ffi.new("struct lyplg_ext_record[2]")
self.cdata[0].module = self.module_name_cstr
self.cdata[0].name = self.name_cstr
self.cdata[0].plugin.id = self.id_cstr
if self.parse_clb is not None:
self.cdata[0].plugin.parse = lib.lypy_lyplg_ext_parse_clb
if self.compile_clb is not None:
self.cdata[0].plugin.compile = lib.lypy_lyplg_ext_compile_clb
if self.parse_free_clb is not None:
self.cdata[0].plugin.pfree = lib.lypy_lyplg_ext_parse_free_clb
if self.compile_free_clb is not None:
self.cdata[0].plugin.cfree = lib.lypy_lyplg_ext_compile_free_clb
ret = lib.lyplg_add_extension_plugin(
context.cdata if context is not None else ffi.NULL,
lib.LYPLG_EXT_API_VERSION,
ffi.cast("const void *", self.cdata),
)
if ret != lib.LY_SUCCESS:
raise LibyangError("Unable to add extension plugin")
if self.cdata[0].plugin not in extensions_plugins:
extensions_plugins[self.cdata[0].plugin] = self

def __del__(self) -> None:
if self.cdata[0].plugin in extensions_plugins:
del extensions_plugins[self.cdata[0].plugin]

@staticmethod
def stmt2str(stmt: int) -> str:
return c2str(lib.lyplg_ext_stmt2str(stmt))

def add_error_message(self, err_msg: str) -> None:
self._error_messages.append(err_msg)

def clear_error_messages(self) -> None:
self._error_messages.clear()

def set_parse_ctx(self, pctx) -> None:
self._pctx = pctx

def set_compile_ctx(self, cctx) -> None:
self._cctx = cctx

def parse_substmts(self, ext: ExtensionParsed) -> int:
return lib.lyplg_ext_parse_extension_instance(self._pctx, ext.cdata)

def compile_substmts(self, pext: ExtensionParsed, cext: ExtensionCompiled) -> int:
return lib.lyplg_ext_compile_extension_instance(
self._cctx, pext.cdata, cext.cdata
)

def free_parse_substmts(self, ext: ExtensionParsed) -> None:
lib.lyplg_ext_pfree_instance_substatements(
self.context.cdata, ext.cdata.substmts
)

def free_compile_substmts(self, ext: ExtensionCompiled) -> None:
lib.lyplg_ext_cfree_instance_substatements(
self.context.cdata, ext.cdata.substmts
)
14 changes: 10 additions & 4 deletions libyang/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
}


def get_libyang_level(py_level):
for ly_lvl, py_lvl in LOG_LEVELS.items():
if py_lvl == py_level:
return ly_lvl
return None


@ffi.def_extern(name="lypy_log_cb")
def libyang_c_logging_callback(level, msg, data_path, schema_path, line):
args = [c2str(msg)]
Expand Down Expand Up @@ -50,10 +57,9 @@ def configure_logging(enable_py_logger: bool, level: int = logging.ERROR) -> Non
:arg level:
Python logging level. By default only ERROR messages are stored/logged.
"""
for ly_lvl, py_lvl in LOG_LEVELS.items():
if py_lvl == level:
lib.ly_log_level(ly_lvl)
break
ly_level = get_libyang_level(level)
if ly_level is not None:
lib.ly_log_level(ly_level)
if enable_py_logger:
lib.ly_log_options(lib.LY_LOLOG | lib.LY_LOSTORE)
lib.ly_set_log_clb(lib.lypy_log_cb)
Expand Down
Loading

0 comments on commit acf7ee0

Please sign in to comment.