testomaton 0.2.2__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- testomaton/__init__.py +0 -0
- testomaton/beaver.py +180 -0
- testomaton/constraint.py +669 -0
- testomaton/exit_codes.py +20 -0
- testomaton/generator.py +300 -0
- testomaton/jigsaw.py +347 -0
- testomaton/model.py +605 -0
- testomaton/solver.py +293 -0
- testomaton/tomato.py +473 -0
- testomaton/validator.py +238 -0
- testomaton-0.2.2.dist-info/LICENSE +661 -0
- testomaton-0.2.2.dist-info/METADATA +1193 -0
- testomaton-0.2.2.dist-info/RECORD +16 -0
- testomaton-0.2.2.dist-info/WHEEL +6 -0
- testomaton-0.2.2.dist-info/entry_points.txt +4 -0
- testomaton-0.2.2.dist-info/top_level.txt +7 -0
testomaton/model.py
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright Testify AS
|
|
3
|
+
#
|
|
4
|
+
# This file is part of testomaton suite
|
|
5
|
+
#
|
|
6
|
+
# testomaton is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# For the commercial license, please contact Testify AS.
|
|
12
|
+
#
|
|
13
|
+
# testomaton is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU Affero General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with testomaton. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
# See LICENSE file for the complete license text.
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
from copy import deepcopy
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
ignore_constraints = False
|
|
28
|
+
ignore_assignments = False
|
|
29
|
+
global_whitelist = []
|
|
30
|
+
global_blacklist = []
|
|
31
|
+
parameters_blacklist = []
|
|
32
|
+
parameters_whitelist = []
|
|
33
|
+
choices_blacklist = []
|
|
34
|
+
choices_whitelist = []
|
|
35
|
+
constraints_blacklist = []
|
|
36
|
+
constraints_whitelist = []
|
|
37
|
+
assignments_blacklist = []
|
|
38
|
+
assignments_whitelist = []
|
|
39
|
+
|
|
40
|
+
def should_parse_element(description, specific_whitelist=[], specific_blacklist=[], extra_labels=[]):
|
|
41
|
+
"""
|
|
42
|
+
Check if an element described by 'description' should be parsed based on global and local whitelists and blacklists,
|
|
43
|
+
as well as other conditions.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
description (dict): The description of the element
|
|
47
|
+
specific_whitelist (list): The list of labels that are allowed
|
|
48
|
+
specific_blacklist (list): The list of labels that are not allowed
|
|
49
|
+
extra_labels (list): The list of labels that are inherited from the parent
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
bool: True if the element should be parsed, False otherwise.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
>>> should_parse_element('choice')
|
|
56
|
+
True
|
|
57
|
+
>>> should_parse_element('constraint', ignore_constraints=True)
|
|
58
|
+
False
|
|
59
|
+
"""
|
|
60
|
+
#if description is a primitive type, return true and allow parsing
|
|
61
|
+
if not isinstance(description, dict):
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
#get the keys of the dictionary and set the element type to the first key
|
|
65
|
+
element_type = list(description.keys())[0]
|
|
66
|
+
if element_type == 'constraint' and ignore_constraints:
|
|
67
|
+
return False
|
|
68
|
+
if element_type == 'assignment' and ignore_assignments:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
name = description[element_type]
|
|
72
|
+
labels = [] + extra_labels
|
|
73
|
+
#if labels are in the description, set the labels list to the labels in the description
|
|
74
|
+
if 'labels' in description:
|
|
75
|
+
labels = description['labels']
|
|
76
|
+
labels.append(name)
|
|
77
|
+
|
|
78
|
+
#set the blacklist to the union of the global_blacklist and the specific_blacklist
|
|
79
|
+
blacklist = list(set(global_blacklist) | set(specific_blacklist))
|
|
80
|
+
#set the whitelist to the union of the global_whitelist and the specific_whitelis
|
|
81
|
+
whitelist = list(set(global_whitelist) | set(specific_whitelist))
|
|
82
|
+
|
|
83
|
+
#if the whitelist is not empty, check if any of the labels in 'labels' are in the whitelist, if not return false
|
|
84
|
+
if len(whitelist) > 0:
|
|
85
|
+
if len([label for label in labels if label in whitelist]) == 0:
|
|
86
|
+
return False
|
|
87
|
+
#if the blacklist is not empty, check if any of the labels in 'labels' are in the blacklist, if so return false
|
|
88
|
+
if len(blacklist) > 0:
|
|
89
|
+
if len([label for label in labels if label in blacklist]) > 0:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
class ParameterParent:
|
|
95
|
+
""" Parent class for parameters, which includes all methods for handling parameters in the model"""
|
|
96
|
+
def __init__(self, description, global_params, inherited_labels=[]) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Initialize a Parameter object.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
description (dict): The description of the parameter.
|
|
102
|
+
global_params (dict): The global parameters.
|
|
103
|
+
inherited_labels (list): The list of labels that are inherited from the parent. Defaults to an empty list.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
Exception: If the parameter has both 'parameters' and 'choices' keys. This is not allowed.
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
name (str): The name of the parameter.
|
|
110
|
+
choices (list): A list of sub-choices, each being represented as a choice object.
|
|
111
|
+
parameters (list): A list of sub-parameters, each being represented as a parameter object.
|
|
112
|
+
aliases (dict): A dictionary of aliases.
|
|
113
|
+
constraints (dict): A dictionary of constraints.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
None
|
|
117
|
+
"""
|
|
118
|
+
if 'parameter' in description:
|
|
119
|
+
self.name = str(description['parameter'])
|
|
120
|
+
elif 'output parameter' in description:
|
|
121
|
+
self.name = str(description['output parameter'])
|
|
122
|
+
|
|
123
|
+
self.parameters = []
|
|
124
|
+
self.output_parameters = []
|
|
125
|
+
#if description is a yaml node, set the name to the value of the 'parameter' key
|
|
126
|
+
if isinstance(description, dict):
|
|
127
|
+
#if description has 'parameters' key, set labels to the inherited labels and the labels in the description
|
|
128
|
+
if 'parameters' in description:
|
|
129
|
+
labels = [] + inherited_labels
|
|
130
|
+
if 'labels' in description:
|
|
131
|
+
labels += description['labels']
|
|
132
|
+
#iterate through the parameters in the description and create parameter objects for each
|
|
133
|
+
for parameter in description['parameters']:
|
|
134
|
+
if not should_parse_element(parameter, parameters_whitelist, parameters_blacklist, labels):
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
#if description has 'output parameter'
|
|
138
|
+
if 'output parameter' in parameter:
|
|
139
|
+
param = OutputParameter(parameter)
|
|
140
|
+
self.parameters.append(param)
|
|
141
|
+
self.output_parameters.append(param)
|
|
142
|
+
|
|
143
|
+
#if description has 'linked parameter' key, copies and modifies parameter before appending to the parameters list
|
|
144
|
+
if 'linked parameter' in parameter:
|
|
145
|
+
linked_to = parameter['linked to']
|
|
146
|
+
name = parameter['linked parameter']
|
|
147
|
+
whitelist = parameter['constraints whitelist'] if 'constraints whitelist' in parameter else None
|
|
148
|
+
blacklist = parameter['constraints blacklist'] if 'constraints blacklist' in parameter else None
|
|
149
|
+
if whitelist is not None and blacklist is not None:
|
|
150
|
+
raise Exception(f'Both constraints whitelist and blacklist are defined for linked parameter {parameter.name}')
|
|
151
|
+
copy = deepcopy(global_params[linked_to])
|
|
152
|
+
copy.name = name
|
|
153
|
+
copy.filter_constraints(whitelist, blacklist)
|
|
154
|
+
self.parameters.append(copy)
|
|
155
|
+
else:
|
|
156
|
+
self.parameters.append(Parameter(parameter, global_params, labels))
|
|
157
|
+
|
|
158
|
+
def get_leaf_choice_names(self):
|
|
159
|
+
# Get all leaf parameters with full path
|
|
160
|
+
parameter_names = self.get_parameter_names()
|
|
161
|
+
result = []
|
|
162
|
+
|
|
163
|
+
for name in parameter_names:
|
|
164
|
+
if name is None:
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
p = self.get_parameter(name)
|
|
168
|
+
if not p.parameters:
|
|
169
|
+
# If the parameter has no sub-parameters, return its own choices
|
|
170
|
+
result.append(p.get_choice_names()) # Append the list of choices as a group
|
|
171
|
+
else:
|
|
172
|
+
# If the parameter has sub-parameters, recursively get their choices
|
|
173
|
+
result.append(p.get_leaf_choice_names())
|
|
174
|
+
|
|
175
|
+
result = [r for r in result if r]
|
|
176
|
+
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
def get_parameter_names(self, current_path=''):
|
|
180
|
+
result = []
|
|
181
|
+
for parameter in self.parameters:
|
|
182
|
+
if isinstance(parameter, OutputParameter):
|
|
183
|
+
continue
|
|
184
|
+
if parameter.parameters:
|
|
185
|
+
if current_path == '':
|
|
186
|
+
result.extend(parameter.get_parameter_names(parameter.name))
|
|
187
|
+
else:
|
|
188
|
+
result.extend(parameter.get_parameter_names(current_path + '::' + parameter.name))
|
|
189
|
+
else:
|
|
190
|
+
if(current_path == ''):
|
|
191
|
+
result.append(parameter.name)
|
|
192
|
+
else:
|
|
193
|
+
result.append(current_path + '::' + parameter.name)
|
|
194
|
+
|
|
195
|
+
return result
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def get_parameter(self, name):
|
|
199
|
+
#print("get_parameter is being called in the parent class")
|
|
200
|
+
#if the name contains '::', split the name into tokens
|
|
201
|
+
if name is None:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
if '::' in name:
|
|
205
|
+
tokens = name.split('::')
|
|
206
|
+
child_name = tokens[0]
|
|
207
|
+
remains = '::'.join(tokens[1:])
|
|
208
|
+
#iterate through the parameters and recursively call get_parameter to get each parameter
|
|
209
|
+
for parameter in self.parameters:
|
|
210
|
+
if parameter.name == child_name:
|
|
211
|
+
return parameter.get_parameter(remains)
|
|
212
|
+
else:
|
|
213
|
+
#iterate through the parameters and return the parameter with the given name
|
|
214
|
+
for parameter in self.parameters:
|
|
215
|
+
if parameter.name == name:
|
|
216
|
+
return parameter
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def get_parameter_index(self, name):
|
|
221
|
+
""" Get the index of a parameter by name."""
|
|
222
|
+
for i, parameter in enumerate(self.get_parameter_names()):
|
|
223
|
+
if parameter == name:
|
|
224
|
+
return i
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
class ConstraintParent(ParameterParent):
|
|
228
|
+
def __init__(self, description) -> None:
|
|
229
|
+
self.aliases = {}
|
|
230
|
+
self.constraints = {}
|
|
231
|
+
if 'logic' in description:
|
|
232
|
+
logic = description['logic']
|
|
233
|
+
|
|
234
|
+
for element in logic:
|
|
235
|
+
#if element is an alias, set the name and expression of the alias
|
|
236
|
+
if 'alias' in element:
|
|
237
|
+
name = str(element['alias'])
|
|
238
|
+
expression = str(element['expression'])
|
|
239
|
+
if name in self.aliases:
|
|
240
|
+
raise Exception(f"Alias '{name}' already defined")
|
|
241
|
+
self.aliases[name] = expression
|
|
242
|
+
|
|
243
|
+
#if the element is a constraint, check if the element should be parsed,
|
|
244
|
+
# then set the name and expression of the constraint
|
|
245
|
+
if 'constraint' in element:
|
|
246
|
+
if not should_parse_element(element, constraints_whitelist, constraints_blacklist):
|
|
247
|
+
continue
|
|
248
|
+
name = str(element['constraint'])
|
|
249
|
+
expression = str(element['expression'])
|
|
250
|
+
if name not in self.constraints:
|
|
251
|
+
self.constraints[name] = []
|
|
252
|
+
self.constraints[name].append((None, expression))
|
|
253
|
+
|
|
254
|
+
#add potential constraints from structures
|
|
255
|
+
# for parameter in [p for p in self.parameters if p.is_structure()]:
|
|
256
|
+
# context = parameter.name
|
|
257
|
+
# for context, name, expression in parameter.get_constraints():
|
|
258
|
+
# if name not in self.constraints:
|
|
259
|
+
# self.constraints[name] = []
|
|
260
|
+
# self.constraints[name].append((context, expression))
|
|
261
|
+
|
|
262
|
+
def filter_constraints(self, whitelist, blacklist):
|
|
263
|
+
if whitelist is not None and blacklist is not None:
|
|
264
|
+
raise Exception(f'Both constraints whitelist and blacklist are defined for parameter {self.name}')
|
|
265
|
+
if whitelist is not None:
|
|
266
|
+
self.constraints = {k: v for k, v in self.constraints.items() if k in whitelist}
|
|
267
|
+
if blacklist is not None:
|
|
268
|
+
self.constraints = {k: v for k, v in self.constraints.items() if k not in blacklist}
|
|
269
|
+
|
|
270
|
+
for parameter in self.parameters:
|
|
271
|
+
parameter.filter_constraints(whitelist, blacklist)
|
|
272
|
+
|
|
273
|
+
class ChoiceParent:
|
|
274
|
+
""" Parent class for choices, which includes all methods for handling choices in the model"""
|
|
275
|
+
def __init__(self, description, inherited_labels=[]) -> None:
|
|
276
|
+
self.choices = []
|
|
277
|
+
|
|
278
|
+
#description is a directory created from parsing the yaml file
|
|
279
|
+
if isinstance(description, dict):
|
|
280
|
+
if 'choices' in description:
|
|
281
|
+
labels = inherited_labels
|
|
282
|
+
if 'labels' in description:
|
|
283
|
+
labels += description['labels']
|
|
284
|
+
self.choices = [Choice(choice, labels) for choice in description['choices'] if should_parse_element(choice, choices_whitelist, choices_blacklist, labels)]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_choice_names(self, current_path=''):
|
|
288
|
+
result = []
|
|
289
|
+
for choice in self.choices:
|
|
290
|
+
if choice.choices:
|
|
291
|
+
if current_path == '':
|
|
292
|
+
result.extend(choice.get_choice_names(choice.name))
|
|
293
|
+
else:
|
|
294
|
+
result.extend(choice.get_choice_names(current_path + '::' + choice.name))
|
|
295
|
+
else:
|
|
296
|
+
if(current_path == ''):
|
|
297
|
+
result.append(choice.name)
|
|
298
|
+
else:
|
|
299
|
+
result.append(current_path + '::' + choice.name)
|
|
300
|
+
|
|
301
|
+
return result
|
|
302
|
+
|
|
303
|
+
def get_choice(self, name):
|
|
304
|
+
"""
|
|
305
|
+
Get a choice by name.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
name (str): The name of the choice to get.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
choice: The choice object with the given name.
|
|
312
|
+
or None if the choice is not found.
|
|
313
|
+
"""
|
|
314
|
+
#if the name contains '::', split the name into tokens
|
|
315
|
+
if '::' in name:
|
|
316
|
+
tokens = name.split('::')
|
|
317
|
+
child_name = tokens[0]
|
|
318
|
+
#set the remains to the concatenation of the tokens from index 1, separated by '::'
|
|
319
|
+
remains = '::'.join(tokens[1:])
|
|
320
|
+
|
|
321
|
+
#for each choice in the choices list, if the name of the choice is equal to the child_name,
|
|
322
|
+
#recursively call get_choice with the remains
|
|
323
|
+
for choice in self.choices:
|
|
324
|
+
if choice.name == child_name:
|
|
325
|
+
return choice.get_choice(remains)
|
|
326
|
+
else:
|
|
327
|
+
#for each choice in the choices list, if the name of the choice
|
|
328
|
+
#is equal to the name, return the choice
|
|
329
|
+
for choice in self.choices:
|
|
330
|
+
if choice.name == name:
|
|
331
|
+
return choice
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
class Choice(ChoiceParent):
|
|
335
|
+
def __init__(self, description, global_params, inherited_labels=[]):
|
|
336
|
+
self.name = ''
|
|
337
|
+
self.value = ''
|
|
338
|
+
|
|
339
|
+
#description is a yaml node
|
|
340
|
+
if isinstance(description, dict):
|
|
341
|
+
children = list(description.keys())
|
|
342
|
+
if 'choice' in children:
|
|
343
|
+
self.name = str(description['choice'])
|
|
344
|
+
if 'value' in description:
|
|
345
|
+
self.value = str(description['value'])
|
|
346
|
+
if 'choices' in description:
|
|
347
|
+
raise Exception(f"Choice '{self.name}' has both value and choices")
|
|
348
|
+
else:
|
|
349
|
+
self.value = self.name
|
|
350
|
+
else:
|
|
351
|
+
self.value = self.name
|
|
352
|
+
else:
|
|
353
|
+
self.name = str(description)
|
|
354
|
+
self.value = str(description)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
super().__init__(description, inherited_labels)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def __str__(self):
|
|
361
|
+
return f'{self.name}: {self.value}'
|
|
362
|
+
|
|
363
|
+
def __repr__(self):
|
|
364
|
+
return str(self)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class OutputParameter(ParameterParent):
|
|
368
|
+
"""
|
|
369
|
+
A class used to represent an output parameter in a model.
|
|
370
|
+
|
|
371
|
+
Output parameters do not have choices which are used in combinatoric test generation.
|
|
372
|
+
However, their value can be defined based on a precondition defined in assignments
|
|
373
|
+
|
|
374
|
+
Attributes:
|
|
375
|
+
name (str): The name of the output parameter.
|
|
376
|
+
default_value (str): The default value linked to the output parameter.
|
|
377
|
+
parameters (list): The list of parameters that are children of the current output parameter.
|
|
378
|
+
|
|
379
|
+
Methods:
|
|
380
|
+
get_choice_names: returns a default_value which has been predefined.
|
|
381
|
+
"""
|
|
382
|
+
def __init__(self, description):
|
|
383
|
+
"""
|
|
384
|
+
Initialize an OutputParameter object.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
description (dict): The description of the output parameter.
|
|
388
|
+
|
|
389
|
+
Attributes:
|
|
390
|
+
name (str): The name of the output parameter.
|
|
391
|
+
default_value (str): The default value linked to the output parameter.
|
|
392
|
+
parameters (list): A list of sub-parameters, each being represented as a parameter object.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
None
|
|
396
|
+
"""
|
|
397
|
+
self.name = str(description['output parameter'])
|
|
398
|
+
self.default_value = str(description['default value'])
|
|
399
|
+
self.parameters = []
|
|
400
|
+
|
|
401
|
+
def __str__(self):
|
|
402
|
+
return f'{self.name}: {self.default_value}'
|
|
403
|
+
|
|
404
|
+
def __repr__(self):
|
|
405
|
+
return str(self)
|
|
406
|
+
|
|
407
|
+
def get_choice_names(self):
|
|
408
|
+
return [str(self.default_value)]
|
|
409
|
+
|
|
410
|
+
def is_structure(self):
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
class Parameter(ConstraintParent, ChoiceParent):
|
|
414
|
+
def __init__(self, description, global_params, inherited_labels=[]) -> None:
|
|
415
|
+
"""
|
|
416
|
+
Initialize a Parameter object.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
description (dict): The description of the parameter.
|
|
420
|
+
global_params (dict): The global parameters.
|
|
421
|
+
inherited_labels (list): The list of labels that are inherited from the parent. Defaults to an empty list.
|
|
422
|
+
|
|
423
|
+
Raises:
|
|
424
|
+
Exception: If the parameter has both 'parameters' and 'choices' keys. This is not allowed.
|
|
425
|
+
|
|
426
|
+
Attributes:
|
|
427
|
+
name (str): The name of the parameter.
|
|
428
|
+
choices (list): A list of sub-choices, each being represented as a choice object.
|
|
429
|
+
parameters (list): A list of sub-parameters, each being represented as a parameter object.
|
|
430
|
+
aliases (dict): A dictionary of aliases.
|
|
431
|
+
constraints (dict): A dictionary of constraints.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
None
|
|
435
|
+
"""
|
|
436
|
+
ChoiceParent.__init__(self, description, inherited_labels)
|
|
437
|
+
ParameterParent.__init__(self, description, global_params, inherited_labels)
|
|
438
|
+
ConstraintParent.__init__(self, description)
|
|
439
|
+
|
|
440
|
+
if isinstance(description, dict):
|
|
441
|
+
#if the description has both 'parameters and 'choices' keys, raise an exception and exit
|
|
442
|
+
if 'parameters' in description and 'choices' in description:
|
|
443
|
+
raise Exception(f'Error: {self.name1} has both parameters and choices')
|
|
444
|
+
|
|
445
|
+
def __str__(self):
|
|
446
|
+
result = f'{self.name}'
|
|
447
|
+
if self.parameters:
|
|
448
|
+
result += '('
|
|
449
|
+
for parameter in self.parameters:
|
|
450
|
+
result += f'{parameter}, '
|
|
451
|
+
result = result[:-2] + ')'
|
|
452
|
+
return result
|
|
453
|
+
|
|
454
|
+
def __repr__(self):
|
|
455
|
+
return str(self)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def get_structures(self):
|
|
459
|
+
return [p for p in self.parameters if p.is_structure()]
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def is_structure(self):
|
|
463
|
+
return len(self.parameters) > 0
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def get_constraints(self):
|
|
467
|
+
result = []
|
|
468
|
+
for name, expressions in self.constraints.items():
|
|
469
|
+
for expression in expressions:
|
|
470
|
+
result.append((self.name, name, expression))
|
|
471
|
+
|
|
472
|
+
if self.parameters:
|
|
473
|
+
for parameter in self.parameters:
|
|
474
|
+
for context, name, expression in parameter.get_constraints():
|
|
475
|
+
result.append((self.name + '::' + context, name, expression))
|
|
476
|
+
|
|
477
|
+
return result
|
|
478
|
+
|
|
479
|
+
def get_aliases(self):
|
|
480
|
+
result = []
|
|
481
|
+
for name, expression in self.aliases.items():
|
|
482
|
+
result.append((self.name, name, expression))
|
|
483
|
+
|
|
484
|
+
if self.parameters:
|
|
485
|
+
for parameter in self.parameters:
|
|
486
|
+
for context, name, expression in parameter.get_aliases():
|
|
487
|
+
result.append((self.name + '::' + context, name, expression))
|
|
488
|
+
return result
|
|
489
|
+
|
|
490
|
+
class Function(ConstraintParent, ParameterParent):
|
|
491
|
+
def __init__(self, global_params, description):
|
|
492
|
+
"""
|
|
493
|
+
Initialize a Function object.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
global_params (dict): The global parameters.
|
|
497
|
+
description (dict): The description of the function.
|
|
498
|
+
|
|
499
|
+
Attributes:
|
|
500
|
+
name (str): The name of the function.
|
|
501
|
+
parameters (list): A list of sub-parameters, each being represented as a parameter object.
|
|
502
|
+
output_parameters (list): A list of sub-output parameters, each being represented as an output parameter object.
|
|
503
|
+
aliases (dict): A dictionary of aliases.
|
|
504
|
+
constraints (dict): A dictionary of constraints.
|
|
505
|
+
assignments (dict): A dictionary of assignments.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
None
|
|
509
|
+
"""
|
|
510
|
+
if 'function' not in description:
|
|
511
|
+
raise ValueError("The 'function' key is missing in the description.")
|
|
512
|
+
|
|
513
|
+
self.name = str(description['function'])
|
|
514
|
+
ParameterParent.__init__(self, description, global_params)
|
|
515
|
+
ConstraintParent.__init__(self, description)
|
|
516
|
+
|
|
517
|
+
self.assignments = {}
|
|
518
|
+
if 'logic' in description:
|
|
519
|
+
logic = description['logic']
|
|
520
|
+
for element in logic:
|
|
521
|
+
#if it is an assignment, check if it should be parsed and set name and expression
|
|
522
|
+
if 'assignment' in element:
|
|
523
|
+
if not should_parse_element(element, assignments_whitelist, assignments_blacklist):
|
|
524
|
+
continue
|
|
525
|
+
name = str(element['assignment'])
|
|
526
|
+
expression = str(element['expression'])
|
|
527
|
+
if name not in self.assignments:
|
|
528
|
+
self.assignments[name] = []
|
|
529
|
+
self.assignments[name].append(expression)
|
|
530
|
+
|
|
531
|
+
def __str__(self):
|
|
532
|
+
return f'{self.name}({[parameter.name for parameter in self.parameters]})'
|
|
533
|
+
|
|
534
|
+
def get_structures(self):
|
|
535
|
+
return [p for p in self.parameters if p.is_structure()]
|
|
536
|
+
|
|
537
|
+
def get_generator_input(self):
|
|
538
|
+
return self.get_parameter_names(), self.get_leaf_choice_names()
|
|
539
|
+
|
|
540
|
+
def parse_function(model, function_name=None, **kwargs):
|
|
541
|
+
"""
|
|
542
|
+
Parse a function from a model file.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
file (str): The path to the model file.
|
|
546
|
+
function_name (str): The name of the function to parse. Defaults to None.
|
|
547
|
+
**kwargs: Additional keyword arguments.
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
function: The function object.
|
|
551
|
+
"""
|
|
552
|
+
global ignore_constraints, ignore_assignments
|
|
553
|
+
global global_whitelist, global_blacklist
|
|
554
|
+
global parameters_blacklist, parameters_whitelist
|
|
555
|
+
global choices_blacklist, choices_whitelist
|
|
556
|
+
global constraints_blacklist, constraints_whitelist
|
|
557
|
+
global assignments_blacklist, assignments_whitelist
|
|
558
|
+
|
|
559
|
+
if 'ignore_constraints' in kwargs:
|
|
560
|
+
ignore_constraints = kwargs['ignore_constraints']
|
|
561
|
+
if 'ignore_assignments' in kwargs:
|
|
562
|
+
ignore_assignments = kwargs['ignore_assignments']
|
|
563
|
+
if 'whitelist' in kwargs:
|
|
564
|
+
global_whitelist = kwargs['whitelist']
|
|
565
|
+
if 'blacklist' in kwargs:
|
|
566
|
+
global_blacklist = kwargs['blacklist']
|
|
567
|
+
if 'parameters_blacklist' in kwargs:
|
|
568
|
+
parameters_blacklist = kwargs['parameters_blacklist']
|
|
569
|
+
if 'parameters_whitelist' in kwargs:
|
|
570
|
+
parameters_whitelist = kwargs['parameters_whitelist']
|
|
571
|
+
if 'choices_blacklist' in kwargs:
|
|
572
|
+
choices_blacklist = kwargs['choices_blacklist']
|
|
573
|
+
if 'choices_whitelist' in kwargs:
|
|
574
|
+
choices_whitelist = kwargs['choices_whitelist']
|
|
575
|
+
if 'constraints_blacklist' in kwargs:
|
|
576
|
+
constraints_blacklist = kwargs['constraints_blacklist']
|
|
577
|
+
if 'constraints_whitelist' in kwargs:
|
|
578
|
+
constraints_whitelist = kwargs['constraints_whitelist']
|
|
579
|
+
if 'assignments_blacklist' in kwargs:
|
|
580
|
+
assignments_blacklist = kwargs['assignments_blacklist']
|
|
581
|
+
if 'assignments_whitelist' in kwargs:
|
|
582
|
+
assignments_whitelist = kwargs['assignments_whitelist']
|
|
583
|
+
|
|
584
|
+
global_params = {}
|
|
585
|
+
#Makes parameter objects for global parameters if they exist
|
|
586
|
+
if 'global parameters' in model:
|
|
587
|
+
for parameter in model['global parameters']:
|
|
588
|
+
global_params[parameter['parameter']] = Parameter(parameter, global_params)
|
|
589
|
+
|
|
590
|
+
if 'functions' not in model:
|
|
591
|
+
raise Exception('No functions defined in model')
|
|
592
|
+
if function_name is None:
|
|
593
|
+
function_def = model['functions'][0]
|
|
594
|
+
#Get the function if the function name is provided
|
|
595
|
+
else:
|
|
596
|
+
function_defs = [function for function in model['functions'] if function['function'] == function_name]
|
|
597
|
+
if len(function_defs) > 1:
|
|
598
|
+
raise Exception(f'Multiple functions with name {function_name}')
|
|
599
|
+
if len(function_defs) == 0:
|
|
600
|
+
raise Exception(f'Function {function_name} not found in the model file')
|
|
601
|
+
function_def = function_defs[0]
|
|
602
|
+
function = Function(global_params, function_def)
|
|
603
|
+
|
|
604
|
+
return function
|
|
605
|
+
|