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/__init__.py
ADDED
|
File without changes
|
testomaton/beaver.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Copyright Testify AS
|
|
5
|
+
#
|
|
6
|
+
# This file is part of testomaton suite
|
|
7
|
+
#
|
|
8
|
+
# testomaton is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# For commercial license, please contact Testify AS.
|
|
14
|
+
#
|
|
15
|
+
# testomaton is distributed in the hope that it will be useful,
|
|
16
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
18
|
+
# GNU Affero General Public License for more details.
|
|
19
|
+
#
|
|
20
|
+
# You should have received a copy of the GNU General Public License
|
|
21
|
+
# along with testomaton. If not, see <http://www.gnu.org/licenses/>.
|
|
22
|
+
#
|
|
23
|
+
# See LICENSE file for the complete license text.
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
import sys
|
|
27
|
+
import argparse
|
|
28
|
+
import importlib
|
|
29
|
+
import importlib.metadata as metadata
|
|
30
|
+
import importlib.util
|
|
31
|
+
import csv
|
|
32
|
+
|
|
33
|
+
global_imports = {}
|
|
34
|
+
appended_columns = prepended_columns = replaced_columns = swapped_columns = []
|
|
35
|
+
columns_whitelist = []
|
|
36
|
+
columns_blacklist = []
|
|
37
|
+
|
|
38
|
+
def read_csv(file, separator=','):
|
|
39
|
+
csv_reader = None
|
|
40
|
+
try:
|
|
41
|
+
if file is None:
|
|
42
|
+
csv_reader = csv.reader(sys.stdin, delimiter=separator)
|
|
43
|
+
for row in csv_reader:
|
|
44
|
+
yield row
|
|
45
|
+
else:
|
|
46
|
+
with open(file) as f:
|
|
47
|
+
csv_reader = csv.reader(f, delimiter=separator)
|
|
48
|
+
for row in csv_reader:
|
|
49
|
+
yield row
|
|
50
|
+
except OSError:
|
|
51
|
+
print(f"Could not open file {file}")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
def parse_args():
|
|
55
|
+
global global_imports
|
|
56
|
+
global appended_columns, prepended_columns, replaced_columns, swapped_columns
|
|
57
|
+
global columns_whitelist, columns_blacklist
|
|
58
|
+
|
|
59
|
+
parser = argparse.ArgumentParser(description='Evaluate expressions in a csv file. Expressions are evaluated in the order they appear in the input list. Use @python <expression> to evaluate python expressions. In the expressions, use {column_name} to refer to a column value. Use {row id} to refer to the row number.')
|
|
60
|
+
parser.add_argument('input_file', type=str, nargs='?', default=None, help='Input file. If not provided, stdin is used')
|
|
61
|
+
parser.add_argument('-v', '--version', action='store_true', help='Print version')
|
|
62
|
+
|
|
63
|
+
parser.add_argument('-n', '--add-linenumber', nargs='?', type=str, default='', help='Add a column with the line number. Optional argument is the name of the column. Default name is empty. The column may be referenced in the expressions by its name.')
|
|
64
|
+
parser.add_argument('-H', '--remove-headrow', action='store_true', default=False, help='Do not output the first line that was read')
|
|
65
|
+
parser.add_argument("-s", "--input-separator", type=str, default=',', help="Separator used in the input")
|
|
66
|
+
parser.add_argument("-S", "--output-separator", type=str, default=None, help="Separator used in the output. If not provided, the input separator is used")
|
|
67
|
+
parser.add_argument("-i", "--imports", type=str, default=None, help="Comma separated list of packages to be imported and used in the expressions. Use 'as' to provide an alias.")
|
|
68
|
+
parser.add_argument("-m", "--modules", type=str, default=None, help="Comma separated list of custom modules (files with python code) to be imported and used in the expressions. Use 'as' to provide an alias. The file should contain a single module.")
|
|
69
|
+
parser.add_argument("-x", "--dont-evaluate", action='store_true', default=False, help="Do not evaluate expressions")
|
|
70
|
+
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
|
|
73
|
+
if args.output_separator is None:
|
|
74
|
+
args.output_separator = args.input_separator
|
|
75
|
+
|
|
76
|
+
if args.version:
|
|
77
|
+
print(f"tomato {version()}")
|
|
78
|
+
sys.exit(0)
|
|
79
|
+
|
|
80
|
+
if args.imports is not None:
|
|
81
|
+
for imp in args.imports.split(','):
|
|
82
|
+
tokens = imp.split(' as ')
|
|
83
|
+
if len(tokens) == 1:
|
|
84
|
+
module_name = alias = tokens[0].strip()
|
|
85
|
+
else:
|
|
86
|
+
module_name = tokens[0].strip()
|
|
87
|
+
alias = tokens[1].strip()
|
|
88
|
+
|
|
89
|
+
module = importlib.import_module(module_name)
|
|
90
|
+
global_imports[alias] = module
|
|
91
|
+
|
|
92
|
+
if args.modules is not None:
|
|
93
|
+
for file_path in args.modules.split(','):
|
|
94
|
+
tokens = file_path.split(' as ')
|
|
95
|
+
file_name = tokens[0].strip().split('/')[-1]
|
|
96
|
+
module_name = file_name.split('.')[0]
|
|
97
|
+
if len(tokens) == 1:
|
|
98
|
+
alias = module_name
|
|
99
|
+
else:
|
|
100
|
+
file_path = tokens[0].strip()
|
|
101
|
+
alias = tokens[1].strip()
|
|
102
|
+
# print(f"Importing module {module_name} from file {file_name} as {alias}")
|
|
103
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
104
|
+
module = importlib.util.module_from_spec(spec)
|
|
105
|
+
sys.modules[alias] = module
|
|
106
|
+
spec.loader.exec_module(module)
|
|
107
|
+
global_imports[alias] = module
|
|
108
|
+
for attr in dir(module):
|
|
109
|
+
if not attr.startswith('__'):
|
|
110
|
+
global_imports[attr] = getattr(module, attr)
|
|
111
|
+
|
|
112
|
+
return args
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def version():
|
|
116
|
+
return metadata.version('testomaton')
|
|
117
|
+
|
|
118
|
+
def evaluate_token(row_id, token, values):
|
|
119
|
+
stripped_token = token.strip()
|
|
120
|
+
values_copy = values.copy()
|
|
121
|
+
values_copy.update(global_imports)
|
|
122
|
+
|
|
123
|
+
def is_numeric(value):
|
|
124
|
+
try:
|
|
125
|
+
float(value)
|
|
126
|
+
return True
|
|
127
|
+
except ValueError:
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
if token.startswith('@python '):
|
|
131
|
+
expr = stripped_token[8:]
|
|
132
|
+
expr = expr.replace('{row id}', str(row_id))
|
|
133
|
+
for key, value in values.items():
|
|
134
|
+
replaced_string = '{' + str(key) + '}'
|
|
135
|
+
#if value is not surrounded by quotes already:
|
|
136
|
+
if (str(value).strip()[0] != "'" and str(value).strip()[-1] != "'") and\
|
|
137
|
+
(str(value).strip()[0] != '"' and str(value).strip()[-1] != '"'):
|
|
138
|
+
|
|
139
|
+
#if value is not a number or bool literal then it should be enclosed in quotes
|
|
140
|
+
if not is_numeric(value) and value.lower() not in ['true', 'false']:
|
|
141
|
+
value = f"'{value}'"
|
|
142
|
+
expr = expr.replace(replaced_string, str(value))
|
|
143
|
+
try:
|
|
144
|
+
try:
|
|
145
|
+
result = eval(expr, {}, values_copy)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
print(f'evaluating {expr} failed: {e}')
|
|
148
|
+
return result
|
|
149
|
+
except Exception as e:
|
|
150
|
+
exit(1)
|
|
151
|
+
else:
|
|
152
|
+
return token
|
|
153
|
+
|
|
154
|
+
def main():
|
|
155
|
+
args = parse_args()
|
|
156
|
+
|
|
157
|
+
input_columns = next(read_csv(args.input_file, separator=args.input_separator))
|
|
158
|
+
|
|
159
|
+
if not args.remove_headrow:
|
|
160
|
+
first_row = input_columns.copy()
|
|
161
|
+
if args.add_linenumber:
|
|
162
|
+
first_row.insert(0, args.add_linenumber)
|
|
163
|
+
print(args.output_separator.join(first_row))
|
|
164
|
+
|
|
165
|
+
csv_writer = csv.writer(sys.stdout, delimiter=args.output_separator)
|
|
166
|
+
row_id = 0
|
|
167
|
+
for row in read_csv(args.input_file, separator=args.input_separator):
|
|
168
|
+
row_id += 1
|
|
169
|
+
values = {}
|
|
170
|
+
for index, token in enumerate(row):
|
|
171
|
+
value = evaluate_token(row_id, token, values) if not args.dont_evaluate else token
|
|
172
|
+
values[input_columns[index]] = value
|
|
173
|
+
output = [str(v) for v in values.values()]
|
|
174
|
+
if args.add_linenumber:
|
|
175
|
+
output.insert(0, str(row_id))
|
|
176
|
+
|
|
177
|
+
csv_writer.writerow(output)
|
|
178
|
+
|
|
179
|
+
if __name__ == '__main__':
|
|
180
|
+
main()
|