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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Vocabulary server.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import division, print_function, unicode_literals, absolute_import
|
|
9
|
+
|
|
10
|
+
import copy
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
from data_request_api.stable.utilities.logger import get_logger
|
|
14
|
+
from data_request_api.stable.utilities.tools import read_json_file
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_link_id_or_value(elt):
|
|
18
|
+
"""
|
|
19
|
+
Check if the input value is a link and transform it into a value if so
|
|
20
|
+
:param elt: element to be transformed into a value
|
|
21
|
+
:return: not link version oof elt
|
|
22
|
+
"""
|
|
23
|
+
if isinstance(elt, str) and elt.startswith("link::"):
|
|
24
|
+
return True, elt.replace("link::", "")
|
|
25
|
+
else:
|
|
26
|
+
return False, elt
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_link_from_id(elt):
|
|
30
|
+
"""
|
|
31
|
+
Check if the input value is already a link and transform it if not
|
|
32
|
+
:param elt: element to be transformed into a link
|
|
33
|
+
:return: link version of elt
|
|
34
|
+
"""
|
|
35
|
+
if not isinstance(elt, str) or elt.startswith("link::"):
|
|
36
|
+
return elt
|
|
37
|
+
else:
|
|
38
|
+
return f"link::{elt}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class VocabularyServer(object):
|
|
42
|
+
"""
|
|
43
|
+
Class to generate a Vocabulary Server from a json file.
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, input_database, **kwargs):
|
|
46
|
+
self.vocabulary_server = copy.deepcopy(input_database)
|
|
47
|
+
self.version = self.vocabulary_server.pop("version")
|
|
48
|
+
self.check_infinite_loop()
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_input(cls, input_database):
|
|
52
|
+
"""
|
|
53
|
+
Generate VocabularyServer from a json file
|
|
54
|
+
:param input_database: json file name
|
|
55
|
+
:return:
|
|
56
|
+
"""
|
|
57
|
+
content = read_json_file(input_database)
|
|
58
|
+
return cls(content)
|
|
59
|
+
|
|
60
|
+
def alias(self, element_type):
|
|
61
|
+
"""
|
|
62
|
+
Find the real element_type if aliased
|
|
63
|
+
:param element_type: input kind of element
|
|
64
|
+
:return:
|
|
65
|
+
"""
|
|
66
|
+
element_type_dict = dict(
|
|
67
|
+
keyword="glossary",
|
|
68
|
+
lead_theme="data_request_themes",
|
|
69
|
+
dimensions="coordinates_and_dimensions",
|
|
70
|
+
coordinates="coordinates_and_dimensions",
|
|
71
|
+
extra_dimensions="coordinates_and_dimensions"
|
|
72
|
+
)
|
|
73
|
+
return element_type_dict.get(element_type, element_type)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def to_plural(element_type):
|
|
77
|
+
if not element_type.endswith("s"):
|
|
78
|
+
if element_type.endswith("y"):
|
|
79
|
+
element_type = element_type.rstrip("y") + "ies"
|
|
80
|
+
else:
|
|
81
|
+
element_type += "s"
|
|
82
|
+
return element_type
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def to_singular(element_type):
|
|
86
|
+
if element_type.endswith("ies"):
|
|
87
|
+
element_type = element_type.removesuffix("ies") + "y"
|
|
88
|
+
elif element_type.endswith("s"):
|
|
89
|
+
element_type = element_type.removesuffix("s")
|
|
90
|
+
return element_type
|
|
91
|
+
|
|
92
|
+
def check_infinite_loop(self):
|
|
93
|
+
"""
|
|
94
|
+
Check that there is no infinite loop in the vocabulary server.
|
|
95
|
+
Raise an error if at least one is found.
|
|
96
|
+
"""
|
|
97
|
+
logger = get_logger()
|
|
98
|
+
# Build the call dict
|
|
99
|
+
call_dict = defaultdict(set)
|
|
100
|
+
for key in self.vocabulary_server:
|
|
101
|
+
for id in self.vocabulary_server[key]:
|
|
102
|
+
for elt in self.vocabulary_server[key][id]:
|
|
103
|
+
if isinstance(self.vocabulary_server[key][id][elt], list) and \
|
|
104
|
+
any(is_link_id_or_value(subelt)[0] for subelt in self.vocabulary_server[key][id][elt]):
|
|
105
|
+
call_dict[key].add(elt)
|
|
106
|
+
elif not(isinstance(self.vocabulary_server[key][id][elt], list)) and \
|
|
107
|
+
is_link_id_or_value(self.vocabulary_server[key][id][elt])[0]:
|
|
108
|
+
call_dict[key].add(elt)
|
|
109
|
+
|
|
110
|
+
# Implement the function to be used
|
|
111
|
+
def follow_loop(current_key, former_keys=list()):
|
|
112
|
+
logger = get_logger()
|
|
113
|
+
found = False
|
|
114
|
+
alias_key, _ = self.get_element_type_ids(current_key)
|
|
115
|
+
if alias_key in former_keys:
|
|
116
|
+
logger.error(f"Infinite loop found: {former_keys + [current_key, ]}")
|
|
117
|
+
found = True
|
|
118
|
+
else:
|
|
119
|
+
for next_key in sorted(list(call_dict[alias_key])):
|
|
120
|
+
found = found or follow_loop(next_key, former_keys + [alias_key, ])
|
|
121
|
+
return found
|
|
122
|
+
|
|
123
|
+
# Follow the call dict to check if there is infinite loop
|
|
124
|
+
found = any(follow_loop(key) for key in sorted(list(call_dict)))
|
|
125
|
+
if found:
|
|
126
|
+
logger.critical("Infinite loop found in vocabulary server, see former error messages.")
|
|
127
|
+
raise ValueError("Infinite loop found in vocabulary server, see former error messages.")
|
|
128
|
+
|
|
129
|
+
def get_element_type_ids(self, element_type):
|
|
130
|
+
"""
|
|
131
|
+
Get elements corresponding a a specific kind
|
|
132
|
+
:param element_type:
|
|
133
|
+
:return:
|
|
134
|
+
"""
|
|
135
|
+
logger = get_logger()
|
|
136
|
+
element_type = self.alias(element_type)
|
|
137
|
+
if element_type not in self.vocabulary_server:
|
|
138
|
+
element_type = self.to_plural(element_type)
|
|
139
|
+
if element_type in self.vocabulary_server:
|
|
140
|
+
return element_type, sorted(list(self.vocabulary_server[element_type]))
|
|
141
|
+
else:
|
|
142
|
+
logger.error(f"Could not find element type {element_type} in the vocabulary server.")
|
|
143
|
+
raise ValueError(f"Could not find element type {element_type} in the vocabulary server.")
|
|
144
|
+
|
|
145
|
+
def get_element(self, element_type, element_id, element_key=None, default=False, id_type="id"):
|
|
146
|
+
"""
|
|
147
|
+
Get an element corresponding to an element_id (corresponding to attribute id_type) of a kind element_type.
|
|
148
|
+
If element_key is specified, get the corresponding attribute.
|
|
149
|
+
If no element found, return default.
|
|
150
|
+
:param element_type:
|
|
151
|
+
:param element_id:
|
|
152
|
+
:param element_key:
|
|
153
|
+
:param default:
|
|
154
|
+
:param id_type:
|
|
155
|
+
:return:
|
|
156
|
+
"""
|
|
157
|
+
logger = get_logger()
|
|
158
|
+
is_id, element_id = is_link_id_or_value(element_id)
|
|
159
|
+
if is_id or id_type != "id":
|
|
160
|
+
element_type, element_type_ids = self.get_element_type_ids(element_type)
|
|
161
|
+
found = False
|
|
162
|
+
if id_type in ["id", ] and element_id in element_type_ids:
|
|
163
|
+
value = self.vocabulary_server[element_type][element_id]
|
|
164
|
+
found = True
|
|
165
|
+
elif isinstance(id_type, str):
|
|
166
|
+
if element_id is None:
|
|
167
|
+
raise ValueError("None element_id found")
|
|
168
|
+
value = list()
|
|
169
|
+
for (key, val) in self.vocabulary_server[element_type].items():
|
|
170
|
+
val = val.get(id_type)
|
|
171
|
+
if (isinstance(val, list) and element_id in val) or element_id == val:
|
|
172
|
+
value.append(key)
|
|
173
|
+
if len(value) == 1:
|
|
174
|
+
found = True
|
|
175
|
+
element_id = value[0]
|
|
176
|
+
value = self.vocabulary_server[element_type][element_id]
|
|
177
|
+
elif len(value) > 1:
|
|
178
|
+
logger.error(f"id_type {id_type} provided is not unique for element type {element_type} and "
|
|
179
|
+
f"value {element_key}.")
|
|
180
|
+
raise ValueError(f"id_type {id_type} provided is not unique for element type {element_type} "
|
|
181
|
+
f"and value {element_key}.")
|
|
182
|
+
if found:
|
|
183
|
+
value = copy.deepcopy(value)
|
|
184
|
+
if element_key is not None:
|
|
185
|
+
if element_key in value:
|
|
186
|
+
value = value.get(element_key)
|
|
187
|
+
else:
|
|
188
|
+
logger.error(f"Could not find key {element_key} of id {element_id} of type {element_type} "
|
|
189
|
+
f"in the vocabulary server.")
|
|
190
|
+
raise ValueError(f"Could not find key {element_key} of id {element_id} of type "
|
|
191
|
+
f"{element_type} in the vocabulary server.")
|
|
192
|
+
elif isinstance(value, dict):
|
|
193
|
+
value["id"] = element_id
|
|
194
|
+
return value
|
|
195
|
+
elif default is not False:
|
|
196
|
+
logger.critical(f"Could not find {id_type} {element_id} of type {element_type}"
|
|
197
|
+
f" in the vocabulary server.")
|
|
198
|
+
return default
|
|
199
|
+
else:
|
|
200
|
+
logger.error(f"Could not find {id_type} {element_id} of type {element_type} "
|
|
201
|
+
f"in the vocabulary server.")
|
|
202
|
+
raise ValueError(f"Could not find {id_type} {element_id} of type {element_type} "
|
|
203
|
+
f"in the vocabulary server.")
|
|
204
|
+
elif element_id in ["???", None]:
|
|
205
|
+
logger.critical(f"Undefined id of type {element_type}")
|
|
206
|
+
return element_id
|
|
207
|
+
else:
|
|
208
|
+
return element_id
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Logger.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import unicode_literals, print_function, absolute_import, division
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
log_dir = os.getcwd()
|
|
15
|
+
log_filename = "log.out"
|
|
16
|
+
log_file = os.sep.join([log_dir, log_filename])
|
|
17
|
+
log_level = "info"
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def change_log_file(logfile=log_file, default=False):
|
|
23
|
+
global log_file, logger
|
|
24
|
+
if default:
|
|
25
|
+
logger = get_logger()
|
|
26
|
+
for hdlr in logger.handlers[:]:
|
|
27
|
+
hdlr.flush()
|
|
28
|
+
hdlr.close()
|
|
29
|
+
logger.removeHandler(hdlr)
|
|
30
|
+
new_hdlr = logging.StreamHandler(sys.stdout)
|
|
31
|
+
else:
|
|
32
|
+
log_file = logfile
|
|
33
|
+
logger = logging.getLogger()
|
|
34
|
+
for hdlr in logger.handlers[:]:
|
|
35
|
+
hdlr.flush()
|
|
36
|
+
hdlr.close()
|
|
37
|
+
logger.removeHandler(hdlr)
|
|
38
|
+
new_hdlr = logging.FileHandler(log_file)
|
|
39
|
+
new_hdlr.setFormatter(logging.Formatter(fmt='%(levelname)s: %(message)s'))
|
|
40
|
+
logger.addHandler(new_hdlr)
|
|
41
|
+
return logger
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_logger():
|
|
45
|
+
return logger
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def log_level_to_int(level):
|
|
49
|
+
if isinstance(level, str):
|
|
50
|
+
if level.lower() in ['debug', ]:
|
|
51
|
+
return logging.DEBUG
|
|
52
|
+
elif level.lower() in ['critical', ]:
|
|
53
|
+
return logging.CRITICAL
|
|
54
|
+
elif level.lower() in ['info', ]:
|
|
55
|
+
return logging.INFO
|
|
56
|
+
elif level.lower() in ['warning', ]:
|
|
57
|
+
return logging.WARNING
|
|
58
|
+
elif level.lower() in ['error', ]:
|
|
59
|
+
return logging.ERROR
|
|
60
|
+
else:
|
|
61
|
+
return level
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def log_msg(level, *args, **kwargs):
|
|
65
|
+
logger.log(log_level_to_int(level), *args, **kwargs)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def change_log_level(level=log_level):
|
|
69
|
+
global logger, log_level
|
|
70
|
+
log_level = level
|
|
71
|
+
logger.setLevel(log_level_to_int(level))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Tools for data request.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import division, absolute_import, print_function, unicode_literals
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import csv
|
|
12
|
+
|
|
13
|
+
from data_request_api.stable.utilities.logger import get_logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read_json_file(filename):
|
|
17
|
+
logger = get_logger()
|
|
18
|
+
if os.path.isfile(filename):
|
|
19
|
+
with open(filename, "r") as fic:
|
|
20
|
+
content = json.load(fic)
|
|
21
|
+
else:
|
|
22
|
+
logger.error(f"Filename {filename} is not readable")
|
|
23
|
+
raise OSError(f"Filename {filename} is not readable")
|
|
24
|
+
return content
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def read_json_input_file_content(filename):
|
|
28
|
+
content = read_json_file(filename)
|
|
29
|
+
return content
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_json_output_file_content(filename, content, **kwargs):
|
|
33
|
+
logger = get_logger()
|
|
34
|
+
logger.debug(f"Writing file {filename}.")
|
|
35
|
+
dirname = os.path.dirname(filename)
|
|
36
|
+
if not os.path.isdir(dirname):
|
|
37
|
+
logger.warning(f"Create directory {dirname}")
|
|
38
|
+
os.makedirs(dirname)
|
|
39
|
+
with open(filename, "w") as fic:
|
|
40
|
+
defaults = dict(indent=4, allow_nan=True, sort_keys=True)
|
|
41
|
+
defaults.update(kwargs)
|
|
42
|
+
json.dump(content, fic, **defaults)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def write_csv_output_file_content(filename, content, **kwargs):
|
|
46
|
+
with open(filename, 'w', newline='') as csvfile:
|
|
47
|
+
csvfile_content = csv.writer(csvfile, **kwargs)
|
|
48
|
+
for elt in content:
|
|
49
|
+
csvfile_content.writerow(elt)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '1.1.2'
|
|
16
|
+
__version_tuple__ = version_tuple = (1, 1, 2)
|