44# SPDX-License-Identifier: MIT
55
66import os
7- from typing import IO , Any , Iterator , Optional , Union
7+ from typing import IO , Any , Callable , Iterator , Optional , Tuple , Union
88
99from _libyang import ffi , lib
1010from .data import (
1919from .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+ """
42+ Implement the C callback function for loading modules from any location.
43+
44+ :arg c_str mod_name:
45+ The YANG module name
46+ :arg c_str mod_rev:
47+ The YANG module revision
48+ :arg c_str submod_name:
49+ The YANG submodule name
50+ :arg c_str submod_rev:
51+ The YANG submodule revision
52+ :arg user_data:
53+ The user data provided by user during registration. In this implementation
54+ it is always considered to be handle of Python object
55+ :arg fmt:
56+ The output pointer where to set the format of schema
57+ :arg module_data:
58+ The output pointer where to set the schema data itself
59+ :arg free_module_data:
60+ The output pointer of callback function which will be called when the schema
61+ data are no longer needed
62+
63+ :returns:
64+ The LY_SUCCESS in case the needed YANG (sub)module schema was found
65+ The LY_ENOT in case the needed YANG (sub)module schema was not found
66+ """
67+ fmt [0 ] = lib .LYS_IN_UNKNOWN
68+ module_data [0 ] = ffi .NULL
69+ free_module_data [0 ] = lib .lypy_module_imp_data_free_clb
70+ instance = ffi .from_handle (user_data )
71+ ret = instance .get_module_data (
72+ c2str (mod_name ), c2str (mod_rev ), c2str (submod_name ), c2str (submod_rev )
73+ )
74+ if ret is None :
75+ return lib .LY_ENOT
76+ in_fmt , content = ret
77+ fmt [0 ] = schema_in_format (in_fmt )
78+ module_data [0 ] = content
79+ return lib .LY_SUCCESS
80+
81+
82+ # -------------------------------------------------------------------------------------
83+ class ContextExternalModuleLoader :
84+ __slots__ = (
85+ "_cdata" ,
86+ "_module_data_clb" ,
87+ "_cffi_handle" ,
88+ "_cdata_modules" ,
89+ )
90+
91+ def __init__ (self , cdata ) -> None :
92+ self ._cdata = cdata # C type: "struct ly_ctx *"
93+ self ._module_data_clb = None
94+ self ._cffi_handle = ffi .new_handle (self )
95+ self ._cdata_modules = []
96+
97+ def free_module_data (self , cdata ) -> None :
98+ """
99+ Free previously stored data, obtained after a get_module_data.
100+
101+ :arg cdata:
102+ The pointer to YANG modelu schema (c_str), which shall be released from memory
103+ """
104+ self ._cdata_modules .remove (cdata )
105+
106+ def get_module_data (
107+ self ,
108+ mod_name : Optional [str ],
109+ mod_rev : Optional [str ],
110+ submod_name : Optional [str ],
111+ submod_rev : Optional [str ],
112+ ) -> Optional [Tuple [str , str ]]:
113+ """
114+ Get the YANG module schema data based requirements from libyang_c_module_imp_clb
115+ function and forward that request to user Python based callback function.
116+
117+ The returned data from callback function are stored within the context to make sure
118+ of no memory access issues. These data a stored until the free_module_data function
119+ is called directly by libyang.
120+
121+ :arg self
122+ This instance on context
123+ :arg mod_name:
124+ The optional YANG module name
125+ :arg mod_rev:
126+ The optional YANG module revision
127+ :arg submod_name:
128+ The optional YANG submodule name
129+ :arg submod_rev:
130+ The optional YANG submodule revision
131+
132+ :returns:
133+ Tuple of format string and YANG (sub)module schema
134+ """
135+ if self ._module_data_clb is None :
136+ return "" , None
137+ fmt_str , module_data = self ._module_data_clb (
138+ mod_name , mod_rev , submod_name , submod_rev
139+ )
140+ if module_data is None :
141+ return fmt_str , None
142+ module_data_c = str2c (module_data )
143+ self ._cdata_modules .append (module_data_c )
144+ return fmt_str , module_data_c
145+
146+ def set_module_data_clb (
147+ self ,
148+ clb : Optional [
149+ Callable [
150+ [Optional [str ], Optional [str ], Optional [str ], Optional [str ]],
151+ Optional [Tuple [str , str ]],
152+ ]
153+ ] = None ,
154+ ) -> None :
155+ """
156+ Set the callback function, which will be called if libyang context would like to
157+ load module or submodule, which is not locally available in context path(s).
158+
159+ :arg self
160+ This instance on context
161+ :arg clb:
162+ The callback function. The expected arguments are:
163+ mod_name: Module name
164+ mod_rev: Module revision
165+ submod_name: Submodule name
166+ submod_rev: Submodule revision
167+ The expeted return value is either:
168+ tuple of:
169+ format: The string format of the loaded data
170+ data: The YANG (sub)module data as string
171+ or None in case of error
172+ """
173+ self ._module_data_clb = clb
174+ if clb is None :
175+ lib .ly_ctx_set_module_imp_clb (self ._cdata , ffi .NULL , ffi .NULL )
176+ else :
177+ lib .ly_ctx_set_module_imp_clb (
178+ self ._cdata , lib .lypy_module_imp_clb , self ._cffi_handle
179+ )
180+
181+
22182# -------------------------------------------------------------------------------------
23183class Context :
24- __slots__ = ("cdata" , "__dict__" )
184+ __slots__ = (
185+ "cdata" ,
186+ "external_module_loader" ,
187+ "__dict__" ,
188+ )
25189
26190 def __init__ (
27191 self ,
@@ -37,6 +201,7 @@ def __init__(
37201 ):
38202 if cdata is not None :
39203 self .cdata = ffi .cast ("struct ly_ctx *" , cdata )
204+ self .external_module_loader = ContextExternalModuleLoader (self .cdata )
40205 return # already initialized
41206
42207 options = 0
@@ -90,6 +255,7 @@ def __init__(
90255 )
91256 if not self .cdata :
92257 raise self .error ("cannot create context" )
258+ self .external_module_loader = ContextExternalModuleLoader (self .cdata )
93259
94260 def compile_schema (self ):
95261 ret = lib .ly_ctx_compile (self .cdata )
0 commit comments