12
12
"""
13
13
14
14
import json
15
- import numpy as np
16
15
import os
17
16
import re
17
+
18
+ from functools import partial
19
+
18
20
import jinja2
21
+ import yaml
22
+
23
+ import numpy as np
19
24
25
+ from lxml import etree
20
26
from opera .util .error_codes import ErrorCode
21
27
from opera .util .logger import PgeLogger
22
28
23
-
24
29
XML_TYPES = {
25
30
str : 'string' ,
26
31
int : 'int' ,
@@ -87,18 +92,18 @@ def _log_message(undef):
87
92
class LoggingUndefined (jinja2 .Undefined ):
88
93
"""Override the default behavior which can raise an exception"""
89
94
90
- def _fail_with_undefined_error (self , * args , ** kwargs ): # pragma no cover
95
+ def _fail_with_undefined_error (self , * args , ** kwargs ): # pragma no cover
91
96
_log_message (self )
92
97
93
98
def __str__ (self ):
94
99
_log_message (self )
95
100
return UNDEFINED_ERROR
96
101
97
- def __iter__ (self ): # pragma no cover
102
+ def __iter__ (self ): # pragma no cover
98
103
_log_message (self )
99
104
return super ().__iter__ ()
100
105
101
- def __bool__ (self ): # pragma no cover
106
+ def __bool__ (self ): # pragma no cover
102
107
_log_message (self )
103
108
return super ().__bool__ ()
104
109
@@ -109,7 +114,21 @@ def __getattr__(self, name):
109
114
return LoggingUndefined
110
115
111
116
112
- def render_jinja2 (template_filename : str , input_data : dict , logger : PgeLogger = None ):
117
+ def _try_parse_xml_string (s : str ):
118
+ _ = etree .fromstring (s .encode ('utf-8' ))
119
+
120
+
121
+ JSON_VALIDATOR = json .loads
122
+ YAML_VALIDATOR = partial (yaml .load , Loader = yaml .SafeLoader )
123
+ XML_VALIDATOR = _try_parse_xml_string
124
+
125
+
126
+ def render_jinja2 (
127
+ template_filename : str ,
128
+ input_data : dict ,
129
+ logger : PgeLogger = None ,
130
+ validator = None
131
+ ):
113
132
"""
114
133
Renders from a jinja2 template using the specified input data.
115
134
Writes the rendered output to the specified output file.
@@ -126,6 +145,9 @@ def render_jinja2(template_filename: str, input_data: dict, logger: PgeLogger =
126
145
will not raise exceptions or abort rendering. If not provided,
127
146
the default Jinja2 error handling will apply for such errors,
128
147
possibly including raised exceptions.
148
+ validator:
149
+ Callable which, if provided, takes the rendered text as an input and
150
+ attempts to parse it. Must raise an exception if given invalid input.
129
151
130
152
Returns
131
153
-------
@@ -155,10 +177,17 @@ def render_jinja2(template_filename: str, input_data: dict, logger: PgeLogger =
155
177
156
178
rendered_text = template .render (input_data )
157
179
180
+ if validator is not None :
181
+ try :
182
+ validator (rendered_text )
183
+ except Exception as err :
184
+ raise RuntimeError (f"Error rendering Jinja2 template: { err } " ) from err
185
+
158
186
return rendered_text
159
187
160
188
161
189
def python_type_to_xml_type (obj ) -> str :
190
+ """Returns a guess for the XML type of a Python object."""
162
191
if isinstance (obj , str ):
163
192
if obj .lower () in ['true' , 'false' ]:
164
193
obj = obj .lower () == 'true'
@@ -172,6 +201,7 @@ def python_type_to_xml_type(obj) -> str:
172
201
173
202
return XML_TYPES [obj ]
174
203
204
+
175
205
class NumpyEncoder (json .JSONEncoder ):
176
206
"""Class to handle serialization of Numpy types during JSON enconding"""
177
207
def default (self , obj ):
@@ -186,5 +216,9 @@ def default(self, obj):
186
216
return super (NumpyEncoder , self ).default (obj )
187
217
188
218
def guess_attribute_display_name (var_name : str ) -> str :
219
+ """
220
+ Returns an approximation of an Additional Attribute's display name from its attribute name by converting snake_case
221
+ to PascalCase.
222
+ Ex. sample_attribute_name -> SampleAttributeName
223
+ """
189
224
return var_name .title ().replace ('_' , '' )
190
-
0 commit comments