grib2sail 0.2.0__py3-none-any.whl → 0.3.0__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.
grib2sail/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.0'
32
- __version_tuple__ = version_tuple = (0, 2, 0)
31
+ __version__ = version = '0.3.0'
32
+ __version_tuple__ = version_tuple = (0, 3, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
grib2sail/cli.py CHANGED
@@ -10,9 +10,10 @@ app = typer.Typer(help='Download GRIB2 meteorological data')
10
10
  # main cli entry point
11
11
  @app.command()
12
12
  def main(
13
- model: str = typer.Option(v.MODELS[0], help='|'.join(v.MODELS)),
14
- step: str = typer.Option(v.STEPS[1], help='|'.join(v.STEPS)),
15
- data: str = typer.Option(v.DATAS[0], help=','.join(v.DATAS)),
13
+ model: str = typer.Option(v.MODELS[0], help='Choose one among: ' + ', '.join(v.MODELS)),
14
+ step: str = typer.Option(v.STEPS[1], help='Choose one among: ' + ', '.join(v.STEPS)),
15
+ days: str = typer.Option(2, help='Forecast duration in days'),
16
+ data: str = typer.Option(v.DATAS[0], help='Choose multiple among: ' + ', '.join(v.DATAS)),
16
17
  lat: str = typer.Option(..., help='latitudes max and min ex: -7,-2'),
17
18
  lon: str = typer.Option(..., help='longitude max and min ex: -62,-60'),
18
19
  debug: bool = typer.Option(False, help='Enable debug prints'),
@@ -24,11 +25,10 @@ def main(
24
25
  logger.debug(f"latitude is now: {lat}")
25
26
  lon = parse_coord(lon)
26
27
  logger.debug(f"longitude is now: {lon}")
27
- validate_input(model, step, data, lat, lon)
28
-
29
- logger.debug(f"model: {model}, step: {step}, data: {data}")
28
+ validate_input(model, step, days, data, lat, lon)
29
+ logger.debug(f"model: {model}, step: {step}, days: {days}, data: {data}")
30
30
  logger.info(f"Downloading from {model}: {data}")
31
- download_gribs(model, step, data, lat, lon)
31
+ download_gribs(model, step, days, data, lat, lon)
32
32
  logger.info('Done')
33
33
 
34
34
  ## HELPER FUNCTIONS
@@ -49,12 +49,16 @@ def convert_to_nb(nb_str):
49
49
  msg = f"failed to convert to int or float: {nb_str}"
50
50
  raise typer.BadParameter(msg)
51
51
 
52
- def validate_input(m, s, d, lat, lon):
53
- if m not in v.MODELS:
52
+ def validate_input(model, step, days, data, lat, lon):
53
+ if model not in v.MODELS:
54
54
  logger.error_exit('model must be one of: ' + '|'.join(v.MODELS))
55
- if s not in v.STEPS:
55
+ if step not in v.STEPS:
56
56
  logger.error_exit('step must be one of: ' + '|'.join(v.STEPS))
57
- for elmnt in d:
57
+ try:
58
+ int(days)
59
+ except ValueError:
60
+ logger.error_exit('days must be an integer, ex --days 4')
61
+ for elmnt in data:
58
62
  if elmnt not in v.DATAS:
59
63
  msg = 'data must be a combinaison of: '
60
64
  logger.error_exit(msg + ','.join(v.DATAS))
grib2sail/downloader.py CHANGED
@@ -1,10 +1,12 @@
1
1
  from concurrent.futures import ThreadPoolExecutor, as_completed
2
2
  import threading
3
3
  import requests
4
+ from pathlib import Path
4
5
  from rich.progress import Progress
5
6
 
6
7
  from grib2sail.logger import logger
7
8
  from grib2sail.downloader_arom import handle_fetch_error_arom, download_arom
9
+ from grib2sail.downloader_gfs import download_gfs
8
10
  import grib2sail.variables as v
9
11
 
10
12
  thread_local = threading.local()
@@ -14,13 +16,16 @@ def get_session():
14
16
  thread_local.session = requests.Session()
15
17
  return thread_local.session
16
18
 
17
- def download_gribs(m, s, d, lat, lon):
18
- if m.startswith('arome'):
19
- download_arom(m, s, d, lat, lon)
19
+ def download_gribs(model, step, days, data, lat, lon):
20
+ if model.startswith('arome'):
21
+ download_arom(model, step, days, data, lat, lon)
22
+ elif model == 'gfs':
23
+ download_gfs(model, step, days, data, lat, lon)
20
24
  else:
21
- logger.error_exit(f"Downloader failed: unexpected model: {m}")
25
+ logger.error_exit(f"Downloader failed: unexpected model: {model}")
22
26
 
23
- def get_layers(model, urls, header):
27
+ # Optimized resource fetcher with threading and common session
28
+ def get_layers(model, urls, header={}):
24
29
  # Downloading every layers
25
30
  layers = [None] * len(urls)
26
31
  with Progress() as progress:
@@ -40,6 +45,7 @@ def get_layers(model, urls, header):
40
45
  progress.advance(task)
41
46
  return layers
42
47
 
48
+ # Fetch an url and handle errors differently depending on the model
43
49
  def fetch(idx, url, headers, model):
44
50
  try:
45
51
  session = get_session()
@@ -52,3 +58,13 @@ def fetch(idx, url, headers, model):
52
58
  else:
53
59
  logger.error_exit(f"Download failed: {e}")
54
60
  return idx, None
61
+
62
+ # Output the file once all the layers have been downloaded
63
+ def write_file(model, run, step, layers):
64
+ file = Path(f"{model}_{run}_{step}.grib2")
65
+ file.unlink(missing_ok=True)
66
+ with open(file, "wb") as outfile:
67
+ for layer in layers:
68
+ if layer:
69
+ outfile.write(layer)
70
+
@@ -1,4 +1,3 @@
1
- from pathlib import Path
2
1
  import re
3
2
  import requests
4
3
  import time as t
@@ -9,20 +8,16 @@ import grib2sail.downloader as d
9
8
  from grib2sail.logger import logger
10
9
  from grib2sail.token import get_arome_token
11
10
 
12
- def download_arom(model, step, data, lat, lon):
11
+ def download_arom(model, step, days, data, lat, lon):
13
12
  token = get_arome_token()
14
-
15
13
  # Coverages list all the individual layers categories to download
16
14
  coverages = []
17
- if v.DATAS[0] in data:
18
- coverages += [va.AROM_DATAS['wind_u'], va.AROM_DATAS['wind_v']]
19
- if v.DATAS[1] in data:
20
- coverages += [va.AROM_DATAS['wind_gust']]
21
- if v.DATAS[2] in data:
22
- coverages += [va.AROM_DATAS['pressure']]
23
- if v.DATAS[3] in data:
24
- coverages += [va.AROM_DATAS['cloud']]
25
-
15
+ for param in data:
16
+ if param == v.DATAS[0]:
17
+ coverages += [va.AROM_DATAS['wind_u'], va.AROM_DATAS['wind_v']]
18
+ else:
19
+ coverages += [va.AROM_DATAS[param]]
20
+
26
21
  # Get latest available forecast date from arome /GetCapabilities api endpoint
27
22
  logger.info('Finding latest available forecast')
28
23
  session = d.get_session()
@@ -55,9 +50,10 @@ def download_arom(model, step, data, lat, lon):
55
50
 
56
51
  # Select forecast prevision time based on user input
57
52
  # 3600 means layer is the prevision for 1h after latestRun
53
+ nbDay = 1 if days == "1" else 2
58
54
  times = list(range(
59
55
  int(step[:-1]) * 3600,
60
- 172800+1,
56
+ nbDay * 24 * 60 * 60 + 1,
61
57
  int(step[:-1]) * 3600)
62
58
  )
63
59
  logger.debug(f"Forecast to download are {times}")
@@ -95,14 +91,9 @@ def download_arom(model, step, data, lat, lon):
95
91
  if i+100 < len(urls):
96
92
  logger.info('Sleeping 1 minute...')
97
93
  t.sleep(60)
98
-
99
- # Output the file once all the layers have been downloaded
100
- file = Path(f"{model}_{latestRun}_{step}.grib2")
101
- file.unlink(missing_ok=True)
102
- with open(file, "wb") as outfile:
103
- for layer in layers:
104
- if layer:
105
- outfile.write(layer)
94
+
95
+ # Write the grib file as the concatenation of the layers
96
+ d.write_file(model, latestRun, step, layers)
106
97
 
107
98
  def handle_fetch_error_arom(e):
108
99
  if isinstance(e, requests.exceptions.HTTPError):
@@ -0,0 +1,68 @@
1
+ from datetime import datetime, timedelta
2
+
3
+ import grib2sail.variables as v
4
+ import grib2sail.variables_gfs as vg
5
+ import grib2sail.downloader as d
6
+ from grib2sail.logger import logger
7
+
8
+ def download_gfs(model, step, days, data, lat, lon):
9
+ session = d.get_session()
10
+ # Get latest available forecast date and run by trying from most recent
11
+ logger.info('Finding latest available forecast')
12
+ date, run = find_latest_forecast(session)
13
+ logger.debug(f"Latest forecast is {date}, {run}z")
14
+
15
+ # Coverages list all the individual layers categories to download
16
+ coverages = []
17
+ for param in data:
18
+ if param == v.DATAS[0]:
19
+ coverages += [vg.GFS_DATAS['wind_u'], vg.GFS_DATAS['wind_v']]
20
+ else:
21
+ coverages += [vg.GFS_DATAS[param]]
22
+
23
+ urls = []
24
+ if int(days) > 16:
25
+ logger.warning(f"Requesting {days} days, max is 16")
26
+ days = "16"
27
+ # Forecast is available on an hourly basis until day 5 then on a 3h basis
28
+ if step == '1h' and days > 5 :
29
+ logger.warning('Only the first 5 days can have a step of 1h, the rest will have a 3h step')
30
+ hours = list(range(0, 120, 1)) + list(range(120, 385, 3))
31
+ else:
32
+ hours = list(range(0, 24 * int(days) + 1, int(step[:-1])))
33
+ for hour in hours:
34
+ url = vg.API_URL
35
+ url += f"?dir=%2Fgfs.{date}%2F{run}%2Fatmos&file=gfs.t{run}z.pgrb2.0p25.f{hour:03d}"
36
+ url += ''.join(coverages) + f"&subregion="
37
+ url += f"&leftlon={lon[0]}&rightlon={lon[1]}" + f"&bottomlat={lat[0]}&toplat={lat[1]}"
38
+ urls.append(url)
39
+ logger.debug(f"First url to download is {urls[0]}")
40
+ layers = d.get_layers(model, urls)
41
+
42
+ # Write the grib file as the concatenation of the layers
43
+ d.write_file(model, f"{date}-{run}z", step, layers)
44
+
45
+ def find_latest_forecast(session):
46
+ today = datetime.today()
47
+ dates = [
48
+ (today + timedelta(days=1)).strftime("%Y%m%d"),
49
+ today.strftime("%Y%m%d"),
50
+ (today - timedelta(days=1)).strftime("%Y%m%d")
51
+ ]
52
+ runs = ['18', '12', '06', '00']
53
+ for date in dates:
54
+ for run in runs:
55
+ url = (
56
+ f"{vg.PROD_URL}/gfs.{date}/{run}/atmos/"
57
+ f"gfs.t{run}z.pgrb2.0p25.f000"
58
+ )
59
+ try:
60
+ r = session.head(url, timeout=10)
61
+ if r.status_code == 200:
62
+ return date, run
63
+ else:
64
+ logger.debug(f"Unavailable forecast {url}, status is: {r.status_code}")
65
+ except Exception as e:
66
+ logger.error_exit(f"Download failed: {e}")
67
+ logger.error_exit("Couldn't find the latest available forecat")
68
+
grib2sail/variables.py CHANGED
@@ -1,3 +1,3 @@
1
- MODELS = ['arome_antilles', 'arome001']
1
+ MODELS = ['arome_antilles', 'arome', 'arome0025', 'arome_guyane', 'arome_indien', 'arome_ncaledonie', 'arome_polynesie', 'gfs']
2
2
  STEPS = ['1h', '3h', '6h', '12h']
3
3
  DATAS = ['wind', 'wind_gust', 'pressure', 'cloud', 'rain']
@@ -12,6 +12,16 @@ AROM_URLS = {
12
12
  'token': 'https://portail-api.meteofrance.fr/token',
13
13
  f"{v.MODELS[0]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-ANTIL-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
14
14
  f"{v.MODELS[1]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-001-FRANCE-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
15
+ f"{v.MODELS[2]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-0025-FRANCE-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
16
+ f"{v.MODELS[3]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-GUYANE-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
17
+ f"{v.MODELS[4]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-INDIEN-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
18
+ f"{v.MODELS[5]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-NCALED-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
19
+ f"{v.MODELS[6]}_cov": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-POLYN-WCS/GetCoverage?service=WCS&version=2.0.1&format=application/wmo-grib',
15
20
  f"{v.MODELS[0]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-ANTIL-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
16
21
  f"{v.MODELS[1]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-001-FRANCE-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
22
+ f"{v.MODELS[2]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-0025-FRANCE-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
23
+ f"{v.MODELS[3]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-GUYANE-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
24
+ f"{v.MODELS[4]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-INDIEN-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
25
+ f"{v.MODELS[5]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-NCALED-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
26
+ f"{v.MODELS[6]}_capa": 'https://public-api.meteofrance.fr/public/arome/1.0/wcs/MF-NWP-HIGHRES-AROME-OM-0025-POLYN-WCS/GetCapabilities?service=WCS&version=1.3.0&language=eng',
17
27
  }
@@ -0,0 +1,11 @@
1
+ PROD_URL = 'https://nomads.ncep.noaa.gov/pub/data/nccf/com/gfs/prod'
2
+ API_URL = 'https://nomads.ncep.noaa.gov/cgi-bin/filter_gfs_0p25.pl'
3
+
4
+ GFS_DATAS = {
5
+ 'wind_u': '&var_UGRD=on&lev_10_m_above_ground=on',
6
+ 'wind_v': '&var_VGRD=on',
7
+ 'wind_gust': '&var_GUST=on&lev_surface=on',
8
+ 'pressure': '&var_PRMSL=on&lev_mean_sea_level=on',
9
+ 'cloud': '&var_TCDC=on&lev_entire_atmosphere=on'
10
+ }
11
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grib2sail
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Grib files downloader for sailing purposes
5
5
  Author-email: Chinkara <poubelledechinkara@outlook.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -16,15 +16,26 @@ Dynamic: license-file
16
16
 
17
17
  # GRIB2Sail
18
18
 
19
- ![PyPI version](https://img.shields.io/pypi/v/grib2sail.svg)
20
- ![CI](https://img.shields.io/github/actions/workflow/status/Ch1nkara/GRIB2Sail/release.yml)
21
- ![License](https://img.shields.io/badge/license-GPL%20v3-blue.svg)
22
-
23
- Grib files downloader for sailing purposes.
19
+ <p align="center">
20
+ <img src="https://raw.githubusercontent.com/Ch1nkara/GRIB2Sail/main/docs/assets/grib2sail_logo.png" alt="GRIB2Sail" width="40%">
21
+ </p>
22
+ <p align="center">
23
+ <em>Grib files downloader for sailing purposes</em>
24
+ </p>
25
+ <p align="center">
26
+ <img src="https://img.shields.io/pypi/v/grib2sail.svg">
27
+ <img src="https://img.shields.io/github/actions/workflow/status/Ch1nkara/GRIB2Sail/release.yml">
28
+ <img src="https://img.shields.io/badge/license-GPL%20v3-blue.svg">
29
+ </p>
24
30
 
25
31
  Currently the supported models are:
26
- - AROME
32
+ - AROME (001 and 0025)
27
33
  - AROME ANTILLE
34
+ - AROME GUYANE
35
+ - AROME INDIEN
36
+ - AROME NOUVELLE CALEDONIE
37
+ - AROME POLYNESIE
38
+ - GFS (0025)
28
39
 
29
40
  ## Installation
30
41
 
@@ -49,7 +60,7 @@ account on meteofrance.fr. The procedure is as follow:
49
60
 
50
61
  ## Usage
51
62
 
52
- To get the GRIB file contianing the wind, the wind_gust, the atmospheric
63
+ To get the GRIB file containing the wind, the wind_gust, the atmospheric
53
64
  pressure and the cloud coverage for the area between latitude 11.5N - 12.5N
54
65
  and longitude 62.5W - 61.5W with a 3 hour step from the AROME ANTILLE model
55
66
  run:
@@ -68,6 +79,6 @@ It can now be imported in a navigation software such as OpenCPN
68
79
 
69
80
  This is still the early stage of the development the main upcoming features
70
81
  are:
71
- - adding more supported models (arpege, gfs, ecmwf...)
82
+ - adding more supported models (arpege, ecmwf...)
72
83
  - adding more supported variables (rain, sea state)
73
84
  - adding a GUI
@@ -0,0 +1,17 @@
1
+ grib2sail/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ grib2sail/__main__.py,sha256=U5PTpeJRtXGpcO8YPUdCD2hORF8T68IM6JW1-dDyYqE,37
3
+ grib2sail/_version.py,sha256=5zTqm8rgXsWYBpB2M3Zw_K1D-aV8wP7NsBLrmMKkrAQ,704
4
+ grib2sail/cli.py,sha256=CJQyuBoPnKh7wEC_gaPZdlibVK01QO2p3BHYF_Ta6t4,2543
5
+ grib2sail/downloader.py,sha256=Efkgf3Np7bEz6231v-tCjjtnRQC7iULrP8z9tWY2GFA,2175
6
+ grib2sail/downloader_arom.py,sha256=ViGj7-HRW3G_bdBpDvVjEFFOUwJWGwXztVl1AlSLZHk,3789
7
+ grib2sail/downloader_gfs.py,sha256=OVaj3SYw8SozUuBZSxsNVehm8SSHCiND5G0QEfQ6H5c,2439
8
+ grib2sail/logger.py,sha256=_erYrZlKPjhDu-reFkyylodFgSVtOcXJg5F1drYeQNY,456
9
+ grib2sail/token.py,sha256=gNYCBNEGmjMb_RGq79wvtflhKxqiiX4GXpqfMR3eiJ0,1443
10
+ grib2sail/variables.py,sha256=jiirlEbp3T6LicM4KK6Y3iIywdiMMW3qgVPFIHQ1plY,221
11
+ grib2sail/variables_arom.py,sha256=FGYRbDZHXN_ud7dLtT6mempETlkPFaFQDn2zC_3Nfpo,2991
12
+ grib2sail/variables_gfs.py,sha256=D38OwokrJ9rf4r4Wu_sZMhdFOMQqKng4AJ0oEc8hwxE,386
13
+ grib2sail-0.3.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
+ grib2sail-0.3.0.dist-info/METADATA,sha256=Wiobw0ALc2d_TuntXosNzUnhhbxsQlwTUs-gXumEpME,2866
15
+ grib2sail-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
+ grib2sail-0.3.0.dist-info/top_level.txt,sha256=ubF1tLZ8ZWARUC9s6_0fRsuUKbVqOZmrp8UEGEBm8aE,10
17
+ grib2sail-0.3.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- grib2sail/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- grib2sail/__main__.py,sha256=U5PTpeJRtXGpcO8YPUdCD2hORF8T68IM6JW1-dDyYqE,37
3
- grib2sail/_version.py,sha256=Dg8AmJomLVpjKL6prJylOONZAPRtB86LOce7dorQS_A,704
4
- grib2sail/cli.py,sha256=3IcGISWNF9XpDhSdSqb6z5bnc2YFmKlQKtTPH0M7hzI,2247
5
- grib2sail/downloader.py,sha256=ZQDaPabDuKlb7CqGxwabI73T5pKC_jVdqcCyT6A6ch4,1566
6
- grib2sail/downloader_arom.py,sha256=FA9IDvhcBiEY7uJuHTcuLY-Ku4US5uPV5wLks3maNuw,4038
7
- grib2sail/logger.py,sha256=_erYrZlKPjhDu-reFkyylodFgSVtOcXJg5F1drYeQNY,456
8
- grib2sail/token.py,sha256=gNYCBNEGmjMb_RGq79wvtflhKxqiiX4GXpqfMR3eiJ0,1443
9
- grib2sail/variables.py,sha256=M0ZcusbURgPuI1ZbRjnCqRr1Ke-2057cz3EycCHYB2E,133
10
- grib2sail/variables_arom.py,sha256=RNJZT7O5EFa4HtVYrUuH7EHwxZ9u_63F1vkBUekfOfo,1169
11
- grib2sail-0.2.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
- grib2sail-0.2.0.dist-info/METADATA,sha256=7NqzFLPAM-6KFKU6Dk_bVF74t8FgxfFufzjpFaPJeRE,2545
13
- grib2sail-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- grib2sail-0.2.0.dist-info/top_level.txt,sha256=ubF1tLZ8ZWARUC9s6_0fRsuUKbVqOZmrp8UEGEBm8aE,10
15
- grib2sail-0.2.0.dist-info/RECORD,,