Skip to content

Commit 61dd1da

Browse files
committed
context: add ability to set custom load callback
This patch adds ability to add custom module load callback, which allows user to load modules from remote source etc. Signed-off-by: Stefan Gula <steweg@gmail.com>
1 parent 26d3930 commit 61dd1da

File tree

3 files changed

+103
-2
lines changed

3 files changed

+103
-2
lines changed

cffi/cdefs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,11 @@ typedef enum {
935935
LY_ERR lys_parse(struct ly_ctx *, struct ly_in *, LYS_INFORMAT, const char **, struct lys_module **);
936936
LY_ERR ly_ctx_new_ylpath(const char *, const char *, LYD_FORMAT, int, struct ly_ctx **);
937937
LY_ERR ly_ctx_get_yanglib_data(const struct ly_ctx *, struct lyd_node **, const char *, ...);
938+
typedef void (*ly_module_imp_data_free_clb)(void *, void *);
939+
typedef LY_ERR (*ly_module_imp_clb)(const char *, const char *, const char *, const char *, void *, LYS_INFORMAT *, const char **, ly_module_imp_data_free_clb *);
940+
void ly_ctx_set_module_imp_clb(struct ly_ctx *, ly_module_imp_clb, void *);
941+
extern "Python" void lypy_module_imp_data_free_clb(void *, void *);
942+
extern "Python" LY_ERR lypy_module_imp_clb(const char *, const char *, const char *, const char *, void *, LYS_INFORMAT *, const char **, ly_module_imp_data_free_clb *);
938943

939944
struct lyd_meta {
940945
struct lyd_node *parent;

libyang/context.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# SPDX-License-Identifier: MIT
55

66
import os
7-
from typing import IO, Any, Iterator, Optional, Union
7+
from typing import IO, Any, Callable, Iterator, Optional, Tuple, Union
88

99
from _libyang import ffi, lib
1010
from .data import (
@@ -19,9 +19,48 @@
1919
from .util import DataType, IOType, LibyangError, c2str, data_load, str2c
2020

2121

22+
# -------------------------------------------------------------------------------------
23+
@ffi.def_extern(name="lypy_module_imp_data_free_clb")
24+
def libyang_c_module_imp_data_free_clb(cdata, user_data):
25+
instance = ffi.from_handle(user_data)
26+
instance.free_module_data(cdata)
27+
28+
29+
# -------------------------------------------------------------------------------------
30+
@ffi.def_extern(name="lypy_module_imp_clb")
31+
def libyang_c_module_imp_clb(
32+
mod_name,
33+
mod_rev,
34+
submod_name,
35+
submod_rev,
36+
user_data,
37+
fmt,
38+
module_data,
39+
free_module_data,
40+
):
41+
fmt[0] = lib.LYS_IN_UNKNOWN
42+
module_data[0] = ffi.NULL
43+
free_module_data[0] = lib.lypy_module_imp_data_free_clb
44+
instance = ffi.from_handle(user_data)
45+
in_fmt, content = instance.get_module_data(
46+
c2str(mod_name), c2str(mod_rev), c2str(submod_name), c2str(submod_rev)
47+
)
48+
if content is None:
49+
return lib.LY_ENOT
50+
fmt[0] = schema_in_format(in_fmt)
51+
module_data[0] = content
52+
return lib.LY_SUCCESS
53+
54+
2255
# -------------------------------------------------------------------------------------
2356
class Context:
24-
__slots__ = ("cdata", "__dict__")
57+
__slots__ = (
58+
"cdata",
59+
"_module_data_clb",
60+
"_cffi_handle",
61+
"_cdata_modules",
62+
"__dict__",
63+
)
2564

2665
def __init__(
2766
self,
@@ -34,6 +73,10 @@ def __init__(
3473
yanglib_fmt: str = "json",
3574
cdata=None, # C type: "struct ly_ctx *"
3675
):
76+
self._module_data_clb = None
77+
self._cffi_handle = ffi.new_handle(self)
78+
self._cdata_modules = []
79+
3780
if cdata is not None:
3881
self.cdata = ffi.cast("struct ly_ctx *", cdata)
3982
return # already initialized
@@ -466,3 +509,41 @@ def __iter__(self) -> Iterator[Module]:
466509
while mod:
467510
yield Module(self, mod)
468511
mod = lib.ly_ctx_get_module_iter(self.cdata, idx)
512+
513+
def free_module_data(self, cdata) -> None:
514+
self._cdata_modules.remove(cdata)
515+
516+
def get_module_data(
517+
self,
518+
mod_name: Optional[str],
519+
mod_rev: Optional[str],
520+
submod_name: Optional[str],
521+
submod_rev: Optional[str],
522+
) -> Tuple[str, Optional[str]]:
523+
if self._module_data_clb is None:
524+
return None
525+
fmt_str, module_data = self._module_data_clb(
526+
mod_name, mod_rev, submod_name, submod_rev
527+
)
528+
if module_data is None:
529+
return fmt_str, None
530+
module_data_c = str2c(module_data)
531+
self._cdata_modules.append(module_data_c)
532+
return fmt_str, module_data_c
533+
534+
def set_module_data_clb(
535+
self,
536+
clb: Optional[
537+
Callable[
538+
[Optional[str], Optional[str], Optional[str], Optional[str]],
539+
Tuple[str, Optional[str]],
540+
]
541+
] = None,
542+
) -> None:
543+
self._module_data_clb = clb
544+
if clb is None:
545+
lib.ly_ctx_set_module_imp_clb(self.cdata, ffi.NULL, ffi.NULL)
546+
else:
547+
lib.ly_ctx_set_module_imp_clb(
548+
self.cdata, lib.lypy_module_imp_clb, self._cffi_handle
549+
)

tests/test_context.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,18 @@ def test_ctx_disable_searchdirs(self):
116116
with Context(YANG_DIR, disable_searchdirs=True) as ctx:
117117
with self.assertRaises(LibyangError):
118118
ctx.load_module("yolo-nodetypes")
119+
120+
def test_ctx_using_clb(self):
121+
def get_module_clb(mod_name, *_):
122+
YOLO_NODETYPES_MOD_PATH = os.path.join(YANG_DIR, "yolo/yolo-nodetypes.yang")
123+
self.assertEqual(mod_name, "yolo-nodetypes")
124+
with open(YOLO_NODETYPES_MOD_PATH, encoding="utf-8") as f:
125+
mod_str = f.read()
126+
return "yang", mod_str
127+
128+
with Context(YANG_DIR, disable_searchdirs=True) as ctx:
129+
with self.assertRaises(LibyangError):
130+
ctx.load_module("yolo-nodetypes")
131+
ctx.set_module_data_clb(get_module_clb)
132+
mod = ctx.load_module("yolo-nodetypes")
133+
self.assertIsInstance(mod, Module)

0 commit comments

Comments
 (0)