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.
Files changed (36) hide show
  1. CMIP7_data_request_api-1.1.2.dist-info/LICENSE +21 -0
  2. CMIP7_data_request_api-1.1.2.dist-info/METADATA +210 -0
  3. CMIP7_data_request_api-1.1.2.dist-info/RECORD +36 -0
  4. CMIP7_data_request_api-1.1.2.dist-info/WHEEL +5 -0
  5. CMIP7_data_request_api-1.1.2.dist-info/entry_points.txt +2 -0
  6. CMIP7_data_request_api-1.1.2.dist-info/top_level.txt +1 -0
  7. data_request_api/__init__.py +1 -0
  8. data_request_api/command_line/__init__.py +0 -0
  9. data_request_api/command_line/export_dreq_lists_json.py +136 -0
  10. data_request_api/dev/JA/__init__.py +0 -0
  11. data_request_api/dev/JA/check_plev_requests.py +416 -0
  12. data_request_api/dev/JA/read_feedback_spreadsheet.py +141 -0
  13. data_request_api/dev/JA/workflow_example_GRtest.py +275 -0
  14. data_request_api/dev/JA/workflow_example_test.py +222 -0
  15. data_request_api/dev/MM/checksum.py +68 -0
  16. data_request_api/dev/MM/walking_data_request.ipynb +380 -0
  17. data_request_api/dev/MS/dreq_content_and_walking_data_request.ipynb +3755 -0
  18. data_request_api/dev/__init__.py +0 -0
  19. data_request_api/stable/__init__.py +0 -0
  20. data_request_api/stable/content/README.MD +106 -0
  21. data_request_api/stable/content/__init__.py +0 -0
  22. data_request_api/stable/content/dreq_api/__init__.py +0 -0
  23. data_request_api/stable/content/dreq_api/consolidate_export.py +488 -0
  24. data_request_api/stable/content/dreq_api/dreq_content.py +593 -0
  25. data_request_api/stable/content/dreq_api/mapping_table.py +335 -0
  26. data_request_api/stable/content/dreq_api/test_dreq_content.py +194 -0
  27. data_request_api/stable/content/dump_transformation.py +550 -0
  28. data_request_api/stable/query/__init__.py +0 -0
  29. data_request_api/stable/query/data_request.py +1120 -0
  30. data_request_api/stable/query/dreq_classes.py +372 -0
  31. data_request_api/stable/query/dreq_query.py +981 -0
  32. data_request_api/stable/query/vocabulary_server.py +208 -0
  33. data_request_api/stable/utilities/__init__.py +0 -0
  34. data_request_api/stable/utilities/logger.py +71 -0
  35. data_request_api/stable/utilities/tools.py +49 -0
  36. data_request_api/version.py +16 -0
@@ -0,0 +1,335 @@
1
+ """
2
+ Mapping Table
3
+
4
+ The mapping_table dictionary defines how to map the three-base structure to the one-base structure.
5
+ Each entry in the dictionary represents a table in the one-base structure and includes the information
6
+ how to obtain it from the three-base structure.
7
+
8
+ Explanation of the dictionary keys:
9
+
10
+ Base ("source_base"):
11
+ The base containing the table to be selected.
12
+
13
+ Table ("source_table"):
14
+ The table to be selected from the "source_base".
15
+
16
+ Internal Mapping of record attributes ("internal_mapping"):
17
+ Record attributes may point to records of other tables.
18
+ However, there is no cross-linkage between the three bases,
19
+ so these links need to be mapped as well.
20
+ "internal_mapping" is a dictionary with the key corresponding
21
+ to the record attributes to be mapped and the values containing
22
+ the actual mapping information.
23
+
24
+ The mapping information is again a dictionary with the following keys:
25
+ - base_copy_of_table:
26
+ If a copy of table corresponding to the record attribute exists in the current base,
27
+ provide the name; otherwise, set to False.
28
+ - base:
29
+ The base containing the original table the record attribute points to.
30
+ - table:
31
+ The original table the record attribute points to.
32
+ - operation:
33
+ The operation to perform on the attribute value (either "split" or "", if it is
34
+ already provided as list or a string without comma separated values).
35
+ - map_by_key:
36
+ A list of keys to map by.
37
+ - entry_type:
38
+ The type of entry (either "record_id" or "name").
39
+
40
+ (Internal) Filters of record attributes ("internal_filters"):
41
+ Not all records of the raw export shall be included since they may be
42
+ labeled as junk or not be approved by the community. The filters are applied on all records
43
+ and also internally on links to other records. "internal_filters" is a dictionary
44
+ with the key corresponding to the record attributes used for filtering and the value
45
+ another dictionary with the following possible keys:
46
+ - operator: Can be one of "nonempty", "in", "not in"
47
+ - values: A list of values, not necessary for "nonempty" operator.
48
+
49
+ Example Configuration
50
+
51
+ Suppose we want to map the "CMIP7 Variable Groups" key in the "Variables" table of the "Data Request Variables (Public)"
52
+ base to a list of record IDs of "Variable Group" records in the "Data Request Opportunities (Public)" base.
53
+
54
+ We would define the mapping_table as follows:
55
+ mapping_table = {
56
+ "Variables": {
57
+ "base": "Data Request Variables (Public)",
58
+ "source_table": "Variables",
59
+ "internal_mapping": {
60
+ "CMIP7 Variable Groups": {
61
+ "base_copy_of_table": False,
62
+ "base": "Data Request Opportunities (Public)",
63
+ "table": "Variable Group",
64
+ "operation": "split",
65
+ "map_by_key": ["Name"],
66
+ "entry_type": "name",
67
+ },
68
+ },
69
+ "internal_filters": {
70
+ "Status": {"operator": "not in", "values": ["Junk"]},
71
+ }
72
+ },
73
+ }
74
+ """
75
+
76
+ mapping_table = {
77
+ "CF Standard Names": {
78
+ "source_base": "Data Request Physical Parameters (Public)",
79
+ "source_table": "CF Standard Name",
80
+ "internal_mapping": {},
81
+ "internal_filters": {},
82
+ "rm_keys": ["Physical Parameters", "Physical Parameters 2"],
83
+ },
84
+ "Cell Measures": {
85
+ "source_base": "Data Request Variables (Public)",
86
+ "source_table": "Cell Measures",
87
+ "internal_mapping": {},
88
+ "internal_filters": {},
89
+ "rm_keys": ["Variables"],
90
+ },
91
+ "Cell Methods": {
92
+ "source_base": "Data Request Variables (Public)",
93
+ "source_table": "Cell Methods",
94
+ "internal_mapping": {},
95
+ "internal_filters": {},
96
+ "rm_keys": ["Structures", "Variables"],
97
+ },
98
+ "Coordinates and Dimensions": {
99
+ "source_base": "Data Request Variables (Public)",
100
+ "source_table": "Coordinate or Dimension",
101
+ "internal_mapping": {},
102
+ "internal_filters": {},
103
+ "rm_keys": ["Structure", "Variables"],
104
+ },
105
+ "Data Request Themes": {
106
+ "source_base": "Data Request Opportunities (Public)",
107
+ "source_table": "Data Request Themes",
108
+ "internal_mapping": {},
109
+ "internal_filters": {},
110
+ "rm_keys": [],
111
+ },
112
+ "Docs for Opportunities": {
113
+ "source_base": "Data Request Opportunities (Public)",
114
+ "source_table": "Docs for Opportunities",
115
+ "internal_mapping": {},
116
+ "internal_filters": {},
117
+ "rm_keys": [],
118
+ },
119
+ "ESM-BCV 1.3": {
120
+ "source_base": "Data Request Variables (Public)",
121
+ "source_table": "ESM-BCV 1.3",
122
+ "internal_mapping": {},
123
+ "internal_filters": {},
124
+ "rm_keys": [],
125
+ },
126
+ "Experiment Group": {
127
+ "source_base": "Data Request Opportunities (Public)",
128
+ "source_table": "Experiment Group",
129
+ "internal_mapping": {},
130
+ "internal_filters": {
131
+ "Status": {"operator": "not in", "values": ["Junk"]},
132
+ "Status (from Opportunity)": {
133
+ "operator": "in",
134
+ "values": ["New", "Under review", "Accepted"],
135
+ },
136
+ },
137
+ "rm_keys": ["Opportunity"],
138
+ },
139
+ "Experiments": {
140
+ "source_base": "Data Request Opportunities (Public)",
141
+ "source_table": "Experiment",
142
+ "internal_mapping": {},
143
+ "internal_filters": {},
144
+ "rm_keys": ["Experiment Group"],
145
+ },
146
+ "CMIP6 Frequency (legacy)": {
147
+ "source_base": "Data Request Variables (Public)",
148
+ "source_table": "CMIP6 Frequency (legacy)",
149
+ "internal_mapping": {},
150
+ "internal_filters": {},
151
+ "rm_keys": [],
152
+ },
153
+ "CMIP7 Frequency": {
154
+ "source_base": "Data Request Variables (Public)",
155
+ "source_table": "CMIP7 Frequency",
156
+ "internal_mapping": {},
157
+ "internal_filters": {},
158
+ "rm_keys": ["Table Identifiers", "Variables"],
159
+ },
160
+ "Glossary": {
161
+ "source_base": "Data Request Opportunities (Public)",
162
+ "source_table": "Glossary",
163
+ "internal_mapping": {},
164
+ "internal_filters": {},
165
+ "rm_keys": ["Opportunity"],
166
+ },
167
+ "MIPs": {
168
+ "source_base": "Data Request Opportunities (Public)",
169
+ "source_table": "MIP",
170
+ "internal_mapping": {},
171
+ "internal_filters": {},
172
+ "rm_keys": ["Variable Group"],
173
+ },
174
+ "Modelling Realm": {
175
+ "source_base": "Data Request Variables (Public)",
176
+ "source_table": "Modelling Realm",
177
+ "internal_mapping": {},
178
+ "internal_filters": {},
179
+ "rm_keys": ["Variables"],
180
+ },
181
+ "Opportunity": {
182
+ "source_base": "Data Request Opportunities (Public)",
183
+ "source_table": "Opportunity",
184
+ "internal_mapping": {},
185
+ "internal_filters": {
186
+ "Status": {"operator": "any", "values": ["Under review", "Accepted"]},
187
+ },
188
+ "rm_keys": [],
189
+ },
190
+ "Opportunity/Variable Group Comments": {
191
+ "source_base": "Data Request Opportunities (Public)",
192
+ "source_table": "Comment",
193
+ "internal_mapping": {},
194
+ "internal_filters": {},
195
+ "rm_keys": ["Experiment Groups", "Opportunities", "Theme", "Variable Groups"],
196
+ },
197
+ "Physical Parameter Comments": {
198
+ "source_base": "Data Request Physical Parameters (Public)",
199
+ "source_table": "Comment",
200
+ "internal_mapping": {},
201
+ "internal_filters": {},
202
+ "rm_keys": ["Physical parameters"],
203
+ },
204
+ "Physical Parameters": {
205
+ "source_base": "Data Request Physical Parameters (Public)",
206
+ "source_table": "Physical Parameter",
207
+ "internal_mapping": {},
208
+ "internal_filters": {},
209
+ "rm_keys": ["Variables"],
210
+ },
211
+ "Priority Level": {
212
+ "source_base": "Data Request Opportunities (Public)",
213
+ "source_table": "Priority Level",
214
+ "internal_mapping": {},
215
+ "internal_filters": {},
216
+ "rm_keys": ["Variable Group"],
217
+ },
218
+ "Ranking": {
219
+ "source_base": "Data Request Variables (Public)",
220
+ "source_table": "Ranking",
221
+ "internal_mapping": {},
222
+ "internal_filters": {},
223
+ "rm_keys": [],
224
+ },
225
+ "Spatial Shape": {
226
+ "source_base": "Data Request Variables (Public)",
227
+ "source_table": "Spatial Shape",
228
+ "internal_mapping": {},
229
+ "internal_filters": {},
230
+ "rm_keys": ["Dimensions", "Structure", "Variables"],
231
+ },
232
+ "Structure": {
233
+ "source_base": "Data Request Variables (Public)",
234
+ "source_table": "Structure",
235
+ "internal_mapping": {},
236
+ "internal_filters": {},
237
+ "rm_keys": ["Variables"],
238
+ },
239
+ "Table Identifiers": {
240
+ "source_base": "Data Request Variables (Public)",
241
+ "source_table": "Table Identifiers",
242
+ "internal_mapping": {},
243
+ "internal_filters": {},
244
+ "rm_keys": ["Variables"],
245
+ },
246
+ "Temporal Shape": {
247
+ "source_base": "Data Request Variables (Public)",
248
+ "source_table": "Temporal Shape",
249
+ "internal_mapping": {},
250
+ "internal_filters": {},
251
+ "rm_keys": ["Dimensions", "Structure", "Variables"],
252
+ },
253
+ "Time Slice": {
254
+ "source_base": "Data Request Opportunities (Public)",
255
+ "source_table": "Time Slice",
256
+ "internal_mapping": {},
257
+ "internal_filters": {},
258
+ "rm_keys": [],
259
+ },
260
+ "Variable Comments": {
261
+ "source_base": "Data Request Variables (Public)",
262
+ "source_table": "Comment",
263
+ "internal_mapping": {},
264
+ "internal_filters": {},
265
+ "rm_keys": ["Variables"],
266
+ },
267
+ "Variable Group": {
268
+ "source_base": "Data Request Opportunities (Public)",
269
+ "source_table": "Variable Group",
270
+ "internal_mapping": {
271
+ "Variables": {
272
+ "base_copy_of_table": "Variables",
273
+ "base": "Data Request Variables (Public)",
274
+ "table": "Variable",
275
+ "operation": "",
276
+ "map_by_key": ["UID", "Compound Name"],
277
+ "entry_type": "record_id",
278
+ }
279
+ },
280
+ "internal_filters": {
281
+ "Final Opportunity selection": {"operator": "nonempty"},
282
+ "Status (from Final Opportunity selection)": {
283
+ "operator": "in",
284
+ "values": ["Under review", "Accepted"],
285
+ },
286
+ },
287
+ "rm_keys": ["Opportunity", "Theme"],
288
+ },
289
+ "Variables": {
290
+ "source_base": "Data Request Variables (Public)",
291
+ "source_table": "Variable",
292
+ "internal_mapping": {
293
+ "CMIP7 Variable Groups": {
294
+ "base_copy_of_table": False,
295
+ "base": "Data Request Opportunities (Public)",
296
+ "table": "Variable Group",
297
+ "operation": "split",
298
+ "map_by_key": ["Name"],
299
+ "entry_type": "name",
300
+ },
301
+ "Physical Parameter": {
302
+ "base_copy_of_table": "Physical Parameter",
303
+ "base": "Data Request Physical Parameters (Public)",
304
+ "table": "Physical Parameter",
305
+ "operation": "",
306
+ "map_by_key": ["UID", "Name"],
307
+ "entry_type": "record_id",
308
+ },
309
+ "CF Standard Name (from MIP Variables)": {
310
+ "base_copy_of_table": False,
311
+ "base": "Data Request Physical Parameters (Public)",
312
+ "table": "CF Standard Name",
313
+ "operation": "",
314
+ "map_by_key": ["name"],
315
+ "entry_type": "name",
316
+ },
317
+ },
318
+ "internal_filters": {
319
+ "CMIP7 Variable Groups": {"operator": "nonempty"},
320
+ "Opportunity Status (from CMIP7 Variable Groups)": {
321
+ "operator": "in",
322
+ "values": ["Under review", "Accepted"],
323
+ },
324
+ },
325
+ "rm_keys": ["CMIP7 Variable Groups"],
326
+ },
327
+ }
328
+
329
+ # Renaming of certain tables dependent on the release version
330
+ # version : {table_name_old : table_name_new}
331
+ version_consistency = {
332
+ "v1.0alpha": {
333
+ "Frequency": "CMIP7 Frequency",
334
+ },
335
+ }
@@ -0,0 +1,194 @@
1
+ import os
2
+
3
+ from data_request_api.stable.content.dreq_api import dreq_content as dc
4
+ import pytest
5
+
6
+
7
+ def test_parse_version():
8
+ "Test the _parse_version function with different version strings."
9
+ assert dc._parse_version("v1.0.0") == (1, 0, 0, "", 0)
10
+ assert dc._parse_version("v1.0alpha2") == (1, 0, 0, "a", 2)
11
+ assert dc._parse_version("1.0.0a3") == (1, 0, 0, "a", 3)
12
+ assert dc._parse_version("1.0.0beta") == (1, 0, 0, "b", 0)
13
+ assert dc._parse_version("something") == (0, 0, 0, "", 0)
14
+ with pytest.raises(TypeError):
15
+ dc._parse_version(None)
16
+
17
+
18
+ def test_get_versions():
19
+ "Test the get_versions function."
20
+ versions = dc.get_versions()
21
+ assert "dev" in versions
22
+ assert "v1.0alpha" in versions
23
+ assert "v1.0beta" in versions
24
+
25
+
26
+ def test_get_versions_list_branches():
27
+ "Test the get_versions function for branches."
28
+ branches = dc.get_versions(target="branches")
29
+ assert "dev" not in branches
30
+ assert "main" not in branches
31
+
32
+
33
+ def test_get_latest_version(monkeypatch):
34
+ "Test the _get_latest_version function."
35
+ monkeypatch.setattr(dc, "get_versions", lambda: ["1.0.0", "2.0.2b", "2.0.2a"])
36
+ assert dc._get_latest_version() == "1.0.0"
37
+ assert dc._get_latest_version(stable=False) == "2.0.2b"
38
+
39
+
40
+ def test_get_cached(tmp_path):
41
+ "Test the get_cached function."
42
+ # Create a temporary directory with a subdirectory containing a dreq.json file
43
+ version_dir = tmp_path / "v1.0.0"
44
+ version_dir.mkdir()
45
+ (version_dir / dc._json_release).touch()
46
+
47
+ # Set the _dreq_res variable to the temporary directory
48
+ dc._dreq_res = str(tmp_path)
49
+
50
+ # Test the get_cached function
51
+ cached_versions = dc.get_cached()
52
+ assert cached_versions == ["v1.0.0"]
53
+
54
+
55
+ def test_retrieve(tmp_path, capfd):
56
+ "Test the retrieval function."
57
+ dc._dreq_res = str(tmp_path)
58
+
59
+ # Retrieve 'dev' version
60
+ json_path = dc.retrieve("dev")["dev"]
61
+ assert os.path.isfile(json_path)
62
+
63
+ # Alter on disk (delete first line)
64
+ with open(json_path) as f:
65
+ lines = f.read().splitlines(keepends=True)
66
+ with open(json_path, "w") as f:
67
+ f.writelines(lines[1:])
68
+
69
+ # Make sure it updates
70
+ json_path = dc.retrieve("dev")["dev"]
71
+ stdout = capfd.readouterr().out.splitlines()
72
+ assert len(stdout) == 2
73
+ assert "Retrieved version 'dev'." in stdout
74
+ assert "Updated version 'dev'." in stdout
75
+ # ... and the file was replaced
76
+ with open(json_path) as f:
77
+ lines_update = f.read().splitlines(keepends=True)
78
+ assert lines == lines_update
79
+
80
+
81
+ def test_retrieve_with_invalid_version(tmp_path):
82
+ "Test the retrieval function with an invalid version."
83
+ dc._dreq_res = str(tmp_path)
84
+ with pytest.raises(ValueError):
85
+ dc.retrieve(" invalid-version ")
86
+
87
+
88
+ def test_api_and_html_request():
89
+ "Test the _send_api_request and _send_html_request functions."
90
+ tags1 = set(dc._send_api_request(dc.REPO_API_URL, "", "tags"))
91
+ tags2 = set(dc._send_html_request(dc.REPO_PAGE_URL, "tags"))
92
+ assert tags1 == tags2
93
+
94
+ branches1 = set(dc._send_api_request(dc.REPO_API_URL, "", "branches"))
95
+ branches2 = set(dc._send_html_request(dc.REPO_PAGE_URL, "branches"))
96
+ assert branches1 == branches2
97
+
98
+
99
+ def test_load(tmp_path):
100
+ "Test the load function."
101
+ dc._dreq_res = str(tmp_path)
102
+
103
+ with pytest.raises(ValueError):
104
+ jsondict = dc.load(" invalid-version ")
105
+
106
+ jsondict = dc.load("dev")
107
+ assert isinstance(jsondict, dict)
108
+ assert os.path.isfile(tmp_path / "dev" / dc._json_raw)
109
+ assert not os.path.isfile(tmp_path / "dev" / dc._json_release)
110
+
111
+ jsondict = dc.load("dev", export="release")
112
+ assert isinstance(jsondict, dict)
113
+ assert os.path.isfile(tmp_path / "dev" / dc._json_release)
114
+
115
+
116
+ class TestDreqContent:
117
+ """
118
+ Test various functions of the dreq_content module.
119
+ """
120
+
121
+ @pytest.fixture(autouse=True, scope="function")
122
+ def setup(self, tmp_path):
123
+ self.versions = ["v1.0.0", "1.0.1", "2.0.1b", "2.0.1", "2.0.2b"]
124
+ self.branches = ["one", "or", "another"]
125
+ self.dreq_res = tmp_path
126
+ for v in self.versions:
127
+ (self.dreq_res / v).mkdir()
128
+ (self.dreq_res / v / dc._json_release).write_text("{}")
129
+ for b in self.branches:
130
+ (self.dreq_res / b).mkdir()
131
+ (self.dreq_res / b / dc._json_raw).write_text("{}")
132
+
133
+ def test_get_cached(self):
134
+ "Test the get_cached function."
135
+ dc._dreq_res = self.dreq_res
136
+ # Basic
137
+ cached_versions = dc.get_cached()
138
+ assert set(cached_versions) == set(self.versions + self.branches)
139
+
140
+ # With export kwarg "release"
141
+ cached_tags = dc.get_cached(export="release")
142
+ assert set(cached_tags) == set(self.versions)
143
+
144
+ # With export kwarg "raw"
145
+ cached_branches = dc.get_cached(export="raw")
146
+ assert set(cached_branches) == set(self.branches)
147
+
148
+ # With invalid export kwarg
149
+ with pytest.warns(UserWarning, match="Unknown export type"):
150
+ dc.get_cached(export="invalid")
151
+
152
+ def test_delete(self, capfd):
153
+ "Test the delete function."
154
+ dc._dreq_res = self.dreq_res
155
+ # Delete non-existent version
156
+ dc.delete("notpresent")
157
+ stdout = capfd.readouterr().out.splitlines()
158
+ assert len(stdout) == 1
159
+ assert "No version(s) found to delete." in stdout
160
+
161
+ # Delete only branches / dryrun
162
+ dc.delete("all", export="raw", dryrun=True)
163
+ stdout = capfd.readouterr().out.splitlines()
164
+ assert len(stdout) == 5
165
+ assert "Deleting the following version(s):" in stdout
166
+ for b in self.branches:
167
+ assert (
168
+ f"Dryrun: would delete '{dc._dreq_res / b / dc._json_raw}'." in stdout
169
+ )
170
+
171
+ # Delete all but latest
172
+ dc.delete(keep_latest=True)
173
+ assert set(dc.get_cached()) == {"2.0.1", "2.0.2b"}
174
+ assert dc.get_cached(export="raw") == []
175
+
176
+ # Delete 2.0.1 with warning
177
+ with pytest.warns(UserWarning, match=" option is ignored "):
178
+ dc.delete("2.0.1", keep_latest=True)
179
+
180
+ # Delete all with ValueError for kwargs
181
+ with pytest.raises(ValueError):
182
+ dc.delete(export="none")
183
+
184
+ # Delete all
185
+ dc.delete()
186
+ assert dc.get_cached() == []
187
+
188
+ # Now there is no ValueError since no version is found
189
+ dc.delete(export="none")
190
+
191
+ # def test_load(self):
192
+ # dc._dreq_res = self.tmp_dir.name
193
+ # data = dc.load("1.0.0")
194
+ # assert data == {}