windborne 1.1.0__py3-none-any.whl → 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.
windborne/api_request.py CHANGED
@@ -199,8 +199,7 @@ 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
- return None
203
-
202
+ return None
204
203
  elif http_err.response.status_code == 502:
205
204
  print(f"Temporary connection failure; sleeping for {2**retry_counter}s before retrying")
206
205
  print(f"Underlying error: 502 Bad Gateway")
windborne/cli.py CHANGED
@@ -139,7 +139,7 @@ def main():
139
139
  points_parser.add_argument('-mh','--min-hour', type=int, help='Minimum forecast hour')
140
140
  points_parser.add_argument('-xh','--max-hour', type=int, help='Maximum forecast hour')
141
141
  points_parser.add_argument('-i', '--init-time', help='Initialization time')
142
- points_parser.add_argument('output_file', help='Output file')
142
+ points_parser.add_argument('output_file', nargs='?', help='Output file')
143
143
 
144
144
  # GRIDDED FORECASTS
145
145
  ####################################################################################################################
@@ -210,9 +210,9 @@ def main():
210
210
  ####################################################################################################################
211
211
 
212
212
  # Tropical Cyclones Command
213
- cyclones_parser = subparsers.add_parser('cyclones', help='Get tropical cyclone forecasts')
214
- cyclones_parser.add_argument('-b', '--basin', help='Optional: filter tropical cyclones on basin[ NA, EP, WP, NI, SI, AU, SP]')
215
- cyclones_parser.add_argument('args', nargs='*',
213
+ tropical_cyclones_parser = subparsers.add_parser('tropical_cyclones', help='Get tropical cyclone forecasts')
214
+ tropical_cyclones_parser.add_argument('-b', '--basin', help='Optional: filter tropical cyclones on basin[ NA, EP, WP, NI, SI, AU, SP]')
215
+ tropical_cyclones_parser.add_argument('args', nargs='*',
216
216
  help='[optional: initialization time (YYYYMMDDHH, YYYY-MM-DDTHH, or YYYY-MM-DDTHH:mm:ss)] output_file')
217
217
 
218
218
  # Initialization Times Command
@@ -365,12 +365,13 @@ def main():
365
365
  )
366
366
 
367
367
  elif args.command == 'flying-missions':
368
- get_flying_missions(from_cli=True, output_file=args.output)
368
+ get_flying_missions(output_file=args.output, print_results=(not args.output))
369
369
 
370
370
  elif args.command == 'launch-site':
371
371
  get_mission_launch_site(
372
372
  mission_id=args.mission_id,
373
- output_file=args.output
373
+ output_file=args.output,
374
+ print_result=(not args.output)
374
375
  )
375
376
 
376
377
  elif args.command == 'predict-path':
@@ -395,15 +396,12 @@ def main():
395
396
  min_forecast_hour=min_forecast_hour,
396
397
  max_forecast_hour=max_forecast_hour,
397
398
  initialization_time=initialization_time,
398
- output_file=args.output_file
399
+ output_file=args.output_file,
400
+ print_response=(not args.output_file)
399
401
  )
400
402
 
401
403
  elif args.command == 'init_times':
402
- if get_initialization_times():
403
- print("Available initialization times for point forecasts:\n")
404
- pprint(get_initialization_times())
405
- else:
406
- print("We can't currently display available initialization times for point forecasts:\n")
404
+ get_initialization_times(print_response=True)
407
405
 
408
406
  elif args.command == 'grid_temp_2m':
409
407
  # Parse grid_temp_2m arguments
@@ -588,39 +586,28 @@ def main():
588
586
  print("Too many arguments")
589
587
  print("\nUsage: windborne hist_500hpa_wind_v initialization_time forecast_hour output_file")
590
588
 
591
- elif args.command == 'cyclones':
589
+ elif args.command == 'tropical_cyclones':
592
590
  # Parse cyclones arguments
593
- basin_name = 'ALL basins'
591
+ basin_name = 'all basins'
594
592
  if args.basin:
595
593
  basin_name = f"{args.basin} basin"
596
- print(f"Checking for tropical cyclones only within {args.basin} basin\n")
597
594
 
598
595
  if len(args.args) == 0:
599
- print("Loading tropical cyclones for our latest available initialization time\n")
600
- if get_tropical_cyclones(basin=args.basin):
601
- print(f"Found {len(get_tropical_cyclones())} cyclone(s)\n")
602
- pprint(get_tropical_cyclones(basin=args.basin))
603
- return
604
- else:
605
- print("There are no active tropical cyclones for our latest available initialization time.")
596
+ get_tropical_cyclones(basin=args.basin, print_response=True)
597
+ return
606
598
  elif len(args.args) == 1:
607
599
  if '.' in args.args[0]:
608
600
  # Save tcs with the latest available initialization time in filename
609
601
  get_tropical_cyclones(basin=args.basin, output_file=args.args[0])
610
602
  else:
611
603
  # Display tcs for selected initialization time
612
- if get_tropical_cyclones(initialization_time=args.args[0], basin=args.basin):
613
- print(f"Loading tropical cyclones for initialization time {args.args[0]}\n")
614
- print(f"Found {len(get_tropical_cyclones(initialization_time=args.args[0]))} cyclone(s)\n")
615
- pprint(get_tropical_cyclones(initialization_time=args.args[0], basin=args.basin))
616
- else:
617
- print(f"No active tropical cyclones for {basin_name} and {args.args[0]} initialization time.")
604
+ get_tropical_cyclones(initialization_time=args.args[0], basin=args.basin, print_response=True)
618
605
  elif len(args.args) == 2:
619
606
  print(f"Saving tropical cyclones for initialization time {args.args[0]} and {basin_name}\n")
620
607
  get_tropical_cyclones(initialization_time=args.args[0], basin=args.basin, output_file=args.args[1])
621
608
  else:
622
609
  print("Error: Too many arguments")
623
- print("Usage: windborne cyclones [initialization_time] output_file")
610
+ print("Usage: windborne tropical_cyclones [initialization_time] output_file")
624
611
 
625
612
  else:
626
613
  parser.print_help()
windborne/data_api.py CHANGED
@@ -6,7 +6,7 @@ import json
6
6
 
7
7
  from .api_request import make_api_request
8
8
  from .observation_formatting import format_little_r, convert_to_netcdf
9
- from .utils import to_unix_timestamp, save_arbitrary_response
9
+ from .utils import to_unix_timestamp, save_arbitrary_response, print_table
10
10
 
11
11
  DATA_API_BASE_URL = "https://sensor-data.windbornesystems.com/api/v1"
12
12
 
@@ -257,6 +257,9 @@ def get_observations_core(api_args, csv_headers, get_page, start_time=None, end_
257
257
  For example, for 6-hour buckets centered on 00 UTC, the start time should be 21 UTC of the previous day.
258
258
 
259
259
  Args:
260
+ api_args (dict): Arguments to pass to the API endpoint.
261
+ csv_headers (list): Headers for CSV files.
262
+ get_page (callable): Function to fetch a page of observations.
260
263
  start_time (str): A date string, supporting formats YYYY-MM-DD HH:MM:SS, YYYY-MM-DD_HH:MM and ISO strings,
261
264
  representing the starting time of fetching data.
262
265
  end_time (str): Optional. A date string, supporting formats YYYY-MM-DD HH:MM:SS, YYYY-MM-DD_HH:MM and ISO strings,
@@ -531,7 +534,7 @@ def poll_super_observations(**kwargs):
531
534
  # ------------
532
535
  # METADATA
533
536
  # ------------
534
- def get_flying_missions(from_cli=None, output_file=None):
537
+ def get_flying_missions(output_file=None, print_results=False):
535
538
  """
536
539
  Retrieves a list of currently flying missions.
537
540
  In CLI mode, displays missions in a formatted table.
@@ -539,6 +542,7 @@ def get_flying_missions(from_cli=None, output_file=None):
539
542
  Args:
540
543
  output_file (str): Optional path to save the response data.
541
544
  If provided, saves the data in CSV or JSON format.
545
+ print_results (bool): Whether to print the results in the CLI.
542
546
 
543
547
  Returns:
544
548
  dict: The API response containing list of flying missions.
@@ -549,35 +553,47 @@ def get_flying_missions(from_cli=None, output_file=None):
549
553
  flying_missions = flying_missions_response.get("missions", [])
550
554
 
551
555
  # Display currently flying missions only if we are in cli and we don't save info in file
552
- if flying_missions and from_cli and not output_file:
553
- print("Currently flying missions:\n")
554
-
555
- # Define headers and data
556
- headers = ["Index", "Mission ID", "Mission Name"]
557
- rows = [
558
- [str(i), mission.get("id", "N/A"), mission.get("name", "Unnamed Mission")]
559
- for i, mission in enumerate(flying_missions, start=1)
560
- ]
561
-
562
- # Kinda overkill | but it's a good practice if we ever change missions naming convention
563
- # Calculate column widths
564
- col_widths = [max(len(cell) for cell in col) + 2 for col in zip(headers, *rows)]
565
-
566
- # Display table
567
- print("".join(f"{headers[i]:<{col_widths[i]}}" for i in range(len(headers))))
568
- print("".join("-" * col_width for col_width in col_widths))
569
- for row in rows:
570
- print("".join(f"{row[i]:<{col_widths[i]}}" for i in range(len(row))))
556
+ if print_results:
557
+ if flying_missions:
558
+ print("Currently flying missions:\n")
559
+
560
+ # Define headers and data
561
+ headers = ["Index", "Mission ID", "Mission Name"]
562
+ rows = [
563
+ [str(i), mission.get("id", "N/A"), mission.get("name", "Unnamed Mission")]
564
+ for i, mission in enumerate(flying_missions, start=1)
565
+ ]
566
+
567
+ # Kinda overkill | but it's a good practice if we ever change missions naming convention
568
+ # Calculate column widths
569
+ col_widths = [max(len(cell) for cell in col) + 2 for col in zip(headers, *rows)]
570
+
571
+ # Display table
572
+ print("".join(f"{headers[i]:<{col_widths[i]}}" for i in range(len(headers))))
573
+ print("".join("-" * col_width for col_width in col_widths))
574
+ for row in rows:
575
+ print("".join(f"{row[i]:<{col_widths[i]}}" for i in range(len(row))))
576
+ else:
577
+ print("No missions are currently flying.")
571
578
 
572
579
  if output_file:
573
580
  save_arbitrary_response(output_file, flying_missions_response, csv_data_key='missions')
574
581
 
575
- return flying_missions_response
582
+ return flying_missions
576
583
 
577
584
 
578
- def get_mission_launch_site(mission_id=None, output_file=None):
585
+ def get_mission_launch_site(mission_id=None, output_file=None, print_result=False):
579
586
  """
580
587
  Retrieves launch site information for a specified mission.
588
+
589
+ Args:
590
+ mission_id (str): The ID of the mission to fetch the launch site for.
591
+ output_file (str): Optional path to save the response data.
592
+ If provided, saves the data in CSV format.
593
+ print_result (bool): Whether to print the results in the CLI.
594
+
595
+ Returns:
596
+ dict: The API response containing the launch site information.
581
597
  """
582
598
  if not mission_id:
583
599
  print("Must provide mission ID")
@@ -586,7 +602,7 @@ def get_mission_launch_site(mission_id=None, output_file=None):
586
602
  url = f"{DATA_API_BASE_URL}/missions/{mission_id}/launch_site.json"
587
603
  response = make_api_request(url)
588
604
 
589
- if response and not output_file:
605
+ if response and print_result:
590
606
  launch_site = response.get('launch_site')
591
607
  if isinstance(launch_site, dict):
592
608
  print("Mission launch site\n")
@@ -599,7 +615,7 @@ def get_mission_launch_site(mission_id=None, output_file=None):
599
615
  if output_file:
600
616
  save_arbitrary_response(output_file, response, csv_data_key='launch_site')
601
617
 
602
- return response
618
+ return response.get('launch_site')
603
619
 
604
620
  def get_predicted_path(mission_id=None, output_file=None):
605
621
  """
@@ -612,7 +628,7 @@ def get_predicted_path(mission_id=None, output_file=None):
612
628
  If provided, saves the data in CSV format.
613
629
 
614
630
  Returns:
615
- dict: The API response containing the predicted flight path data.
631
+ list: The API response containing the predicted flight path data.
616
632
  """
617
633
  if not mission_id:
618
634
  print("To get the predicted flight path for a given mission you must provide a mission ID.")
@@ -629,22 +645,7 @@ def get_predicted_path(mission_id=None, output_file=None):
629
645
  if flying_missions:
630
646
  print("\nCurrently flying missions:\n")
631
647
 
632
- # Define headers and data
633
- headers = ["Index", "Mission ID", "Mission Name"]
634
- rows = [
635
- [str(i), mission.get("id", "N/A"), mission.get("name", "Unnamed Mission")]
636
- for i, mission in enumerate(flying_missions, start=1)
637
- ]
638
-
639
- # Kinda overkill | but it's a good practice if we ever change missions naming convention
640
- # Calculate column widths
641
- col_widths = [max(len(cell) for cell in col) + 2 for col in zip(headers, *rows)]
642
-
643
- # Display table
644
- print("".join(f"{headers[i]:<{col_widths[i]}}" for i in range(len(headers))))
645
- print("".join("-" * col_width for col_width in col_widths))
646
- for row in rows:
647
- print("".join(f"{row[i]:<{col_widths[i]}}" for i in range(len(row))))
648
+ print_table(flying_missions, keys=['i', 'id', 'name'], headers=['Index', 'Mission ID', 'Mission Name'])
648
649
  else:
649
650
  print("No missions are currently flying.")
650
651
  return
@@ -655,4 +656,4 @@ def get_predicted_path(mission_id=None, output_file=None):
655
656
  if output_file:
656
657
  save_arbitrary_response(output_file, response, csv_data_key='prediction')
657
658
 
658
- return response
659
+ return response.get('prediction')
@@ -1,8 +1,10 @@
1
1
  import requests
2
+ import json
2
3
 
3
4
  from .utils import (
4
5
  parse_time,
5
- save_arbitrary_response
6
+ save_arbitrary_response,
7
+ print_table
6
8
  )
7
9
 
8
10
  from .api_request import (
@@ -24,7 +26,7 @@ TCS_SUPPORTED_FORMATS = ('.csv', '.json', '.geojson', '.gpx', '.kml', 'little_r'
24
26
 
25
27
 
26
28
  # Point forecasts
27
- def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=None, min_forecast_hour=None, max_forecast_hour=None, initialization_time=None, output_file=None):
29
+ def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=None, min_forecast_hour=None, max_forecast_hour=None, initialization_time=None, output_file=None, print_response=False):
28
30
  """
29
31
  Get point forecasts from the API.
30
32
 
@@ -38,6 +40,7 @@ def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=N
38
40
  initialization_time (str, optional): Initialization time in ISO 8601 format (YYYY-MM-DDTHH:00:00)
39
41
  output_file (str, optional): Path to save the response data
40
42
  Supported formats: .json, .csv
43
+ print_response (bool, optional): Whether to print the response data
41
44
  """
42
45
 
43
46
  # coordinates should be formatted as a semi-colon separated list of latitude,longitude tuples, eg 37,-121;40.3,-100
@@ -91,13 +94,25 @@ def get_point_forecasts(coordinates, min_forecast_time=None, max_forecast_time=N
91
94
  initialization_time = parse_time(initialization_time,init_time_flag=True)
92
95
  params["initialization_time"] = initialization_time
93
96
 
94
- print("Generating point forecast...")
97
+ if print_response:
98
+ print("Generating point forecast...")
95
99
 
96
100
  response = make_api_request(f"{FORECASTS_API_BASE_URL}/points", params=params)
97
101
 
98
102
  if output_file:
99
103
  save_arbitrary_response(output_file, response, csv_data_key='forecasts')
100
104
 
105
+ if print_response:
106
+ unformatted_coordinates = formatted_coordinates.split(';')
107
+
108
+ keys = ['time', 'temperature_2m', 'dewpoint_2m', 'wind_u_10m', 'wind_v_10m', 'precipitation', 'pressure_msl']
109
+ headers = ['Time', '2m Temperature (°C)', '2m Dewpoint (°C)', 'Wind U (m/s)', 'Wind V (m/s)', 'Precipitation (mm)', 'MSL Pressure (hPa)']
110
+
111
+ for i in range(len(response['forecasts'])):
112
+ latitude, longitude = unformatted_coordinates[i].split(',')
113
+ print(f"\nForecast for ({latitude}, {longitude})")
114
+ print_table(response['forecasts'][i], keys=keys, headers=headers)
115
+
101
116
  return response
102
117
 
103
118
 
@@ -126,6 +141,9 @@ def get_gridded_forecast(time, variable, output_file=None):
126
141
 
127
142
  response = make_api_request(f"{FORECASTS_GRIDDED_URL}/{variable}", params=params, as_json=False)
128
143
 
144
+ if response is None:
145
+ return None
146
+
129
147
  if output_file:
130
148
  print(f"Output URL found; downloading to {output_file}...")
131
149
  download_and_save_output(output_file, response)
@@ -181,6 +199,9 @@ def get_historical_output(initialization_time, forecast_hour, variable, output_f
181
199
 
182
200
  response = make_api_request(f"{FORECASTS_HISTORICAL_URL}/{variable}", params=params, as_json=False)
183
201
 
202
+ if response is None:
203
+ return None
204
+
184
205
  if output_file:
185
206
  print(f"Output URL found; downloading to {output_file}...")
186
207
  download_and_save_output(output_file, response)
@@ -201,7 +222,7 @@ def get_historical_500hpa_wind_v(initialization_time, forecast_hour, output_file
201
222
  return get_historical_output(initialization_time, forecast_hour, "500/wind_v", output_file)
202
223
 
203
224
 
204
- def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None):
225
+ def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None, print_response=False):
205
226
  """
206
227
  Get tropical cyclone data from the API.
207
228
 
@@ -209,8 +230,10 @@ def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None
209
230
  initialization_time (str): Date in either ISO 8601 format (YYYY-MM-DDTHH:00:00)
210
231
  or compact format (YYYYMMDDHH)
211
232
  where HH must be 00, 06, 12, or 18
233
+ basin (str, optional): Basin code (e.g., 'NA', 'EP', 'WP', 'NI', 'SI', 'AU', 'SP')
212
234
  output_file (str, optional): Path to save the response data
213
235
  Supported formats: .json, .csv, .gpx, .geojson, .kml, .little_r
236
+ print_response (bool, optional): Whether to print the response data
214
237
 
215
238
  Returns:
216
239
  dict: API response data or None if there's an error
@@ -221,7 +244,6 @@ def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None
221
244
  initialization_time_parsed = parse_time(initialization_time, init_time_flag=True)
222
245
  params["initialization_time"] = initialization_time_parsed
223
246
  else:
224
- # Madee this for our displaying message when no active tcs found
225
247
  initialization_time = 'latest'
226
248
 
227
249
  if basin:
@@ -286,10 +308,19 @@ def get_tropical_cyclones(initialization_time=None, basin=None, output_file=None
286
308
  elif output_file.lower().endswith('.little_r'):
287
309
  save_track_as_little_r(output_file, response)
288
310
 
311
+ if print_response:
312
+ if len(response) == 0:
313
+ print("No tropical cyclones for initialization time:", initialization_time)
314
+ else:
315
+ print("Tropical Cyclones for initialization time:", initialization_time)
316
+ for cyclone_id, tracks in response.items():
317
+ print(f"\nCyclone ID: {cyclone_id}")
318
+ print_table(tracks, keys=['time', 'latitude', 'longitude'], headers=['Time', 'Latitude', 'Longitude'])
319
+
289
320
  return response
290
321
 
291
322
 
292
- def get_initialization_times():
323
+ def get_initialization_times(print_response=False):
293
324
  """
294
325
  Get available WeatherMesh initialization times (also known as cycle times).
295
326
 
@@ -298,6 +329,12 @@ def get_initialization_times():
298
329
 
299
330
  response = make_api_request(f"{FORECASTS_API_BASE_URL}/initialization_times.json")
300
331
 
332
+ if print_response:
333
+ print("Latest initialization time:", response['latest'])
334
+ print("Available initialization times:")
335
+ for time in response['available']:
336
+ print(f" - {time}")
337
+
301
338
  return response
302
339
 
303
340
 
windborne/utils.py CHANGED
@@ -46,7 +46,7 @@ def to_unix_timestamp(date_string):
46
46
 
47
47
  # Supported date format
48
48
  # Compact format YYYYMMDDHH
49
- def parse_time(time, init_time_flag=None):
49
+ def parse_time(time, init_time_flag=None, require_past=False):
50
50
  """
51
51
  Parse and validate initialization time with support for multiple formats.
52
52
  Returns validated initialization time in ISO format, or None if invalid.
@@ -78,10 +78,9 @@ def parse_time(time, init_time_flag=None):
78
78
  print(" - Initialization time hour must be 00, 06, 12, or 18")
79
79
  exit(2)
80
80
 
81
- if parsed_date > datetime.now():
82
- print(f"How would it be to live in {parsed_date} ?\n")
83
- print("Looks like you are coming from the future!\n")
84
- exit(1111)
81
+ if require_past and parsed_date > datetime.now():
82
+ print(f"Invalid date: {time} -- cannot be in the future")
83
+ exit(2)
85
84
 
86
85
  return parsed_date.strftime('%Y-%m-%dT%H:00:00')
87
86
 
@@ -158,3 +157,33 @@ def save_arbitrary_response(output_file, response, csv_data_key=None):
158
157
  else:
159
158
  print("Unsupported file format. Please use either .json or .csv.")
160
159
  exit(4)
160
+
161
+
162
+ def print_table(data, keys=None, headers=None):
163
+ if len(data) == 0:
164
+ print("No data found")
165
+ return
166
+
167
+ if keys is None:
168
+ keys = list(data[0].keys())
169
+
170
+ if headers is None:
171
+ headers = keys
172
+
173
+ # headers = ["Index", "Mission ID", "Mission Name"]
174
+ rows = [
175
+ [
176
+ str(value.get(key)) if key != 'i' else str(i)
177
+ for key in keys
178
+ ]
179
+ for i, value in enumerate(data, start=1)
180
+ ]
181
+
182
+ # Calculate column widths
183
+ col_widths = [max(len(cell) for cell in col) + 2 for col in zip(headers, *rows)]
184
+
185
+ # Display table
186
+ print("".join(f"{headers[i]:<{col_widths[i]}}" for i in range(len(headers))))
187
+ print("".join("-" * col_width for col_width in col_widths))
188
+ for row in rows:
189
+ print("".join(f"{row[i]:<{col_widths[i]}}" for i in range(len(row))))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: windborne
3
- Version: 1.1.0
3
+ Version: 1.1.2
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=0bPtPzBG3djZMVfyUNhapiEqCSyN8SSDsm_eCZ4kwhc,1783
2
+ windborne/api_request.py,sha256=zh1TaaZAaRfAXp2NYMja75fKeduWLfao02JRRFVpQCA,11108
3
+ windborne/cli.py,sha256=XvI9a9mO921a_LpI1ojN9Rn_x3sYq3XTkmUUT37wg4g,34835
4
+ windborne/cyclone_formatting.py,sha256=0S8S_PflRGm6ftUlyR2aGGyX6IRn0hbUTEWptu7f8Q8,7886
5
+ windborne/data_api.py,sha256=qVA0UGcnkBGYe8TFgqgFlFjZYCsS4ly1IL97tSpgKgI,29751
6
+ windborne/forecasts_api.py,sha256=QzdayDr3MaswsFibOdBoo6SCdRY86eE2E7kWqn-Jc1Y,15479
7
+ windborne/observation_formatting.py,sha256=c739aaun6aaYhXl5VI-SRGR-TDS355_0Bfu1t6McoiM,14993
8
+ windborne/utils.py,sha256=H8gvZ4Lrr0UmLl25iMZs6NsZliCY_73Ved_rBIqxJg4,7240
9
+ windborne-1.1.2.dist-info/METADATA,sha256=NR1lkRL5m-j3UoWVj_2ti14bSrIXZLtCbwS4DfUvko0,1235
10
+ windborne-1.1.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
11
+ windborne-1.1.2.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
12
+ windborne-1.1.2.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
13
+ windborne-1.1.2.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- windborne/__init__.py,sha256=0bPtPzBG3djZMVfyUNhapiEqCSyN8SSDsm_eCZ4kwhc,1783
2
- windborne/api_request.py,sha256=J7kcCC3xiISHnrTLxMkFQCEu8rg8UntYvFfP5wkLabQ,11117
3
- windborne/cli.py,sha256=JqIjVXwZr_x9kuHYlXm45XYvl7NJBBckn-R69qne2Q8,35745
4
- windborne/cyclone_formatting.py,sha256=0S8S_PflRGm6ftUlyR2aGGyX6IRn0hbUTEWptu7f8Q8,7886
5
- windborne/data_api.py,sha256=rAcXUunz1lGOtxx59ID5Ki4thdGbtCl-aAw0DeVuouo,29645
6
- windborne/forecasts_api.py,sha256=-IM78Dr10rV-CKVqRWXpptJPe7Hh64DNMO3P3zVgvuQ,13790
7
- windborne/observation_formatting.py,sha256=c739aaun6aaYhXl5VI-SRGR-TDS355_0Bfu1t6McoiM,14993
8
- windborne/utils.py,sha256=AJhvdwtfDrV0NQyN_I5JxXr6K3UgFlPCwUGqApe-c2E,6431
9
- windborne-1.1.0.dist-info/METADATA,sha256=2LBcuwXPOXfguHSqGFqQtg2GgGi6adNtjyENNUF3swQ,1235
10
- windborne-1.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
11
- windborne-1.1.0.dist-info/entry_points.txt,sha256=j_YrqdCDrCd7p5MIwQ2BYwNXEi95VNANzLRJmcXEg1U,49
12
- windborne-1.1.0.dist-info/top_level.txt,sha256=PE9Lauriu5S5REf7JKhXprufZ_V5RiZ_TnfnrLGJrmE,10
13
- windborne-1.1.0.dist-info/RECORD,,