4
4
# SPDX-License-Identifier: MIT
5
5
6
6
import os
7
- from typing import IO , Any , Iterator , Optional , Union
7
+ from typing import IO , Any , Callable , Iterator , Optional , Tuple , Union
8
8
9
9
from _libyang import ffi , lib
10
10
from .data import (
19
19
from .util import DataType , IOType , LibyangError , c2str , data_load , str2c
20
20
21
21
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
+
22
182
# -------------------------------------------------------------------------------------
23
183
class Context :
24
- __slots__ = ("cdata" , "__dict__" )
184
+ __slots__ = (
185
+ "cdata" ,
186
+ "external_module_loader" ,
187
+ "__dict__" ,
188
+ )
25
189
26
190
def __init__ (
27
191
self ,
@@ -37,6 +201,7 @@ def __init__(
37
201
):
38
202
if cdata is not None :
39
203
self .cdata = ffi .cast ("struct ly_ctx *" , cdata )
204
+ self .external_module_loader = ContextExternalModuleLoader (self .cdata )
40
205
return # already initialized
41
206
42
207
options = 0
@@ -90,6 +255,7 @@ def __init__(
90
255
)
91
256
if not self .cdata :
92
257
raise self .error ("cannot create context" )
258
+ self .external_module_loader = ContextExternalModuleLoader (self .cdata )
93
259
94
260
def compile_schema (self ):
95
261
ret = lib .ly_ctx_compile (self .cdata )
0 commit comments