python-table-converter 0.2.4__tar.gz → 0.2.6__tar.gz
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_converter-0.2.4 → python_table_converter-0.2.6}/PKG-INFO +1 -1
- {python_table_converter-0.2.4 → python_table_converter-0.2.6}/pyproject.toml +1 -1
- python_table_converter-0.2.6/table_converter/__init__.py +2 -0
- python_table_converter-0.2.6/table_converter/core/config.py +123 -0
- python_table_converter-0.2.6/table_converter/core/constants.py +3 -0
- {python_table_converter-0.2.4 → python_table_converter-0.2.6}/table_converter/core/convert.py +30 -91
- python_table_converter-0.2.6/table_converter/core/functions/assign_id.py +108 -0
- python_table_converter-0.2.6/table_converter/core/functions/get_field_value.py +15 -0
- python_table_converter-0.2.6/table_converter/core/functions/search_column_value.py +30 -0
- python_table_converter-0.2.6/table_converter/core/functions/set_field_value.py +18 -0
- python_table_converter-0.2.4/table_converter/__init__.py +0 -2
- python_table_converter-0.2.4/table_converter/core/config.py +0 -71
- {python_table_converter-0.2.4 → python_table_converter-0.2.6}/LICENSE +0 -0
- {python_table_converter-0.2.4 → python_table_converter-0.2.6}/README.md +0 -0
- {python_table_converter-0.2.4 → python_table_converter-0.2.6}/table_converter/cli.py +0 -0
- {python_table_converter-0.2.4 → python_table_converter-0.2.6}/table_converter/commands/convert_tables.py +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
import dataclasses
|
|
5
|
+
from typing import Mapping
|
|
6
|
+
|
|
7
|
+
from icecream import ic
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
type FlatFieldMap = Mapping[str, str]
|
|
11
|
+
type FieldMap = Mapping[str, str|FieldMap]
|
|
12
|
+
|
|
13
|
+
@dataclasses.dataclass
|
|
14
|
+
class AssignIdConfig:
|
|
15
|
+
primary: list[str]
|
|
16
|
+
#given: list[str] | None = None
|
|
17
|
+
context: list[str] | None = None
|
|
18
|
+
|
|
19
|
+
@dataclasses.dataclass
|
|
20
|
+
class ProcessConfig:
|
|
21
|
+
assign_constants: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
22
|
+
assign_formats: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
23
|
+
#assign_ids: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
24
|
+
assign_ids: Mapping[str, AssignIdConfig] = dataclasses.field(default_factory=OrderedDict)
|
|
25
|
+
split_by_newline: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
26
|
+
|
|
27
|
+
def __setitem__(self, key, value):
|
|
28
|
+
setattr(self, key, value)
|
|
29
|
+
|
|
30
|
+
@dataclasses.dataclass
|
|
31
|
+
class Config:
|
|
32
|
+
map: FieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
33
|
+
process: ProcessConfig = dataclasses.field(default_factory=ProcessConfig)
|
|
34
|
+
|
|
35
|
+
def flatten(
|
|
36
|
+
mapping: FieldMap,
|
|
37
|
+
parent_key: str = '',
|
|
38
|
+
new_mapping: FlatFieldMap | None = None,
|
|
39
|
+
) -> FlatFieldMap:
|
|
40
|
+
if new_mapping is None:
|
|
41
|
+
new_mapping = OrderedDict()
|
|
42
|
+
for key, mapped in mapping.items():
|
|
43
|
+
new_key = f'{parent_key}.{key}' if parent_key else key
|
|
44
|
+
if isinstance(mapped, Mapping):
|
|
45
|
+
flatten(mapped, new_key, new_mapping)
|
|
46
|
+
else:
|
|
47
|
+
new_mapping[new_key] = mapped
|
|
48
|
+
return new_mapping
|
|
49
|
+
|
|
50
|
+
def setup_config(
|
|
51
|
+
config_path: str | None = None,
|
|
52
|
+
):
|
|
53
|
+
config = Config()
|
|
54
|
+
if config_path:
|
|
55
|
+
if config_path.endswith('.yaml'):
|
|
56
|
+
yaml.add_constructor(
|
|
57
|
+
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
|
58
|
+
lambda loader, node: OrderedDict(loader.construct_pairs(node)),
|
|
59
|
+
)
|
|
60
|
+
with open(config_path, 'r') as f:
|
|
61
|
+
loaded = yaml.load(f, yaml.Loader)
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
'Only YAML configuration files are supported.'
|
|
65
|
+
)
|
|
66
|
+
ic(loaded)
|
|
67
|
+
if 'map' in loaded:
|
|
68
|
+
config.map = flatten(loaded['map'])
|
|
69
|
+
setup_process_config(config, loaded)
|
|
70
|
+
return config
|
|
71
|
+
|
|
72
|
+
def setup_process_config(
|
|
73
|
+
config: Config,
|
|
74
|
+
loaded: Mapping,
|
|
75
|
+
):
|
|
76
|
+
dict_process = loaded.get('process')
|
|
77
|
+
if isinstance(dict_process, Mapping):
|
|
78
|
+
for process_key in [
|
|
79
|
+
'assign_constants',
|
|
80
|
+
'assign_formats',
|
|
81
|
+
'split_by_newline',
|
|
82
|
+
]:
|
|
83
|
+
dict_subprocess = dict_process.get(process_key)
|
|
84
|
+
if isinstance(dict_subprocess, Mapping):
|
|
85
|
+
config.process[process_key] = flatten(loaded['process'][process_key])
|
|
86
|
+
setup_process_assign_ids_config(config, dict_process)
|
|
87
|
+
|
|
88
|
+
def setup_process_assign_ids_config(
|
|
89
|
+
config: Config,
|
|
90
|
+
dict_process: Mapping,
|
|
91
|
+
):
|
|
92
|
+
dict_subprocess = dict_process.get('assign_ids')
|
|
93
|
+
if isinstance(dict_subprocess, Mapping):
|
|
94
|
+
for key, value in dict_subprocess.items():
|
|
95
|
+
if isinstance(value, Mapping):
|
|
96
|
+
primary = value.get('primary')
|
|
97
|
+
if not primary:
|
|
98
|
+
ic.enable()
|
|
99
|
+
ic(value)
|
|
100
|
+
ic(value.get('primary'))
|
|
101
|
+
raise ValueError(
|
|
102
|
+
'Primary field is required for assign_ids.'
|
|
103
|
+
)
|
|
104
|
+
config.process.assign_ids[key] = AssignIdConfig(
|
|
105
|
+
primary = value.get('primary', []),
|
|
106
|
+
#given = value.get('given', None),
|
|
107
|
+
context = value.get('context', None),
|
|
108
|
+
)
|
|
109
|
+
elif isinstance(value, list):
|
|
110
|
+
config.process.assign_ids[key] = AssignIdConfig(
|
|
111
|
+
primary = value,
|
|
112
|
+
)
|
|
113
|
+
elif isinstance(value, str):
|
|
114
|
+
config.process.assign_ids[key] = AssignIdConfig(
|
|
115
|
+
primary = [value],
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
ic.enable()
|
|
119
|
+
ic(value)
|
|
120
|
+
ic(type(value))
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f'Unsupported assign_ids value type: {type(value)}'
|
|
123
|
+
)
|
{python_table_converter-0.2.4 → python_table_converter-0.2.6}/table_converter/core/convert.py
RENAMED
|
@@ -14,6 +14,18 @@ import pandas as pd
|
|
|
14
14
|
# local
|
|
15
15
|
|
|
16
16
|
from . config import setup_config
|
|
17
|
+
from . constants import (
|
|
18
|
+
FILE_FIELD,
|
|
19
|
+
INPUT_FIELD,
|
|
20
|
+
STAGING_FIELD,
|
|
21
|
+
)
|
|
22
|
+
from . functions.assign_id import (
|
|
23
|
+
assign_id,
|
|
24
|
+
create_id_context_map,
|
|
25
|
+
setup_assign_ids,
|
|
26
|
+
)
|
|
27
|
+
from . functions.get_field_value import get_field_value
|
|
28
|
+
from . functions.set_field_value import set_field_value
|
|
17
29
|
|
|
18
30
|
dict_loaders: dict[str, callable] = {}
|
|
19
31
|
def register_loader(
|
|
@@ -85,41 +97,16 @@ def save_jsonl(
|
|
|
85
97
|
)
|
|
86
98
|
f.write('\n')
|
|
87
99
|
|
|
88
|
-
def set_field_value(
|
|
89
|
-
data: OrderedDict,
|
|
90
|
-
field: str,
|
|
91
|
-
value: any,
|
|
92
|
-
):
|
|
93
|
-
if '.' in field:
|
|
94
|
-
field, rest = field.split('.', 1)
|
|
95
|
-
if field not in data:
|
|
96
|
-
data[field] = OrderedDict()
|
|
97
|
-
set_field_value(data[field], rest, value)
|
|
98
|
-
else:
|
|
99
|
-
data[field] = value
|
|
100
|
-
|
|
101
|
-
def get_field_value(
|
|
102
|
-
data: OrderedDict,
|
|
103
|
-
field: str,
|
|
104
|
-
):
|
|
105
|
-
if field in data:
|
|
106
|
-
return data[field], True
|
|
107
|
-
if '.' in field:
|
|
108
|
-
field, rest = field.split('.', 1)
|
|
109
|
-
if field in data:
|
|
110
|
-
return get_field_value(data[field], rest)
|
|
111
|
-
return None, False
|
|
112
|
-
|
|
113
100
|
def search_column_value(
|
|
114
101
|
row: OrderedDict,
|
|
115
102
|
column: str,
|
|
116
103
|
):
|
|
117
|
-
if
|
|
118
|
-
value, found = get_field_value(row[
|
|
104
|
+
if STAGING_FIELD in row:
|
|
105
|
+
value, found = get_field_value(row[STAGING_FIELD], column)
|
|
119
106
|
if found:
|
|
120
107
|
return value, True
|
|
121
|
-
value, found = get_field_value(row[
|
|
122
|
-
original, found = get_field_value(row, '
|
|
108
|
+
value, found = get_field_value(row[STAGING_FIELD], column)
|
|
109
|
+
original, found = get_field_value(row, f'{STAGING_FIELD}.{INPUT_FIELD}')
|
|
123
110
|
if found:
|
|
124
111
|
value, found = get_field_value(original, column)
|
|
125
112
|
if found:
|
|
@@ -137,7 +124,7 @@ def map_constants(
|
|
|
137
124
|
new_row = OrderedDict(row)
|
|
138
125
|
for column in dict_constants.keys():
|
|
139
126
|
#set_field_value(new_row, column, dict_constants[column])
|
|
140
|
-
set_field_value(new_row, f'
|
|
127
|
+
set_field_value(new_row, f'{STAGING_FIELD}.{column}', dict_constants[column])
|
|
141
128
|
return new_row
|
|
142
129
|
|
|
143
130
|
def map_formats(
|
|
@@ -148,7 +135,7 @@ def map_formats(
|
|
|
148
135
|
for column in dict_formats.keys():
|
|
149
136
|
template = dict_formats[column]
|
|
150
137
|
params = {}
|
|
151
|
-
params.update(row[
|
|
138
|
+
params.update(row[STAGING_FIELD])
|
|
152
139
|
params.update(row)
|
|
153
140
|
formatted = None
|
|
154
141
|
while formatted is None:
|
|
@@ -159,10 +146,10 @@ def map_formats(
|
|
|
159
146
|
#ic(e.args)
|
|
160
147
|
#ic(e.args[0])
|
|
161
148
|
key = e.args[0]
|
|
162
|
-
params[key] = '__undefined__'
|
|
149
|
+
params[key] = f'__{key}__undefined__'
|
|
163
150
|
except:
|
|
164
151
|
raise
|
|
165
|
-
set_field_value(new_row, f'
|
|
152
|
+
set_field_value(new_row, f'{STAGING_FIELD}.{column}', formatted)
|
|
166
153
|
return new_row
|
|
167
154
|
|
|
168
155
|
def remap_columns(
|
|
@@ -175,7 +162,7 @@ def remap_columns(
|
|
|
175
162
|
if found:
|
|
176
163
|
set_field_value(new_row, column, value)
|
|
177
164
|
for column in row.keys():
|
|
178
|
-
if column ==
|
|
165
|
+
if column == STAGING_FIELD:
|
|
179
166
|
# NOTE: Ignore debug fields
|
|
180
167
|
set_field_value(new_row, column, row[column])
|
|
181
168
|
return new_row
|
|
@@ -191,49 +178,9 @@ def apply_fields_split_by_newline(
|
|
|
191
178
|
if found:
|
|
192
179
|
if isinstance(value, str):
|
|
193
180
|
new_value = value.split('\n')
|
|
194
|
-
set_field_value(new_row, f'
|
|
181
|
+
set_field_value(new_row, f'{STAGING_FIELD}.{column}', new_value)
|
|
195
182
|
else:
|
|
196
|
-
set_field_value(new_row, f'
|
|
197
|
-
return new_row
|
|
198
|
-
|
|
199
|
-
def create_id_stat_node():
|
|
200
|
-
return {
|
|
201
|
-
'max_id': 0,
|
|
202
|
-
'dict_value_to_id': {},
|
|
203
|
-
'dict_id_to_node': {},
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
def assign_id_in_node(
|
|
207
|
-
row: OrderedDict,
|
|
208
|
-
column: str,
|
|
209
|
-
dict_assignment: OrderedDict,
|
|
210
|
-
id_stat_node: dict,
|
|
211
|
-
):
|
|
212
|
-
value, found = search_column_value(row, dict_assignment[column])
|
|
213
|
-
if not found:
|
|
214
|
-
raise KeyError(f'Column not found: {column}, existing columns: {row.keys()}')
|
|
215
|
-
if value not in id_stat_node['dict_value_to_id']:
|
|
216
|
-
field_id = id_stat_node['max_id'] + 1
|
|
217
|
-
id_stat_node['max_id'] = field_id
|
|
218
|
-
id_stat_node['dict_value_to_id'][value] = field_id
|
|
219
|
-
node = create_id_stat_node()
|
|
220
|
-
id_stat_node['dict_id_to_node'][field_id] = node
|
|
221
|
-
else:
|
|
222
|
-
field_id = id_stat_node['dict_value_to_id'][value]
|
|
223
|
-
node = id_stat_node['dict_id_to_node'][field_id]
|
|
224
|
-
set_field_value(row, f'__debug__.{column}', field_id)
|
|
225
|
-
set_field_value(row, f'__debug__.__ids__.{column}', field_id)
|
|
226
|
-
return node
|
|
227
|
-
|
|
228
|
-
def assign_id(
|
|
229
|
-
row: OrderedDict,
|
|
230
|
-
dict_assignment: OrderedDict,
|
|
231
|
-
root_id_stat_node: dict,
|
|
232
|
-
):
|
|
233
|
-
new_row = OrderedDict(row)
|
|
234
|
-
node = root_id_stat_node
|
|
235
|
-
for column in dict_assignment:
|
|
236
|
-
node = assign_id_in_node(new_row, column, dict_assignment, node)
|
|
183
|
+
set_field_value(new_row, f'{STAGING_FIELD}.{column}', value)
|
|
237
184
|
return new_row
|
|
238
185
|
|
|
239
186
|
def convert(
|
|
@@ -251,8 +198,7 @@ def convert(
|
|
|
251
198
|
ic()
|
|
252
199
|
ic(input_files)
|
|
253
200
|
df_list = []
|
|
254
|
-
|
|
255
|
-
root_id_stat = create_id_stat_node()
|
|
201
|
+
id_context_map = create_id_context_map()
|
|
256
202
|
config = setup_config(config_path)
|
|
257
203
|
ic(config)
|
|
258
204
|
if assign_constants:
|
|
@@ -288,14 +234,7 @@ def convert(
|
|
|
288
234
|
else:
|
|
289
235
|
raise ValueError(f'Invalid split by newline: {field}')
|
|
290
236
|
if fields_to_assign_ids:
|
|
291
|
-
|
|
292
|
-
fields = fields_to_assign_ids.split(',')
|
|
293
|
-
for field in fields:
|
|
294
|
-
if '=' in field:
|
|
295
|
-
dst, src = field.split('=')
|
|
296
|
-
dict_assign_ids[dst] = src
|
|
297
|
-
else:
|
|
298
|
-
raise ValueError(f'Invalid id assignment: {field}')
|
|
237
|
+
setup_assign_ids(config, fields_to_assign_ids)
|
|
299
238
|
if output_file:
|
|
300
239
|
ext = os.path.splitext(output_file)[1]
|
|
301
240
|
if ext not in dict_savers:
|
|
@@ -322,22 +261,22 @@ def convert(
|
|
|
322
261
|
for index, row in df.iterrows():
|
|
323
262
|
orig = OrderedDict(row)
|
|
324
263
|
new_row = OrderedDict(row)
|
|
325
|
-
set_field_value(new_row, '
|
|
326
|
-
set_field_value(new_row, '
|
|
264
|
+
set_field_value(new_row, f'{STAGING_FIELD}.{INPUT_FIELD}', orig)
|
|
265
|
+
set_field_value(new_row, f'{STAGING_FIELD}.{FILE_FIELD}', input_file)
|
|
327
266
|
if config.process.assign_constants:
|
|
328
267
|
new_row = map_constants(new_row, config.process.assign_constants)
|
|
329
268
|
if config.map:
|
|
330
269
|
new_row = remap_columns(new_row, config.map)
|
|
331
270
|
if config.process.split_by_newline:
|
|
332
271
|
new_row = apply_fields_split_by_newline(new_row, config.process.split_by_newline)
|
|
333
|
-
if
|
|
334
|
-
new_row = assign_id(new_row,
|
|
272
|
+
if config.process.assign_ids:
|
|
273
|
+
new_row = assign_id(new_row, config.process.assign_ids, id_context_map)
|
|
335
274
|
if config.process.assign_formats:
|
|
336
275
|
new_row = map_formats(new_row, config.process.assign_formats)
|
|
337
276
|
if config.map:
|
|
338
277
|
new_row = remap_columns(new_row, config.map)
|
|
339
278
|
if not output_debug:
|
|
340
|
-
new_row.pop(
|
|
279
|
+
new_row.pop(STAGING_FIELD, None)
|
|
341
280
|
new_rows.append(new_row)
|
|
342
281
|
new_df = pd.DataFrame(new_rows)
|
|
343
282
|
df_list.append(new_df)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from typing import Mapping
|
|
10
|
+
|
|
11
|
+
# 3-rd party modules
|
|
12
|
+
|
|
13
|
+
from icecream import ic
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
|
|
17
|
+
# local
|
|
18
|
+
|
|
19
|
+
from ..config import (
|
|
20
|
+
AssignIdConfig,
|
|
21
|
+
Config,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from . search_column_value import search_column_value
|
|
25
|
+
from . set_field_value import set_field_value
|
|
26
|
+
|
|
27
|
+
type ContextColumnTuple = tuple[str]
|
|
28
|
+
type ContextValueTuple = tuple
|
|
29
|
+
type PrimaryColumnTuple = tuple[str]
|
|
30
|
+
type PrimaryValueTuple = tuple
|
|
31
|
+
|
|
32
|
+
@dataclasses.dataclass
|
|
33
|
+
class IdMap:
|
|
34
|
+
max_id: int = 0
|
|
35
|
+
dict_value_to_id: Mapping[PrimaryValueTuple, int] = \
|
|
36
|
+
dataclasses.field(default_factory=defaultdict)
|
|
37
|
+
dict_id_to_value: Mapping[int, PrimaryValueTuple] = \
|
|
38
|
+
dataclasses.field(default_factory=defaultdict)
|
|
39
|
+
|
|
40
|
+
type IdContextMap = Mapping[
|
|
41
|
+
(
|
|
42
|
+
ContextColumnTuple,
|
|
43
|
+
ContextValueTuple,
|
|
44
|
+
PrimaryColumnTuple,
|
|
45
|
+
),
|
|
46
|
+
IdMap
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
def create_id_context_map() -> IdContextMap:
|
|
50
|
+
return defaultdict(IdMap)
|
|
51
|
+
|
|
52
|
+
def assign_id(
|
|
53
|
+
row: OrderedDict,
|
|
54
|
+
dict_assignment: Mapping[str, AssignIdConfig],
|
|
55
|
+
id_context_map: IdContextMap,
|
|
56
|
+
):
|
|
57
|
+
new_row = OrderedDict(row)
|
|
58
|
+
for column, config in dict_assignment.items():
|
|
59
|
+
context_columns = []
|
|
60
|
+
context_values = []
|
|
61
|
+
if config.context:
|
|
62
|
+
for context_column in config.context:
|
|
63
|
+
value, found = search_column_value(new_row, context_column)
|
|
64
|
+
if not found:
|
|
65
|
+
raise KeyError(f'Column not found: {context_column}, existing columns: {new_row.keys()}')
|
|
66
|
+
context_columns.append(context_column)
|
|
67
|
+
context_values.append(value)
|
|
68
|
+
primary_columns = []
|
|
69
|
+
primary_values = []
|
|
70
|
+
for primary_column in config.primary:
|
|
71
|
+
value, found = search_column_value(new_row, primary_column)
|
|
72
|
+
if not found:
|
|
73
|
+
raise KeyError(f'Column not found: {primary_column}, existing columns: {new_row.keys()}')
|
|
74
|
+
primary_columns.append(primary_column)
|
|
75
|
+
primary_values.append(value)
|
|
76
|
+
context_key = (
|
|
77
|
+
tuple(context_columns),
|
|
78
|
+
tuple(context_values),
|
|
79
|
+
tuple(primary_columns),
|
|
80
|
+
)
|
|
81
|
+
primary_value = tuple(primary_values)
|
|
82
|
+
id_map = id_context_map[context_key]
|
|
83
|
+
if primary_value not in id_map.dict_value_to_id:
|
|
84
|
+
field_id = id_map.max_id + 1
|
|
85
|
+
id_map.max_id = field_id
|
|
86
|
+
id_map.dict_value_to_id[primary_value] = field_id
|
|
87
|
+
id_map.dict_id_to_value[field_id] = primary_value
|
|
88
|
+
else:
|
|
89
|
+
field_id = id_map.dict_value_to_id[primary_value]
|
|
90
|
+
set_field_value(new_row, f'__debug__.{column}', field_id)
|
|
91
|
+
return new_row
|
|
92
|
+
|
|
93
|
+
def setup_assign_ids(
|
|
94
|
+
config: Config,
|
|
95
|
+
fields_to_assign_ids: str,
|
|
96
|
+
):
|
|
97
|
+
if fields_to_assign_ids:
|
|
98
|
+
fields = fields_to_assign_ids.split(',')
|
|
99
|
+
context = []
|
|
100
|
+
for field in fields:
|
|
101
|
+
if '=' in field:
|
|
102
|
+
dst, src = field.split('=')
|
|
103
|
+
config.process.assign_ids[dst] = AssignIdConfig(
|
|
104
|
+
primary = [src],
|
|
105
|
+
context = context,
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError(f'Invalid id assignment: {field}')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Description: Get the value of a field in a dictionary.
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
|
|
5
|
+
def get_field_value(
|
|
6
|
+
data: OrderedDict,
|
|
7
|
+
field: str,
|
|
8
|
+
):
|
|
9
|
+
if field in data:
|
|
10
|
+
return data[field], True
|
|
11
|
+
if '.' in field:
|
|
12
|
+
field, rest = field.split('.', 1)
|
|
13
|
+
if field in data:
|
|
14
|
+
return get_field_value(data[field], rest)
|
|
15
|
+
return None, False
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'''
|
|
2
|
+
This function is used to search for a column value in a row. It will first search in the '__debug__' field, then in the '__debug__.__original__' field, and finally in the row itself. If the value is found, it will be set in the row and returned.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from .. constants import STAGING_FIELD
|
|
6
|
+
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
|
|
9
|
+
from . get_field_value import get_field_value
|
|
10
|
+
from . set_field_value import set_field_value
|
|
11
|
+
|
|
12
|
+
def search_column_value(
|
|
13
|
+
row: OrderedDict,
|
|
14
|
+
column: str,
|
|
15
|
+
):
|
|
16
|
+
if STAGING_FIELD in row:
|
|
17
|
+
value, found = get_field_value(row[STAGING_FIELD], column)
|
|
18
|
+
if found:
|
|
19
|
+
return value, True
|
|
20
|
+
value, found = get_field_value(row[STAGING_FIELD], column)
|
|
21
|
+
original, found = get_field_value(row, f'{STAGING_FIELD}.__original__')
|
|
22
|
+
if found:
|
|
23
|
+
value, found = get_field_value(original, column)
|
|
24
|
+
if found:
|
|
25
|
+
return value, True
|
|
26
|
+
value, found = get_field_value(row, column)
|
|
27
|
+
if found:
|
|
28
|
+
set_field_value(row, column, value)
|
|
29
|
+
return value, True
|
|
30
|
+
return None, False
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Set the value of a field in a nested dictionary.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
|
|
7
|
+
def set_field_value(
|
|
8
|
+
data: OrderedDict,
|
|
9
|
+
field: str,
|
|
10
|
+
value: any,
|
|
11
|
+
):
|
|
12
|
+
if '.' in field:
|
|
13
|
+
field, rest = field.split('.', 1)
|
|
14
|
+
if field not in data:
|
|
15
|
+
data[field] = OrderedDict()
|
|
16
|
+
set_field_value(data[field], rest, value)
|
|
17
|
+
else:
|
|
18
|
+
data[field] = value
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
from collections import OrderedDict
|
|
4
|
-
import dataclasses
|
|
5
|
-
from typing import Mapping
|
|
6
|
-
|
|
7
|
-
from icecream import ic
|
|
8
|
-
import yaml
|
|
9
|
-
|
|
10
|
-
type FlatFieldMap = Mapping[str, str]
|
|
11
|
-
type FieldMap = Mapping[str, str|FieldMap]
|
|
12
|
-
|
|
13
|
-
@dataclasses.dataclass
|
|
14
|
-
class ProcessConfig:
|
|
15
|
-
assign_constants: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
16
|
-
assign_formats: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
17
|
-
split_by_newline: FlatFieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
18
|
-
|
|
19
|
-
def __setitem__(self, key, value):
|
|
20
|
-
setattr(self, key, value)
|
|
21
|
-
|
|
22
|
-
@dataclasses.dataclass
|
|
23
|
-
class Config:
|
|
24
|
-
map: FieldMap = dataclasses.field(default_factory=OrderedDict)
|
|
25
|
-
process: ProcessConfig = dataclasses.field(default_factory=ProcessConfig)
|
|
26
|
-
|
|
27
|
-
def flatten(
|
|
28
|
-
mapping: FieldMap,
|
|
29
|
-
parent_key: str = '',
|
|
30
|
-
new_mapping: FlatFieldMap | None = None,
|
|
31
|
-
) -> FlatFieldMap:
|
|
32
|
-
if new_mapping is None:
|
|
33
|
-
new_mapping = OrderedDict()
|
|
34
|
-
for key, mapped in mapping.items():
|
|
35
|
-
new_key = f'{parent_key}.{key}' if parent_key else key
|
|
36
|
-
if isinstance(mapped, Mapping):
|
|
37
|
-
flatten(mapped, new_key, new_mapping)
|
|
38
|
-
else:
|
|
39
|
-
new_mapping[new_key] = mapped
|
|
40
|
-
return new_mapping
|
|
41
|
-
|
|
42
|
-
def setup_config(
|
|
43
|
-
config_path: str | None = None,
|
|
44
|
-
):
|
|
45
|
-
config = Config()
|
|
46
|
-
if config_path:
|
|
47
|
-
if config_path.endswith('.yaml'):
|
|
48
|
-
yaml.add_constructor(
|
|
49
|
-
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
|
50
|
-
lambda loader, node: OrderedDict(loader.construct_pairs(node)),
|
|
51
|
-
)
|
|
52
|
-
with open(config_path, 'r') as f:
|
|
53
|
-
loaded = yaml.load(f, yaml.Loader)
|
|
54
|
-
else:
|
|
55
|
-
raise ValueError(
|
|
56
|
-
'Only YAML configuration files are supported.'
|
|
57
|
-
)
|
|
58
|
-
ic(loaded)
|
|
59
|
-
if 'map' in loaded:
|
|
60
|
-
config.map = flatten(loaded['map'])
|
|
61
|
-
dict_process = loaded.get('process')
|
|
62
|
-
if isinstance(dict_process, Mapping):
|
|
63
|
-
for process_key in [
|
|
64
|
-
'assign_constants',
|
|
65
|
-
'assign_formats',
|
|
66
|
-
'split_by_newline',
|
|
67
|
-
]:
|
|
68
|
-
dict_subprocess = dict_process.get(process_key)
|
|
69
|
-
if isinstance(dict_subprocess, Mapping):
|
|
70
|
-
config.process[process_key] = flatten(loaded['process'][process_key])
|
|
71
|
-
return config
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|