python-table-processor 0.2.29__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.
- python_table_processor-0.2.29.dist-info/LICENSE +21 -0
- python_table_processor-0.2.29.dist-info/METADATA +28 -0
- python_table_processor-0.2.29.dist-info/RECORD +21 -0
- python_table_processor-0.2.29.dist-info/WHEEL +4 -0
- python_table_processor-0.2.29.dist-info/entry_points.txt +4 -0
- table_processor/__init__.py +2 -0
- table_processor/cli.py +66 -0
- table_processor/commands/convert_tables.py +80 -0
- table_processor/core/actions.py +601 -0
- table_processor/core/config.py +365 -0
- table_processor/core/constants.py +5 -0
- table_processor/core/convert.py +402 -0
- table_processor/core/functions/assign_id.py +75 -0
- table_processor/core/functions/flatten_row.py +24 -0
- table_processor/core/functions/get_nested_field_value.py +22 -0
- table_processor/core/functions/nest_row.py +25 -0
- table_processor/core/functions/search_column_value.py +26 -0
- table_processor/core/functions/set_flat_field_value.py +23 -0
- table_processor/core/functions/set_nested_field_value.py +24 -0
- table_processor/core/functions/set_row_value.py +33 -0
- table_processor/core/types.py +118 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Actions are used to transform the data in the table.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from collections import OrderedDict
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import (
|
|
12
|
+
Any,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from icecream import ic
|
|
16
|
+
|
|
17
|
+
from . config import (
|
|
18
|
+
Config,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from . constants import (
|
|
22
|
+
INPUT_FIELD,
|
|
23
|
+
STAGING_FIELD,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from . types import (
|
|
27
|
+
AssignConfig,
|
|
28
|
+
AssignConstantConfig,
|
|
29
|
+
AssignFormatConfig,
|
|
30
|
+
AssignIdConfig,
|
|
31
|
+
FilterConfig,
|
|
32
|
+
GlobalStatus,
|
|
33
|
+
JoinConfig,
|
|
34
|
+
OmitConfig,
|
|
35
|
+
ParseConfig,
|
|
36
|
+
PickConfig,
|
|
37
|
+
SplitConfig,
|
|
38
|
+
Row,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from . functions.assign_id import assign_id
|
|
42
|
+
from . functions.flatten_row import flatten_row
|
|
43
|
+
from . functions.nest_row import nest_row
|
|
44
|
+
from . functions.search_column_value import search_column_value
|
|
45
|
+
from . functions.set_flat_field_value import set_flat_field_value
|
|
46
|
+
from . functions.set_row_value import (
|
|
47
|
+
set_row_staging_value,
|
|
48
|
+
)
|
|
49
|
+
from . functions.set_nested_field_value import set_nested_field_value
|
|
50
|
+
|
|
51
|
+
def setup_actions_with_args(
|
|
52
|
+
config: Config,
|
|
53
|
+
list_actions: list[str],
|
|
54
|
+
action_delimiter: str = ':',
|
|
55
|
+
):
|
|
56
|
+
ic(list_actions)
|
|
57
|
+
for str_action in list_actions:
|
|
58
|
+
fields = str_action.split(action_delimiter)
|
|
59
|
+
if len(fields) >= 1:
|
|
60
|
+
action_name = fields[0].strip()
|
|
61
|
+
if action_name == 'assign-format':
|
|
62
|
+
setup_assign_format_action(config, str_action, action_delimiter)
|
|
63
|
+
continue
|
|
64
|
+
if action_name == 'filter':
|
|
65
|
+
setup_filter_action(config, str_action, action_delimiter)
|
|
66
|
+
continue
|
|
67
|
+
if len(fields) not in [2,3]:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
'Action must have 2 or 3 delimiter-separated fields: ' +
|
|
70
|
+
f'delimiter:{action_delimiter!r}, action string: {str_action!r}'
|
|
71
|
+
)
|
|
72
|
+
str_fields = fields[1].strip()
|
|
73
|
+
if len(fields) == 3:
|
|
74
|
+
str_options = fields[2].strip()
|
|
75
|
+
else:
|
|
76
|
+
str_options = ''
|
|
77
|
+
options = OrderedDict()
|
|
78
|
+
if str_options:
|
|
79
|
+
for str_option in str_options.split(','):
|
|
80
|
+
if '=' in str_option:
|
|
81
|
+
key, value = str_option.split('=')
|
|
82
|
+
options[key.strip()] = value.strip()
|
|
83
|
+
else:
|
|
84
|
+
options[str_option.strip()] = True
|
|
85
|
+
fields = str_fields.split(',')
|
|
86
|
+
for field in fields:
|
|
87
|
+
if '=' in field:
|
|
88
|
+
target, source = field.split('=')
|
|
89
|
+
target = target.strip()
|
|
90
|
+
source = source.strip()
|
|
91
|
+
else:
|
|
92
|
+
target = field.strip()
|
|
93
|
+
source = field.strip()
|
|
94
|
+
if action_name == 'assign':
|
|
95
|
+
assign_default = False
|
|
96
|
+
default_value = None
|
|
97
|
+
if 'default' in options:
|
|
98
|
+
assign_default = True
|
|
99
|
+
default_value = options['default']
|
|
100
|
+
if default_value in ['None', 'none', 'Null', 'null']:
|
|
101
|
+
default_value = None
|
|
102
|
+
required = options.get('required', False)
|
|
103
|
+
config.actions.append(AssignConfig(
|
|
104
|
+
target = target,
|
|
105
|
+
source = source,
|
|
106
|
+
assign_default = assign_default,
|
|
107
|
+
default_value = default_value,
|
|
108
|
+
required = required,
|
|
109
|
+
))
|
|
110
|
+
continue
|
|
111
|
+
if action_name == 'assign-constant':
|
|
112
|
+
str_type = options.get('type', 'str')
|
|
113
|
+
if str_type in ['str', 'string']:
|
|
114
|
+
value = source
|
|
115
|
+
elif str_type in ['int', 'integer']:
|
|
116
|
+
value = int(source)
|
|
117
|
+
elif str_type == 'float':
|
|
118
|
+
value = float(source)
|
|
119
|
+
elif str_type in ['bool', 'boolean']:
|
|
120
|
+
value = bool(source)
|
|
121
|
+
else:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f'Unsupported type: {str_type}'
|
|
124
|
+
)
|
|
125
|
+
config.actions.append(AssignConstantConfig(
|
|
126
|
+
target = target,
|
|
127
|
+
value = value,
|
|
128
|
+
))
|
|
129
|
+
continue
|
|
130
|
+
if action_name == 'assign-id':
|
|
131
|
+
context = options.get('context', None)
|
|
132
|
+
if context:
|
|
133
|
+
context = context.split(',')
|
|
134
|
+
config.actions.append(AssignIdConfig(
|
|
135
|
+
target = target,
|
|
136
|
+
primary = [source],
|
|
137
|
+
context = context,
|
|
138
|
+
))
|
|
139
|
+
continue
|
|
140
|
+
if action_name == 'filter-empty':
|
|
141
|
+
config.actions.append(FilterConfig(
|
|
142
|
+
field = target,
|
|
143
|
+
operator = 'empty',
|
|
144
|
+
value = '',
|
|
145
|
+
))
|
|
146
|
+
continue
|
|
147
|
+
if action_name == 'filter-not-empty':
|
|
148
|
+
config.actions.append(FilterConfig(
|
|
149
|
+
field = target,
|
|
150
|
+
operator = 'not-empty',
|
|
151
|
+
value = '',
|
|
152
|
+
))
|
|
153
|
+
continue
|
|
154
|
+
if action_name == 'join':
|
|
155
|
+
delimiter = options.get('delimiter', None)
|
|
156
|
+
config.actions.append(JoinConfig(
|
|
157
|
+
target = target,
|
|
158
|
+
source = source,
|
|
159
|
+
delimiter = delimiter,
|
|
160
|
+
))
|
|
161
|
+
continue
|
|
162
|
+
if action_name == 'omit':
|
|
163
|
+
config.actions.append(OmitConfig(
|
|
164
|
+
field = target,
|
|
165
|
+
))
|
|
166
|
+
continue
|
|
167
|
+
if action_name == 'parse':
|
|
168
|
+
as_type = options.get('as', 'literal')
|
|
169
|
+
required = options.get('required', False)
|
|
170
|
+
if as_type not in ['json', 'literal']:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f'Unsupported as type: {as_type}'
|
|
173
|
+
)
|
|
174
|
+
config.actions.append(ParseConfig(
|
|
175
|
+
target = target,
|
|
176
|
+
source = source,
|
|
177
|
+
as_type = as_type,
|
|
178
|
+
required = required,
|
|
179
|
+
))
|
|
180
|
+
continue
|
|
181
|
+
if action_name == 'parse-json':
|
|
182
|
+
required = options.get('required', False)
|
|
183
|
+
config.actions.append(ParseConfig(
|
|
184
|
+
target = target,
|
|
185
|
+
source = source,
|
|
186
|
+
as_type = 'json',
|
|
187
|
+
required = required,
|
|
188
|
+
))
|
|
189
|
+
continue
|
|
190
|
+
if action_name == 'split':
|
|
191
|
+
delimiter = options.get('delimiter', None)
|
|
192
|
+
if delimiter == '\\n':
|
|
193
|
+
delimiter = '\n'
|
|
194
|
+
config.actions.append(SplitConfig(
|
|
195
|
+
target = target,
|
|
196
|
+
source = source,
|
|
197
|
+
delimiter = delimiter,
|
|
198
|
+
))
|
|
199
|
+
continue
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f'Unsupported action: {action_name}'
|
|
202
|
+
)
|
|
203
|
+
return config
|
|
204
|
+
|
|
205
|
+
def setup_assign_format_action(
|
|
206
|
+
config: Config,
|
|
207
|
+
str_action: str,
|
|
208
|
+
delimiter: str = ':',
|
|
209
|
+
):
|
|
210
|
+
action_fields = str_action.split(delimiter, 1)
|
|
211
|
+
if len(action_fields) != 2:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f'Expected 2 fields separated by ":": {str_action}'
|
|
214
|
+
)
|
|
215
|
+
action_name = action_fields[0].strip()
|
|
216
|
+
assert action_name == 'assign-format'
|
|
217
|
+
assignment_fields = action_fields[1].split('=')
|
|
218
|
+
if len(assignment_fields) != 2:
|
|
219
|
+
raise ValueError(
|
|
220
|
+
f'Expected 2 fields separated by "=": {action_fields[1]}'
|
|
221
|
+
)
|
|
222
|
+
target = assignment_fields[0].strip()
|
|
223
|
+
format = assignment_fields[1].strip()
|
|
224
|
+
config.actions.append(AssignFormatConfig(
|
|
225
|
+
target = target,
|
|
226
|
+
format = format,
|
|
227
|
+
))
|
|
228
|
+
return config
|
|
229
|
+
|
|
230
|
+
def setup_filter_action(
|
|
231
|
+
config: Config,
|
|
232
|
+
str_action: str,
|
|
233
|
+
delimiter: str = ':',
|
|
234
|
+
):
|
|
235
|
+
action_fields = str_action.split(delimiter, 1)
|
|
236
|
+
if len(action_fields) != 2:
|
|
237
|
+
raise ValueError(
|
|
238
|
+
f'Expected 2 fields separated by ":": {str_action}'
|
|
239
|
+
)
|
|
240
|
+
action_name = action_fields[0].strip()
|
|
241
|
+
assert action_name == 'filter'
|
|
242
|
+
str_filter = action_fields[1].strip()
|
|
243
|
+
if '==' in str_filter:
|
|
244
|
+
field, value = str_filter.split('==')
|
|
245
|
+
config.actions.append(FilterConfig(
|
|
246
|
+
field = field.strip(),
|
|
247
|
+
operator = '==',
|
|
248
|
+
value = value.strip(),
|
|
249
|
+
))
|
|
250
|
+
return config
|
|
251
|
+
if '!=' in str_filter:
|
|
252
|
+
field, value = str_filter.split('!=')
|
|
253
|
+
config.actions.append(FilterConfig(
|
|
254
|
+
field = field.strip(),
|
|
255
|
+
operator = '!=',
|
|
256
|
+
value = value.strip(),
|
|
257
|
+
))
|
|
258
|
+
return config
|
|
259
|
+
if '=~' in str_filter:
|
|
260
|
+
field, value = str_filter.split('=~')
|
|
261
|
+
config.actions.append(FilterConfig(
|
|
262
|
+
field = field.strip(),
|
|
263
|
+
operator = '=~',
|
|
264
|
+
value = value.strip(),
|
|
265
|
+
))
|
|
266
|
+
return config
|
|
267
|
+
raise ValueError(
|
|
268
|
+
f'Unsupported filter: {str_filter}'
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def do_actions(
|
|
272
|
+
status: GlobalStatus,
|
|
273
|
+
row: Row,
|
|
274
|
+
actions: list[AssignConstantConfig],
|
|
275
|
+
):
|
|
276
|
+
for action in actions:
|
|
277
|
+
row = do_action(status, row, action)
|
|
278
|
+
if row is None:
|
|
279
|
+
return None
|
|
280
|
+
return row
|
|
281
|
+
|
|
282
|
+
def do_action(
|
|
283
|
+
status: GlobalStatus,
|
|
284
|
+
row: Row,
|
|
285
|
+
action: AssignConstantConfig,
|
|
286
|
+
):
|
|
287
|
+
if isinstance(action, AssignConfig):
|
|
288
|
+
return assign(row, action)
|
|
289
|
+
if isinstance(action, AssignConstantConfig):
|
|
290
|
+
return assign_constant(row, action)
|
|
291
|
+
if isinstance(action, AssignFormatConfig):
|
|
292
|
+
return assign_format(row, action)
|
|
293
|
+
if isinstance(action, AssignIdConfig):
|
|
294
|
+
return assign_id(status.id_context_map, row, action)
|
|
295
|
+
if isinstance(action, FilterConfig):
|
|
296
|
+
if filter_row(row, action):
|
|
297
|
+
return row
|
|
298
|
+
return None
|
|
299
|
+
if isinstance(action, JoinConfig):
|
|
300
|
+
return join_field(row, action)
|
|
301
|
+
if isinstance(action, ParseConfig):
|
|
302
|
+
return parse(row, action)
|
|
303
|
+
if isinstance(action, OmitConfig):
|
|
304
|
+
return omit_field(row, action)
|
|
305
|
+
if isinstance(action, SplitConfig):
|
|
306
|
+
return split_field(row, action)
|
|
307
|
+
raise ValueError(
|
|
308
|
+
f'Unsupported action: {action}'
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def prepare_row(
|
|
312
|
+
flat_row: OrderedDict | None = None,
|
|
313
|
+
):
|
|
314
|
+
if flat_row is None:
|
|
315
|
+
flat_row = OrderedDict()
|
|
316
|
+
try:
|
|
317
|
+
nested_row = nest_row(flat_row)
|
|
318
|
+
except:
|
|
319
|
+
ic(flat_row)
|
|
320
|
+
raise
|
|
321
|
+
return Row(
|
|
322
|
+
flat = OrderedDict(flat_row),
|
|
323
|
+
nested = nested_row,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def delete_flat_row_value(
|
|
327
|
+
flat_row: OrderedDict,
|
|
328
|
+
target: str,
|
|
329
|
+
):
|
|
330
|
+
prefix = f'{target}.'
|
|
331
|
+
for key in list(flat_row.keys()):
|
|
332
|
+
if key == target or key.startswith(prefix):
|
|
333
|
+
del flat_row[key]
|
|
334
|
+
|
|
335
|
+
def pop_nested_row_value(
|
|
336
|
+
nested_row: OrderedDict,
|
|
337
|
+
key: str,
|
|
338
|
+
default: Any = None,
|
|
339
|
+
):
|
|
340
|
+
keys = key.split('.')
|
|
341
|
+
for key in keys[:-1]:
|
|
342
|
+
if key not in nested_row:
|
|
343
|
+
return default, False
|
|
344
|
+
nested_row = nested_row[key]
|
|
345
|
+
return nested_row.pop(keys[-1], default), True
|
|
346
|
+
|
|
347
|
+
def pop_row_value(
|
|
348
|
+
row: Row,
|
|
349
|
+
key: str,
|
|
350
|
+
default: Any = None,
|
|
351
|
+
):
|
|
352
|
+
delete_flat_row_value(row.flat, key)
|
|
353
|
+
return pop_nested_row_value(row.nested, key, default)
|
|
354
|
+
|
|
355
|
+
def pop_row_staging(
|
|
356
|
+
row: Row,
|
|
357
|
+
default: Any = None,
|
|
358
|
+
):
|
|
359
|
+
return pop_row_value(row, STAGING_FIELD, default)
|
|
360
|
+
|
|
361
|
+
def assign_constant(
|
|
362
|
+
row: Row,
|
|
363
|
+
config: AssignConstantConfig,
|
|
364
|
+
):
|
|
365
|
+
set_row_staging_value(row, config.target, config.value)
|
|
366
|
+
return row
|
|
367
|
+
|
|
368
|
+
def split_field(
|
|
369
|
+
row: Row,
|
|
370
|
+
config: SplitConfig,
|
|
371
|
+
):
|
|
372
|
+
value, found = search_column_value(row.flat, config.source)
|
|
373
|
+
if found:
|
|
374
|
+
if isinstance(value, str):
|
|
375
|
+
new_value = value.split(config.delimiter)
|
|
376
|
+
new_value = map(str.strip, new_value)
|
|
377
|
+
new_value = list(filter(None, new_value))
|
|
378
|
+
value = new_value
|
|
379
|
+
set_row_staging_value(row, config.target, value)
|
|
380
|
+
return row
|
|
381
|
+
|
|
382
|
+
def remap_columns(
|
|
383
|
+
row: Row,
|
|
384
|
+
list_config: list[PickConfig],
|
|
385
|
+
):
|
|
386
|
+
if not list_config:
|
|
387
|
+
list_config = []
|
|
388
|
+
for key in row.nested[STAGING_FIELD][INPUT_FIELD].keys():
|
|
389
|
+
list_config.append(PickConfig(
|
|
390
|
+
source = key,
|
|
391
|
+
target = key,
|
|
392
|
+
))
|
|
393
|
+
new_flat_row = OrderedDict()
|
|
394
|
+
picked = []
|
|
395
|
+
for config in list_config:
|
|
396
|
+
value, key = search_column_value(row.nested, config.source)
|
|
397
|
+
if key:
|
|
398
|
+
set_flat_field_value(new_flat_row, config.target, value)
|
|
399
|
+
picked.append(key)
|
|
400
|
+
for key in row.flat.keys():
|
|
401
|
+
if key in picked:
|
|
402
|
+
if not key.startswith(f'{STAGING_FIELD}.{INPUT_FIELD}.'):
|
|
403
|
+
continue
|
|
404
|
+
if key in new_flat_row:
|
|
405
|
+
continue
|
|
406
|
+
if key.startswith(f'{STAGING_FIELD}.'):
|
|
407
|
+
# NOTE: Skip staging fields
|
|
408
|
+
new_flat_row[key] = row.flat[key]
|
|
409
|
+
else:
|
|
410
|
+
input_key = f'{STAGING_FIELD}.{INPUT_FIELD}.{key}'
|
|
411
|
+
if input_key in row.flat:
|
|
412
|
+
value = row.flat[key]
|
|
413
|
+
input_value = row.flat[input_key]
|
|
414
|
+
if value == input_value:
|
|
415
|
+
# NOTE: Skip if the same value in the input field
|
|
416
|
+
continue
|
|
417
|
+
# NOTE: Set the unused value to the staging field
|
|
418
|
+
new_flat_row[f'{STAGING_FIELD}.{key}'] = row.flat[key]
|
|
419
|
+
row.flat = new_flat_row
|
|
420
|
+
row.nested = nest_row(new_flat_row)
|
|
421
|
+
return row
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def search_with_operator(
|
|
425
|
+
row: Row,
|
|
426
|
+
source: str,
|
|
427
|
+
):
|
|
428
|
+
or_operator = '\|\|'
|
|
429
|
+
null_or_operator = '\?\?'
|
|
430
|
+
operator_group = f'{or_operator}|{null_or_operator}'
|
|
431
|
+
matched = re.split(f'({operator_group})', source, 1)
|
|
432
|
+
#ic(source, matched)
|
|
433
|
+
if len(matched) == 1:
|
|
434
|
+
return search_column_value(row.nested, source)
|
|
435
|
+
matched = map(str.strip, matched)
|
|
436
|
+
left, operator, rest = matched
|
|
437
|
+
value, found = search_column_value(row.nested, left)
|
|
438
|
+
if operator == '||':
|
|
439
|
+
if bool(value):
|
|
440
|
+
return value, found
|
|
441
|
+
if operator == '??':
|
|
442
|
+
if found and value is not None:
|
|
443
|
+
return value, found
|
|
444
|
+
return search_with_operator(row, rest)
|
|
445
|
+
|
|
446
|
+
def assign(
|
|
447
|
+
row: Row,
|
|
448
|
+
config: AssignConfig,
|
|
449
|
+
):
|
|
450
|
+
value, found = search_with_operator(row, config.source)
|
|
451
|
+
if config.required:
|
|
452
|
+
if not found or bool(value) == False:
|
|
453
|
+
raise ValueError(
|
|
454
|
+
'Required field not found or empty, ' +
|
|
455
|
+
f'field: {config.source}, found: {found}, value: {value}'
|
|
456
|
+
)
|
|
457
|
+
if found:
|
|
458
|
+
set_row_staging_value(row, config.target, value)
|
|
459
|
+
else:
|
|
460
|
+
if config.assign_default:
|
|
461
|
+
set_row_staging_value(row, config.target, config.default_value)
|
|
462
|
+
return row
|
|
463
|
+
|
|
464
|
+
def assign_format(
|
|
465
|
+
row: Row,
|
|
466
|
+
config: AssignFormatConfig,
|
|
467
|
+
):
|
|
468
|
+
template = config.format
|
|
469
|
+
params = {}
|
|
470
|
+
for key, value in row.flat.items():
|
|
471
|
+
for prefix in [
|
|
472
|
+
f'{STAGING_FIELD}.{INPUT_FIELD}.',
|
|
473
|
+
f'{STAGING_FIELD}.',
|
|
474
|
+
]:
|
|
475
|
+
if key.startswith(prefix):
|
|
476
|
+
rest = key[len(prefix):]
|
|
477
|
+
params[rest] = value
|
|
478
|
+
params.update(row.flat)
|
|
479
|
+
formatted = None
|
|
480
|
+
while formatted is None:
|
|
481
|
+
try:
|
|
482
|
+
formatted = template.format(**params)
|
|
483
|
+
except KeyError as e:
|
|
484
|
+
#ic(e)
|
|
485
|
+
#ic(e.args)
|
|
486
|
+
#ic(e.args[0])
|
|
487
|
+
key = e.args[0]
|
|
488
|
+
params[key] = f'__{key}__undefined__'
|
|
489
|
+
except:
|
|
490
|
+
#ic(params)
|
|
491
|
+
ic(params.keys())
|
|
492
|
+
raise
|
|
493
|
+
set_row_staging_value(row, config.target, formatted)
|
|
494
|
+
return row
|
|
495
|
+
|
|
496
|
+
def check_empty(
|
|
497
|
+
value: Any,
|
|
498
|
+
found: str | None,
|
|
499
|
+
):
|
|
500
|
+
if not found:
|
|
501
|
+
return True
|
|
502
|
+
return not bool(value)
|
|
503
|
+
|
|
504
|
+
def filter_row(
|
|
505
|
+
row: Row,
|
|
506
|
+
config: list[FilterConfig],
|
|
507
|
+
):
|
|
508
|
+
value, found = search_column_value(row.nested, config.field)
|
|
509
|
+
#ic(config, value, found)
|
|
510
|
+
if config.operator == '==':
|
|
511
|
+
if not found:
|
|
512
|
+
return False
|
|
513
|
+
if value != config.value and str(value) != str(config.value):
|
|
514
|
+
return False
|
|
515
|
+
elif config.operator == '!=':
|
|
516
|
+
if str(value) == str(config.value) or value == config.value:
|
|
517
|
+
return False
|
|
518
|
+
elif config.operator == '=~':
|
|
519
|
+
if not found:
|
|
520
|
+
return False
|
|
521
|
+
if not re.search(config.value, value):
|
|
522
|
+
return False
|
|
523
|
+
elif config.operator == 'not-in':
|
|
524
|
+
if isinstance(config.value, list):
|
|
525
|
+
if value in config.value:
|
|
526
|
+
return False
|
|
527
|
+
if str(value) in config.value:
|
|
528
|
+
return False
|
|
529
|
+
else:
|
|
530
|
+
raise ValueError(f'Unsupported filter value type: type{config.value}')
|
|
531
|
+
elif config.operator == 'empty':
|
|
532
|
+
if not check_empty(value, found):
|
|
533
|
+
return False
|
|
534
|
+
elif config.operator == 'not-empty':
|
|
535
|
+
if check_empty(value, found):
|
|
536
|
+
return False
|
|
537
|
+
else:
|
|
538
|
+
raise ValueError(f'Unsupported operator: {config.operator}')
|
|
539
|
+
return True
|
|
540
|
+
|
|
541
|
+
def omit_field(
|
|
542
|
+
row: Row,
|
|
543
|
+
config: OmitConfig,
|
|
544
|
+
):
|
|
545
|
+
value, found = pop_row_value(row, config.field)
|
|
546
|
+
if not found:
|
|
547
|
+
return row
|
|
548
|
+
if f'{STAGING_FIELD}.{config.field}' not in row.flat:
|
|
549
|
+
set_row_staging_value(row, config.field, value)
|
|
550
|
+
return row
|
|
551
|
+
|
|
552
|
+
def join_field(
|
|
553
|
+
row: Row,
|
|
554
|
+
config: JoinConfig,
|
|
555
|
+
):
|
|
556
|
+
value, found = search_column_value(row.nested, config.source)
|
|
557
|
+
if found:
|
|
558
|
+
delimiter = config.delimiter
|
|
559
|
+
if delimiter is None:
|
|
560
|
+
delimiter = ';'
|
|
561
|
+
if delimiter == '\\n':
|
|
562
|
+
delimiter = '\n'
|
|
563
|
+
if isinstance(value, list):
|
|
564
|
+
value = delimiter.join(value)
|
|
565
|
+
set_row_staging_value(row, config.target, value)
|
|
566
|
+
return row
|
|
567
|
+
|
|
568
|
+
def parse(
|
|
569
|
+
row: Row,
|
|
570
|
+
config: AssignConfig,
|
|
571
|
+
):
|
|
572
|
+
value, found = search_column_value(row.nested, config.source)
|
|
573
|
+
if config.required:
|
|
574
|
+
if not found:
|
|
575
|
+
raise ValueError(
|
|
576
|
+
f'Required field not found, field: {config.source}'
|
|
577
|
+
)
|
|
578
|
+
if found:
|
|
579
|
+
if type(value) == str:
|
|
580
|
+
if config.as_type == 'literal':
|
|
581
|
+
try:
|
|
582
|
+
parsed = ast.literal_eval(value)
|
|
583
|
+
except:
|
|
584
|
+
raise ValueError(
|
|
585
|
+
f'Failed to parse literal: {value}'
|
|
586
|
+
)
|
|
587
|
+
elif config.as_type == 'json':
|
|
588
|
+
try:
|
|
589
|
+
parsed = json.loads(value)
|
|
590
|
+
except:
|
|
591
|
+
raise ValueError(
|
|
592
|
+
f'Failed to parse JSON: {value}'
|
|
593
|
+
)
|
|
594
|
+
else:
|
|
595
|
+
raise ValueError(
|
|
596
|
+
f'Unsupported as type: {config.as_type}'
|
|
597
|
+
)
|
|
598
|
+
else:
|
|
599
|
+
parsed = value
|
|
600
|
+
set_row_staging_value(row, config.target, parsed)
|
|
601
|
+
return row
|