windborne 1.2.1__py3-none-any.whl → 1.2.4__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.
- windborne/__init__.py +3 -1
- windborne/api_request.py +43 -85
- windborne/cli.py +9 -0
- windborne/forecasts_api.py +23 -0
- {windborne-1.2.1.dist-info → windborne-1.2.4.dist-info}/METADATA +3 -1
- windborne-1.2.4.dist-info/RECORD +13 -0
- {windborne-1.2.1.dist-info → windborne-1.2.4.dist-info}/WHEEL +1 -1
- windborne-1.2.1.dist-info/RECORD +0 -13
- {windborne-1.2.1.dist-info → windborne-1.2.4.dist-info}/entry_points.txt +0 -0
- {windborne-1.2.1.dist-info → windborne-1.2.4.dist-info}/top_level.txt +0 -0
windborne/__init__.py
CHANGED
@@ -23,6 +23,7 @@ from .forecasts_api import (
|
|
23
23
|
get_point_forecasts,
|
24
24
|
get_initialization_times,
|
25
25
|
get_forecast_hours,
|
26
|
+
get_generation_times,
|
26
27
|
|
27
28
|
get_gridded_forecast,
|
28
29
|
get_full_gridded_forecast,
|
@@ -61,7 +62,8 @@ __all__ = [
|
|
61
62
|
"get_point_forecasts",
|
62
63
|
"get_initialization_times",
|
63
64
|
"get_forecast_hours",
|
64
|
-
|
65
|
+
"get_generation_times",
|
66
|
+
|
65
67
|
"get_gridded_forecast",
|
66
68
|
"get_full_gridded_forecast",
|
67
69
|
"get_temperature_2m",
|
windborne/api_request.py
CHANGED
@@ -21,99 +21,62 @@ def get_api_credentials():
|
|
21
21
|
|
22
22
|
def verify_api_credentials(client_id, api_key):
|
23
23
|
if not client_id and not api_key:
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
"for instruction on how to set your credentials for code usage.")
|
30
|
-
print("--------------------------------------")
|
31
|
-
print("To get an API key, email data@windbornesystems.com.")
|
32
|
-
exit(80)
|
24
|
+
raise ValueError(
|
25
|
+
"To access the WindBorne API, set your Client ID and API key by setting the environment variables WB_CLIENT_ID and WB_API_KEY. "
|
26
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction. "
|
27
|
+
"To get an API key, email data@windbornesystems.com."
|
28
|
+
)
|
33
29
|
|
34
30
|
if not client_id:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
"for instruction on how to set your credentials for code usage.")
|
41
|
-
print("--------------------------------------")
|
42
|
-
print("To get an API key, email data@windbornesystems.com.")
|
43
|
-
exit(90)
|
31
|
+
raise ValueError(
|
32
|
+
"To access the WindBorne API, you need to set your Client ID by setting the environment variable WB_CLIENT_ID. "
|
33
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction. "
|
34
|
+
"To get an API key, email data@windbornesystems.com."
|
35
|
+
)
|
44
36
|
|
45
37
|
if not api_key:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
"for instruction on how to set your credentials for code usage.")
|
52
|
-
print("--------------------------------------")
|
53
|
-
print("To get an API key, email data@windbornesystems.com.")
|
54
|
-
exit(91)
|
38
|
+
raise ValueError(
|
39
|
+
"To access the WindBorne API, you need to set your API key by setting the environment variable WB_API_KEY. "
|
40
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction. "
|
41
|
+
"To get an API key, email data@windbornesystems.com."
|
42
|
+
)
|
55
43
|
|
56
44
|
if len(client_id) in [32, 35] and len(api_key) not in [32, 35]:
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
"for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
|
63
|
-
"and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
|
64
|
-
"for instruction on how to set your credentials for code usage.")
|
65
|
-
print("--------------------------------------")
|
66
|
-
print(f"Current Client ID: {client_id}")
|
67
|
-
print(f"Current API Key: {api_key}")
|
68
|
-
exit(95)
|
45
|
+
raise ValueError(
|
46
|
+
f"Your Client ID and API Key are likely swapped. Current Client ID: {client_id}, Current API Key: {api_key}. "
|
47
|
+
"Swap them or modify them accordingly to get access to WindBorne API. "
|
48
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction."
|
49
|
+
)
|
69
50
|
|
70
51
|
# Validate WB_CLIENT_ID format
|
71
52
|
if is_valid_uuid_v4(client_id):
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
53
|
+
raise NotImplementedError(
|
54
|
+
"Personal API tokens are not yet supported. "
|
55
|
+
"You will need to get a globally-authorizing API key. "
|
56
|
+
"For questions, email data@windbornesystems.com."
|
57
|
+
)
|
77
58
|
|
78
59
|
if not (is_valid_uuid_v4(client_id) or is_valid_client_id_format(client_id)):
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
"for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
|
85
|
-
"and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
|
86
|
-
"for instruction on how to set your credentials for code usage.")
|
87
|
-
print("--------------------------------------")
|
88
|
-
print(f"Current Client ID: {client_id}")
|
89
|
-
exit(92)
|
60
|
+
raise ValueError(
|
61
|
+
f"Your Client ID is misformatted: {client_id}. "
|
62
|
+
"It should either be a valid UUID v4 or consist of only lowercase letters, digits, and underscores ([a-z0-9_]). "
|
63
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction."
|
64
|
+
)
|
90
65
|
|
91
66
|
# Validate WB_API_KEY for both newer and older formats
|
92
67
|
if api_key.startswith("wb_"):
|
93
68
|
if len(api_key) != 35:
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
|
100
|
-
"and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
|
101
|
-
"for instruction on how to set your credentials for code usage.")
|
102
|
-
print("--------------------------------------")
|
103
|
-
print(f"Current API key: {api_key}")
|
104
|
-
exit(93)
|
69
|
+
raise ValueError(
|
70
|
+
f"Your API key is misformatted: {api_key}. "
|
71
|
+
"API keys starting with 'wb_' must be 35 characters long (including the 'wb_' prefix). "
|
72
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction."
|
73
|
+
)
|
105
74
|
elif len(api_key) != 32: # For early tokens
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
"for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
|
112
|
-
"and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
|
113
|
-
"for instruction on how to set your credentials for code usage.")
|
114
|
-
print("--------------------------------------")
|
115
|
-
print(f"Current API key: {api_key}")
|
116
|
-
exit(94)
|
75
|
+
raise ValueError(
|
76
|
+
f"Your API key is misformatted: {api_key}. "
|
77
|
+
"API keys created in 2023 or earlier must be exactly 32 characters long. "
|
78
|
+
"For instructions, refer to https://windbornesystems.com/docs/api/cli#introduction or https://windbornesystems.com/docs/api/pip_data#introduction."
|
79
|
+
)
|
117
80
|
|
118
81
|
|
119
82
|
VERIFIED_WB_CLIENT_ID = None
|
@@ -143,8 +106,7 @@ def make_api_request(url, params=None, as_json=True, retry_counter=0):
|
|
143
106
|
:return:
|
144
107
|
"""
|
145
108
|
if retry_counter >= 5:
|
146
|
-
|
147
|
-
exit(1)
|
109
|
+
raise ConnectionError("Max retries to API reached.")
|
148
110
|
|
149
111
|
client_id, api_key = get_verified_api_credentials()
|
150
112
|
|
@@ -209,12 +171,8 @@ def make_api_request(url, params=None, as_json=True, retry_counter=0):
|
|
209
171
|
time.sleep(2**retry_counter)
|
210
172
|
return make_api_request(url, params, as_json, retry_counter + 1)
|
211
173
|
else:
|
212
|
-
|
213
|
-
|
214
|
-
print("\nParameters provided:")
|
215
|
-
for key, value in params.items():
|
216
|
-
print(f" {key}: {value}")
|
217
|
-
exit(1)
|
174
|
+
# Re-raise the HTTP error instead of exiting
|
175
|
+
raise http_err
|
218
176
|
except requests.exceptions.ConnectionError as conn_err:
|
219
177
|
print(f"Temporary connection failure; sleeping for {2**retry_counter}s before retrying")
|
220
178
|
print(f"Underlying error: \n\n{conn_err}")
|
windborne/cli.py
CHANGED
@@ -20,6 +20,7 @@ from . import (
|
|
20
20
|
get_point_forecasts,
|
21
21
|
get_initialization_times,
|
22
22
|
get_forecast_hours,
|
23
|
+
get_generation_times,
|
23
24
|
get_full_gridded_forecast,
|
24
25
|
get_gridded_forecast,
|
25
26
|
get_tropical_cyclones
|
@@ -235,6 +236,11 @@ def main():
|
|
235
236
|
forecast_hours_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
|
236
237
|
forecast_hours_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
|
237
238
|
|
239
|
+
# Output Creation Times Command
|
240
|
+
output_creation_times_parser = subparsers.add_parser('generation_times', help='Get the time at which each forecast hour output file was generated')
|
241
|
+
output_creation_times_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
|
242
|
+
output_creation_times_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
|
243
|
+
|
238
244
|
args = parser.parse_args()
|
239
245
|
|
240
246
|
####################################################################################################################
|
@@ -437,6 +443,9 @@ def main():
|
|
437
443
|
elif args.command == 'forecast_hours':
|
438
444
|
get_forecast_hours(print_response=True, ensemble_member=args.ens_member, intracycle=args.intracycle)
|
439
445
|
|
446
|
+
elif args.command == 'generation_times':
|
447
|
+
get_generation_times(print_response=True, ensemble_member=args.ens_member, intracycle=args.intracycle)
|
448
|
+
|
440
449
|
elif args.command == 'gridded':
|
441
450
|
if len(args.args) in [0,1,2]:
|
442
451
|
print(f"To get the gridded forecast for a variable you need to provide the variable, time, and an output file.")
|
windborne/forecasts_api.py
CHANGED
@@ -324,6 +324,29 @@ def get_forecast_hours(print_response=False, ensemble_member=None, intracycle=Fa
|
|
324
324
|
return response
|
325
325
|
|
326
326
|
|
327
|
+
def get_generation_times(print_response=False, ensemble_member=None, intracycle=False):
|
328
|
+
"""
|
329
|
+
Get the creation time for each forecast hour output file.
|
330
|
+
|
331
|
+
Returns dict with keys of initialization times and values of dicts, each of which has keys of forecast hours and values of creation times (as ISO strings)
|
332
|
+
"""
|
333
|
+
|
334
|
+
params = {
|
335
|
+
'ens_member': ensemble_member,
|
336
|
+
'intracycle': intracycle
|
337
|
+
}
|
338
|
+
response = make_api_request(f"{FORECASTS_API_BASE_URL}/generation_times.json", params=params)
|
339
|
+
|
340
|
+
if print_response:
|
341
|
+
print("Generation times:")
|
342
|
+
for time, hours in response.items():
|
343
|
+
print(f" - {time}:")
|
344
|
+
for hour, creation_time in hours.items():
|
345
|
+
print(f" - {hour}: {creation_time}")
|
346
|
+
|
347
|
+
return response
|
348
|
+
|
349
|
+
|
327
350
|
# Tropical cyclones
|
328
351
|
def print_tc_supported_formats():
|
329
352
|
"""Print supported file formats for saving tcs data."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: windborne
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.4
|
4
4
|
Summary: A Python library for interacting with WindBorne Data and Forecasts API
|
5
5
|
Author-email: WindBorne Systems <data@windbornesystems.com>
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
@@ -29,3 +29,5 @@ Run `windborne --help` for more information.
|
|
29
29
|
|
30
30
|
## Further information and help request
|
31
31
|
If you encounter issues or have questions, please ask your WindBorne Systems contact or email data@windbornesystems.com.
|
32
|
+
|
33
|
+
For development of this package, see [README_dev.md](README_dev.md)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
windborne/__init__.py,sha256=dslI8_5Wozr96w7c0E7_HEpYIHt9YaTLo3ADF2yapSk,2102
|
2
|
+
windborne/api_request.py,sha256=moFKZFKMrHCINtve6ZfQYgrGgd6tXQffVGuNkZzprV4,8508
|
3
|
+
windborne/cli.py,sha256=TzEZtfzOgMgPU11ARgJ6O3YZFV3TcXPPYOabHBKb01Y,29247
|
4
|
+
windborne/data_api.py,sha256=sYZkcQog8RuLErfdLLa4gC8fRkAoPGNKKQJ8i6o-LTQ,33826
|
5
|
+
windborne/forecasts_api.py,sha256=mg7rJkBUE9xSyKIx4RMN0-RIZBdqqSz7Jp5JqFg8DhI,17053
|
6
|
+
windborne/observation_formatting.py,sha256=c739aaun6aaYhXl5VI-SRGR-TDS355_0Bfu1t6McoiM,14993
|
7
|
+
windborne/track_formatting.py,sha256=LaLfTyjpWoOtHmReJPLViY0MKm_iPL_5I2OB_lNvGGA,10054
|
8
|
+
windborne/utils.py,sha256=H8gvZ4Lrr0UmLl25iMZs6NsZliCY_73Ved_rBIqxJg4,7240
|
9
|
+
windborne-1.2.4.dist-info/METADATA,sha256=IueZfHUEH8-ZAqiUm-pODRED1uRRsRKfecGq7lokM8E,1304
|
10
|
+
windborne-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
windborne-1.2.4.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
|
12
|
+
windborne-1.2.4.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
|
13
|
+
windborne-1.2.4.dist-info/RECORD,,
|
windborne-1.2.1.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
windborne/__init__.py,sha256=nRvcreJarSKLwx40adsZSbYm-pc3htE2HZJvgY0MQUA,2044
|
2
|
-
windborne/api_request.py,sha256=wh8-LANzB62eJt699Zk3YHv4pIzwa1DIhiTJdWRp-To,11264
|
3
|
-
windborne/cli.py,sha256=Y0oLQLqHAhGX1xCM4vpREpGAMhECM4SK87h2DkUjalU,28643
|
4
|
-
windborne/data_api.py,sha256=sYZkcQog8RuLErfdLLa4gC8fRkAoPGNKKQJ8i6o-LTQ,33826
|
5
|
-
windborne/forecasts_api.py,sha256=tp0tirS91xBwv9CuScJwjNiiVhrV3fHL6U5UxAdWLXk,16266
|
6
|
-
windborne/observation_formatting.py,sha256=c739aaun6aaYhXl5VI-SRGR-TDS355_0Bfu1t6McoiM,14993
|
7
|
-
windborne/track_formatting.py,sha256=LaLfTyjpWoOtHmReJPLViY0MKm_iPL_5I2OB_lNvGGA,10054
|
8
|
-
windborne/utils.py,sha256=H8gvZ4Lrr0UmLl25iMZs6NsZliCY_73Ved_rBIqxJg4,7240
|
9
|
-
windborne-1.2.1.dist-info/METADATA,sha256=jKnNK7Ar8N3mn9Ab1ptcim8rTX-gPD6L5w6tG_nhIzM,1235
|
10
|
-
windborne-1.2.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
11
|
-
windborne-1.2.1.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
|
12
|
-
windborne-1.2.1.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
|
13
|
-
windborne-1.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|