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 <[email protected]>
  • Loading branch information
steweg committed Aug 4, 2024
1 parent f14116c commit 91b56e5
Show file tree
Hide file tree
Showing 6 changed files with 381 additions and 5 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 @@ -1256,6 +1277,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
178 changes: 178 additions & 0 deletions libyang/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# 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:
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
18 changes: 17 additions & 1 deletion libyang/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ def __str__(self):
class ExtensionParsed(Extension):
__slots__ = ("module_parent",)

def __init__(self, context: "libyang.Context", cdata, module_parent: Module = None):
def __init__(self, context: "libyang.Context", cdata, module_parent: Module):
super().__init__(context, cdata)
self.module_parent = module_parent

Expand All @@ -411,6 +411,14 @@ def name(self) -> str:
def module(self) -> Module:
return self._module_from_parsed()

def parent_node(self) -> Optional["PNode"]:
if not bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK):
return None
try:
return PNode.new(self.context, self.cdata.parent, self.module_parent)
except LibyangError:
return None


# -------------------------------------------------------------------------------------
class ExtensionCompiled(Extension):
Expand All @@ -428,6 +436,14 @@ def module(self) -> Module:
raise self.context.error("cannot get module")
return Module(self.context, self.cdata_def.module)

def parent_node(self) -> Optional["SNode"]:
if not bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK):
return None
try:
return SNode.new(self.context, self.cdata.parent)
except LibyangError:
return None


# -------------------------------------------------------------------------------------
class _EnumBit:
Expand Down
Loading

0 comments on commit 91b56e5

Please sign in to comment.