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
+ Implements 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
+ Gets the YANG module schema data based requirements from libyang_c_module_imp_clb
100
+ function and forward that request to user Python based callback function.
101
+ The returned data from callback function are stored within the context to make sure
102
+ of no memory access issues. These data a stored until the free_module_data function
103
+ is called directly by libyang
104
+
105
+ :arg cdata:
106
+ The pointer to YANG modelu schema (c_str), which shall be released from memory
107
+ """
108
+ self ._cdata_modules .remove (cdata )
109
+
110
+ def get_module_data (
111
+ self ,
112
+ mod_name : Optional [str ],
113
+ mod_rev : Optional [str ],
114
+ submod_name : Optional [str ],
115
+ submod_rev : Optional [str ],
116
+ ) -> Optional [Tuple [str , str ]]:
117
+ """
118
+ Gets the YANG module schema data based requirements from libyang_c_module_imp_clb
119
+ function and forward that request to user Python based callback function.
120
+ The returned data from callback function are stored within the context to make sure
121
+ of no memory access issues. These data a stored until the free_module_data function
122
+ is called directly by libyang
123
+
124
+ :arg self
125
+ This instance on context
126
+ :arg mod_name:
127
+ The optional YANG module name
128
+ :arg mod_rev:
129
+ The optional YANG module revision
130
+ :arg submod_name:
131
+ The optional YANG submodule name
132
+ :arg submod_rev:
133
+ The optional YANG submodule revision
134
+
135
+ :returns:
136
+ Tuple of format string and YANG (sub)module schema
137
+ """
138
+ if self ._module_data_clb is None :
139
+ return "" , None
140
+ ret = self ._module_data_clb (
141
+ mod_name , mod_rev , submod_name , submod_rev
142
+ )
143
+ if ret is None :
144
+ return None
145
+ fmt_str , module_data = ret
146
+ module_data_c = str2c (module_data )
147
+ self ._cdata_modules .append (module_data_c )
148
+ return fmt_str , module_data_c
149
+
150
+ def set_module_data_clb (
151
+ self ,
152
+ clb : Optional [
153
+ Callable [
154
+ [Optional [str ], Optional [str ], Optional [str ], Optional [str ]],
155
+ Optional [Tuple [str , str ]],
156
+ ]
157
+ ] = None ,
158
+ ) -> None :
159
+ """
160
+ Sets the callback function, which will be called if libyang context would like to
161
+ load module or submodule, which is not locally available in context path(s).
162
+
163
+ :arg self
164
+ This instance on context
165
+ :arg clb:
166
+ The callback function. The expected arguments are:
167
+ mod_name: Module name
168
+ mod_rev: Module revision
169
+ submod_name: Submodule name
170
+ submod_rev: Submodule revision
171
+ The expeted return value is either:
172
+ tuple of:
173
+ format: The string format of the loaded data
174
+ data: The YANG (sub)module data as string
175
+ or None in case of error
176
+ """
177
+ self ._module_data_clb = clb
178
+ if clb is None :
179
+ lib .ly_ctx_set_module_imp_clb (self ._cdata , ffi .NULL , ffi .NULL )
180
+ else :
181
+ lib .ly_ctx_set_module_imp_clb (
182
+ self ._cdata , lib .lypy_module_imp_clb , self ._cffi_handle
183
+ )
184
+
185
+
22
186
# -------------------------------------------------------------------------------------
23
187
class Context :
24
- __slots__ = ("cdata" , "__dict__" )
188
+ __slots__ = (
189
+ "cdata" ,
190
+ "external_module_loader" ,
191
+ "__dict__" ,
192
+ )
25
193
26
194
def __init__ (
27
195
self ,
@@ -37,6 +205,7 @@ def __init__(
37
205
):
38
206
if cdata is not None :
39
207
self .cdata = ffi .cast ("struct ly_ctx *" , cdata )
208
+ self .external_module_loader = ContextExternalModuleLoader (self .cdata )
40
209
return # already initialized
41
210
42
211
options = 0
@@ -90,6 +259,7 @@ def __init__(
90
259
)
91
260
if not self .cdata :
92
261
raise self .error ("cannot create context" )
262
+ self .external_module_loader = ContextExternalModuleLoader (self .cdata )
93
263
94
264
def compile_schema (self ):
95
265
ret = lib .ly_ctx_compile (self .cdata )
0 commit comments