Skip to content

Commit a19cab6

Browse files
stewegsamuel-gauthier
authored andcommitted
extension: add ExtensionPlugin class
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 Closes: #116 Signed-off-by: Stefan Gula <[email protected]> Signed-off-by: Samuel Gauthier <[email protected]>
1 parent 1afc2a6 commit a19cab6

File tree

9 files changed

+498
-6
lines changed

9 files changed

+498
-6
lines changed

cffi/cdefs.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ enum ly_stmt {
174174
LY_STMT_ARG_VALUE
175175
};
176176

177+
#define LY_STMT_OP_MASK ...
178+
#define LY_STMT_DATA_NODE_MASK ...
177179
#define LY_STMT_NODE_MASK ...
178180

179181
#define LY_LOLOG ...
@@ -359,6 +361,7 @@ LY_ERR lys_print_module(struct ly_out *, const struct lys_module *, LYS_OUTFORMA
359361
#define LYS_PRINT_SHRINK ...
360362

361363
struct lys_module {
364+
struct ly_ctx *ctx;
362365
const char *name;
363366
const char *revision;
364367
const char *ns;
@@ -428,6 +431,22 @@ struct lysc_node_container {
428431
struct lysc_node_notif *notifs;
429432
};
430433

434+
struct lysp_stmt {
435+
const char *stmt;
436+
const char *arg;
437+
LY_VALUE_FORMAT format;
438+
void *prefix_data;
439+
struct lysp_stmt *next;
440+
struct lysp_stmt *child;
441+
uint16_t flags;
442+
enum ly_stmt kw;
443+
};
444+
445+
struct lysp_ext_substmt {
446+
enum ly_stmt stmt;
447+
...;
448+
};
449+
431450
struct lysp_ext_instance {
432451
const char *name;
433452
const char *argument;
@@ -1271,6 +1290,42 @@ struct lyd_leafref_links_rec {
12711290

12721291
LY_ERR lyd_leafref_get_links(const struct lyd_node_term *e, const struct lyd_leafref_links_rec **);
12731292
LY_ERR lyd_leafref_link_node_tree(struct lyd_node *);
1293+
const char *lyplg_ext_stmt2str(enum ly_stmt stmt);
1294+
const struct lysp_module *lyplg_ext_parse_get_cur_pmod(const struct lysp_ctx *);
1295+
struct ly_ctx *lyplg_ext_compile_get_ctx(const struct lysc_ctx *);
1296+
void lyplg_ext_parse_log(const struct lysp_ctx *, const struct lysp_ext_instance *, LY_LOG_LEVEL, LY_ERR, const char *, ...);
1297+
void lyplg_ext_compile_log(const struct lysc_ctx *, const struct lysc_ext_instance *, LY_LOG_LEVEL, LY_ERR, const char *, ...);
1298+
LY_ERR lyplg_ext_parse_extension_instance(struct lysp_ctx *, struct lysp_ext_instance *);
1299+
LY_ERR lyplg_ext_compile_extension_instance(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
1300+
void lyplg_ext_pfree_instance_substatements(const struct ly_ctx *ctx, struct lysp_ext_substmt *substmts);
1301+
void lyplg_ext_cfree_instance_substatements(const struct ly_ctx *ctx, struct lysc_ext_substmt *substmts);
1302+
typedef LY_ERR (*lyplg_ext_parse_clb)(struct lysp_ctx *, struct lysp_ext_instance *);
1303+
typedef LY_ERR (*lyplg_ext_compile_clb)(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
1304+
typedef void (*lyplg_ext_parse_free_clb)(const struct ly_ctx *, struct lysp_ext_instance *);
1305+
typedef void (*lyplg_ext_compile_free_clb)(const struct ly_ctx *, struct lysc_ext_instance *);
1306+
struct lyplg_ext {
1307+
const char *id;
1308+
lyplg_ext_parse_clb parse;
1309+
lyplg_ext_compile_clb compile;
1310+
lyplg_ext_parse_free_clb pfree;
1311+
lyplg_ext_compile_free_clb cfree;
1312+
...;
1313+
};
1314+
1315+
struct lyplg_ext_record {
1316+
const char *module;
1317+
const char *revision;
1318+
const char *name;
1319+
struct lyplg_ext plugin;
1320+
...;
1321+
};
1322+
1323+
#define LYPLG_EXT_API_VERSION ...
1324+
LY_ERR lyplg_add_extension_plugin(struct ly_ctx *, uint32_t, const struct lyplg_ext_record *);
1325+
extern "Python" LY_ERR lypy_lyplg_ext_parse_clb(struct lysp_ctx *, struct lysp_ext_instance *);
1326+
extern "Python" LY_ERR lypy_lyplg_ext_compile_clb(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
1327+
extern "Python" void lypy_lyplg_ext_parse_free_clb(const struct ly_ctx *, struct lysp_ext_instance *);
1328+
extern "Python" void lypy_lyplg_ext_compile_free_clb(const struct ly_ctx *, struct lysc_ext_instance *);
12741329

12751330
/* from libc, needed to free allocated strings */
12761331
void free(void *);

libyang/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@
6363
UnitsRemoved,
6464
schema_diff,
6565
)
66+
from .extension import ExtensionPlugin, LibyangExtensionError
6667
from .keyed_list import KeyedList
6768
from .log import configure_logging
6869
from .schema import (
6970
Extension,
71+
ExtensionCompiled,
7072
ExtensionParsed,
7173
Feature,
7274
IfAndFeatures,
@@ -144,6 +146,9 @@
144146
"EnumRemoved",
145147
"Extension",
146148
"ExtensionAdded",
149+
"ExtensionCompiled",
150+
"ExtensionParsed",
151+
"ExtensionPlugin",
147152
"ExtensionRemoved",
148153
"Feature",
149154
"IfAndFeatures",

libyang/extension.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Copyright (c) 2018-2019 Robin Jarry
2+
# Copyright (c) 2020 6WIND S.A.
3+
# Copyright (c) 2021 RACOM s.r.o.
4+
# SPDX-License-Identifier: MIT
5+
6+
from typing import Callable, Optional
7+
8+
from _libyang import ffi, lib
9+
from .context import Context
10+
from .log import get_libyang_level
11+
from .schema import ExtensionCompiled, ExtensionParsed, Module
12+
from .util import LibyangError, c2str, str2c
13+
14+
15+
# -------------------------------------------------------------------------------------
16+
extensions_plugins = {}
17+
18+
19+
class LibyangExtensionError(LibyangError):
20+
def __init__(self, message: str, ret: int, log_level: int) -> None:
21+
super().__init__(message)
22+
self.ret = ret
23+
self.log_level = log_level
24+
25+
26+
@ffi.def_extern(name="lypy_lyplg_ext_parse_clb")
27+
def libyang_c_lyplg_ext_parse_clb(pctx, pext):
28+
plugin = extensions_plugins[pext.record.plugin]
29+
module_cdata = lib.lyplg_ext_parse_get_cur_pmod(pctx).mod
30+
context = Context(cdata=module_cdata.ctx)
31+
module = Module(context, module_cdata)
32+
parsed_ext = ExtensionParsed(context, pext, module)
33+
plugin.set_parse_ctx(pctx)
34+
try:
35+
plugin.parse_clb(module, parsed_ext)
36+
return lib.LY_SUCCESS
37+
except LibyangExtensionError as e:
38+
ly_level = get_libyang_level(e.log_level)
39+
if ly_level is None:
40+
ly_level = lib.LY_EPLUGIN
41+
e_str = str(e)
42+
plugin.add_error_message(e_str)
43+
lib.lyplg_ext_parse_log(pctx, pext, ly_level, e.ret, str2c(e_str))
44+
return e.ret
45+
46+
47+
@ffi.def_extern(name="lypy_lyplg_ext_compile_clb")
48+
def libyang_c_lyplg_ext_compile_clb(cctx, pext, cext):
49+
plugin = extensions_plugins[pext.record.plugin]
50+
context = Context(cdata=lib.lyplg_ext_compile_get_ctx(cctx))
51+
module = Module(context, cext.module)
52+
parsed_ext = ExtensionParsed(context, pext, module)
53+
compiled_ext = ExtensionCompiled(context, cext)
54+
plugin.set_compile_ctx(cctx)
55+
try:
56+
plugin.compile_clb(parsed_ext, compiled_ext)
57+
return lib.LY_SUCCESS
58+
except LibyangExtensionError as e:
59+
ly_level = get_libyang_level(e.log_level)
60+
if ly_level is None:
61+
ly_level = lib.LY_EPLUGIN
62+
e_str = str(e)
63+
plugin.add_error_message(e_str)
64+
lib.lyplg_ext_compile_log(cctx, cext, ly_level, e.ret, str2c(e_str))
65+
return e.ret
66+
67+
68+
@ffi.def_extern(name="lypy_lyplg_ext_parse_free_clb")
69+
def libyang_c_lyplg_ext_parse_free_clb(ctx, pext):
70+
plugin = extensions_plugins[pext.record.plugin]
71+
context = Context(cdata=ctx)
72+
parsed_ext = ExtensionParsed(context, pext, None)
73+
plugin.parse_free_clb(parsed_ext)
74+
75+
76+
@ffi.def_extern(name="lypy_lyplg_ext_compile_free_clb")
77+
def libyang_c_lyplg_ext_compile_free_clb(ctx, cext):
78+
plugin = extensions_plugins[getattr(cext, "def").plugin]
79+
context = Context(cdata=ctx)
80+
compiled_ext = ExtensionCompiled(context, cext)
81+
plugin.compile_free_clb(compiled_ext)
82+
83+
84+
class ExtensionPlugin:
85+
ERROR_SUCCESS = lib.LY_SUCCESS
86+
ERROR_MEM = lib.LY_EMEM
87+
ERROR_INVALID_INPUT = lib.LY_EINVAL
88+
ERROR_NOT_VALID = lib.LY_EVALID
89+
ERROR_DENIED = lib.LY_EDENIED
90+
ERROR_NOT = lib.LY_ENOT
91+
92+
def __init__(
93+
self,
94+
module_name: str,
95+
name: str,
96+
id_str: str,
97+
context: Optional[Context] = None,
98+
parse_clb: Optional[Callable[[Module, ExtensionParsed], None]] = None,
99+
compile_clb: Optional[
100+
Callable[[ExtensionParsed, ExtensionCompiled], None]
101+
] = None,
102+
parse_free_clb: Optional[Callable[[ExtensionParsed], None]] = None,
103+
compile_free_clb: Optional[Callable[[ExtensionCompiled], None]] = None,
104+
) -> None:
105+
"""
106+
Set the callback functions, which will be called if libyang will be processing
107+
given extension defined by name from module defined by module_name.
108+
109+
:arg self:
110+
This instance of extension plugin
111+
:arg module_name:
112+
The name of module in which the extension is defined
113+
:arg name:
114+
The name of extension itself
115+
:arg id_str:
116+
The unique ID of extension plugin within the libyang context
117+
:arg context:
118+
The context in which the extension plugin will be used. If set to None,
119+
the plugin will be used for all existing and even future contexts
120+
:arg parse_clb:
121+
The optional callback function of which will be called during extension parsing
122+
Expected arguments are:
123+
module: The module which is being parsed
124+
extension: The exact extension instance
125+
Expected raises:
126+
LibyangExtensionError in case of processing error
127+
:arg compile_clb:
128+
The optional callback function of which will be called during extension compiling
129+
Expected arguments are:
130+
extension_parsed: The parsed extension instance
131+
extension_compiled: The compiled extension instance
132+
Expected raises:
133+
LibyangExtensionError in case of processing error
134+
:arg parse_free_clb
135+
The optional callback function of which will be called during freeing of parsed extension
136+
Expected arguments are:
137+
extension: The parsed extension instance to be freed
138+
:arg compile_free_clb
139+
The optional callback function of which will be called during freeing of compiled extension
140+
Expected arguments are:
141+
extension: The compiled extension instance to be freed
142+
"""
143+
self.context = context
144+
self.module_name = module_name
145+
self.module_name_cstr = str2c(self.module_name)
146+
self.name = name
147+
self.name_cstr = str2c(self.name)
148+
self.id_str = id_str
149+
self.id_cstr = str2c(self.id_str)
150+
self.parse_clb = parse_clb
151+
self.compile_clb = compile_clb
152+
self.parse_free_clb = parse_free_clb
153+
self.compile_free_clb = compile_free_clb
154+
self._error_messages = []
155+
self._pctx = ffi.NULL
156+
self._cctx = ffi.NULL
157+
158+
self.cdata = ffi.new("struct lyplg_ext_record[2]")
159+
self.cdata[0].module = self.module_name_cstr
160+
self.cdata[0].name = self.name_cstr
161+
self.cdata[0].plugin.id = self.id_cstr
162+
if self.parse_clb is not None:
163+
self.cdata[0].plugin.parse = lib.lypy_lyplg_ext_parse_clb
164+
if self.compile_clb is not None:
165+
self.cdata[0].plugin.compile = lib.lypy_lyplg_ext_compile_clb
166+
if self.parse_free_clb is not None:
167+
self.cdata[0].plugin.pfree = lib.lypy_lyplg_ext_parse_free_clb
168+
if self.compile_free_clb is not None:
169+
self.cdata[0].plugin.cfree = lib.lypy_lyplg_ext_compile_free_clb
170+
ret = lib.lyplg_add_extension_plugin(
171+
context.cdata if context is not None else ffi.NULL,
172+
lib.LYPLG_EXT_API_VERSION,
173+
ffi.cast("const void *", self.cdata),
174+
)
175+
if ret != lib.LY_SUCCESS:
176+
raise LibyangError("Unable to add extension plugin")
177+
if self.cdata[0].plugin not in extensions_plugins:
178+
extensions_plugins[self.cdata[0].plugin] = self
179+
180+
def __del__(self) -> None:
181+
if self.cdata[0].plugin in extensions_plugins:
182+
del extensions_plugins[self.cdata[0].plugin]
183+
184+
@staticmethod
185+
def stmt2str(stmt: int) -> str:
186+
return c2str(lib.lyplg_ext_stmt2str(stmt))
187+
188+
def add_error_message(self, err_msg: str) -> None:
189+
self._error_messages.append(err_msg)
190+
191+
def clear_error_messages(self) -> None:
192+
self._error_messages.clear()
193+
194+
def set_parse_ctx(self, pctx) -> None:
195+
self._pctx = pctx
196+
197+
def set_compile_ctx(self, cctx) -> None:
198+
self._cctx = cctx
199+
200+
def parse_substmts(self, ext: ExtensionParsed) -> int:
201+
return lib.lyplg_ext_parse_extension_instance(self._pctx, ext.cdata)
202+
203+
def compile_substmts(self, pext: ExtensionParsed, cext: ExtensionCompiled) -> int:
204+
return lib.lyplg_ext_compile_extension_instance(
205+
self._cctx, pext.cdata, cext.cdata
206+
)
207+
208+
def free_parse_substmts(self, ext: ExtensionParsed) -> None:
209+
lib.lyplg_ext_pfree_instance_substatements(
210+
self.context.cdata, ext.cdata.substmts
211+
)
212+
213+
def free_compile_substmts(self, ext: ExtensionCompiled) -> None:
214+
lib.lyplg_ext_cfree_instance_substatements(
215+
self.context.cdata, ext.cdata.substmts
216+
)

libyang/log.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
}
2020

2121

22+
def get_libyang_level(py_level):
23+
for ly_lvl, py_lvl in LOG_LEVELS.items():
24+
if py_lvl == py_level:
25+
return ly_lvl
26+
return None
27+
28+
2229
@ffi.def_extern(name="lypy_log_cb")
2330
def libyang_c_logging_callback(level, msg, data_path, schema_path, line):
2431
args = [c2str(msg)]
@@ -50,10 +57,9 @@ def configure_logging(enable_py_logger: bool, level: int = logging.ERROR) -> Non
5057
:arg level:
5158
Python logging level. By default only ERROR messages are stored/logged.
5259
"""
53-
for ly_lvl, py_lvl in LOG_LEVELS.items():
54-
if py_lvl == level:
55-
lib.ly_log_level(ly_lvl)
56-
break
60+
ly_level = get_libyang_level(level)
61+
if ly_level is not None:
62+
lib.ly_log_level(ly_level)
5763
if enable_py_logger:
5864
lib.ly_log_options(lib.LY_LOLOG | lib.LY_LOSTORE)
5965
lib.ly_set_log_clb(lib.lypy_log_cb)

libyang/schema.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def __str__(self):
374374
class Extension:
375375
__slots__ = ("context", "cdata", "__dict__")
376376

377-
def __init__(self, context: "libyang.Context", cdata, module_parent: Module = None):
377+
def __init__(self, context: "libyang.Context", cdata):
378378
self.context = context
379379
self.cdata = cdata
380380

@@ -402,6 +402,8 @@ def __init__(self, context: "libyang.Context", cdata, module_parent: Module = No
402402

403403
def _module_from_parsed(self) -> Module:
404404
prefix = c2str(self.cdata.name).split(":")[0]
405+
if self.module_parent is None:
406+
raise self.context.error("cannot get module")
405407
for cdata_imp_mod in ly_array_iter(self.module_parent.cdata.parsed.imports):
406408
if ffi.string(cdata_imp_mod.prefix).decode() == prefix:
407409
return Module(self.context, cdata_imp_mod.module)
@@ -417,7 +419,7 @@ def parent_node(self) -> Optional["PNode"]:
417419
if not bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK):
418420
return None
419421
try:
420-
return PNode.new(self.context, self.cdata.parent, self.module())
422+
return PNode.new(self.context, self.cdata.parent, self.module_parent)
421423
except LibyangError:
422424
return None
423425

tests/test_diff.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
EnumRemoved,
1313
EnumStatusAdded,
1414
EnumStatusRemoved,
15+
ExtensionAdded,
1516
NodeTypeAdded,
1617
NodeTypeRemoved,
1718
SNodeAdded,
@@ -82,6 +83,8 @@ class DiffTest(unittest.TestCase):
8283
(EnumRemoved, "/yolo-system:state/url/proto"),
8384
(EnumStatusAdded, "/yolo-system:conf/url/proto"),
8485
(EnumStatusAdded, "/yolo-system:state/url/proto"),
86+
(ExtensionAdded, "/yolo-system:conf/url/proto"),
87+
(ExtensionAdded, "/yolo-system:state/url/proto"),
8588
(EnumStatusRemoved, "/yolo-system:conf/url/proto"),
8689
(EnumStatusRemoved, "/yolo-system:state/url/proto"),
8790
(SNodeAdded, "/yolo-system:conf/pill/red/out"),

0 commit comments

Comments
 (0)