130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 | @cache
def get_dicts():
"""Constructs the dictionaries which contain information about every PROCESS variable.
WARNING: this function must be used carefully because it re-initialises the PROCESS state
"""
dict_objects = []
# Different dict objects, e.g. variable descriptions
logging_model_handler.clear_logs()
# Make dict objects
# Some dicts depend on other dicts already existing in output_dicts, so
# be careful if changing the order!
dict_objects.extend([
HardcodedDictionary("DICT_DEFAULT", {}),
HardcodedDictionary("DICT_MODULE", {}),
HardcodedDictionary("DICT_DESCRIPTIONS", {}),
SourceDictionary("DICT_VAR_TYPE", dict_var_type),
SourceDictionary("DICT_IXC_FULL", dict_ixc_full),
SourceDictionary("DICT_IXC_BOUNDS", dict_ixc_bounds),
])
# Make individual dicts within dict objects, process, then add to output_dict
for dict_object in dict_objects:
dict_object.make_dict()
dict_object.post_process()
dict_object.publish()
for module_name in import_module("process.data_structure").__all__:
if module_name == "__init__.py":
continue
module = import_module(f"process.data_structure.{module_name.split('.', 1)[0]}")
module_tree = ast.parse(inspect.getsource(module))
initial_values_dict = {}
variable_names = []
var_names_and_descriptions = {}
dict_module_entry = {}
variable_types = {}
# Check whether to get the initial value from the global data structure
# or some dataclass
object_containing_initial_values = (
module
if not hasattr(module, "CREATE_DICTS_FROM_DATACLASS")
else module.CREATE_DICTS_FROM_DATACLASS()
)
# get the variable names and initial values
for node in ast.walk(module_tree):
if isinstance(node, ast.AnnAssign):
# for each variable in the file, get the initial value
# (either is None, or value initialised in init_variables fn)
# set default to be None if variable is not being initialised eg if you
# just have `example_double: float` instead of `example_double: float = None`
initial_value = getattr(object_containing_initial_values, node.target.id)
# JSON doesn't like np arrays
if type(initial_value) is np.ndarray:
initial_value = initial_value.tolist()
initial_values_dict[node.target.id] = initial_value
# get the variable name and add to variable_names list
var_name = node.target.id
variable_names.append(var_name)
# Now want to get the types of these variables
if isinstance(node.annotation, ast.Subscript):
if node.annotation.value.id == "list":
if node.annotation.slice.id == "str":
var_type = "string_array"
elif node.annotation.slice.id == "float":
var_type = "real_array"
elif node.annotation.slice.id == "int":
var_type = "int_array"
elif node.annotation.slice.id == "bool":
var_type = "bool_array"
else:
raise TypeError(
f"The type annotation of variable {node.target.id} is "
f"{node.annotation.value.id}[{node.annotation.slice.id}], and "
"this is not recognised. Please change your type annotation for "
"this variable. PROCESS recognises the following type annotations: "
"list[float], list[int], list[str], list[bool]."
)
elif node.annotation.id == "float":
var_type = "real_variable"
elif node.annotation.id == "int":
var_type = "int_variable"
elif node.annotation.id == "str":
var_type = "str_variable"
elif node.annotation.id == "bool":
var_type = "bool_variable"
else:
raise TypeError(
f"The type annotation of variable {node.target.id} is "
f"{node.annotation.id}, and this is not recognised. Please change your "
"type annotation for this variable. PROCESS recognises the following "
"type annotations: float, int, str, bool."
)
variable_types[node.target.id] = var_type
# Variable descriptions are found under the ast.ClassDef node
# within ast.ClassDef - need to check for pairs of ast.AnnAssign followed by an
# ast.Expr - this is the form of a variable being declared followed by a
# docstring expression. can get these var descriptions from here, and if there
# is no ast.Expr immediately after an ast.AnnAssign then this var does not
# have a docstring and so set the description to be ""
for node in module_tree.body:
if isinstance(node, ast.ClassDef):
for node1, node2 in pairwise(node.body):
if isinstance(node1, ast.AnnAssign) and isinstance(node2, ast.Expr):
# if docstring immediately follows the variable declaration,
# add docstring to descriptions dict
var_names_and_descriptions[node1.target.id] = node2.value.value
if isinstance(node1, ast.AnnAssign) and not isinstance(
node2, ast.Expr
):
# if no docstring for variable, have a blank description
var_names_and_descriptions[node1.target.id] = ""
# check if last entry of ast.body is declaring a var. if it is then this
# var has no description and will be missing from
# var_names_and_descriptions.
# need to add to var_names_and_descriptions dict
last_var = node.body[-1]
if (
isinstance(last_var, ast.AnnAssign)
and last_var not in var_names_and_descriptions
):
var_names_and_descriptions[last_var.target.id] = ""
dict_module_entry[module_name] = variable_names
output_dict["DICT_MODULE"].update(dict_module_entry)
output_dict["DICT_DEFAULT"].update(initial_values_dict)
output_dict["DICT_DESCRIPTIONS"].update(var_names_and_descriptions)
output_dict["DICT_VAR_TYPE"].update(variable_types)
return output_dict
|