CMIP7-data-request-api 1.1.2__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.
- CMIP7_data_request_api-1.1.2.dist-info/LICENSE +21 -0
- CMIP7_data_request_api-1.1.2.dist-info/METADATA +210 -0
- CMIP7_data_request_api-1.1.2.dist-info/RECORD +36 -0
- CMIP7_data_request_api-1.1.2.dist-info/WHEEL +5 -0
- CMIP7_data_request_api-1.1.2.dist-info/entry_points.txt +2 -0
- CMIP7_data_request_api-1.1.2.dist-info/top_level.txt +1 -0
- data_request_api/__init__.py +1 -0
- data_request_api/command_line/__init__.py +0 -0
- data_request_api/command_line/export_dreq_lists_json.py +136 -0
- data_request_api/dev/JA/__init__.py +0 -0
- data_request_api/dev/JA/check_plev_requests.py +416 -0
- data_request_api/dev/JA/read_feedback_spreadsheet.py +141 -0
- data_request_api/dev/JA/workflow_example_GRtest.py +275 -0
- data_request_api/dev/JA/workflow_example_test.py +222 -0
- data_request_api/dev/MM/checksum.py +68 -0
- data_request_api/dev/MM/walking_data_request.ipynb +380 -0
- data_request_api/dev/MS/dreq_content_and_walking_data_request.ipynb +3755 -0
- data_request_api/dev/__init__.py +0 -0
- data_request_api/stable/__init__.py +0 -0
- data_request_api/stable/content/README.MD +106 -0
- data_request_api/stable/content/__init__.py +0 -0
- data_request_api/stable/content/dreq_api/__init__.py +0 -0
- data_request_api/stable/content/dreq_api/consolidate_export.py +488 -0
- data_request_api/stable/content/dreq_api/dreq_content.py +593 -0
- data_request_api/stable/content/dreq_api/mapping_table.py +335 -0
- data_request_api/stable/content/dreq_api/test_dreq_content.py +194 -0
- data_request_api/stable/content/dump_transformation.py +550 -0
- data_request_api/stable/query/__init__.py +0 -0
- data_request_api/stable/query/data_request.py +1120 -0
- data_request_api/stable/query/dreq_classes.py +372 -0
- data_request_api/stable/query/dreq_query.py +981 -0
- data_request_api/stable/query/vocabulary_server.py +208 -0
- data_request_api/stable/utilities/__init__.py +0 -0
- data_request_api/stable/utilities/logger.py +71 -0
- data_request_api/stable/utilities/tools.py +49 -0
- data_request_api/version.py +16 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
**dreq_content.py**
|
|
3
|
+
====================
|
|
4
|
+
|
|
5
|
+
A Python script for retrieving and managing CMIP7 DReq content.
|
|
6
|
+
It is meant as a building block of the DReq API.
|
|
7
|
+
|
|
8
|
+
**Visible Functions**
|
|
9
|
+
--------------------
|
|
10
|
+
|
|
11
|
+
The following functions are available for use:
|
|
12
|
+
|
|
13
|
+
### `retrieve(version="latest_stable")`
|
|
14
|
+
|
|
15
|
+
Retrieve the JSON file for the specified version.
|
|
16
|
+
|
|
17
|
+
* `version`: The version to retrieve. Can be 'latest', 'latest_stable', 'dev', or a specific version, eg. '1.0.0', or even a branch like 'first_export'.
|
|
18
|
+
'dev' points to the main development branch (in this case 'main'). In case of 'latest' or 'latest_stable', the tags on GitHub will be taken into account.
|
|
19
|
+
Default is 'latest_stable'.
|
|
20
|
+
* Returns: A dictionary containing the path to the retrieved JSON file.
|
|
21
|
+
|
|
22
|
+
### `load(version="latest_stable")`
|
|
23
|
+
|
|
24
|
+
Load the JSON file for the specified version.
|
|
25
|
+
|
|
26
|
+
* `version`: The version to load. Can be 'latest', 'latest_stable', 'dev', or a specific version/branch, eg. '1.0.0'. Default is 'latest_stable'. Will attempt to `retrieve` the version to the local cache if needed.
|
|
27
|
+
* Returns: A dictionary containing the loaded JSON data.
|
|
28
|
+
|
|
29
|
+
### `get_versions(target="tags")`
|
|
30
|
+
|
|
31
|
+
Fetch the list of available versions (tags and main development branch).
|
|
32
|
+
|
|
33
|
+
* `target`: Can be 'tags' or 'branches'. The main development branch is excluded from 'branches' and considered under 'tags'.
|
|
34
|
+
* Returns: A list of available versions.
|
|
35
|
+
|
|
36
|
+
### `get_cached()`
|
|
37
|
+
|
|
38
|
+
* Returns: A list of locally cached versions (tags and branches).
|
|
39
|
+
|
|
40
|
+
### `delete(version="all", keep_latest=False)`
|
|
41
|
+
|
|
42
|
+
Delete one or all cached versions with option to keep latest versions.
|
|
43
|
+
|
|
44
|
+
* `version`: The version to delete. Can be 'all' or a specific version, eg. '1.0.0'. Default is 'all'.
|
|
45
|
+
* `keep_latest`: If True, keep the latest stable, prerelease and "dev" versions. If False, delete all locally cached versions.
|
|
46
|
+
Has no application if `version` is not `"all"`. Note that 'latest' and 'latest_stable' apply on the locally cached
|
|
47
|
+
versions only. More recent versions that might be available online are not considered. Default is False.
|
|
48
|
+
|
|
49
|
+
### Function `kwargs`
|
|
50
|
+
|
|
51
|
+
Mainly to support the development and not intended for common usage, some functions allow `kwargs`to be passed
|
|
52
|
+
These are:
|
|
53
|
+
- export: "raw" or "release" (supported for `get_cached`, `retrieve`, `load`, `delete`)
|
|
54
|
+
whether to respect the raw or release export json file. Per default, for official releases / tags, the release export json file is processed,
|
|
55
|
+
and else the raw export json file.
|
|
56
|
+
- consolidate: True or False (supported for `load`)
|
|
57
|
+
whether to consolidate in case a raw export json file is loaded. The default is to consolidate.
|
|
58
|
+
- dryrun: True or False (supported for `delete`)
|
|
59
|
+
whether to only list the files that would be deleted instead of actually deleting them. The default is to delete the files and not to list them.
|
|
60
|
+
|
|
61
|
+
**Usage Examples**
|
|
62
|
+
-----------------
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import data_request_api.stable.content.dreq_api.dreq_content as dc
|
|
66
|
+
|
|
67
|
+
# Get the list of available versions
|
|
68
|
+
versions = dc.get_versions()
|
|
69
|
+
print(versions)
|
|
70
|
+
|
|
71
|
+
# List all available branches
|
|
72
|
+
branches = dc.get_versions('branches')
|
|
73
|
+
print(branches)
|
|
74
|
+
|
|
75
|
+
# Retrieve the latest stable version
|
|
76
|
+
version_dict = dc.retrieve() # or dc.retrieve("latest_stable")
|
|
77
|
+
print(version_dict)
|
|
78
|
+
|
|
79
|
+
# Retrieve a certain branch/tag
|
|
80
|
+
version_dict = dc.retrieve("v1.0beta")
|
|
81
|
+
print(version_dict)
|
|
82
|
+
|
|
83
|
+
# Retrieve all available releases
|
|
84
|
+
version_dict = dc.retrieve("all")
|
|
85
|
+
print(version_dict)
|
|
86
|
+
|
|
87
|
+
# Load the latest stable version
|
|
88
|
+
dreq = dc.load() # or dc.load("latest_stable")
|
|
89
|
+
|
|
90
|
+
# Load the latest version
|
|
91
|
+
dreq = dc.load("latest")
|
|
92
|
+
|
|
93
|
+
# Load the development version
|
|
94
|
+
dreq = dc.load("dev")
|
|
95
|
+
|
|
96
|
+
# Get list of locally cached versions
|
|
97
|
+
versions = dc.get_cached()
|
|
98
|
+
print(versions)
|
|
99
|
+
|
|
100
|
+
# Delete all cached versions except the latest, latest stable and "dev" versions
|
|
101
|
+
dc.delete(keep_latest=True)
|
|
102
|
+
|
|
103
|
+
# Delete a certain version
|
|
104
|
+
dc.delete("v1.0alpha")
|
|
105
|
+
```
|
|
106
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from data_request_api.stable.utilities.logger import get_logger # noqa
|
|
7
|
+
from .mapping_table import version_consistency
|
|
8
|
+
|
|
9
|
+
# UID generation
|
|
10
|
+
default_count = 0
|
|
11
|
+
default_template = "default_{:d}"
|
|
12
|
+
|
|
13
|
+
# Filtered records
|
|
14
|
+
filtered_records = []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _correct_key_string(input_string, *to_remove_strings):
|
|
18
|
+
"""
|
|
19
|
+
Corrects a string by removing certain strings, stripping, and replacing some characters.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
input_string : str
|
|
24
|
+
The string to be corrected
|
|
25
|
+
*to_remove_strings : str
|
|
26
|
+
The strings to be removed from the input string
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
str
|
|
31
|
+
The corrected string
|
|
32
|
+
"""
|
|
33
|
+
# Convert the input string to lowercase
|
|
34
|
+
input_string = input_string.lower()
|
|
35
|
+
# Remove the specified strings from the input string
|
|
36
|
+
for to_remove_string in to_remove_strings:
|
|
37
|
+
input_string = input_string.replace(to_remove_string, "")
|
|
38
|
+
# Strip leading and trailing whitespace from the input string
|
|
39
|
+
input_string = input_string.strip()
|
|
40
|
+
# Replace '&' with 'and' and ' ' with '_' in the input string
|
|
41
|
+
input_string = input_string.replace("&", "and").replace(" ", "_")
|
|
42
|
+
return input_string
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _correct_dictionaries(input_dict):
|
|
46
|
+
"""
|
|
47
|
+
Corrects the keys in a dictionary.
|
|
48
|
+
"""
|
|
49
|
+
rep = dict()
|
|
50
|
+
for key, value in input_dict.items():
|
|
51
|
+
# Correct the key using the correct_key_string function
|
|
52
|
+
new_key = _correct_key_string(key)
|
|
53
|
+
# If the value is a dictionary, recursively correct its keys
|
|
54
|
+
if isinstance(value, dict):
|
|
55
|
+
for elt in value:
|
|
56
|
+
value[elt] = _correct_dictionaries(value[elt])
|
|
57
|
+
rep[new_key] = value
|
|
58
|
+
# If the value is not a dictionary, simply assign it to the corrected key
|
|
59
|
+
else:
|
|
60
|
+
rep[new_key] = value
|
|
61
|
+
return rep
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _map_record_id(record, records, keys):
|
|
65
|
+
"""
|
|
66
|
+
Identifies a record_id in list of records using key.
|
|
67
|
+
"""
|
|
68
|
+
global filtered_records
|
|
69
|
+
matches = []
|
|
70
|
+
# For each of the specified "keys", check if there is an entry in "records" that matches with "record"
|
|
71
|
+
for key in keys:
|
|
72
|
+
if key in record:
|
|
73
|
+
recval = record[key]
|
|
74
|
+
matches_tmp = [
|
|
75
|
+
r for r, v in records.items() if key in v and v[key] == recval
|
|
76
|
+
]
|
|
77
|
+
matches = [m for m in matches_tmp if m not in filtered_records]
|
|
78
|
+
if len(matches) == 1:
|
|
79
|
+
break
|
|
80
|
+
if len(matches) == 1:
|
|
81
|
+
return matches[0]
|
|
82
|
+
elif len(matches) == 0:
|
|
83
|
+
if len(matches_tmp) == 0:
|
|
84
|
+
raise KeyError(f"No matches when consolidating '{record}' via '{keys}'.")
|
|
85
|
+
else:
|
|
86
|
+
raise KeyError(f"Multiple matches when consolidating '{record}'.")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _map_attribute(attr, records, key):
|
|
90
|
+
"""
|
|
91
|
+
Identifies a record_id in list of records using key and matching with the attribute value.
|
|
92
|
+
"""
|
|
93
|
+
global filtered_records
|
|
94
|
+
# For the specified "key", check if there is an entry in "records" that matches with "attr"
|
|
95
|
+
matches_tmp = [r for r, v in records.items() if key in v and v[key] == attr]
|
|
96
|
+
matches = [m for m in matches_tmp if m not in filtered_records]
|
|
97
|
+
if len(matches) == 1:
|
|
98
|
+
return matches[0]
|
|
99
|
+
elif len(matches) == 0:
|
|
100
|
+
if len(matches_tmp) == 0:
|
|
101
|
+
raise KeyError(f"No matches when consolidating '{attr}' via '{key}'.")
|
|
102
|
+
else:
|
|
103
|
+
raise KeyError(f"Multiple matches when consolidating '{attr}' via '{key}'.")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def map_data(data, mapping_table):
|
|
107
|
+
"""
|
|
108
|
+
Maps the data to the one-base structure using the mapping table.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
data : dict
|
|
113
|
+
Three-base or one-base Airtable export.
|
|
114
|
+
mapping_table dict
|
|
115
|
+
The mapping table to apply to map to one base.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
dict
|
|
120
|
+
Mapped data with one-base structure.
|
|
121
|
+
|
|
122
|
+
Note
|
|
123
|
+
----
|
|
124
|
+
Returns the input dict if the data is already one-base.
|
|
125
|
+
"""
|
|
126
|
+
logger = get_logger()
|
|
127
|
+
missing_bases = []
|
|
128
|
+
missing_tables = []
|
|
129
|
+
mapped_data = {"Data Request": {}}
|
|
130
|
+
|
|
131
|
+
# Reset filtered records
|
|
132
|
+
global filtered_records
|
|
133
|
+
if filtered_records:
|
|
134
|
+
filtered_records = []
|
|
135
|
+
|
|
136
|
+
if len(data.keys()) in [3, 4]:
|
|
137
|
+
|
|
138
|
+
# Get filtered records
|
|
139
|
+
for table, mapinfo in mapping_table.items():
|
|
140
|
+
if (
|
|
141
|
+
mapinfo["source_base"] in data
|
|
142
|
+
and mapinfo["source_table"] in data[mapinfo["source_base"]]
|
|
143
|
+
):
|
|
144
|
+
if "internal_filters" in mapinfo:
|
|
145
|
+
for record_id, record in data[mapinfo["source_base"]][
|
|
146
|
+
mapinfo["source_table"]
|
|
147
|
+
]["records"].items():
|
|
148
|
+
filter_results = []
|
|
149
|
+
for filter_key, filter_val in mapinfo[
|
|
150
|
+
"internal_filters"
|
|
151
|
+
].items():
|
|
152
|
+
if filter_key not in record:
|
|
153
|
+
filter_results.append(False)
|
|
154
|
+
elif filter_val["operator"] == "nonempty":
|
|
155
|
+
filter_results.append(bool(record[filter_key]))
|
|
156
|
+
elif filter_val["operator"] == "in":
|
|
157
|
+
if isinstance(record[filter_key], list):
|
|
158
|
+
filter_results.append(
|
|
159
|
+
any(
|
|
160
|
+
fj in filter_val["values"]
|
|
161
|
+
for fj in record[filter_key]
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
filter_results.append(
|
|
166
|
+
record[filter_key] in filter_val["values"]
|
|
167
|
+
)
|
|
168
|
+
elif filter_val["operator"] == "not in":
|
|
169
|
+
if isinstance(record[filter_key], list):
|
|
170
|
+
filter_results.append(
|
|
171
|
+
any(
|
|
172
|
+
fj not in filter_val["values"]
|
|
173
|
+
for fj in record[filter_key]
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
filter_results.append(
|
|
178
|
+
record[filter_key] not in filter_val["values"]
|
|
179
|
+
)
|
|
180
|
+
if not all(filter_results):
|
|
181
|
+
logger.debug(
|
|
182
|
+
f"Filtered out record '{record_id}' {'('+record['name']+')' if 'name' in record else ''} from '{table}'."
|
|
183
|
+
)
|
|
184
|
+
filtered_records.append(record_id)
|
|
185
|
+
logger.info(f"Filtered {len(filtered_records)} records.")
|
|
186
|
+
|
|
187
|
+
# Perform mapping in case of three-base structure
|
|
188
|
+
for table, mapinfo in mapping_table.items():
|
|
189
|
+
intm = mapinfo["internal_mapping"]
|
|
190
|
+
if (
|
|
191
|
+
mapinfo["source_base"] in data
|
|
192
|
+
and mapinfo["source_table"] in data[mapinfo["source_base"]]
|
|
193
|
+
):
|
|
194
|
+
# Copy the selected data to the one-base structure
|
|
195
|
+
logger.debug(f"Mapping '{mapinfo['source_base']}' -> '{table}'")
|
|
196
|
+
mapped_data["Data Request"][table] = {
|
|
197
|
+
**data[mapinfo["source_base"]][mapinfo["source_table"]],
|
|
198
|
+
"records": {
|
|
199
|
+
record_id: record
|
|
200
|
+
for record_id, record in data[mapinfo["source_base"]][
|
|
201
|
+
mapinfo["source_table"]
|
|
202
|
+
]["records"].items()
|
|
203
|
+
if record_id not in filtered_records
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# If record attributes require mapping
|
|
208
|
+
if intm != {}:
|
|
209
|
+
# for each attribute that requires mapping
|
|
210
|
+
for attr in intm.keys():
|
|
211
|
+
for record_id, record in data[mapinfo["source_base"]][
|
|
212
|
+
mapinfo["source_table"]
|
|
213
|
+
]["records"].items():
|
|
214
|
+
if (
|
|
215
|
+
attr not in record
|
|
216
|
+
or record[attr] is None
|
|
217
|
+
or record[attr] == ""
|
|
218
|
+
or record[attr] == []
|
|
219
|
+
):
|
|
220
|
+
logger.debug(
|
|
221
|
+
f"{table}: Attribute '{attr}' not found for record '{record_id}'."
|
|
222
|
+
)
|
|
223
|
+
continue
|
|
224
|
+
attr_vals = record[attr]
|
|
225
|
+
|
|
226
|
+
# operation
|
|
227
|
+
if intm[attr]["operation"] == "split":
|
|
228
|
+
attr_vals = re.split(r"\s*,\s*", attr_vals)
|
|
229
|
+
elif intm[attr]["operation"] == "":
|
|
230
|
+
if isinstance(attr_vals, str):
|
|
231
|
+
attr_vals = [attr_vals]
|
|
232
|
+
else:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"Unknown internal mapping operation for attribute '{attr}' ('{mapinfo['source_table']}'): '{intm[attr]['operation']}'"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Get mapped record_ids
|
|
238
|
+
# entry_type - single record_id or list of record_ids
|
|
239
|
+
# - map by record_id
|
|
240
|
+
if intm[attr]["entry_type"] == "record_id":
|
|
241
|
+
if not intm[attr]["base_copy_of_table"]:
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"A copy of the table in the same base is required if 'entry_type' is set to 'record_id', "
|
|
244
|
+
f"but 'base_copy_of_table' is set to False: '{mapinfo['source_table']}' - '{attr}'"
|
|
245
|
+
)
|
|
246
|
+
elif not intm[attr]["base"] in data:
|
|
247
|
+
raise KeyError(
|
|
248
|
+
f"Base '{intm[attr]['base']}' not found in data."
|
|
249
|
+
)
|
|
250
|
+
elif (
|
|
251
|
+
intm[attr]["base_copy_of_table"]
|
|
252
|
+
not in data[mapinfo["source_base"]]
|
|
253
|
+
):
|
|
254
|
+
raise KeyError(
|
|
255
|
+
f"Table '{intm[attr]['table']}' not found in base '{intm[attr]['base_copy']}'."
|
|
256
|
+
)
|
|
257
|
+
recordIDs_new = []
|
|
258
|
+
for attr_val in attr_vals:
|
|
259
|
+
# The record copy in the current base
|
|
260
|
+
record_copy = data[mapinfo["source_base"]][
|
|
261
|
+
intm[attr]["base_copy_of_table"]
|
|
262
|
+
]["records"][attr_val]
|
|
263
|
+
# The entire list of records in the base of origin
|
|
264
|
+
recordlist = data[intm[attr]["base"]][
|
|
265
|
+
intm[attr]["table"]
|
|
266
|
+
]["records"]
|
|
267
|
+
recordID_new = _map_record_id(
|
|
268
|
+
record_copy,
|
|
269
|
+
recordlist,
|
|
270
|
+
intm[attr]["map_by_key"],
|
|
271
|
+
)
|
|
272
|
+
if recordID_new:
|
|
273
|
+
recordIDs_new.append(recordID_new)
|
|
274
|
+
# entry_type - name (eg. unique label or similar)
|
|
275
|
+
# - map by attribute value
|
|
276
|
+
elif intm[attr]["entry_type"] == "name":
|
|
277
|
+
recordIDs_new = []
|
|
278
|
+
for attr_val in attr_vals:
|
|
279
|
+
recordID_new = _map_attribute(
|
|
280
|
+
attr_val,
|
|
281
|
+
data[intm[attr]["base"]][intm[attr]["table"]][
|
|
282
|
+
"records"
|
|
283
|
+
],
|
|
284
|
+
(
|
|
285
|
+
intm[attr]["map_by_key"]
|
|
286
|
+
if isinstance(intm[attr]["map_by_key"], str)
|
|
287
|
+
else intm[attr]["map_by_key"][0]
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
if recordID_new:
|
|
291
|
+
recordIDs_new.append(recordID_new)
|
|
292
|
+
else:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"Unknown 'entry_type' specified for attribute '{attr}' ('{mapinfo['source_table']}'): '{intm[attr]['entry_type']}'"
|
|
295
|
+
)
|
|
296
|
+
if not recordIDs_new:
|
|
297
|
+
raise KeyError(
|
|
298
|
+
f"{table} (record '{record_id}'): For attribute '{attr}' no records could be mapped."
|
|
299
|
+
)
|
|
300
|
+
mapped_data["Data Request"][table]["records"][record_id][
|
|
301
|
+
attr
|
|
302
|
+
] = recordIDs_new
|
|
303
|
+
|
|
304
|
+
else:
|
|
305
|
+
if mapinfo["source_base"] not in data:
|
|
306
|
+
missing_tables.append(mapinfo["source_base"])
|
|
307
|
+
elif mapinfo["source_table"] not in data[mapinfo["source_base"]]:
|
|
308
|
+
missing_bases.append(mapinfo["source_table"])
|
|
309
|
+
if len(missing_bases) > 0:
|
|
310
|
+
warnings.warn(
|
|
311
|
+
f"Encountered missing bases when consolidating the data: {set(missing_bases)}"
|
|
312
|
+
)
|
|
313
|
+
if len(missing_tables) > 0:
|
|
314
|
+
warnings.warn(
|
|
315
|
+
f"Encountered missing tables when consolidating the data: {missing_tables}"
|
|
316
|
+
)
|
|
317
|
+
return mapped_data
|
|
318
|
+
# Return the data if it is already one-base
|
|
319
|
+
elif len(data.keys()) == 1:
|
|
320
|
+
version = next(iter(data.keys())).replace("Data Request ", "")
|
|
321
|
+
mapped_data = next(iter(data.values()))
|
|
322
|
+
if version in version_consistency:
|
|
323
|
+
for tfrom, tto in version_consistency[version].items():
|
|
324
|
+
logger.debug(
|
|
325
|
+
f"Consistency across versions - renaming table: {tfrom} -> {tto}"
|
|
326
|
+
)
|
|
327
|
+
mapped_data[tto] = mapped_data.pop(tfrom)
|
|
328
|
+
return {"Data Request": mapped_data}
|
|
329
|
+
else:
|
|
330
|
+
raise ValueError("The loaded Data Request has an unexpected data structure.")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def transform_content(data):
|
|
334
|
+
"""
|
|
335
|
+
Transform the data request content into a tidy format.
|
|
336
|
+
|
|
337
|
+
This function takes the data request content as input, tidies it up by removing
|
|
338
|
+
unnecessary keys and renaming others, and returns the transformed data request
|
|
339
|
+
and vocabulary server.
|
|
340
|
+
|
|
341
|
+
Parameters:
|
|
342
|
+
data (dict): The data request content to be transformed.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
tuple: A tuple containing the transformed data request and vocabulary server.
|
|
346
|
+
"""
|
|
347
|
+
logger = get_logger()
|
|
348
|
+
global default_count
|
|
349
|
+
|
|
350
|
+
# Create an index to map record IDs to UIDs
|
|
351
|
+
record_to_uid_index = dict()
|
|
352
|
+
# Separate dreq and vocabulary information
|
|
353
|
+
data_request = dict()
|
|
354
|
+
vocabulary_server = dict()
|
|
355
|
+
# Get the content of the Data Request
|
|
356
|
+
content = data["Data Request"]
|
|
357
|
+
|
|
358
|
+
# Define the keys to remove from each table
|
|
359
|
+
to_remove_keys = {}
|
|
360
|
+
|
|
361
|
+
# Iterate over each table in the content
|
|
362
|
+
for subelt in sorted(list(content)):
|
|
363
|
+
for record_id in sorted(list(content[subelt]["records"])):
|
|
364
|
+
# Get the keys to remove for this table
|
|
365
|
+
if subelt in to_remove_keys:
|
|
366
|
+
keys_to_remove = to_remove_keys[subelt]
|
|
367
|
+
else:
|
|
368
|
+
keys_to_remove = list()
|
|
369
|
+
|
|
370
|
+
# Get the list of keys for this record
|
|
371
|
+
list_keys = list(content[subelt]["records"][record_id])
|
|
372
|
+
|
|
373
|
+
# Add keys that match certain patterns to the list of keys to remove
|
|
374
|
+
keys_to_remove.extend(
|
|
375
|
+
[
|
|
376
|
+
key
|
|
377
|
+
for key in list_keys
|
|
378
|
+
if "(MJ)" in key
|
|
379
|
+
or "test" in key.lower()
|
|
380
|
+
or ("last" in key.lower() and "modified" in key.lower())
|
|
381
|
+
or "count" in key.lower()
|
|
382
|
+
]
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Remove the keys that should be removed
|
|
386
|
+
for key in set(keys_to_remove) & set(list_keys):
|
|
387
|
+
del content[subelt]["records"][record_id][key]
|
|
388
|
+
|
|
389
|
+
# Rename the "UID" key to "uid" if it exists
|
|
390
|
+
if "UID" in list_keys:
|
|
391
|
+
content[subelt]["records"][record_id]["uid"] = content[subelt][
|
|
392
|
+
"records"
|
|
393
|
+
][record_id].pop("UID")
|
|
394
|
+
elif "uid" not in list_keys:
|
|
395
|
+
# If no "uid" key exists, create a default one
|
|
396
|
+
uid = default_template.format(default_count)
|
|
397
|
+
content[subelt]["records"][record_id]["uid"] = uid
|
|
398
|
+
default_count += 1
|
|
399
|
+
logger.debug(
|
|
400
|
+
f"Undefined uid for element {os.sep.join([subelt, 'records', record_id])}, set {uid}"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# Add the record ID to UID mapping to the index
|
|
404
|
+
record_to_uid_index[record_id] = content[subelt]["records"][record_id][
|
|
405
|
+
"uid"
|
|
406
|
+
]
|
|
407
|
+
if (
|
|
408
|
+
subelt
|
|
409
|
+
in [
|
|
410
|
+
"Opportunity",
|
|
411
|
+
]
|
|
412
|
+
and "Title of Opportunity" in list_keys
|
|
413
|
+
):
|
|
414
|
+
content[subelt]["records"][record_id]["name"] = content[subelt][
|
|
415
|
+
"records"
|
|
416
|
+
][record_id].pop("Title of Opportunity")
|
|
417
|
+
elif "name" not in list_keys and "Name" not in list_keys:
|
|
418
|
+
content[subelt]["records"][record_id]["name"] = "undef"
|
|
419
|
+
|
|
420
|
+
# Replace record_id by uid
|
|
421
|
+
logger.debug("Replace record ids by uids")
|
|
422
|
+
content_string = json.dumps(content)
|
|
423
|
+
for record_id, uid in record_to_uid_index.items():
|
|
424
|
+
content_string = content_string.replace(f'"{record_id}"', f'"{uid}"')
|
|
425
|
+
content = json.loads(content_string)
|
|
426
|
+
|
|
427
|
+
# Alternative
|
|
428
|
+
# for key, value in content.items():
|
|
429
|
+
# if isinstance(value, dict):
|
|
430
|
+
# content[key] = {record_to_uid_index.get(k, k): v for k, v in value.items()}
|
|
431
|
+
# elif isinstance(value, list):
|
|
432
|
+
# content[key] = [{record_to_uid_index.get(k, k): v for k, v in item.items()} if isinstance(item, dict) else item for item in value]
|
|
433
|
+
|
|
434
|
+
# Build the data request
|
|
435
|
+
logger.debug("Build DR and VS")
|
|
436
|
+
for subelt in sorted(list(content)):
|
|
437
|
+
if subelt in [
|
|
438
|
+
"Opportunity",
|
|
439
|
+
]:
|
|
440
|
+
new_subelt = "opportunities"
|
|
441
|
+
data_request[new_subelt] = dict()
|
|
442
|
+
vocabulary_server[new_subelt] = dict()
|
|
443
|
+
for uid in content[subelt]["records"]:
|
|
444
|
+
value = content[subelt]["records"][uid]
|
|
445
|
+
data_request[new_subelt][uid] = dict(
|
|
446
|
+
experiments_groups=value.pop("Experiment Groups", list()),
|
|
447
|
+
variables_groups=value.pop("Variable Groups", list()),
|
|
448
|
+
themes=value.pop("Themes", list()),
|
|
449
|
+
ensemble_size=value.pop("Ensemble Size", 1),
|
|
450
|
+
)
|
|
451
|
+
vocabulary_server[new_subelt][uid] = value
|
|
452
|
+
elif subelt in [
|
|
453
|
+
"Variable Group",
|
|
454
|
+
]:
|
|
455
|
+
new_subelt = "variable_groups"
|
|
456
|
+
data_request[new_subelt] = dict()
|
|
457
|
+
vocabulary_server[new_subelt] = dict()
|
|
458
|
+
for uid in content[subelt]["records"]:
|
|
459
|
+
value = content[subelt]["records"][uid]
|
|
460
|
+
data_request[new_subelt][uid] = dict(
|
|
461
|
+
variables=value.pop("Variables", list()),
|
|
462
|
+
mips=value.pop("MIPs", list()),
|
|
463
|
+
priority=value.pop("Priority Level", None),
|
|
464
|
+
)
|
|
465
|
+
vocabulary_server[new_subelt][uid] = value
|
|
466
|
+
elif subelt in [
|
|
467
|
+
"Experiment Group",
|
|
468
|
+
]:
|
|
469
|
+
new_subelt = "experiment_groups"
|
|
470
|
+
data_request[new_subelt] = dict()
|
|
471
|
+
vocabulary_server[new_subelt] = dict()
|
|
472
|
+
for uid in content[subelt]["records"]:
|
|
473
|
+
value = content[subelt]["records"][uid]
|
|
474
|
+
data_request[new_subelt][uid] = dict(
|
|
475
|
+
experiments=value.pop("Experiments", list())
|
|
476
|
+
)
|
|
477
|
+
vocabulary_server[new_subelt][uid] = value
|
|
478
|
+
else:
|
|
479
|
+
vocabulary_server[subelt] = content[subelt]["records"]
|
|
480
|
+
return data_request, vocabulary_server
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# def write_json_output_file_content(filename, content):
|
|
484
|
+
# with open(filename, "w") as fic:
|
|
485
|
+
# json.dump(content, fic, indent=4, allow_nan=True, sort_keys=True)
|
|
486
|
+
# data_request, vocabulary_server = transform_content(content, args.version)
|
|
487
|
+
# write_json_output_file_content(os.path.sep.join([output_directory, "DR_content.json"]), data_request)
|
|
488
|
+
# write_json_output_file_content(os.path.sep.join([output_directory, "VS_content.json"]), vocabulary_server)
|