python-table-processor 0.2.29__py3-none-any.whl → 0.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-table-processor
3
- Version: 0.2.29
3
+ Version: 0.3.0
4
4
  Summary: A table data processor
5
5
  Home-page: https://github.com/akivajp/python-table-processor
6
6
  License: MIT
@@ -0,0 +1,23 @@
1
+ table_processor/__init__.py,sha256=gybnWB0ziz1cPSSQQek-Mj66Zh8ogh_WyQsnqrNmZ3g,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=tZeStD07jRC_q-2r5dKXtN4wIE52JO82TTrUyHR-ias,18645
6
+ table_processor/core/config.py,sha256=iiHAcJlO4RyXyrXFAd2ZRU3bJBnJIFM79AdfcEMMI5o,11903
7
+ table_processor/core/constants.py,sha256=w-1TxtpXywb6Qh8M4YWuzcCHUvCjy5zJYxaXC3RQS6A,158
8
+ table_processor/core/convert.py,sha256=SbDMJnXuXKuZ-7PBSqWl7pVsaBOforXQlRekq4XYKbY,12536
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=pQrNtf5pt5Unn3x8WK6ZtDpMhU9r8iYRAucToZQEBh0,2391
19
+ python_table_processor-0.3.0.dist-info/LICENSE,sha256=1_Gn0I1neLPxDLfLiHEyxjDg-pbAra1p2iHEqutGS_c,1068
20
+ python_table_processor-0.3.0.dist-info/METADATA,sha256=7j1SD0x8oegMJFKwjnraXyMoN_WQMhIFzCCPD14yXDc,1098
21
+ python_table_processor-0.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
22
+ python_table_processor-0.3.0.dist-info/entry_points.txt,sha256=lq97F6m51yZ93qmVIqkUHJfbILZuYLxrMWXSqrP4JpM,118
23
+ python_table_processor-0.3.0.dist-info/RECORD,,
@@ -1,2 +1,2 @@
1
- __version__ = "0.2.29"
2
- __version_tuple__ = (0, 2, 29)
1
+ __version__ = "0.3.0"
2
+ __version_tuple__ = (0, 3, 0)
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 table_converter.commands.convert_tables import setup_parser
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)
@@ -131,10 +131,12 @@ def setup_actions_with_args(
131
131
  context = options.get('context', None)
132
132
  if context:
133
133
  context = context.split(',')
134
+ reverse = options.get('reverse', False)
134
135
  config.actions.append(AssignIdConfig(
135
136
  target = target,
136
137
  primary = [source],
137
138
  context = context,
139
+ reverse = reverse,
138
140
  ))
139
141
  continue
140
142
  if action_name == 'filter-empty':
@@ -488,7 +490,8 @@ def assign_format(
488
490
  params[key] = f'__{key}__undefined__'
489
491
  except:
490
492
  #ic(params)
491
- ic(params.keys())
493
+ #ic(params.keys())
494
+ ic(row.flat)
492
495
  raise
493
496
  set_row_staging_value(row, config.target, formatted)
494
497
  return row
@@ -63,6 +63,15 @@ def register_loader(
63
63
  return loader
64
64
  return decorator
65
65
 
66
+ def load(
67
+ input_file: str,
68
+ ):
69
+ ext = os.path.splitext(input_file)[1]
70
+ if ext not in dict_loaders:
71
+ raise ValueError(f'Unsupported file type: {ext}')
72
+ loader = dict_loaders[ext]
73
+ return loader(input_file)
74
+
66
75
  dict_savers: dict[str, callable] = {}
67
76
  def register_saver(
68
77
  ext: str,
@@ -72,6 +81,16 @@ def register_saver(
72
81
  return saver
73
82
  return decorator
74
83
 
84
+ def save(
85
+ df: pd.DataFrame,
86
+ output_file: str,
87
+ ):
88
+ ext = os.path.splitext(output_file)[1]
89
+ if ext not in dict_savers:
90
+ raise ValueError(f'Unsupported file type: {ext}')
91
+ saver = dict_savers[ext]
92
+ saver(df, output_file)
93
+
75
94
  @register_loader('.csv')
76
95
  def load_csv(
77
96
  input_file: str,
@@ -35,15 +35,17 @@ from .. types import (
35
35
  Row,
36
36
  )
37
37
 
38
- def assign_id(
39
- id_context_map: IdContextMap,
38
+ def get_key_value(
40
39
  row: Row,
41
- config: Config,
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 config.context:
46
- for context_column in config.context:
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 config.primary:
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)
@@ -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:
@@ -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,,