windborne 1.2.3__py3-none-any.whl → 1.2.5__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 CHANGED
@@ -39,7 +39,9 @@ from .forecasts_api import (
39
39
  get_historical_500hpa_geopotential,
40
40
  get_historical_500hpa_wind_u, get_historical_500hpa_wind_v,
41
41
 
42
- get_tropical_cyclones
42
+ get_tropical_cyclones,
43
+
44
+ get_population_weighted_hdd
43
45
  )
44
46
 
45
47
  # Define what should be available when users import *
@@ -82,5 +84,7 @@ __all__ = [
82
84
  "get_historical_500hpa_geopotential",
83
85
  "get_historical_500hpa_wind_u",
84
86
  "get_historical_500hpa_wind_v",
85
- "get_tropical_cyclones"
87
+ "get_tropical_cyclones",
88
+
89
+ "get_population_weighted_hdd"
86
90
  ]
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
- print("To access the WindBorne API, set your Client ID and API key by setting the environment variables WB_CLIENT_ID and WB_API_KEY.")
25
- print("--------------------------------------")
26
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
27
- "for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
28
- "and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
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
- print("To access the WindBorne API, you need to set your Client ID by setting the environment variable WB_CLIENT_ID.")
36
- print("--------------------------------------")
37
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
38
- "for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
39
- "and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
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
- print("To access the WindBorne API, you need to set your API key by setting the environment variable WB_API_KEY.")
47
- print("--------------------------------------")
48
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
49
- "for instructions on how to set your credentials as environment variables for CLI and Code usage\n\n"
50
- "and to https://windbornesystems.com/docs/api/pip_data#introduction\n"
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
- print("Your Client ID and API Key are likely swapped.")
58
- print("--------------------------------------")
59
- print("Swap them or modify them accordingly to get access to WindBorne API.")
60
- print("--------------------------------------")
61
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
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
- print("Personal API tokens are not yet supported.")
73
- print("--------------------------------------")
74
- print("You will need to get a globally-authorizing API key.")
75
- print("For questions, email data@windbornesystems.com.")
76
- exit(1)
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
- print("Your Client ID is misformatted.")
80
- print("--------------------------------------")
81
- print("It should either be a valid UUID v4 or consist of only lowercase letters, digits, and underscores ([a-z0-9_]).")
82
- print("--------------------------------------")
83
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
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
- print("Your API key is misformatted.")
95
- print("--------------------------------------")
96
- print("API keys starting with 'wb_' must be 35 characters long (including the 'wb_' prefix).")
97
- print("--------------------------------------")
98
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
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
- print("Your API key is misformatted.")
107
- print("--------------------------------------")
108
- print("API keys created in 2023 or earlier must be exactly 32 characters long.")
109
- print("--------------------------------------")
110
- print("You may refer to https://windbornesystems.com/docs/api/cli#introduction\n"
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
- print("Max retries to API reached. Exiting.")
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
- print(f"Unrecoverable HTTP error occurred \n\n{http_err}")
213
- if params:
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
@@ -23,7 +23,8 @@ from . import (
23
23
  get_generation_times,
24
24
  get_full_gridded_forecast,
25
25
  get_gridded_forecast,
26
- get_tropical_cyclones
26
+ get_tropical_cyclones,
27
+ get_population_weighted_hdd
27
28
 
28
29
  )
29
30
 
@@ -226,6 +227,14 @@ def main():
226
227
  tropical_cyclones_parser.add_argument('args', nargs='*',
227
228
  help='[optional: initialization time (YYYYMMDDHH, YYYY-MM-DDTHH, or YYYY-MM-DDTHH:mm:ss)] output_file')
228
229
 
230
+ # Population Weighted HDD Command
231
+ hdd_parser = subparsers.add_parser('hdd', help='Get population weighted heating degree days (HDD) forecasts')
232
+ hdd_parser.add_argument('initialization_time', help='Initialization time (YYYYMMDDHH, YYYY-MM-DDTHH, or YYYY-MM-DDTHH:mm:ss)')
233
+ hdd_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
234
+ hdd_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
235
+ hdd_parser.add_argument('-m', '--external-model', help='External model (eg gfs, ifs, hrrr, aifs)')
236
+ hdd_parser.add_argument('-o', '--output', help='Output file (supports .csv and .json formats)')
237
+
229
238
  # Initialization Times Command
230
239
  initialization_times_parser = subparsers.add_parser('init_times', help='Get available initialization times for point forecasts')
231
240
  initialization_times_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
@@ -527,6 +536,17 @@ def main():
527
536
  print("Error: Too many arguments")
528
537
  print("Usage: windborne tropical_cyclones [initialization_time] output_file")
529
538
 
539
+ elif args.command == 'hdd':
540
+ # Handle population weighted HDD
541
+ get_population_weighted_hdd(
542
+ initialization_time=args.initialization_time,
543
+ intracycle=args.intracycle,
544
+ ens_member=args.ens_member,
545
+ external_model=args.external_model,
546
+ output_file=args.output,
547
+ print_response=(not args.output)
548
+ )
549
+
530
550
  else:
531
551
  parser.print_help()
532
552
 
@@ -389,3 +389,41 @@ def download_and_save_output(output_file, response, silent=False):
389
389
  if not silent:
390
390
  print(f"Error processing the file: {e}")
391
391
  return False
392
+
393
+ def get_population_weighted_hdd(initialization_time, intracycle=False, ens_member=None, external_model=None, output_file=None, print_response=False):
394
+ """
395
+ Get population weighted HDD data from the API.
396
+ """
397
+ params = {
398
+ "initialization_time": initialization_time,
399
+ "intracycle": intracycle,
400
+ "ens_member": ens_member,
401
+ "external_model": external_model
402
+ }
403
+ response = make_api_request(f"{FORECASTS_API_BASE_URL}/hdd", params=params, as_json=True)
404
+
405
+ if output_file:
406
+ if output_file.endswith('.csv'):
407
+ import csv
408
+
409
+ # save as csv, with a row for each region, and a column for each date, sorted alphabetically by region
410
+ regions = sorted(response['hdd'].keys())
411
+ dates = response['dates']
412
+ data = [[response['hdd'][region][dates[i]] for region in regions] for i in range(len(dates))]
413
+
414
+ with open(output_file, 'w') as f:
415
+ writer = csv.writer(f)
416
+ writer.writerow(['Region'] + dates)
417
+
418
+ for region in regions:
419
+ writer.writerow([region] + [response['hdd'][region][date] for date in dates])
420
+
421
+ if print_response:
422
+ dates = response['dates']
423
+ print(response['hdd']['Alabama'])
424
+ for region in sorted(response['hdd'].keys()):
425
+ print(f"{region}:")
426
+ for i in range(len(dates)):
427
+ print(f" {dates[i]}: {response['hdd'][region][dates[i]]}")
428
+
429
+ return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: windborne
3
- Version: 1.2.3
3
+ Version: 1.2.5
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=tbcmwRlhTZAZCzOBeGSuaiOArA1OK-j94ZvEQN910eE,2172
2
+ windborne/api_request.py,sha256=moFKZFKMrHCINtve6ZfQYgrGgd6tXQffVGuNkZzprV4,8508
3
+ windborne/cli.py,sha256=qcETAJ0KXEmdJaIVrZqC7X742K9cNo6cS3racth1IGs,30353
4
+ windborne/data_api.py,sha256=sYZkcQog8RuLErfdLLa4gC8fRkAoPGNKKQJ8i6o-LTQ,33826
5
+ windborne/forecasts_api.py,sha256=kWpUPSzuepZ0G_mwtKgts2lmxlZAzcT7UB7kzurQ6D4,18558
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.5.dist-info/METADATA,sha256=j22__ATrIramyQnlHMxQf7bY2ds1ITdfiDs4v4ZTWlY,1304
10
+ windborne-1.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ windborne-1.2.5.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
12
+ windborne-1.2.5.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
13
+ windborne-1.2.5.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- windborne/__init__.py,sha256=dslI8_5Wozr96w7c0E7_HEpYIHt9YaTLo3ADF2yapSk,2102
2
- windborne/api_request.py,sha256=wh8-LANzB62eJt699Zk3YHv4pIzwa1DIhiTJdWRp-To,11264
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.3.dist-info/METADATA,sha256=UvrhPEAgHC8C7kZ0o0Ii8qqOWVZCa3HEw72M7QfqLkI,1235
10
- windborne-1.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- windborne-1.2.3.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
12
- windborne-1.2.3.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
13
- windborne-1.2.3.dist-info/RECORD,,