windborne 1.1.5__py3-none-any.whl → 1.2.1__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
@@ -22,10 +22,12 @@ from .data_api import (
22
22
  from .forecasts_api import (
23
23
  get_point_forecasts,
24
24
  get_initialization_times,
25
+ get_forecast_hours,
25
26
 
27
+ get_gridded_forecast,
26
28
  get_full_gridded_forecast,
27
29
  get_temperature_2m,
28
- # get_dewpoint_2m,
30
+ get_dewpoint_2m,
29
31
  get_wind_u_10m, get_wind_v_10m,
30
32
  get_pressure_msl,
31
33
  get_500hpa_wind_u, get_500hpa_wind_v,
@@ -58,10 +60,12 @@ __all__ = [
58
60
 
59
61
  "get_point_forecasts",
60
62
  "get_initialization_times",
63
+ "get_forecast_hours",
61
64
 
65
+ "get_gridded_forecast",
62
66
  "get_full_gridded_forecast",
63
67
  "get_temperature_2m",
64
- # "get_dewpoint_2m",
68
+ "get_dewpoint_2m",
65
69
  "get_wind_u_10m",
66
70
  "get_wind_v_10m",
67
71
  "get_500hpa_wind_u",
windborne/api_request.py CHANGED
@@ -199,6 +199,9 @@ def make_api_request(url, params=None, as_json=True, retry_counter=0):
199
199
  mission_id = url.split('/missions/')[1].split('/')[0]
200
200
  print(f"Mission ID provided: {mission_id}")
201
201
  print(f"No mission found with id: {mission_id}")
202
+ print("-------------------------------------------------------")
203
+ print("Response text:")
204
+ print(http_err.response.text)
202
205
  return None
203
206
  elif http_err.response.status_code == 502:
204
207
  print(f"Temporary connection failure; sleeping for {2**retry_counter}s before retrying")
@@ -223,4 +226,4 @@ def make_api_request(url, params=None, as_json=True, retry_counter=0):
223
226
  time.sleep(2**retry_counter)
224
227
  return make_api_request(url, params, as_json, retry_counter + 1)
225
228
  except requests.exceptions.RequestException as req_err:
226
- print(f"An error occurred\n\n{req_err}")
229
+ print(f"An error occurred\n\n{req_err}")
windborne/cli.py CHANGED
@@ -19,18 +19,9 @@ from . import (
19
19
 
20
20
  get_point_forecasts,
21
21
  get_initialization_times,
22
+ get_forecast_hours,
22
23
  get_full_gridded_forecast,
23
- get_temperature_2m,
24
- # get_dewpoint_2m,
25
- get_wind_u_10m, get_wind_v_10m,
26
- get_500hpa_wind_u, get_500hpa_wind_v,
27
- get_500hpa_temperature, get_850hpa_temperature,
28
- get_pressure_msl,
29
- get_500hpa_geopotential, get_850hpa_geopotential,
30
-
31
- get_historical_temperature_2m,
32
- get_historical_500hpa_geopotential,
33
- get_historical_500hpa_wind_u, get_historical_500hpa_wind_v,
24
+ get_gridded_forecast,
34
25
  get_tropical_cyclones
35
26
 
36
27
  )
@@ -156,70 +147,73 @@ def main():
156
147
 
157
148
  # GRIDDED FORECASTS
158
149
  ####################################################################################################################
159
- full_gridded_parser = subparsers.add_parser('grid_full', help='Get full gridded forecast')
160
- full_gridded_parser.add_argument('args', nargs='*', help='time output_file')
161
-
162
- # Gridded 2m temperature Command
163
- gridded_temperature_2m_parser = subparsers.add_parser('grid_temp_2m', help='Get gridded output of global 2m temperature forecasts')
164
- gridded_temperature_2m_parser.add_argument('args', nargs='*', help='time output_file')
165
-
166
- # Gridded 2m dewpoint Command
167
- gridded_dewpoint_2m_parser = subparsers.add_parser('grid_dewpoint_2m', help='Get gridded output of global dewpoint forecasts')
168
- gridded_dewpoint_2m_parser.add_argument('args', nargs='*', help='time output_file')
169
-
170
- # Gridded wind-u 10m Command
171
- gridded_wind_u_10m_parser = subparsers.add_parser('grid_wind_u_10m', help='Get gridded output of global 10m u-component of wind forecasts')
172
- gridded_wind_u_10m_parser.add_argument('args', nargs='*', help='time output_file')
173
-
174
- # Gridded wind-v 10m Command
175
- gridded_wind_v_10m_parser = subparsers.add_parser('grid_wind_v_10m', help='Get gridded output of global 10m v-component of wind forecasts')
176
- gridded_wind_v_10m_parser.add_argument('args', nargs='*', help='time output_file')
177
-
178
- # Gridded 500hPa wind-u Command
179
- gridded_500hpa_wind_u_parser = subparsers.add_parser('grid_500hpa_wind_u', help='Get gridded output of global 500hPa wind v-component of wind forecasts')
180
- gridded_500hpa_wind_u_parser.add_argument('args', nargs='*', help='time output_file')
181
-
182
- # Gridded 500hPa wind-v Command
183
- gridded_500hpa_wind_v_parser = subparsers.add_parser('grid_500hpa_wind_v', help='Get gridded output of global 500hPa wind u-component of wind forecasts')
184
- gridded_500hpa_wind_v_parser.add_argument('args', nargs='*', help='time output_file')
185
-
186
- # Gridded 500hPa temperature Command
187
- gridded_500hpa_temperature_parser = subparsers.add_parser('grid_500hpa_temperature', help='Get gridded output of global 500hPa temperature forecasts')
188
- gridded_500hpa_temperature_parser.add_argument('args', nargs='*', help='time output_file')
189
-
190
- # Gridded 850hPa temperature Command
191
- gridded_850hpa_temperature_parser = subparsers.add_parser('grid_850hpa_temperature', help='Get gridded output of global 850hPa temperature forecasts')
192
- gridded_850hpa_temperature_parser.add_argument('args', nargs='*', help='time output_file')
193
-
194
- # Gridded mean sea level pressure Command
195
- gridded_pressure_msl_parser = subparsers.add_parser('grid_pressure_msl', help='Get gridded output of global mean sea level pressure forecasts')
196
- gridded_pressure_msl_parser.add_argument('args', nargs='*', help='time output_file')
197
-
198
- # Gridded 500hPa geopotential Command
199
- gridded_500hpa_geopotential_parser = subparsers.add_parser('grid_500hpa_geopotential', help='Get gridded output of global 500hPa geopotential forecasts')
200
- gridded_500hpa_geopotential_parser.add_argument('args', nargs='*', help='time output_file')
150
+ gridded_parser = subparsers.add_parser('gridded', help='Get gridded forecast for a variable')
151
+ gridded_parser.add_argument('args', nargs='*', help='variable time output_file')
152
+ gridded_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
153
+ gridded_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
201
154
 
202
- # Gridded 850hPa geopotential Command
203
- gridded_850hpa_geopotential_parser = subparsers.add_parser('grid_850hpa_geopotential', help='Get gridded output of global 500hPa geopotential forecasts')
204
- gridded_850hpa_geopotential_parser.add_argument('args', nargs='*', help='time output_file')
155
+ hist_gridded_parser = subparsers.add_parser('hist_gridded', help='Get historical gridded forecast for a variable')
156
+ hist_gridded_parser.add_argument('args', nargs='*', help='variable initialization_time forecast_hour output_file')
157
+ hist_gridded_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
158
+ hist_gridded_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
205
159
 
206
- # HISTORICAL FORECASTS
207
- ####################################################################################################################
208
- # Historical 500hpa geopotential Command
209
- historical_temperature_2m_parser = subparsers.add_parser('hist_temp_2m', help='Get historical output of global temperature forecasts')
210
- historical_temperature_2m_parser.add_argument('args', nargs='*', help='initialization_time forecast_hour output_file')
211
-
212
- # Historical 500hpa geopotential Command
213
- historical_500hpa_geopotential_parser = subparsers.add_parser('hist_500hpa_geopotential', help='Get historical output of global 500hPa geopotential forecasts')
214
- historical_500hpa_geopotential_parser.add_argument('args', nargs='*', help='initialization_time forecast_hour output_file')
215
-
216
- # Historical 500hpa wind u Command
217
- historical_500hpa_wind_u_parser = subparsers.add_parser('hist_500hpa_wind_u', help='Get historical output of global 500hPa wind u forecasts')
218
- historical_500hpa_wind_u_parser.add_argument('args', nargs='*', help='initialization_time forecast_hour output_file')
160
+ full_gridded_parser = subparsers.add_parser('grid_full', help='Get full gridded forecast')
161
+ full_gridded_parser.add_argument('args', nargs='*', help='time output_file')
219
162
 
220
- # Historical 500hpa wind v Command
221
- historical_500hpa_wind_v_parser = subparsers.add_parser('hist_500hpa_wind_v', help='Get historical output of global 500hPa wind v forecasts')
222
- historical_500hpa_wind_v_parser.add_argument('args', nargs='*', help='initialization_time forecast_hour output_file')
163
+ # Define variables for gridded forecasts - the command name will be derived from the variable name
164
+ # except for special cases like temperature_2m -> temp_2m
165
+ gridded_variables = [
166
+ 'temperature_2m',
167
+ 'dewpoint_2m',
168
+ 'wind_u_10m',
169
+ 'wind_v_10m',
170
+ 'pressure_msl',
171
+ '500/temperature',
172
+ '500/wind_u',
173
+ '500/wind_v',
174
+ '500/geopotential',
175
+ '850/temperature',
176
+ '850/geopotential'
177
+ ]
178
+
179
+ gridded_human_names = {
180
+ 'temperature_2m': '2m temperature',
181
+ 'dewpoint_2m': '2m dewpoint',
182
+ 'wind_u_10m': '10m u-component of wind',
183
+ 'wind_v_10m': '10m v-component of wind',
184
+ }
185
+
186
+ gridded_forecast_mapping = {}
187
+ for var in gridded_variables:
188
+ cmd_name = var.replace('/', 'hpa_')
189
+ if var == 'temperature_2m':
190
+ cmd_name = 'temp_2m'
191
+
192
+ human_name = gridded_human_names.get(var, var)
193
+ if '/' in var:
194
+ level, real_var = var.split('/')
195
+ human_name = f"{level}hPa {real_var}"
196
+
197
+ gridded_forecast_mapping[cmd_name] = {
198
+ 'variable': var,
199
+ 'human_name': human_name
200
+ }
201
+
202
+ # Dynamically create parsers for gridded forecasts
203
+ for cmd_name, config in gridded_forecast_mapping.items():
204
+ grid_help = f"Get gridded output of global {config['human_name']} forecasts"
205
+ grid_parser = subparsers.add_parser(f'grid_{cmd_name}', help=grid_help)
206
+ grid_parser.add_argument('args', nargs='*', help='time output_file')
207
+ grid_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
208
+ grid_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
209
+ grid_parser.set_defaults(variable=config['variable'])
210
+
211
+ hist_help = f"Get historical output of global {config['human_name']} forecasts"
212
+ hist_parser = subparsers.add_parser(f'hist_{cmd_name}', help=hist_help)
213
+ hist_parser.add_argument('args', nargs='*', help='initialization_time forecast_hour output_file')
214
+ hist_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
215
+ hist_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
216
+ hist_parser.set_defaults(variable=config['variable'])
223
217
 
224
218
  # OTHER
225
219
  # TCS
@@ -233,7 +227,13 @@ def main():
233
227
 
234
228
  # Initialization Times Command
235
229
  initialization_times_parser = subparsers.add_parser('init_times', help='Get available initialization times for point forecasts')
230
+ initialization_times_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
231
+ initialization_times_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
236
232
 
233
+ # Forecast Hours Command
234
+ forecast_hours_parser = subparsers.add_parser('forecast_hours', help='Get available forecast hours for WeatherMesh')
235
+ forecast_hours_parser.add_argument('-i', '--intracycle', action='store_true', help='Use the intracycle forecast')
236
+ forecast_hours_parser.add_argument('-e', '--ens-member', help='Ensemble member (eg 1 or mean)')
237
237
 
238
238
  args = parser.parse_args()
239
239
 
@@ -432,201 +432,68 @@ def main():
432
432
  )
433
433
 
434
434
  elif args.command == 'init_times':
435
- get_initialization_times(print_response=True)
436
-
437
- if args.command == 'grid_full':
438
- # Parse get_full_gridded_forecast arguments
439
- if len(args.args) in [0,1]:
440
- print("To get the full gridded forecast you need to provide the time for which to get the forecast and an output file.")
441
- print("\nUsage: windborne get_full_gridded_forecast time output_file")
442
- elif len(args.args) == 2:
443
- get_full_gridded_forecast(time=args.args[0], output_file=args.args[1])
444
- else:
445
- print("Too many arguments")
446
- print("\nUsage: windborne get_full_gridded_forecast time output_file")
435
+ get_initialization_times(print_response=True, ensemble_member=args.ens_member, intracycle=args.intracycle)
447
436
 
448
- elif args.command == 'grid_temp_2m':
449
- # Parse grid_temp_2m arguments
450
- if len(args.args) in [0,1]:
451
- print("To get the gridded output of global 2m temperature forecast you need to provide the time for which to get the forecast and an output file.")
452
- print("\nUsage: windborne grid_temp_2m time output_file")
453
- elif len(args.args) == 2:
454
- get_temperature_2m(time=args.args[0], output_file=args.args[1])
455
- else:
456
- print("Too many arguments")
457
- print("\nUsage: windborne grid_temp_2m time output_file")
458
-
459
- # elif args.command == 'grid_dewpoint_2m':
460
- # # Parse grid_dewpoint_2m arguments
461
- # if len(args.args) in [0,1]:
462
- # print(f"To get the gridded output of global 2m dew point forecast you need to provide the time for which to get the forecast and an output file.")
463
- # print("\nUsage: windborne grid_dewpoint_2m time output_file")
464
- # elif len(args.args) == 2:
465
- # get_dewpoint_2m(time=args.args[0], output_file=args.args[1])
466
- # else:
467
- # print("Too many arguments")
468
- # print("\nUsage: windborne grid_dewpoint_2m time output_file")
469
-
470
- elif args.command == 'grid_wind_u_10m':
471
- # Parse grid_wind_u_10m arguments
472
- if len(args.args) in [0,1]:
473
- print(f"To get the gridded output of global 10m u-component of wind forecasts you need to provide the time for which to get the forecast and an output file.")
474
- print("\nUsage: windborne grid_wind_u_10m time output_file")
475
- elif len(args.args) == 2:
476
- get_wind_u_10m(time=args.args[0], output_file=args.args[1])
477
- else:
478
- print("Too many arguments")
479
- print("\nUsage: windborne grid_wind_u_10m time output_file")
480
-
481
- elif args.command == 'grid_wind_v_10m':
482
- # Parse grid_wind_v_10m arguments
483
- if len(args.args) in [0,1]:
484
- print(f"To get the gridded output of global 10m v-component of wind forecasts you need to provide the time for which to get the forecast and an output file.")
485
- print("\nUsage: windborne grid_wind_v_10m time output_file")
486
- elif len(args.args) == 2:
487
- get_wind_v_10m(time=args.args[0], output_file=args.args[1])
488
- else:
489
- print("Too many arguments")
490
- print("\nUsage: windborne grid_wind_v_10m time output_file")
491
-
492
- elif args.command == 'grid_500hpa_wind_u':
493
- # Parse grid_500hpa_wind_u arguments
494
- if len(args.args) in [0,1]:
495
- print(f"To get the gridded output of global 500hPa u-component of wind forecasts you need to provide the time for which to get the forecast and an output file.")
496
- print("\nUsage: windborne grid_500hpa_wind_u time output_file")
497
- elif len(args.args) == 2:
498
- get_500hpa_wind_u(time=args.args[0], output_file=args.args[1])
499
- else:
500
- print("Too many arguments")
501
- print("\nUsage: windborne grid_500hpa_wind_u time output_file")
437
+ elif args.command == 'forecast_hours':
438
+ get_forecast_hours(print_response=True, ensemble_member=args.ens_member, intracycle=args.intracycle)
502
439
 
503
- elif args.command == 'grid_500hpa_wind_v':
504
- # Parse grid_500hpa_wind_v arguments
505
- if len(args.args) in [0,1]:
506
- print(f"To get the gridded output of global 500hPa v-component of wind forecasts you need to provide the time for which to get the forecast and an output file.")
507
- print("\nUsage: windborne grid_500hpa_wind_v time output_file")
508
- elif len(args.args) == 2:
509
- get_500hpa_wind_v(time=args.args[0], output_file=args.args[1])
510
- else:
511
- print("Too many arguments")
512
- print("\nUsage: windborne grid_500hpa_wind_v time output_file")
513
-
514
- elif args.command == 'grid_500hpa_temperature':
515
- # Parse grid_500hpa_temperature arguments
516
- if len(args.args) in [0,1]:
517
- print(f"To get the gridded output of global 500hPa temperature forecasts you need to provide the time for which to get the forecast and an output file.")
518
- print("\nUsage: windborne grid_500hpa_temperature time output_file")
519
- return
520
- elif len(args.args) == 2:
521
- get_500hpa_temperature(time=args.args[0], output_file=args.args[1])
522
- else:
523
- print("Too many arguments")
524
- print("\nUsage: windborne grid_500hpa_temperature time output_file")
525
-
526
- elif args.command == 'grid_850hpa_temperature':
527
- # Parse grid_850hpa_temperature arguments
528
- if len(args.args) in [0,1]:
529
- print(f"To get the gridded output of global 850hPa temperature forecasts you need to provide the time for which to get the forecast and an output file.")
530
- print("\nUsage: windborne grid_850hpa_temperature time output_file")
531
- return
532
- elif len(args.args) == 2:
533
- get_850hpa_temperature(time=args.args[0], output_file=args.args[1])
534
- else:
535
- print("Too many arguments")
536
- print("\nUsage: windborne grid_850hpa_temperature time output_file")
537
-
538
- elif args.command == 'grid_pressure_msl':
539
- # Parse grid_pressure_msl arguments
540
- if len(args.args) in [0,1]:
541
- print(f"To get the gridded output of global mean sea level pressure forecasts you need to provide the time for which to get the forecast and an output file.")
542
- print("\nUsage: windborne grid_pressure_msl time output_file")
543
- elif len(args.args) == 2:
544
- get_pressure_msl(time=args.args[0], output_file=args.args[1])
545
- else:
546
- print("Too many arguments")
547
- print("\nUsage: windborne grid_pressure_msl time output_file")
548
-
549
- elif args.command == 'grid_500hpa_geopotential':
550
- # Parse grid_500hpa_geopotential arguments
551
- if len(args.args) in [0,1]:
552
- print(f"To get the gridded output of global 500hPa geopotential forecasts you need to provide the time for which to get the forecast and an output file.")
553
- print("\nUsage: windborne grid_500hpa_geopotential time output_file")
554
- return
555
- elif len(args.args) == 2:
556
- get_500hpa_geopotential(time=args.args[0], output_file=args.args[1])
557
- else:
558
- print("Too many arguments")
559
- print("\nUsage: windborne grid_500hpa_geopotential time output_file")
560
-
561
- elif args.command == 'grid_850hpa_geopotential':
562
- # Parse grid_850hpa_geopotential arguments
563
- if len(args.args) in [0,1]:
564
- print(f"To get the gridded output of global 850hPa geopotential forecasts you need to provide the time for which to get the forecast and an output file.")
565
- print("\nUsage: windborne grid_850hpa_geopotential time output_file")
566
- return
567
- elif len(args.args) == 2:
568
- get_850hpa_geopotential(time=args.args[0], output_file=args.args[1])
569
- else:
570
- print("Too many arguments")
571
- print("\nUsage: windborne grid_850hpa_geopotential time output_file")
572
-
573
- # HISTORICAL
574
-
575
- elif args.command == 'hist_temp_2m':
576
- # Parse historical temperature arguments
577
- if len(args.args) in [0,1,2]:
578
- print("To get the historical output of global temperature forecasts you need to provide\n"
579
- " - initialization time of the forecast\n"
580
- " - How many hours after the run time the forecast is valid at\n"
581
- " - An ouput file to save the data")
582
- print("\nUsage: windborne hist_temp_2m initialization_time forecast_hour output_file")
583
- return
584
- elif len(args.args) == 3:
585
- get_historical_temperature_2m(initialization_time=args.args[0], forecast_hour=args.args[1], output_file=args.args[2])
586
- else:
587
- print("Too many arguments")
588
- print("\nUsage: windborne hist_temp_2m initialization_time forecast_hour output_file")
589
-
590
- elif args.command == 'hist_500hpa_geopotential':
591
- # Parse historical 500 hpa geopotential arguments
440
+ elif args.command == 'gridded':
592
441
  if len(args.args) in [0,1,2]:
593
- print("To get the historical output of global 500hPa geopotential forecasts you need to provide\n"
594
- " - initialization time of the forecast\n"
595
- " - How many hours after the run time the forecast is valid at\n"
596
- " - An ouput file to save the data")
597
- print("\nUsage: windborne hist_500hpa_geopotential initialization_time forecast_hour output_file")
598
- return
442
+ print(f"To get the gridded forecast for a variable you need to provide the variable, time, and an output file.")
443
+ print(f"\nUsage: windborne gridded variable time output_file")
599
444
  elif len(args.args) == 3:
600
- get_historical_500hpa_geopotential(initialization_time=args.args[0], forecast_hour=args.args[1], output_file=args.args[2])
445
+ get_gridded_forecast(variable=args.args[0], time=args.args[1], output_file=args.args[2], ensemble_member=args.ens_member, intracycle=args.intracycle)
601
446
  else:
602
447
  print("Too many arguments")
603
- print("\nUsage: windborne hist_500hpa_geopotential initialization_time forecast_hour output_file")
604
448
 
605
- elif args.command == 'hist_500hpa_wind_u':
606
- if len(args.args) in [0,1,2]:
607
- print("To get the historical output of global 500hPa wind u forecasts you need to provide\n"
608
- " - initialization time of the forecast\n"
609
- " - How many hours after the run time the forecast is valid at\n"
610
- " - An ouput file to save the data")
611
- print("\nUsage: windborne hist_500hpa_wind_u initialization_time forecast_hour output_file")
612
- elif len(args.args) == 3:
613
- get_historical_500hpa_wind_u(initialization_time=args.args[0], forecast_hour=args.args[1], output_file=args.args[2])
449
+ elif args.command == 'grid_full':
450
+ if len(args.args) in [0,1]:
451
+ print("To get the full gridded forecast you need to provide the time for which to get the forecast and an output file.")
452
+ print("\nUsage: windborne grid_full time output_file")
453
+ elif len(args.args) == 2:
454
+ get_full_gridded_forecast(time=args.args[0], output_file=args.args[1])
614
455
  else:
615
456
  print("Too many arguments")
616
- print("\nUsage: windborne hist_500hpa_wind_u initialization_time forecast_hour output_file")
617
-
618
- elif args.command == 'hist_500hpa_wind_v':
619
- if len(args.args) in [0,1,2]:
620
- print("To get the historical output of global 500hPa wind v forecasts you need to provide\n"
621
- " - initialization time of the forecast\n"
622
- " - How many hours after the run time the forecast is valid at\n"
623
- " - An ouput file to save the data")
624
- print("\nUsage: windborne hist_500hpa_wind_u initialization_time forecast_hour output_file")
625
- elif len(args.args) == 3:
626
- get_historical_500hpa_wind_v(initialization_time=args.args[0], forecast_hour=args.args[1], output_file=args.args[2])
457
+ print("\nUsage: windborne grid_full time output_file")
458
+
459
+ elif args.command == 'hist_gridded':
460
+ if len(args.args) in [0,1,2,3]:
461
+ print(f"To get the historical gridded forecast for a variable you need to provide the variable, initialization time, forecast hour, and an output file.")
462
+ print(f"\nUsage: windborne hist_gridded variable initialization_time forecast_hour output_file")
463
+ elif len(args.args) == 4:
464
+ get_gridded_forecast(variable=args.args[0], initialization_time=args.args[1], forecast_hour=args.args[2], output_file=args.args[3], ensemble_member=args.ens_member, intracycle=args.intracycle)
627
465
  else:
628
466
  print("Too many arguments")
629
- print("\nUsage: windborne hist_500hpa_wind_v initialization_time forecast_hour output_file")
467
+ print(f"\nUsage: windborne hist_gridded variable initialization_time forecast_hour output_file")
468
+
469
+ # Handle all gridded forecast commands
470
+ elif args.command.startswith('grid_'):
471
+ cmd_name = args.command[5:] # Remove 'grid_' prefix
472
+ if cmd_name in gridded_forecast_mapping:
473
+ if len(args.args) in [0,1]:
474
+ print(f"To get {gridded_forecast_mapping[cmd_name]['human_name']} you need to provide the time for which to get the forecast and an output file.")
475
+ print(f"\nUsage: windborne {args.command} time output_file")
476
+ elif len(args.args) == 2:
477
+ get_gridded_forecast(variable=args.variable, time=args.args[0], output_file=args.args[1], ensemble_member=args.ens_member, intracycle=args.intracycle)
478
+ else:
479
+ print("Too many arguments")
480
+ print(f"\nUsage: windborne {args.command} time output_file")
481
+
482
+ # Handle all historical forecast commands
483
+ elif args.command.startswith('hist_'):
484
+ cmd_name = args.command[5:] # Remove 'hist_' prefix
485
+ if cmd_name in gridded_forecast_mapping:
486
+ if len(args.args) in [0,1,2]:
487
+ print(f"To get {gridded_forecast_mapping[cmd_name]['human_name']} you need to provide\n"
488
+ " - initialization time of the forecast\n"
489
+ " - How many hours after the initialization time the forecast is valid at\n"
490
+ " - An output file to save the data")
491
+ print(f"\nUsage: windborne {args.command} initialization_time forecast_hour output_file")
492
+ elif len(args.args) == 3:
493
+ get_gridded_forecast(variable=args.variable, initialization_time=args.args[0], forecast_hour=args.args[1], output_file=args.args[2], ensemble_member=args.ens_member, intracycle=args.intracycle)
494
+ else:
495
+ print("Too many arguments")
496
+ print(f"\nUsage: windborne {args.command} initialization_time forecast_hour output_file")
630
497
 
631
498
  elif args.command == 'tropical_cyclones':
632
499
  # Parse cyclones arguments
@@ -655,4 +522,4 @@ def main():
655
522
  parser.print_help()
656
523
 
657
524
  if __name__ == '__main__':
658
- main()
525
+ main()
@@ -107,28 +107,45 @@ def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=N
107
107
  return response
108
108
 
109
109
 
110
- def get_gridded_forecast(time, variable, output_file=None):
110
+ def get_gridded_forecast(variable, time=None, initialization_time=None, forecast_hour=None, output_file=None, silent=False, intracycle=False, ensemble_member=None):
111
111
  """
112
112
  Get gridded forecast data from the API.
113
113
  Note that this is primarily meant to be used internally by the other functions in this module.
114
114
 
115
115
  Args:
116
- time (str): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
117
- or compact format (YYYYMMDDHH)
118
- where HH must be 00, 06, 12, or 18
116
+ time (str, optional): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
117
+ or compact format (YYYYMMDDHH). May be used instead of initialization_time and forecast_hour.
118
+ initialization_time (str, optional): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
119
+ or compact format (YYYYMMDDHH). May be used in conjunction with forecast_hour instead of time.
120
+ forecast_hour (int, optional): The forecast hour to get the forecast for. May be used in conjunction with initialization_time instead of time.
119
121
  variable (str): The variable you want the forecast for
120
122
  output_file (str, optional): Path to save the response data
121
123
  Supported formats: .nc
122
124
  """
123
125
 
124
- params = {}
126
+ # backwards compatibility for time and variable order swap
127
+ if time in ['temperature_2m', 'dewpoint_2m', 'wind_u_10m', 'wind_v_10m', '500/wind_u', '500/wind_v', '500/temperature', '850/temperature', 'pressure_msl', '500/geopotential', '850/geopotential', 'FULL']:
128
+ variable, time = time, variable
125
129
 
126
- if not time:
127
- print("Error: the time you want the forecast for is required.")
130
+ # require either time or initialization_time and forecast_hour
131
+ if time is None and (initialization_time is None or forecast_hour is None):
132
+ print("Error: you must provide either time or initialization_time and forecast_hour.")
128
133
  return
129
- else:
130
- time_parsed = parse_time(time)
131
- params["time"] = time_parsed
134
+ elif time is not None and (initialization_time is not None or forecast_hour is not None):
135
+ print("Warning: time, initialization_time, forecast_hour all provided; using initialization_time and forecast_hour.")
136
+
137
+ params = {}
138
+ if initialization_time is not None and forecast_hour is not None:
139
+ params["initialization_time"] = parse_time(initialization_time, init_time_flag=True)
140
+ params["forecast_hour"] = forecast_hour
141
+ elif time:
142
+ params["time"] = parse_time(time)
143
+
144
+ if intracycle:
145
+ params["intracycle"] = intracycle
146
+
147
+ if ensemble_member:
148
+ params["ens_member"] = ensemble_member
132
149
 
133
150
  response = make_api_request(f"{FORECASTS_GRIDDED_URL}/{variable}", params=params, as_json=False)
134
151
 
@@ -136,84 +153,59 @@ def get_gridded_forecast(time, variable, output_file=None):
136
153
  return None
137
154
 
138
155
  if output_file:
139
- print(f"Output URL found; downloading to {output_file}...")
156
+ if not silent:
157
+ print(f"Output URL found; downloading to {output_file}...")
140
158
  download_and_save_output(output_file, response)
141
159
 
142
160
  return response
143
161
 
144
162
  def get_full_gridded_forecast(time, output_file=None):
145
- return get_gridded_forecast(time, "FULL", output_file)
163
+ return get_gridded_forecast(variable="FULL", time=time, output_file=output_file)
146
164
 
147
165
  def get_temperature_2m(time, output_file=None):
148
- return get_gridded_forecast(time, "temperature_2m", output_file)
166
+ return get_gridded_forecast(variable="temperature_2m", time=time, output_file=output_file)
149
167
 
150
- # Not yet implemented
151
- # def get_dewpoint_2m(time, output_file=None):
152
- # return get_gridded_forecast(time, "dewpoint_2m", output_file)
168
+ def get_dewpoint_2m(time, output_file=None):
169
+ return get_gridded_forecast(variable="dewpoint_2m", time=time, output_file=output_file)
153
170
 
154
171
  def get_wind_u_10m(time, output_file=None):
155
- return get_gridded_forecast(time, "wind_u_10m", output_file)
172
+ return get_gridded_forecast(variable="wind_u_10m", time=time, output_file=output_file)
156
173
 
157
174
  def get_wind_v_10m(time, output_file=None):
158
- return get_gridded_forecast(time, "wind_v_10m", output_file)
175
+ return get_gridded_forecast(variable="wind_v_10m", time=time, output_file=output_file)
159
176
 
160
177
  def get_500hpa_wind_u(time, output_file=None):
161
- return get_gridded_forecast(time, "500/wind_u", output_file)
178
+ return get_gridded_forecast(variable="500/wind_u", time=time, output_file=output_file)
162
179
 
163
180
  def get_500hpa_wind_v(time, output_file=None):
164
- return get_gridded_forecast(time, "500/wind_v", output_file)
181
+ return get_gridded_forecast(variable="500/wind_v", time=time, output_file=output_file)
165
182
 
166
183
  def get_500hpa_temperature(time, output_file=None):
167
- return get_gridded_forecast(time, "500/temperature", output_file)
184
+ return get_gridded_forecast(variable="500/temperature", time=time, output_file=output_file)
168
185
 
169
186
  def get_850hpa_temperature(time, output_file=None):
170
- return get_gridded_forecast(time, "850/temperature", output_file)
187
+ return get_gridded_forecast(variable="850/temperature", time=time, output_file=output_file)
171
188
 
172
189
  def get_pressure_msl(time, output_file=None):
173
- return get_gridded_forecast(time, "pressure_msl", output_file)
190
+ return get_gridded_forecast(variable="pressure_msl", time=time, output_file=output_file)
174
191
 
175
192
  def get_500hpa_geopotential(time, output_file=None):
176
- return get_gridded_forecast(time, "500/geopotential", output_file)
193
+ return get_gridded_forecast(variable="500/geopotential", time=time, output_file=output_file)
177
194
 
178
195
  def get_850hpa_geopotential(time, output_file=None):
179
- return get_gridded_forecast(time, "850/geopotential", output_file)
180
-
181
-
182
- def get_historical_output(initialization_time, forecast_hour, variable, output_file=None):
183
- params = {}
184
-
185
- if not initialization_time or not forecast_hour:
186
- print("To get the historical output of global temperature forecasts you need to provide:\n"
187
- "- the initialization time of the forecast\n"
188
- "- how many hours after the run time the forecast is valid at.\n")
189
- return
190
- else:
191
- params["initialization_time"] = parse_time(initialization_time, init_time_flag=True)
192
- params["forecast_hour"] = forecast_hour
193
-
194
- response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/{variable}", params=params, as_json=False)
195
-
196
- if response is None:
197
- return None
198
-
199
- if output_file:
200
- print(f"Output URL found; downloading to {output_file}...")
201
- download_and_save_output(output_file, response)
202
-
203
- return response
204
-
196
+ return get_gridded_forecast(variable="850/geopotential", time=time, output_file=output_file)
205
197
 
206
198
  def get_historical_temperature_2m(initialization_time, forecast_hour, output_file=None):
207
- return get_historical_output(initialization_time, forecast_hour, "temperature_2m", output_file)
199
+ return get_gridded_forecast(variable="temperature_2m", initialization_time=initialization_time, forecast_hour=forecast_hour, output_file=output_file)
208
200
 
209
201
  def get_historical_500hpa_geopotential(initialization_time, forecast_hour, output_file=None):
210
- return get_historical_output(initialization_time, forecast_hour, "500/geopotential", output_file)
202
+ return get_gridded_forecast(variable="500/geopotential", initialization_time=initialization_time, forecast_hour=forecast_hour, output_file=output_file)
211
203
 
212
204
  def get_historical_500hpa_wind_u(initialization_time, forecast_hour, output_file=None):
213
- return get_historical_output(initialization_time, forecast_hour, "500/wind_u", output_file)
205
+ return get_gridded_forecast(variable="500/wind_u", initialization_time=initialization_time, forecast_hour=forecast_hour, output_file=output_file)
214
206
 
215
207
  def get_historical_500hpa_wind_v(initialization_time, forecast_hour, output_file=None):
216
- return get_historical_output(initialization_time, forecast_hour, "500/wind_v", output_file)
208
+ return get_gridded_forecast(variable="500/wind_v", initialization_time=initialization_time, forecast_hour=forecast_hour, output_file=output_file)
217
209
 
218
210
 
219
211
  def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None, print_response=False):
@@ -287,14 +279,18 @@ def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None
287
279
  return response
288
280
 
289
281
 
290
- def get_initialization_times(print_response=False):
282
+ def get_initialization_times(print_response=False, ensemble_member=None, intracycle=False):
291
283
  """
292
284
  Get available WeatherMesh initialization times (also known as cycle times).
293
285
 
294
- Returns dict with keys "latest" and "available" (a list)
286
+ Returns dict with keys "latest" and "available"
295
287
  """
296
288
 
297
- response = make_api_request(f"{FORECASTS_API_BASE_URL}/initialization_times.json")
289
+ params = {
290
+ 'ens_member': ensemble_member,
291
+ 'intracycle': intracycle
292
+ }
293
+ response = make_api_request(f"{FORECASTS_API_BASE_URL}/initialization_times.json", params=params)
298
294
 
299
295
  if print_response:
300
296
  print("Latest initialization time:", response['latest'])
@@ -305,6 +301,29 @@ def get_initialization_times(print_response=False):
305
301
  return response
306
302
 
307
303
 
304
+ def get_forecast_hours(print_response=False, ensemble_member=None, intracycle=False):
305
+ """
306
+ Get available forecast hours for WeatherMesh
307
+ This may include initialization times that are not included in the initialization times API that represent outputs
308
+ that are still being generated.
309
+
310
+ Returns dict with keys of initialization times and values of available forecast hours
311
+ """
312
+
313
+ params = {
314
+ 'ens_member': ensemble_member,
315
+ 'intracycle': intracycle
316
+ }
317
+ response = make_api_request(f"{FORECASTS_API_BASE_URL}/forecast_hours.json", params=params)
318
+
319
+ if print_response:
320
+ print("Available forecast hours:")
321
+ for time, hours in response.items():
322
+ print(f" - {time}: {', '.join([str(hour) for hour in hours])}")
323
+
324
+ return response
325
+
326
+
308
327
  # Tropical cyclones
309
328
  def print_tc_supported_formats():
310
329
  """Print supported file formats for saving tcs data."""
@@ -313,7 +332,7 @@ def print_tc_supported_formats():
313
332
  print(f" - {fmt}")
314
333
 
315
334
 
316
- def download_and_save_output(output_file, response):
335
+ def download_and_save_output(output_file, response, silent=False):
317
336
  """
318
337
  Downloads a forecast output from a presigned S3 url contained in a response and saves it as a .nc file.
319
338
 
@@ -333,12 +352,17 @@ def download_and_save_output(output_file, response):
333
352
  # Save the content directly to file
334
353
  with open(output_file, 'wb') as f:
335
354
  f.write(response.content)
336
- print(f"Data Successfully saved to {output_file}")
355
+
356
+ if not silent:
357
+ print(f"Data Successfully saved to {output_file}")
358
+
337
359
  return True
338
360
 
339
361
  except requests.exceptions.RequestException as e:
340
- print(f"Error downloading the file: {e}")
362
+ if not silent:
363
+ print(f"Error downloading the file: {e}")
341
364
  return False
342
365
  except Exception as e:
343
- print(f"Error processing the file: {e}")
344
- return False
366
+ if not silent:
367
+ print(f"Error processing the file: {e}")
368
+ return False
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: windborne
3
- Version: 1.1.5
3
+ Version: 1.2.1
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
@@ -0,0 +1,13 @@
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- windborne/__init__.py,sha256=jPqxHTqOEcAnPU-7VCJionGToE2tqY7Q3SuSdQrdl9Q,1944
2
- windborne/api_request.py,sha256=zh1TaaZAaRfAXp2NYMja75fKeduWLfao02JRRFVpQCA,11108
3
- windborne/cli.py,sha256=fJ4cLcBpc7YasfMu3pNrqCX9idGCWA-2j2necNpDfck,36696
4
- windborne/data_api.py,sha256=sYZkcQog8RuLErfdLLa4gC8fRkAoPGNKKQJ8i6o-LTQ,33826
5
- windborne/forecasts_api.py,sha256=Jea1hGd2CQHbyRXnJXLamF1GdgBsoqX7oGyg8S6YoRo,14102
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.1.5.dist-info/METADATA,sha256=M_INOFolggNfzz5IyYivgBT-lzgmp4ax8G-cSNhjZIg,1235
10
- windborne-1.1.5.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
11
- windborne-1.1.5.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
12
- windborne-1.1.5.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
13
- windborne-1.1.5.dist-info/RECORD,,