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,593 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
import warnings
|
|
6
|
+
from filecmp import cmp
|
|
7
|
+
from shutil import move
|
|
8
|
+
|
|
9
|
+
import pooch
|
|
10
|
+
import requests
|
|
11
|
+
from bs4 import BeautifulSoup
|
|
12
|
+
|
|
13
|
+
from . import consolidate_export as ce
|
|
14
|
+
from data_request_api.stable.content.dreq_api.mapping_table import mapping_table
|
|
15
|
+
from data_request_api.stable.utilities.logger import get_logger # noqa
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Suppress pooch info output
|
|
19
|
+
pooch.get_logger().setLevel("WARNING")
|
|
20
|
+
|
|
21
|
+
# File names of Airtable exports in JSON format
|
|
22
|
+
_json_raw = "dreq_raw_export.json"
|
|
23
|
+
_json_release = "dreq_release_export.json"
|
|
24
|
+
|
|
25
|
+
# Base URL template for fetching Dreq content json files from GitHub
|
|
26
|
+
# _github_org = "WCRP-CMIP"
|
|
27
|
+
_github_org = "CMIP-Data-Request"
|
|
28
|
+
REPO_RAW_URL = "https://raw.githubusercontent.com/{_github_org}/CMIP7_DReq_Content/{version}/airtable_export/{_json_export}"
|
|
29
|
+
_dev_branch = "main"
|
|
30
|
+
|
|
31
|
+
# API URL for fetching tags or branches
|
|
32
|
+
REPO_API_URL = f"https://api.github.com/repos/{_github_org}/CMIP7_DReq_content/"
|
|
33
|
+
|
|
34
|
+
# Alternative Repo URL for fetching tags
|
|
35
|
+
REPO_PAGE_URL = f"https://github.com/{_github_org}/CMIP7_DReq_content/"
|
|
36
|
+
|
|
37
|
+
# List of versions (tags, branches) - will be populated by get_versions(target="tags" or "branches")
|
|
38
|
+
versions = {"tags": [], "branches": []}
|
|
39
|
+
_versions_retrieved_last = {"tags": 0, "branches": 0}
|
|
40
|
+
|
|
41
|
+
# When retrieving versions (tags, branches), fall back to parsing the public GitHub page for
|
|
42
|
+
# the GitHub API returning the following status codes:
|
|
43
|
+
# 403 Forbidden
|
|
44
|
+
# 429 Too Many Requests
|
|
45
|
+
# 500 Internal Server Error
|
|
46
|
+
# 502 Bad Gateway
|
|
47
|
+
# 503 Service Unavailable
|
|
48
|
+
# 504 Gateway Timeout
|
|
49
|
+
_fallback_status_codes = [403, 429, 500, 502, 503, 504]
|
|
50
|
+
|
|
51
|
+
# Regex pattern for version parsing (captures major, minor, patch and optional pre-release parts)
|
|
52
|
+
_version_pattern = re.compile(
|
|
53
|
+
r"^v?(\d+)\.(\d+)(?:\.(\d+))?((?:alpha|beta|a|b)?)?(\d*)$", re.IGNORECASE
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Directory where to find/store the data request JSON files
|
|
57
|
+
_dreq_res = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dreq_res")
|
|
58
|
+
|
|
59
|
+
_dreq_content_loaded = {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _parse_version(version):
|
|
63
|
+
"""Parse a version tag and return a tuple for sorting.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
version : str
|
|
68
|
+
The version tag to parse.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
tuple
|
|
73
|
+
The parsed version tuple:
|
|
74
|
+
(major, minor, patch, pre_release_type, pre_release_number)
|
|
75
|
+
"""
|
|
76
|
+
match = _version_pattern.match(version)
|
|
77
|
+
if match:
|
|
78
|
+
major, minor, patch = map(lambda x: int(x) if x else 0, match.groups()[:3])
|
|
79
|
+
# 'a' for alpha, 'b' for beta, or None
|
|
80
|
+
pre_release_type = match.group(4)[0] if match.group(4) else None
|
|
81
|
+
# alpha/beta version number or 0
|
|
82
|
+
pre_release_number = (
|
|
83
|
+
int(match.group(5)) if match.group(5) and pre_release_type else 0
|
|
84
|
+
)
|
|
85
|
+
return (major, minor, patch, pre_release_type or "", pre_release_number)
|
|
86
|
+
# if no valid version
|
|
87
|
+
return (0, 0, 0, "", 0)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_cached(**kwargs):
|
|
91
|
+
"""Get list of cached versions.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
kwargs : dict, optional
|
|
96
|
+
Additional parameters to pass to the function.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
list
|
|
101
|
+
The list of cached versions.
|
|
102
|
+
|
|
103
|
+
Raises
|
|
104
|
+
------
|
|
105
|
+
Warning
|
|
106
|
+
If known kwargs have an invalid value.
|
|
107
|
+
"""
|
|
108
|
+
local_versions = []
|
|
109
|
+
if os.path.isdir(_dreq_res):
|
|
110
|
+
# List all subdirectories in the dreq_res directory that include both dreq.json files
|
|
111
|
+
# - the subdirectory name is the tag name
|
|
112
|
+
json_export = False
|
|
113
|
+
if "export" in kwargs:
|
|
114
|
+
if kwargs["export"] == "raw":
|
|
115
|
+
json_export = _json_raw
|
|
116
|
+
elif kwargs["export"] == "release":
|
|
117
|
+
json_export = _json_release
|
|
118
|
+
else:
|
|
119
|
+
warnings.warn(f"Unknown export type '{kwargs['export']}'.")
|
|
120
|
+
if json_export:
|
|
121
|
+
local_versions = [
|
|
122
|
+
name
|
|
123
|
+
for name in os.listdir(_dreq_res)
|
|
124
|
+
if os.path.isfile(os.path.join(_dreq_res, name, json_export))
|
|
125
|
+
]
|
|
126
|
+
else:
|
|
127
|
+
local_versions = [
|
|
128
|
+
name
|
|
129
|
+
for name in os.listdir(_dreq_res)
|
|
130
|
+
if (
|
|
131
|
+
os.path.isfile(os.path.join(_dreq_res, name, _json_raw))
|
|
132
|
+
and not _version_pattern.match(name)
|
|
133
|
+
)
|
|
134
|
+
or (
|
|
135
|
+
os.path.isfile(os.path.join(_dreq_res, name, _json_release))
|
|
136
|
+
and _version_pattern.match(name)
|
|
137
|
+
)
|
|
138
|
+
]
|
|
139
|
+
return local_versions
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _send_api_request(api_url, page_url, target="tags"):
|
|
143
|
+
"""
|
|
144
|
+
Send a request to the GitHub API for a list of tags or branches.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
api_url : str
|
|
149
|
+
The base URL to send the request to.
|
|
150
|
+
page_url : str
|
|
151
|
+
The page URL to send the request to.
|
|
152
|
+
target : str, optional
|
|
153
|
+
The target to send the request for, either 'tags' or 'branches' (default is 'tags').
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
list
|
|
158
|
+
A list of tags (or optionally branches).
|
|
159
|
+
|
|
160
|
+
Raises
|
|
161
|
+
------
|
|
162
|
+
Warning
|
|
163
|
+
If the GitHub API is not accessible.
|
|
164
|
+
Warning
|
|
165
|
+
If a HTTP error occurs when retrieving the list of tags or branches.
|
|
166
|
+
Warning
|
|
167
|
+
If an exception occurs when retrieving the list of tags or branches.
|
|
168
|
+
"""
|
|
169
|
+
# Request the list of tags or branches via the GitHub API
|
|
170
|
+
global _fallback_status_codes
|
|
171
|
+
results = []
|
|
172
|
+
response = requests.get(api_url + target)
|
|
173
|
+
try:
|
|
174
|
+
# Raise an error for bad responses
|
|
175
|
+
response.raise_for_status()
|
|
176
|
+
|
|
177
|
+
# Extract the list of tags or branches from the response
|
|
178
|
+
results = [
|
|
179
|
+
entry["name"]
|
|
180
|
+
for entry in response.json()
|
|
181
|
+
if "name" in entry and entry["name"] != _dev_branch
|
|
182
|
+
] or []
|
|
183
|
+
|
|
184
|
+
except requests.exceptions.HTTPError as http_err:
|
|
185
|
+
if response.status_code in _fallback_status_codes:
|
|
186
|
+
warnings.warn(
|
|
187
|
+
f"GitHub API not accessible, falling back to parsing the public GitHub page: {http_err}"
|
|
188
|
+
)
|
|
189
|
+
results = _send_html_request(page_url, target)
|
|
190
|
+
else:
|
|
191
|
+
warnings.warn(
|
|
192
|
+
f"A HTTP error occurred when retrieving '{target}' ({response.status_code}): {http_err}"
|
|
193
|
+
)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
warnings.warn(f"An error occurred when retrieving '{target}': {e}")
|
|
196
|
+
|
|
197
|
+
return results
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _send_html_request(page_url, target="tags"):
|
|
201
|
+
"""
|
|
202
|
+
Fallback method: Parse the the public GitHub page to get the list of tags or branches.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
page_url : str
|
|
207
|
+
The base URL to send the request to.
|
|
208
|
+
target : str, optional
|
|
209
|
+
The target to send the request for, either 'tags' or 'branches' (default is 'tags').
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
list
|
|
214
|
+
A list of tags (or optionally branches).
|
|
215
|
+
|
|
216
|
+
Raises
|
|
217
|
+
------
|
|
218
|
+
ValueError
|
|
219
|
+
If the html response cannot be parsed.
|
|
220
|
+
Warning
|
|
221
|
+
If a HTTP error occurs when retrieving the list of tags or branches.
|
|
222
|
+
|
|
223
|
+
Notes
|
|
224
|
+
-----
|
|
225
|
+
Making use of the pagination mechanism of GitHub could only be tested for tags
|
|
226
|
+
so might not work for branches.
|
|
227
|
+
"""
|
|
228
|
+
# Request the list of tags or (active) branches via the GitHub Page
|
|
229
|
+
results = []
|
|
230
|
+
addon = ""
|
|
231
|
+
if target == "branches":
|
|
232
|
+
addon = "/active"
|
|
233
|
+
current_url = page_url + target + addon
|
|
234
|
+
current_urls = list()
|
|
235
|
+
while current_url:
|
|
236
|
+
response = requests.get(current_url)
|
|
237
|
+
try:
|
|
238
|
+
# Raise an error for bad responses
|
|
239
|
+
response.raise_for_status()
|
|
240
|
+
|
|
241
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
|
242
|
+
|
|
243
|
+
if target == "branches":
|
|
244
|
+
# Find the branches on the page - GitHub embeds json data under the script tag
|
|
245
|
+
script_tag = soup.find(
|
|
246
|
+
"script", {"data-target": "react-app.embeddedData"}
|
|
247
|
+
)
|
|
248
|
+
if not script_tag:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
"Could not find the 'script' tag in the html response."
|
|
251
|
+
)
|
|
252
|
+
json_response = json.loads(script_tag.string)
|
|
253
|
+
results_json = json_response["payload"][target]
|
|
254
|
+
results += [
|
|
255
|
+
entry["name"]
|
|
256
|
+
for entry in results_json
|
|
257
|
+
if "name" in entry and entry["name"] != _dev_branch
|
|
258
|
+
] or []
|
|
259
|
+
else:
|
|
260
|
+
# Find the tags on the page - GitHub uses "Link--primary" class for tags / branches
|
|
261
|
+
results += [
|
|
262
|
+
entry.text.strip()
|
|
263
|
+
for entry in soup.find_all("a", class_="Link--primary")
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
# Check for pagination links and construct URL for the next page
|
|
267
|
+
# ToDo: I could not find a repo with more branches than fit on a single page
|
|
268
|
+
# so the next_page_links may have to be adapted for branches
|
|
269
|
+
next_page_links = soup.find_all(
|
|
270
|
+
"a", {"href": lambda x: x and "after=" in x}
|
|
271
|
+
)
|
|
272
|
+
if next_page_links:
|
|
273
|
+
current_urls.append(current_url)
|
|
274
|
+
current_url = "https://github.com" + next_page_links[-1]["href"]
|
|
275
|
+
if current_url in current_urls:
|
|
276
|
+
current_url = None
|
|
277
|
+
else:
|
|
278
|
+
current_url = None
|
|
279
|
+
except Exception as e:
|
|
280
|
+
warnings.warn(f"An error occurred when retrieving '{target}': {e}")
|
|
281
|
+
current_url = None
|
|
282
|
+
|
|
283
|
+
return results
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def get_versions(target="tags"):
|
|
287
|
+
"""Fetch list of tags from the GitHub repository using the GitHub API.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
target (str): The target to send the request for, either 'tags' or 'branches'.
|
|
291
|
+
The default is 'tags'.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
target : str, optional
|
|
296
|
+
The target to send the request for, either 'tags' or 'branches' (default is 'tags').
|
|
297
|
+
Please note that the main development branch is excluded from the list of branches
|
|
298
|
+
and is included in the list of tags.
|
|
299
|
+
|
|
300
|
+
Returns
|
|
301
|
+
-------
|
|
302
|
+
list
|
|
303
|
+
A list of tags or branches.
|
|
304
|
+
|
|
305
|
+
Raises
|
|
306
|
+
------
|
|
307
|
+
ValueError
|
|
308
|
+
If target is not 'tags' or 'branches'.
|
|
309
|
+
"""
|
|
310
|
+
global versions
|
|
311
|
+
global _versions_retrieved_last
|
|
312
|
+
|
|
313
|
+
if target not in ["tags", "branches"]:
|
|
314
|
+
raise ValueError("target must be 'tags' or 'branches'.")
|
|
315
|
+
|
|
316
|
+
# Retrieve the list of tags or branches from the GitHub API
|
|
317
|
+
if not versions[target] or _versions_retrieved_last[target] - time.time() > 60 * 60:
|
|
318
|
+
versions[target] = _send_api_request(REPO_API_URL, REPO_PAGE_URL, target)
|
|
319
|
+
|
|
320
|
+
# Update the last time the tags/branches were retrieved
|
|
321
|
+
_versions_retrieved_last[target] = time.time()
|
|
322
|
+
|
|
323
|
+
if target == "tags" and "dev" not in versions[target]:
|
|
324
|
+
versions[target].append("dev")
|
|
325
|
+
|
|
326
|
+
# List tags hosted on GitHub
|
|
327
|
+
return versions[target]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _get_latest_version(stable=True):
|
|
331
|
+
"""Get the latest version
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
stable : bool, optional
|
|
336
|
+
If True, return the latest stable version. If False, return the latest version
|
|
337
|
+
(i.e. incl. alpha/beta versions) (default is True).
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
str
|
|
342
|
+
The latest version, or None if no versions are found.
|
|
343
|
+
"""
|
|
344
|
+
versions = get_versions()
|
|
345
|
+
if stable:
|
|
346
|
+
sversions = [
|
|
347
|
+
version
|
|
348
|
+
for version in versions
|
|
349
|
+
if all([x not in version for x in ["a", "b", "dev"]])
|
|
350
|
+
]
|
|
351
|
+
return max(sversions, key=_parse_version) if sversions else None
|
|
352
|
+
return max(versions, key=_parse_version)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def retrieve(version="latest_stable", **kwargs):
|
|
356
|
+
"""Retrieve the JSON file for the specified version
|
|
357
|
+
|
|
358
|
+
Parameters
|
|
359
|
+
----------
|
|
360
|
+
version: str, optional
|
|
361
|
+
The version to retrieve. Can be 'latest', 'latest_stable',
|
|
362
|
+
'dev', or 'all' or a specific version, eg. '1.0.0'.
|
|
363
|
+
(default is 'latest_stable').
|
|
364
|
+
kwargs: dict, optional:
|
|
365
|
+
Additional parameters to pass to the retrieve function.
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
dict
|
|
370
|
+
The path to the retrieved JSON file.
|
|
371
|
+
|
|
372
|
+
Raises
|
|
373
|
+
------
|
|
374
|
+
ValueError
|
|
375
|
+
If the specified version is not found.
|
|
376
|
+
Warning
|
|
377
|
+
If the specified version does not have the specified export type.
|
|
378
|
+
Warning
|
|
379
|
+
If the known kwargs have an invalid value.
|
|
380
|
+
Warning
|
|
381
|
+
If the specified version could not be downloaded or (if applicable) updated.
|
|
382
|
+
"""
|
|
383
|
+
logger = get_logger()
|
|
384
|
+
if version == "latest":
|
|
385
|
+
versions = [_get_latest_version(stable=False)]
|
|
386
|
+
elif version == "latest_stable":
|
|
387
|
+
versions = [_get_latest_version(stable=True)]
|
|
388
|
+
elif version == "dev":
|
|
389
|
+
versions = ["dev"]
|
|
390
|
+
elif version == "all":
|
|
391
|
+
versions = get_versions()
|
|
392
|
+
else:
|
|
393
|
+
if version not in get_versions() + get_versions(target="branches"):
|
|
394
|
+
if version not in get_cached(**kwargs):
|
|
395
|
+
raise ValueError(f"Version '{version}' not found.")
|
|
396
|
+
versions = [version]
|
|
397
|
+
|
|
398
|
+
if versions == [None] or not versions:
|
|
399
|
+
raise ValueError(f"Version '{version}' not found.")
|
|
400
|
+
elif version in ["v1.0alpha"] and "export" in kwargs and kwargs["export"] == "raw":
|
|
401
|
+
warnings.warn(f"For version '{version}' no raw export exists.")
|
|
402
|
+
|
|
403
|
+
json_paths = dict()
|
|
404
|
+
for version in versions:
|
|
405
|
+
# Define the path for storing the dreq.json in the installation directory
|
|
406
|
+
# Store it as path_to_dreqapi/dreq_api/dreq_res/version/{_json_raw/release}
|
|
407
|
+
retrieve_to_dir = os.path.join(_dreq_res, version)
|
|
408
|
+
# Decide whether to download release or raw json file
|
|
409
|
+
if "export" in kwargs:
|
|
410
|
+
if kwargs["export"] == "release" or version == "v1.0alpha":
|
|
411
|
+
json_export = _json_release
|
|
412
|
+
elif kwargs["export"] == "raw":
|
|
413
|
+
json_export = _json_raw
|
|
414
|
+
else:
|
|
415
|
+
warnings.warn(f"Unknown export type '{kwargs['export']}'.")
|
|
416
|
+
elif _version_pattern.match(version):
|
|
417
|
+
json_export = _json_release
|
|
418
|
+
else:
|
|
419
|
+
json_export = _json_raw
|
|
420
|
+
json_path = os.path.join(retrieve_to_dir, json_export)
|
|
421
|
+
os.makedirs(retrieve_to_dir, exist_ok=True)
|
|
422
|
+
|
|
423
|
+
# If not already cached download with POOCH
|
|
424
|
+
if not os.path.isfile(json_path):
|
|
425
|
+
# Download with pooch - use "main" branch for "dev"
|
|
426
|
+
try:
|
|
427
|
+
json_path = pooch.retrieve(
|
|
428
|
+
path=retrieve_to_dir,
|
|
429
|
+
url=REPO_RAW_URL.format(
|
|
430
|
+
version=_dev_branch if version == "dev" else version,
|
|
431
|
+
_json_export=json_export,
|
|
432
|
+
_github_org=_github_org,
|
|
433
|
+
),
|
|
434
|
+
known_hash=None,
|
|
435
|
+
fname=json_export,
|
|
436
|
+
)
|
|
437
|
+
except Exception as e:
|
|
438
|
+
warnings.warn(f"Could not retrieve version '{version}': {e}")
|
|
439
|
+
continue
|
|
440
|
+
logger.info(f"Retrieved version '{version}'.")
|
|
441
|
+
|
|
442
|
+
# or if the version is "dev" or a branch rather than a tag
|
|
443
|
+
elif version == "dev" or version not in get_versions():
|
|
444
|
+
# Download with pooch to temporary file and compare to cached version
|
|
445
|
+
json_path_temp = json_path + ".tmp"
|
|
446
|
+
try:
|
|
447
|
+
# Delete temp file if it exists
|
|
448
|
+
if os.path.exists(json_path_temp):
|
|
449
|
+
os.remove(json_path_temp)
|
|
450
|
+
# Retrieve
|
|
451
|
+
json_path_temp = pooch.retrieve(
|
|
452
|
+
path=retrieve_to_dir,
|
|
453
|
+
url=REPO_RAW_URL.format(
|
|
454
|
+
version=_dev_branch if version == "dev" else version,
|
|
455
|
+
_json_export=json_export,
|
|
456
|
+
_github_org=_github_org,
|
|
457
|
+
),
|
|
458
|
+
known_hash=None,
|
|
459
|
+
fname=json_export + ".tmp",
|
|
460
|
+
)
|
|
461
|
+
# Compare files
|
|
462
|
+
if not cmp(json_path, json_path_temp, shallow=False):
|
|
463
|
+
move(json_path_temp, json_path)
|
|
464
|
+
logger.info(f"Updated version '{version}'.")
|
|
465
|
+
else:
|
|
466
|
+
os.remove(json_path_temp)
|
|
467
|
+
except Exception as e:
|
|
468
|
+
warnings.warn(f"Potential update for version '{version}' failed: {e}")
|
|
469
|
+
|
|
470
|
+
# Store the path to the dreq.json in the json_paths dictionary
|
|
471
|
+
json_paths[version] = json_path
|
|
472
|
+
|
|
473
|
+
return json_paths
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def delete(version="all", keep_latest=False, **kwargs):
|
|
477
|
+
"""Delete one or all cached versions with option to keep latest versions.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
version : str, optional
|
|
482
|
+
The version to delete. Can be 'all' or a specific version,
|
|
483
|
+
eg. '1.0.0' (default is 'all').
|
|
484
|
+
keep_latest : bool, optional
|
|
485
|
+
If True, keep the latest stable, prerelease and "dev" versions.
|
|
486
|
+
If False, delete all locally cached versions (default is False).
|
|
487
|
+
kwargs : dict, optional
|
|
488
|
+
Additional parameters to pass to the function.
|
|
489
|
+
|
|
490
|
+
Returns
|
|
491
|
+
-------
|
|
492
|
+
None
|
|
493
|
+
|
|
494
|
+
Raises
|
|
495
|
+
------
|
|
496
|
+
ValueError
|
|
497
|
+
If the known kwargs have an invalid value.
|
|
498
|
+
Warning
|
|
499
|
+
If 'keep_latest' option is active when 'version' is not 'all'.
|
|
500
|
+
"""
|
|
501
|
+
logger = get_logger()
|
|
502
|
+
# Get locally cached versions
|
|
503
|
+
local_versions = get_cached(**kwargs)
|
|
504
|
+
|
|
505
|
+
if version == "all":
|
|
506
|
+
if keep_latest:
|
|
507
|
+
# Identify the latest stable and prerelease versions
|
|
508
|
+
valid_versions = [v for v in local_versions if _version_pattern.match(v)]
|
|
509
|
+
valid_sversions = [
|
|
510
|
+
v for v in valid_versions if "a" not in v and "b" not in v
|
|
511
|
+
]
|
|
512
|
+
latest = False
|
|
513
|
+
latest_stable = False
|
|
514
|
+
if valid_versions:
|
|
515
|
+
latest = max(valid_versions, key=_parse_version)
|
|
516
|
+
if valid_sversions:
|
|
517
|
+
latest_stable = max(valid_sversions, key=_parse_version)
|
|
518
|
+
to_keep = [v for v in ["dev", latest, latest_stable] if v]
|
|
519
|
+
local_versions = [v for v in local_versions if v not in to_keep]
|
|
520
|
+
else:
|
|
521
|
+
if keep_latest:
|
|
522
|
+
warnings.warn(
|
|
523
|
+
"'keep_latest' option is ignored when 'version' is not 'all'."
|
|
524
|
+
)
|
|
525
|
+
local_versions = [version] if version in local_versions else []
|
|
526
|
+
|
|
527
|
+
# Deletion
|
|
528
|
+
if local_versions:
|
|
529
|
+
logger.info("Deleting the following version(s):")
|
|
530
|
+
logger.info(local_versions)
|
|
531
|
+
else:
|
|
532
|
+
logger.info("No version(s) found to delete.")
|
|
533
|
+
return
|
|
534
|
+
|
|
535
|
+
# Compile file paths
|
|
536
|
+
cached_files = []
|
|
537
|
+
cached_files_raw = [os.path.join(_dreq_res, v, _json_raw) for v in local_versions]
|
|
538
|
+
cached_files_release = [
|
|
539
|
+
os.path.join(_dreq_res, v, _json_release) for v in local_versions
|
|
540
|
+
]
|
|
541
|
+
if "export" in kwargs:
|
|
542
|
+
if kwargs["export"] == "raw":
|
|
543
|
+
cached_files = cached_files_raw
|
|
544
|
+
elif kwargs["export"] == "release":
|
|
545
|
+
cached_files = cached_files_release
|
|
546
|
+
else:
|
|
547
|
+
raise ValueError(f"Unknown export type '{kwargs['export']}'.")
|
|
548
|
+
else:
|
|
549
|
+
cached_files = cached_files_raw + cached_files_release
|
|
550
|
+
|
|
551
|
+
# Delete files
|
|
552
|
+
for f in cached_files:
|
|
553
|
+
if os.path.isfile(f):
|
|
554
|
+
if "dryrun" in kwargs and kwargs["dryrun"]:
|
|
555
|
+
logger.info(f"Dryrun: would delete '{f}'.")
|
|
556
|
+
else:
|
|
557
|
+
os.remove(f)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def load(version="latest_stable", **kwargs):
|
|
561
|
+
"""Load the JSON file for the specified version.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
version (str): The version to load.
|
|
565
|
+
Can be 'latest', 'latest_stable', 'dev',
|
|
566
|
+
or a specific version, eg. '1.0.0'.
|
|
567
|
+
The default is 'latest_stable'.
|
|
568
|
+
kwargs (dict): Additional parameters to pass to the retrieve function
|
|
569
|
+
Returns:
|
|
570
|
+
dict: of the loaded JSON file.
|
|
571
|
+
"""
|
|
572
|
+
_dreq_content_loaded['json_path'] = ''
|
|
573
|
+
logger = get_logger()
|
|
574
|
+
if version == "all":
|
|
575
|
+
raise ValueError("Cannot load 'all' versions.")
|
|
576
|
+
|
|
577
|
+
version_dict = retrieve(version, **kwargs)
|
|
578
|
+
if version_dict == {}:
|
|
579
|
+
logger.info(f"Version '{version}' could not be loaded.")
|
|
580
|
+
return {}
|
|
581
|
+
else:
|
|
582
|
+
json_path = next(iter(version_dict.values()))
|
|
583
|
+
logger.info(f"Loading version {next(iter(version_dict.keys()))}'.")
|
|
584
|
+
|
|
585
|
+
_dreq_content_loaded['json_path'] = json_path
|
|
586
|
+
with open(json_path) as f:
|
|
587
|
+
if "consolidate" in kwargs:
|
|
588
|
+
if kwargs["consolidate"]:
|
|
589
|
+
return ce.map_data(json.load(f), mapping_table)
|
|
590
|
+
else:
|
|
591
|
+
return json.load(f)
|
|
592
|
+
else:
|
|
593
|
+
return ce.map_data(json.load(f), mapping_table)
|