python-table-processor 0.2.29__py3-none-any.whl → 0.3.1__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 → python_table_processor-0.3.1.dist-info}/METADATA +1 -1
- python_table_processor-0.3.1.dist-info/RECORD +23 -0
- table_processor/__init__.py +2 -2
- table_processor/cli.py +22 -1
- table_processor/commands/merge_tables.py +58 -0
- table_processor/core/actions.py +37 -1
- table_processor/core/config.py +0 -7
- table_processor/core/convert.py +19 -25
- table_processor/core/functions/assign_id.py +84 -6
- table_processor/core/functions/get_nested_field_value.py +3 -0
- table_processor/core/merge.py +145 -0
- table_processor/core/types.py +7 -0
- python_table_processor-0.2.29.dist-info/RECORD +0 -21
- {python_table_processor-0.2.29.dist-info → python_table_processor-0.3.1.dist-info}/LICENSE +0 -0
- {python_table_processor-0.2.29.dist-info → python_table_processor-0.3.1.dist-info}/WHEEL +0 -0
- {python_table_processor-0.2.29.dist-info → python_table_processor-0.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
table_processor/__init__.py,sha256=7U6Yq5pgarhomICnrlEYAI32ycMhvYb4mYoUd4axAyU,52
|
|
2
|
+
table_processor/cli.py,sha256=uluf_VsBzq3Ldxh3ePGseSrv8--lI7huu-6RjDaHPPg,2170
|
|
3
|
+
table_processor/commands/convert_tables.py,sha256=j25KL0zRl5ikrBk73jAkKn4rPmDq7SecChZxecDpILQ,2123
|
|
4
|
+
table_processor/commands/merge_tables.py,sha256=Uu-hjfPh69Of0_OZrY8sY0oscpLSdK4jwJ_t1qgfPMg,1485
|
|
5
|
+
table_processor/core/actions.py,sha256=rXdXXo8AXUaLnrzDp8vRduU0XxIOD7PQdkjHiJzOzq8,19702
|
|
6
|
+
table_processor/core/config.py,sha256=irJxJBhBQNz5cp9XlMjVpZ47hxclG6pkTphK-SCrZQ0,11727
|
|
7
|
+
table_processor/core/constants.py,sha256=w-1TxtpXywb6Qh8M4YWuzcCHUvCjy5zJYxaXC3RQS6A,158
|
|
8
|
+
table_processor/core/convert.py,sha256=2R9zf0SzRZEpNEd51eym10mJ3kb5vLSnsY81z4dDXCA,11566
|
|
9
|
+
table_processor/core/functions/assign_id.py,sha256=dA-wgZA7E01gbx8qGvo1PeMd3aq9PhMdyITzr3BKXIE,4204
|
|
10
|
+
table_processor/core/functions/flatten_row.py,sha256=2l-s4YXgBM58IthpfVzd7L4EGUCekSi7ss5CaM_3MUU,686
|
|
11
|
+
table_processor/core/functions/get_nested_field_value.py,sha256=vF4pH6nxR3-2bDfAvrMfHcPFNiS4MnGkXL8FdcScdIw,685
|
|
12
|
+
table_processor/core/functions/nest_row.py,sha256=b5AKfxE39g-NOLranQRy0dfTZq-27Or8ZlgCB8N5kFs,640
|
|
13
|
+
table_processor/core/functions/search_column_value.py,sha256=GscwLaMTORHFDhdCdjzdoy46UGyFHrGwDQA2b_tZhsQ,740
|
|
14
|
+
table_processor/core/functions/set_flat_field_value.py,sha256=7-BgD2yEUGyBaFaIJ18bYv82Qdy0CN8NiC5ZtsAdv44,531
|
|
15
|
+
table_processor/core/functions/set_nested_field_value.py,sha256=csO0v_nW94q-M9bNL91xR-TLHPXDDxnLK8gN-0LLd6k,595
|
|
16
|
+
table_processor/core/functions/set_row_value.py,sha256=wlF_nVGZY74XCzd6pBJJyti62m6y0s1kc5Q6uq9jn8o,641
|
|
17
|
+
table_processor/core/merge.py,sha256=Yc5OV9n1xV9QtgY4srl6Susd5cOKbuYseEtYuBampDw,4672
|
|
18
|
+
table_processor/core/types.py,sha256=vqm7Jyktrcd3qvfJspu8421MwsXI-jxnCeXAbmpTDHw,2498
|
|
19
|
+
python_table_processor-0.3.1.dist-info/LICENSE,sha256=1_Gn0I1neLPxDLfLiHEyxjDg-pbAra1p2iHEqutGS_c,1068
|
|
20
|
+
python_table_processor-0.3.1.dist-info/METADATA,sha256=nAAAO4VF74FJv3Qd-Jd1lNRTQVvEOKDcZWu-13C04_g,1098
|
|
21
|
+
python_table_processor-0.3.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
22
|
+
python_table_processor-0.3.1.dist-info/entry_points.txt,sha256=lq97F6m51yZ93qmVIqkUHJfbILZuYLxrMWXSqrP4JpM,118
|
|
23
|
+
python_table_processor-0.3.1.dist-info/RECORD,,
|
table_processor/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.
|
|
2
|
-
__version_tuple__ = (0,
|
|
1
|
+
__version__ = "0.3.1"
|
|
2
|
+
__version_tuple__ = (0, 3, 1)
|
table_processor/cli.py
CHANGED
|
@@ -34,7 +34,21 @@ def command_convert_tables(
|
|
|
34
34
|
)
|
|
35
35
|
else:
|
|
36
36
|
command_parser = parser
|
|
37
|
-
from
|
|
37
|
+
from table_processor.commands.convert_tables import setup_parser
|
|
38
|
+
setup_parser(command_parser)
|
|
39
|
+
if parser is None:
|
|
40
|
+
parse_and_run(command_parser)
|
|
41
|
+
|
|
42
|
+
def command_merge_tables(
|
|
43
|
+
parser: argparse.ArgumentParser|None = None,
|
|
44
|
+
):
|
|
45
|
+
if parser is None:
|
|
46
|
+
command_parser = argparse.ArgumentParser(
|
|
47
|
+
description='Merge tables.'
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
command_parser = parser
|
|
51
|
+
from table_processor.commands.merge_tables import setup_parser
|
|
38
52
|
setup_parser(command_parser)
|
|
39
53
|
if parser is None:
|
|
40
54
|
parse_and_run(command_parser)
|
|
@@ -60,6 +74,13 @@ def main():
|
|
|
60
74
|
setup_common_args(parser_convert_tables)
|
|
61
75
|
command_convert_tables(parser_convert_tables)
|
|
62
76
|
|
|
77
|
+
parser_merge_tables = subparsers.add_parser(
|
|
78
|
+
'merge',
|
|
79
|
+
help='Merge tables.'
|
|
80
|
+
)
|
|
81
|
+
setup_common_args(parser_merge_tables)
|
|
82
|
+
command_merge_tables(parser_merge_tables)
|
|
83
|
+
|
|
63
84
|
parse_and_run(parser)
|
|
64
85
|
|
|
65
86
|
if __name__ == '__main__':
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from icecream import ic
|
|
6
|
+
|
|
7
|
+
from .. core.merge import merge
|
|
8
|
+
|
|
9
|
+
def run(
|
|
10
|
+
args: argparse.Namespace,
|
|
11
|
+
):
|
|
12
|
+
merge(
|
|
13
|
+
previous_files=args.previous_files,
|
|
14
|
+
modification_files=args.modification_files,
|
|
15
|
+
keys=args.keys,
|
|
16
|
+
allow_duplicate_keys=args.allow_duplicate_keys,
|
|
17
|
+
ignore_not_found=args.ignore_not_found,
|
|
18
|
+
output_file=args.output_file,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def setup_parser(
|
|
22
|
+
parser: argparse.ArgumentParser,
|
|
23
|
+
):
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--previous-files', '--previous-file', '--previous', '--old', '-P',
|
|
26
|
+
nargs='+',
|
|
27
|
+
required=True,
|
|
28
|
+
help='Previous files to merge',
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'--modification-files', '--modification', '--modify', '--modified', '--new', '-M',
|
|
32
|
+
nargs='+',
|
|
33
|
+
required=True,
|
|
34
|
+
help='Modification files to merge',
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'--keys', '-K',
|
|
38
|
+
nargs='+',
|
|
39
|
+
required=True,
|
|
40
|
+
help='Primary keys',
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
'--allow-duplicate-keys', '--allow-duplicate',
|
|
44
|
+
action='store_true',
|
|
45
|
+
help='Allow duplicate keys',
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
'--ignore-not-found',
|
|
49
|
+
action='store_true',
|
|
50
|
+
help='Ignore not found',
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
'--output-file', '--output', '-o',
|
|
54
|
+
metavar='OUTPUT_FILE',
|
|
55
|
+
required=False,
|
|
56
|
+
help='Path to the output file.',
|
|
57
|
+
)
|
|
58
|
+
parser.set_defaults(handler=run)
|
table_processor/core/actions.py
CHANGED
|
@@ -34,6 +34,7 @@ from . types import (
|
|
|
34
34
|
OmitConfig,
|
|
35
35
|
ParseConfig,
|
|
36
36
|
PickConfig,
|
|
37
|
+
PushConfig,
|
|
37
38
|
SplitConfig,
|
|
38
39
|
Row,
|
|
39
40
|
)
|
|
@@ -131,10 +132,12 @@ def setup_actions_with_args(
|
|
|
131
132
|
context = options.get('context', None)
|
|
132
133
|
if context:
|
|
133
134
|
context = context.split(',')
|
|
135
|
+
reverse = options.get('reverse', False)
|
|
134
136
|
config.actions.append(AssignIdConfig(
|
|
135
137
|
target = target,
|
|
136
138
|
primary = [source],
|
|
137
139
|
context = context,
|
|
140
|
+
reverse = reverse,
|
|
138
141
|
))
|
|
139
142
|
continue
|
|
140
143
|
if action_name == 'filter-empty':
|
|
@@ -187,6 +190,14 @@ def setup_actions_with_args(
|
|
|
187
190
|
required = required,
|
|
188
191
|
))
|
|
189
192
|
continue
|
|
193
|
+
if action_name == 'push':
|
|
194
|
+
condition = options.get('condition', None)
|
|
195
|
+
config.actions.append(PushConfig(
|
|
196
|
+
target = target,
|
|
197
|
+
source = source,
|
|
198
|
+
condition = condition,
|
|
199
|
+
))
|
|
200
|
+
continue
|
|
190
201
|
if action_name == 'split':
|
|
191
202
|
delimiter = options.get('delimiter', None)
|
|
192
203
|
if delimiter == '\\n':
|
|
@@ -302,6 +313,8 @@ def do_action(
|
|
|
302
313
|
return parse(row, action)
|
|
303
314
|
if isinstance(action, OmitConfig):
|
|
304
315
|
return omit_field(row, action)
|
|
316
|
+
if isinstance(action, PushConfig):
|
|
317
|
+
return push_field(row, action)
|
|
305
318
|
if isinstance(action, SplitConfig):
|
|
306
319
|
return split_field(row, action)
|
|
307
320
|
raise ValueError(
|
|
@@ -488,7 +501,8 @@ def assign_format(
|
|
|
488
501
|
params[key] = f'__{key}__undefined__'
|
|
489
502
|
except:
|
|
490
503
|
#ic(params)
|
|
491
|
-
ic(params.keys())
|
|
504
|
+
#ic(params.keys())
|
|
505
|
+
ic(row.flat)
|
|
492
506
|
raise
|
|
493
507
|
set_row_staging_value(row, config.target, formatted)
|
|
494
508
|
return row
|
|
@@ -599,3 +613,25 @@ def parse(
|
|
|
599
613
|
parsed = value
|
|
600
614
|
set_row_staging_value(row, config.target, parsed)
|
|
601
615
|
return row
|
|
616
|
+
|
|
617
|
+
def push_field(
|
|
618
|
+
row: Row,
|
|
619
|
+
config: PushConfig,
|
|
620
|
+
):
|
|
621
|
+
source_value, found = search_column_value(row.nested, config.source)
|
|
622
|
+
do_append = False
|
|
623
|
+
if config.condition is None:
|
|
624
|
+
do_append = True
|
|
625
|
+
else:
|
|
626
|
+
condition_value, found = search_column_value(row.nested, config.condition)
|
|
627
|
+
if condition_value:
|
|
628
|
+
do_append = True
|
|
629
|
+
if do_append:
|
|
630
|
+
target_value, found = search_column_value(row.nested, config.target)
|
|
631
|
+
if found:
|
|
632
|
+
array = target_value
|
|
633
|
+
else:
|
|
634
|
+
array = []
|
|
635
|
+
set_row_staging_value(row, config.target, array)
|
|
636
|
+
array.append(source_value)
|
|
637
|
+
return row
|
table_processor/core/config.py
CHANGED
|
@@ -31,17 +31,10 @@ class AssignArrayConfig:
|
|
|
31
31
|
field: str
|
|
32
32
|
optional: bool = True
|
|
33
33
|
|
|
34
|
-
@dataclasses.dataclass
|
|
35
|
-
class PushConfig:
|
|
36
|
-
target: str
|
|
37
|
-
source: str
|
|
38
|
-
condition: str | None = None
|
|
39
|
-
|
|
40
34
|
@dataclasses.dataclass
|
|
41
35
|
class ProcessConfig:
|
|
42
36
|
assign_array: Mapping[str, list[AssignArrayConfig]] = dataclasses.field(default_factory=OrderedDict)
|
|
43
37
|
assign_length: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
44
|
-
push: list[PushConfig] = dataclasses.field(default_factory=list)
|
|
45
38
|
|
|
46
39
|
def __setitem__(self, key, value):
|
|
47
40
|
setattr(self, key, value)
|
table_processor/core/convert.py
CHANGED
|
@@ -20,7 +20,6 @@ import pandas as pd
|
|
|
20
20
|
|
|
21
21
|
from . config import (
|
|
22
22
|
AssignArrayConfig,
|
|
23
|
-
PushConfig,
|
|
24
23
|
setup_config,
|
|
25
24
|
setup_pick_with_args,
|
|
26
25
|
)
|
|
@@ -63,6 +62,15 @@ def register_loader(
|
|
|
63
62
|
return loader
|
|
64
63
|
return decorator
|
|
65
64
|
|
|
65
|
+
def load(
|
|
66
|
+
input_file: str,
|
|
67
|
+
):
|
|
68
|
+
ext = os.path.splitext(input_file)[1]
|
|
69
|
+
if ext not in dict_loaders:
|
|
70
|
+
raise ValueError(f'Unsupported file type: {ext}')
|
|
71
|
+
loader = dict_loaders[ext]
|
|
72
|
+
return loader(input_file)
|
|
73
|
+
|
|
66
74
|
dict_savers: dict[str, callable] = {}
|
|
67
75
|
def register_saver(
|
|
68
76
|
ext: str,
|
|
@@ -72,6 +80,16 @@ def register_saver(
|
|
|
72
80
|
return saver
|
|
73
81
|
return decorator
|
|
74
82
|
|
|
83
|
+
def save(
|
|
84
|
+
df: pd.DataFrame,
|
|
85
|
+
output_file: str,
|
|
86
|
+
):
|
|
87
|
+
ext = os.path.splitext(output_file)[1]
|
|
88
|
+
if ext not in dict_savers:
|
|
89
|
+
raise ValueError(f'Unsupported file type: {ext}')
|
|
90
|
+
saver = dict_savers[ext]
|
|
91
|
+
saver(df, output_file)
|
|
92
|
+
|
|
75
93
|
@register_loader('.csv')
|
|
76
94
|
def load_csv(
|
|
77
95
|
input_file: str,
|
|
@@ -245,28 +263,6 @@ def search_column_value_from_nested(
|
|
|
245
263
|
return None, False
|
|
246
264
|
|
|
247
265
|
|
|
248
|
-
def push_fields(
|
|
249
|
-
row: OrderedDict,
|
|
250
|
-
list_config: list[PushConfig],
|
|
251
|
-
):
|
|
252
|
-
nested_row = nest(row)
|
|
253
|
-
for config in list_config:
|
|
254
|
-
target_value, found = search_column_value_from_nested(nested_row, config.target)
|
|
255
|
-
if found:
|
|
256
|
-
array = target_value
|
|
257
|
-
else:
|
|
258
|
-
array = []
|
|
259
|
-
#set_field_value(nested_row, f'{STAGING_FIELD}.{config.target}', array)
|
|
260
|
-
set_row_staging_value(nested_row, config.target, array)
|
|
261
|
-
source_value, found = search_column_value_from_nested(nested_row, config.source)
|
|
262
|
-
if config.condition is None:
|
|
263
|
-
array.append(source_value)
|
|
264
|
-
continue
|
|
265
|
-
condition_value, found = search_column_value_from_nested(nested_row, config.condition)
|
|
266
|
-
if condition_value:
|
|
267
|
-
array.append(source_value)
|
|
268
|
-
return flatten_row(nested_row)
|
|
269
|
-
|
|
270
266
|
def assign_length(
|
|
271
267
|
row: OrderedDict,
|
|
272
268
|
dict_fields: OrderedDict,
|
|
@@ -352,8 +348,6 @@ def convert(
|
|
|
352
348
|
set_row_staging_value(row, INPUT_FIELD, orig_row.nested)
|
|
353
349
|
if config.process.assign_array:
|
|
354
350
|
row.flat= assign_array(row.flat, config.process.assign_array)
|
|
355
|
-
if config.process.push:
|
|
356
|
-
row.flat = push_fields(row.flat, config.process.push)
|
|
357
351
|
if config.process.assign_length:
|
|
358
352
|
row.flat = assign_length(row.flat, config.process.assign_length)
|
|
359
353
|
if config.actions:
|
|
@@ -35,15 +35,17 @@ from .. types import (
|
|
|
35
35
|
Row,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
def
|
|
39
|
-
id_context_map: IdContextMap,
|
|
38
|
+
def get_key_value(
|
|
40
39
|
row: Row,
|
|
41
|
-
|
|
40
|
+
primary: list[str],
|
|
41
|
+
context: list[str],
|
|
42
42
|
):
|
|
43
|
+
if not primary:
|
|
44
|
+
raise ValueError('Primary columns must be specified')
|
|
43
45
|
context_columns = []
|
|
44
46
|
context_values = []
|
|
45
|
-
if
|
|
46
|
-
for context_column in
|
|
47
|
+
if context:
|
|
48
|
+
for context_column in context:
|
|
47
49
|
value, found = search_column_value(row.nested, context_column)
|
|
48
50
|
if not found:
|
|
49
51
|
raise KeyError(f'Column not found: {context_column}, existing columns: {row.flat.keys()}')
|
|
@@ -51,7 +53,7 @@ def assign_id(
|
|
|
51
53
|
context_values.append(value)
|
|
52
54
|
primary_columns = []
|
|
53
55
|
primary_values = []
|
|
54
|
-
for primary_column in
|
|
56
|
+
for primary_column in primary:
|
|
55
57
|
value, found = search_column_value(row.nested, primary_column)
|
|
56
58
|
if not found:
|
|
57
59
|
raise KeyError(f'Column not found: {primary_column}, existing columns: {row.flat.keys()}')
|
|
@@ -63,13 +65,89 @@ def assign_id(
|
|
|
63
65
|
tuple(primary_columns),
|
|
64
66
|
)
|
|
65
67
|
primary_value = tuple(primary_values)
|
|
68
|
+
return context_key, primary_value
|
|
69
|
+
|
|
70
|
+
def get_id(
|
|
71
|
+
id_context_map: IdContextMap,
|
|
72
|
+
row: Row,
|
|
73
|
+
primary: list[str],
|
|
74
|
+
context: list[str],
|
|
75
|
+
):
|
|
76
|
+
context_key, primary_value = get_key_value(
|
|
77
|
+
row=row,
|
|
78
|
+
primary=primary,
|
|
79
|
+
context=context,
|
|
80
|
+
)
|
|
66
81
|
id_map = id_context_map[context_key]
|
|
82
|
+
#ic(primary_value, context_key)
|
|
83
|
+
#ic(primary_value in id_map.dict_value_to_id)
|
|
67
84
|
if primary_value not in id_map.dict_value_to_id:
|
|
68
85
|
field_id = id_map.max_id + 1
|
|
69
86
|
id_map.max_id = field_id
|
|
70
87
|
id_map.dict_value_to_id[primary_value] = field_id
|
|
71
88
|
id_map.dict_id_to_value[field_id] = primary_value
|
|
89
|
+
id_exists = False
|
|
72
90
|
else:
|
|
73
91
|
field_id = id_map.dict_value_to_id[primary_value]
|
|
92
|
+
id_exists = True
|
|
93
|
+
return field_id, id_exists
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def set_id(
|
|
97
|
+
id_context_map: IdContextMap,
|
|
98
|
+
row: Row,
|
|
99
|
+
primary: list[str],
|
|
100
|
+
context: list[str],
|
|
101
|
+
id_value: int,
|
|
102
|
+
):
|
|
103
|
+
context_key, primary_value = get_key_value(
|
|
104
|
+
row=row,
|
|
105
|
+
primary=primary,
|
|
106
|
+
context=context,
|
|
107
|
+
)
|
|
108
|
+
id_map = id_context_map[context_key]
|
|
109
|
+
if id_value in id_map.dict_id_to_value:
|
|
110
|
+
#raise ValueError(f'ID already exists: {id_value}')
|
|
111
|
+
field_id = id_map.dict_id_to_value[id_value]
|
|
112
|
+
if field_id != primary_value:
|
|
113
|
+
raise ValueError(f'ID already exists: {id_value} for {field_id}')
|
|
114
|
+
id_map.dict_value_to_id[primary_value] = id_value
|
|
115
|
+
id_map.dict_id_to_value[id_value] = primary_value
|
|
116
|
+
id_map.max_id = max(id_map.max_id, id_value)
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def assign_id(
|
|
121
|
+
id_context_map: IdContextMap,
|
|
122
|
+
row: Row,
|
|
123
|
+
config: Config,
|
|
124
|
+
):
|
|
125
|
+
if config.reverse:
|
|
126
|
+
#ic(row)
|
|
127
|
+
#ic(type(row))
|
|
128
|
+
#ic(config)
|
|
129
|
+
value, found = search_column_value(row.nested, config.target)
|
|
130
|
+
#ic(row, value, found)
|
|
131
|
+
if type(value) is str:
|
|
132
|
+
if value.isdigit():
|
|
133
|
+
value = int(value)
|
|
134
|
+
if type(value) is int:
|
|
135
|
+
field_id = value
|
|
136
|
+
set_id(
|
|
137
|
+
id_context_map=id_context_map,
|
|
138
|
+
row=row,
|
|
139
|
+
primary=config.primary,
|
|
140
|
+
context=config.context,
|
|
141
|
+
id_value=field_id,
|
|
142
|
+
)
|
|
143
|
+
#return field_id, True
|
|
144
|
+
return row
|
|
145
|
+
field_id, id_exists = get_id(
|
|
146
|
+
id_context_map=id_context_map,
|
|
147
|
+
row=row,
|
|
148
|
+
primary=config.primary,
|
|
149
|
+
context=config.context,
|
|
150
|
+
)
|
|
151
|
+
#ic(config, field_id, id_exists)
|
|
74
152
|
set_row_staging_value(row, config.target, field_id)
|
|
75
153
|
return row
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from collections import OrderedDict
|
|
4
4
|
|
|
5
|
+
from icecream import ic
|
|
6
|
+
|
|
5
7
|
def get_nested_field_value(
|
|
6
8
|
data: OrderedDict | list,
|
|
7
9
|
field: str,
|
|
8
10
|
):
|
|
9
11
|
if isinstance(data, list):
|
|
12
|
+
#ic(data, field)
|
|
10
13
|
if field.isdigit():
|
|
11
14
|
index = int(field)
|
|
12
15
|
if index < len(data):
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
|
|
9
|
+
from typing import (
|
|
10
|
+
Mapping,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
# 3-rd party modules
|
|
14
|
+
|
|
15
|
+
from icecream import ic
|
|
16
|
+
import numpy as np
|
|
17
|
+
import pandas as pd
|
|
18
|
+
from tqdm.auto import tqdm
|
|
19
|
+
|
|
20
|
+
# local
|
|
21
|
+
|
|
22
|
+
from . functions.flatten_row import flatten_row
|
|
23
|
+
from . functions.get_nested_field_value import get_nested_field_value
|
|
24
|
+
from . functions.get_nested_field_value import get_nested_field_value
|
|
25
|
+
from . functions.nest_row import nest_row as nest
|
|
26
|
+
from . functions.search_column_value import search_column_value
|
|
27
|
+
from . functions.set_nested_field_value import set_nested_field_value
|
|
28
|
+
from . functions.set_row_value import (
|
|
29
|
+
set_row_value,
|
|
30
|
+
set_row_staging_value,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from . actions import (
|
|
34
|
+
prepare_row,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
from . convert import (
|
|
38
|
+
load,
|
|
39
|
+
save,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def merge(
|
|
43
|
+
previous_files: list[str],
|
|
44
|
+
modification_files: list[str],
|
|
45
|
+
keys: list[str],
|
|
46
|
+
allow_duplicate_keys: bool = False,
|
|
47
|
+
ignore_not_found: bool = False,
|
|
48
|
+
output_file: str | None = None,
|
|
49
|
+
):
|
|
50
|
+
ic.enable()
|
|
51
|
+
ic()
|
|
52
|
+
ic(previous_files)
|
|
53
|
+
ic(modification_files)
|
|
54
|
+
ic(keys)
|
|
55
|
+
dict_key_to_row = {}
|
|
56
|
+
all_rows = []
|
|
57
|
+
list_ignored_keys = []
|
|
58
|
+
num_modified = 0
|
|
59
|
+
for previous_file in previous_files:
|
|
60
|
+
if not os.path.exists(previous_file):
|
|
61
|
+
raise FileNotFoundError(f'File not found: {previous_file}')
|
|
62
|
+
df = load(previous_file)
|
|
63
|
+
# NOTE: NaN を None に変換しておかないと厄介
|
|
64
|
+
df = df.replace([np.nan], [None])
|
|
65
|
+
#ic(df)
|
|
66
|
+
ic(len(df))
|
|
67
|
+
#ic(df.columns)
|
|
68
|
+
#ic(df.iloc[0])
|
|
69
|
+
for index, flat_row in tqdm(
|
|
70
|
+
df.iterrows(),
|
|
71
|
+
desc=f'Loading: {previous_file}',
|
|
72
|
+
total=len(df),
|
|
73
|
+
):
|
|
74
|
+
row = prepare_row(flat_row)
|
|
75
|
+
list_keys = []
|
|
76
|
+
for key in keys:
|
|
77
|
+
value, found = search_column_value(row.nested, key)
|
|
78
|
+
if not found:
|
|
79
|
+
raise KeyError(f'Column not found: {key}, existing columns: {row.flat.keys()}')
|
|
80
|
+
list_keys.append(value)
|
|
81
|
+
primary_key = tuple(list_keys)
|
|
82
|
+
#ic(key)
|
|
83
|
+
if not allow_duplicate_keys:
|
|
84
|
+
if primary_key in dict_key_to_row:
|
|
85
|
+
ic(index)
|
|
86
|
+
raise ValueError(f'Duplicate key: {key}')
|
|
87
|
+
dict_key_to_row[primary_key] = row
|
|
88
|
+
all_rows.append(row)
|
|
89
|
+
for modification_file in modification_files:
|
|
90
|
+
if not os.path.exists(modification_file):
|
|
91
|
+
raise FileNotFoundError(f'File not found: {modification_file}')
|
|
92
|
+
df = load(modification_file)
|
|
93
|
+
# NOTE: NaN を None に変換しておかないと厄介
|
|
94
|
+
df = df.replace([np.nan], [None])
|
|
95
|
+
#ic(df)
|
|
96
|
+
ic(len(df))
|
|
97
|
+
#ic(df.columns)
|
|
98
|
+
#ic(df.iloc[0])
|
|
99
|
+
for index, flat_row in tqdm(
|
|
100
|
+
df.iterrows(),
|
|
101
|
+
desc=f'Processing: {modification_file}',
|
|
102
|
+
total=len(df),
|
|
103
|
+
):
|
|
104
|
+
row = prepare_row(flat_row)
|
|
105
|
+
list_keys = []
|
|
106
|
+
for key in keys:
|
|
107
|
+
value, found = search_column_value(row.nested, key)
|
|
108
|
+
if not found:
|
|
109
|
+
raise KeyError(f'Column not found: {key}, existing columns: {row.flat.keys()}')
|
|
110
|
+
list_keys.append(value)
|
|
111
|
+
primary_key = tuple(list_keys)
|
|
112
|
+
#ic(key)
|
|
113
|
+
#if key not in dict_key_to_row:
|
|
114
|
+
# dict_key_to_row[key] = row
|
|
115
|
+
#else:
|
|
116
|
+
# dict_key_to_row[key].flat.update(row.flat)
|
|
117
|
+
if primary_key not in dict_key_to_row:
|
|
118
|
+
if ignore_not_found:
|
|
119
|
+
list_ignored_keys.append(primary_key)
|
|
120
|
+
continue
|
|
121
|
+
ic(index)
|
|
122
|
+
raise ValueError(f'Key not found: {key}')
|
|
123
|
+
previous_row = dict_key_to_row[primary_key]
|
|
124
|
+
#ic(previous_row)
|
|
125
|
+
#ic(previous_row.flat)
|
|
126
|
+
for key, value in row.flat.items():
|
|
127
|
+
if key.startswith('__staging__.'):
|
|
128
|
+
continue
|
|
129
|
+
ic(key, value)
|
|
130
|
+
set_row_value(previous_row, key, value)
|
|
131
|
+
#set_row_value(previous_row, '指示追従性?', 'test')
|
|
132
|
+
#set_row_value(previous_row, 'modified', True)
|
|
133
|
+
#ic(previous_row)
|
|
134
|
+
#ic(previous_row.flat)
|
|
135
|
+
#raise
|
|
136
|
+
num_modified += 1
|
|
137
|
+
ic(num_modified)
|
|
138
|
+
if ignore_not_found:
|
|
139
|
+
ic(len(list_ignored_keys))
|
|
140
|
+
ic(list_ignored_keys)
|
|
141
|
+
if output_file:
|
|
142
|
+
#all_df = pd.DataFrame(all_rows)
|
|
143
|
+
all_df = pd.DataFrame([row.flat for row in all_rows])
|
|
144
|
+
ic('Saving to: ', output_file)
|
|
145
|
+
save(all_df, output_file)
|
table_processor/core/types.py
CHANGED
|
@@ -44,6 +44,7 @@ class AssignIdConfig:
|
|
|
44
44
|
target: str
|
|
45
45
|
primary: list[str]
|
|
46
46
|
context: list[str] | None = None
|
|
47
|
+
reverse: bool = False
|
|
47
48
|
|
|
48
49
|
@dataclasses.dataclass
|
|
49
50
|
class FilterConfig:
|
|
@@ -76,6 +77,12 @@ class PickConfig:
|
|
|
76
77
|
target: str
|
|
77
78
|
source: str
|
|
78
79
|
|
|
80
|
+
@dataclasses.dataclass
|
|
81
|
+
class PushConfig:
|
|
82
|
+
target: str
|
|
83
|
+
source: str
|
|
84
|
+
condition: str | None = None
|
|
85
|
+
|
|
79
86
|
@dataclasses.dataclass
|
|
80
87
|
class SplitConfig:
|
|
81
88
|
target: str
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
table_processor/__init__.py,sha256=i7SFej_-ia2PplNZ0Wf3m0M9G_ymn8Q72-ONSBkO8r8,54
|
|
2
|
-
table_processor/cli.py,sha256=Q5eshXdgfeoiyJkFlQOCX695_6xi1dcibin0kUKOMX0,1574
|
|
3
|
-
table_processor/commands/convert_tables.py,sha256=j25KL0zRl5ikrBk73jAkKn4rPmDq7SecChZxecDpILQ,2123
|
|
4
|
-
table_processor/core/actions.py,sha256=-xyZp-RHjPz75JHNeKjDvdaBkDlZYAZbE2lpIJZfRDg,18524
|
|
5
|
-
table_processor/core/config.py,sha256=iiHAcJlO4RyXyrXFAd2ZRU3bJBnJIFM79AdfcEMMI5o,11903
|
|
6
|
-
table_processor/core/constants.py,sha256=w-1TxtpXywb6Qh8M4YWuzcCHUvCjy5zJYxaXC3RQS6A,158
|
|
7
|
-
table_processor/core/convert.py,sha256=uoBHgB85VLn_Yg9ugcC0KgwTVtJHnCyEZnv2FCpbwos,12062
|
|
8
|
-
table_processor/core/functions/assign_id.py,sha256=5xJuLpyWvSQ1DhzXiAOLQAwfPo9RKICzS_oka0wzHgA,2065
|
|
9
|
-
table_processor/core/functions/flatten_row.py,sha256=2l-s4YXgBM58IthpfVzd7L4EGUCekSi7ss5CaM_3MUU,686
|
|
10
|
-
table_processor/core/functions/get_nested_field_value.py,sha256=3u1TA2Ov5biHDLLZQLqwO0WdHMmJNsbfrI8EgstX6yA,635
|
|
11
|
-
table_processor/core/functions/nest_row.py,sha256=b5AKfxE39g-NOLranQRy0dfTZq-27Or8ZlgCB8N5kFs,640
|
|
12
|
-
table_processor/core/functions/search_column_value.py,sha256=GscwLaMTORHFDhdCdjzdoy46UGyFHrGwDQA2b_tZhsQ,740
|
|
13
|
-
table_processor/core/functions/set_flat_field_value.py,sha256=7-BgD2yEUGyBaFaIJ18bYv82Qdy0CN8NiC5ZtsAdv44,531
|
|
14
|
-
table_processor/core/functions/set_nested_field_value.py,sha256=csO0v_nW94q-M9bNL91xR-TLHPXDDxnLK8gN-0LLd6k,595
|
|
15
|
-
table_processor/core/functions/set_row_value.py,sha256=wlF_nVGZY74XCzd6pBJJyti62m6y0s1kc5Q6uq9jn8o,641
|
|
16
|
-
table_processor/core/types.py,sha256=jRPRut8Jr2E458SOYvB3kxKxuMWDHk5KJ8aCqmiFvMs,2365
|
|
17
|
-
python_table_processor-0.2.29.dist-info/LICENSE,sha256=1_Gn0I1neLPxDLfLiHEyxjDg-pbAra1p2iHEqutGS_c,1068
|
|
18
|
-
python_table_processor-0.2.29.dist-info/METADATA,sha256=6YjaO5gDbtQupOZiv45NHODCGYdDBX7oqKiO-WfYs4k,1099
|
|
19
|
-
python_table_processor-0.2.29.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
20
|
-
python_table_processor-0.2.29.dist-info/entry_points.txt,sha256=lq97F6m51yZ93qmVIqkUHJfbILZuYLxrMWXSqrP4JpM,118
|
|
21
|
-
python_table_processor-0.2.29.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{python_table_processor-0.2.29.dist-info → python_table_processor-0.3.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|