wxdata 1.6__tar.gz → 1.7__tar.gz

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.
Files changed (95) hide show
  1. {wxdata-1.6/src/wxdata.egg-info → wxdata-1.7}/PKG-INFO +6 -1
  2. {wxdata-1.6 → wxdata-1.7}/README.md +5 -0
  3. {wxdata-1.6 → wxdata-1.7}/pyproject.toml +1 -1
  4. {wxdata-1.6 → wxdata-1.7}/src/wxdata/__init__.py +1 -0
  5. wxdata-1.7/src/wxdata/client/byte_range.py +204 -0
  6. {wxdata-1.6 → wxdata-1.7}/src/wxdata/client/client.py +167 -1
  7. wxdata-1.7/src/wxdata/client/level_coords.py +141 -0
  8. {wxdata-1.6 → wxdata-1.7}/src/wxdata/noaa/nws.py +2 -2
  9. {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/process.py +1 -1
  10. {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/rtma.py +1 -1
  11. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/progress_bar.py +2 -5
  12. {wxdata-1.6 → wxdata-1.7/src/wxdata.egg-info}/PKG-INFO +6 -1
  13. {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/SOURCES.txt +2 -0
  14. {wxdata-1.6 → wxdata-1.7}/LICENSE +0 -0
  15. {wxdata-1.6 → wxdata-1.7}/setup.cfg +0 -0
  16. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/__init__.py +0 -0
  17. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/aigefs.py +0 -0
  18. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/paths.py +0 -0
  19. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigefs/url_scanners.py +0 -0
  20. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/__init__.py +0 -0
  21. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/aigfs.py +0 -0
  22. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/paths.py +0 -0
  23. {wxdata-1.6 → wxdata-1.7}/src/wxdata/aigfs/url_scanners.py +0 -0
  24. {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/__init__.py +0 -0
  25. {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/derived_fields.py +0 -0
  26. {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/kinematics.py +0 -0
  27. {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/thermodynamics.py +0 -0
  28. {wxdata-1.6 → wxdata-1.7}/src/wxdata/calc/unit_conversion.py +0 -0
  29. {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/__init__.py +0 -0
  30. {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/cfs.py +0 -0
  31. {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/file_scanner.py +0 -0
  32. {wxdata-1.6 → wxdata-1.7}/src/wxdata/cfs/url_scanners.py +0 -0
  33. {wxdata-1.6 → wxdata-1.7}/src/wxdata/client/__init__.py +0 -0
  34. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/__init__.py +0 -0
  35. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/ecmwf.py +0 -0
  36. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/file_funcs.py +0 -0
  37. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/keys.py +0 -0
  38. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/parsers.py +0 -0
  39. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/paths.py +0 -0
  40. {wxdata-1.6 → wxdata-1.7}/src/wxdata/ecmwf/url_scanners.py +0 -0
  41. {wxdata-1.6 → wxdata-1.7}/src/wxdata/fems/__init__.py +0 -0
  42. {wxdata-1.6 → wxdata-1.7}/src/wxdata/fems/meta_data.py +0 -0
  43. {wxdata-1.6 → wxdata-1.7}/src/wxdata/fems/observations.py +0 -0
  44. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/__init__.py +0 -0
  45. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/exception_messages.py +0 -0
  46. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/file_funcs.py +0 -0
  47. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/gefs.py +0 -0
  48. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/paths.py +0 -0
  49. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/process.py +0 -0
  50. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gefs/url_scanners.py +0 -0
  51. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/__init__.py +0 -0
  52. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/exception_messages.py +0 -0
  53. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/gfs.py +0 -0
  54. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/paths.py +0 -0
  55. {wxdata-1.6 → wxdata-1.7}/src/wxdata/gfs/url_scanners.py +0 -0
  56. {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/__init__.py +0 -0
  57. {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/hgefs.py +0 -0
  58. {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/paths.py +0 -0
  59. {wxdata-1.6 → wxdata-1.7}/src/wxdata/hgefs/url_scanner.py +0 -0
  60. {wxdata-1.6 → wxdata-1.7}/src/wxdata/metars/__init__.py +0 -0
  61. {wxdata-1.6 → wxdata-1.7}/src/wxdata/metars/_clean_data.py +0 -0
  62. {wxdata-1.6 → wxdata-1.7}/src/wxdata/metars/metar_obs.py +0 -0
  63. {wxdata-1.6 → wxdata-1.7}/src/wxdata/noaa/__init__.py +0 -0
  64. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/__init__.py +0 -0
  65. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/aigefs_post_processing.py +0 -0
  66. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/aigfs_post_processing.py +0 -0
  67. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/cfs_post_processing.py +0 -0
  68. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/ecmwf_post_processing.py +0 -0
  69. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/gefs_post_processing.py +0 -0
  70. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/gfs_post_processing.py +0 -0
  71. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/hgefs_post_processing.py +0 -0
  72. {wxdata-1.6 → wxdata-1.7}/src/wxdata/post_processors/rtma_post_processing.py +0 -0
  73. {wxdata-1.6 → wxdata-1.7}/src/wxdata/radar/__init__.py +0 -0
  74. {wxdata-1.6 → wxdata-1.7}/src/wxdata/radar/nexrad2.py +0 -0
  75. {wxdata-1.6 → wxdata-1.7}/src/wxdata/radar/url_scanner.py +0 -0
  76. {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/__init__.py +0 -0
  77. {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/file_funcs.py +0 -0
  78. {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/keys.py +0 -0
  79. {wxdata-1.6 → wxdata-1.7}/src/wxdata/rtma/url_scanners.py +0 -0
  80. {wxdata-1.6 → wxdata-1.7}/src/wxdata/soundings/__init__.py +0 -0
  81. {wxdata-1.6 → wxdata-1.7}/src/wxdata/soundings/_exceptions.py +0 -0
  82. {wxdata-1.6 → wxdata-1.7}/src/wxdata/soundings/wyoming_soundings.py +0 -0
  83. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/__init__.py +0 -0
  84. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/coords.py +0 -0
  85. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/exceptions.py +0 -0
  86. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/file_funcs.py +0 -0
  87. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/file_scanner.py +0 -0
  88. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/nomads_gribfilter.py +0 -0
  89. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/recycle_bin.py +0 -0
  90. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/scripts.py +0 -0
  91. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/tools.py +0 -0
  92. {wxdata-1.6 → wxdata-1.7}/src/wxdata/utils/xmacis2_cleanup.py +0 -0
  93. {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/dependency_links.txt +0 -0
  94. {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/requires.txt +0 -0
  95. {wxdata-1.6 → wxdata-1.7}/src/wxdata.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wxdata
3
- Version: 1.6
3
+ Version: 1.7
4
4
  Summary: A Python package of end-to-end weather data clients & raw data clients with VPN/PROXY support, data processors that decode variable keys from GRIB format into a plain-language format & various tools for assisting Python automated workflows, querying meteorological datasets and filling gaps in meteorological data.
5
5
  Author: Eric J. Drewitz
6
6
  Project-URL: Documentation, https://github.com/edrewitz/wxdata/blob/main/Documentation/Landing%20Page.md
@@ -204,6 +204,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
204
204
  *Advanced Users*
205
205
  1) [Using the `client` module to download the latest HadCRUT5 Analysis netCDF file and open this dataset in xarray](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/hadcrut5.ipynb)
206
206
  2) [Downloading the GFS0P25 for temperature fields and using run_external_scripts() to post-process this GFS0P25 dataset in an external Python script](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/external_scripts.ipynb)
207
+ 3) [Downloading GFS Data Using Byte-Range Requests](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/bytes_range_request.ipynb)
207
208
 
208
209
  ---------------------------------------------------
209
210
 
@@ -340,6 +341,9 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
340
341
  ***AWS Open Data***
341
342
  1. [AWS Open Data](https://github.com/edrewitz/WxData/blob/main/Documentation/get_open_aws_data.md#get-aws-open-data)
342
343
 
344
+ ***Byte-Range Requests***
345
+ 1. [Byte-Range Requests](https://github.com/edrewitz/WxData/blob/main/Documentation/bytes_range_request.md#bytes-range-request)
346
+
343
347
  ---------------------------------------------------------------
344
348
 
345
349
  ### Data Querying Tools
@@ -626,6 +630,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
626
630
  # - get_excel_data()
627
631
  # - get_xmacis_data()
628
632
  # - get_aws_open_data()
633
+ # - byte_range_request()
629
634
  import wxdata.client.client as client
630
635
 
631
636
  -------------------------------------------
@@ -172,6 +172,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
172
172
  *Advanced Users*
173
173
  1) [Using the `client` module to download the latest HadCRUT5 Analysis netCDF file and open this dataset in xarray](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/hadcrut5.ipynb)
174
174
  2) [Downloading the GFS0P25 for temperature fields and using run_external_scripts() to post-process this GFS0P25 dataset in an external Python script](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/external_scripts.ipynb)
175
+ 3) [Downloading GFS Data Using Byte-Range Requests](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/bytes_range_request.ipynb)
175
176
 
176
177
  ---------------------------------------------------
177
178
 
@@ -308,6 +309,9 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
308
309
  ***AWS Open Data***
309
310
  1. [AWS Open Data](https://github.com/edrewitz/WxData/blob/main/Documentation/get_open_aws_data.md#get-aws-open-data)
310
311
 
312
+ ***Byte-Range Requests***
313
+ 1. [Byte-Range Requests](https://github.com/edrewitz/WxData/blob/main/Documentation/bytes_range_request.md#bytes-range-request)
314
+
311
315
  ---------------------------------------------------------------
312
316
 
313
317
  ### Data Querying Tools
@@ -594,6 +598,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
594
598
  # - get_excel_data()
595
599
  # - get_xmacis_data()
596
600
  # - get_aws_open_data()
601
+ # - byte_range_request()
597
602
  import wxdata.client.client as client
598
603
 
599
604
  -------------------------------------------
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "wxdata"
3
- version = "1.6"
3
+ version = "1.7"
4
4
  description = "A Python package of end-to-end weather data clients & raw data clients with VPN/PROXY support, data processors that decode variable keys from GRIB format into a plain-language format & various tools for assisting Python automated workflows, querying meteorological datasets and filling gaps in meteorological data."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -249,4 +249,5 @@ These clients can be easily configured to work on VPN/PROXY connections.
249
249
  # - get_excel_data()
250
250
  # - get_xmacis_data()
251
251
  # - get_aws_open_data()
252
+ # - byte_range_request()
252
253
  import wxdata.client.client as client
@@ -0,0 +1,204 @@
1
+ """
2
+ This file hosts the function that fetches the byte-range for a variable.
3
+
4
+ (C) Eric J. Drewitz 2025-2026
5
+ """
6
+
7
+ import requests
8
+
9
+ from tqdm.auto import tqdm
10
+
11
+
12
+ def fetch_range(url,
13
+ start,
14
+ end,
15
+ proxies):
16
+
17
+ """
18
+ This function downloads the data corresponding to the bytes-range for each variable
19
+
20
+ Required Arguments:
21
+
22
+ 1) url (String) - The URL of the GRIB file that will be downloaded.
23
+
24
+ 2) start (Integer) - The starting byte.
25
+
26
+ 3) end (Integer) - The ending byte.
27
+
28
+ 4) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
29
+
30
+ proxies=None ---> proxies={
31
+ 'http':'http://your-proxy-address:port',
32
+ 'https':'http://your-proxy-address:port'
33
+ }
34
+
35
+ Optional Arguments: None
36
+
37
+ Returns
38
+ -------
39
+
40
+ The data to be written to a GRIB file.
41
+ """
42
+
43
+ headers = {"Range": f"bytes={start}-{end}" if end else f"bytes={start}-"}
44
+
45
+ if proxies == None:
46
+ response = requests.get(url,
47
+ headers=headers,
48
+ stream=True)
49
+ else:
50
+ response = requests.get(url,
51
+ headers=headers,
52
+ proxies=proxies,
53
+ stream=True)
54
+
55
+ response.raise_for_status()
56
+ response.close()
57
+
58
+ if len(response.content) == 0:
59
+ if proxies == None:
60
+ response = requests.get(url,
61
+ headers=headers)
62
+ else:
63
+ response = requests.get(url,
64
+ headers=headers,
65
+ proxies=proxies)
66
+ else:
67
+ pass
68
+
69
+ return response.content
70
+
71
+
72
+ def fetch_data(ranges,
73
+ url,
74
+ start,
75
+ end,
76
+ proxies):
77
+
78
+ """
79
+ This function downloads the data corresponding to the bytes-range for each variable
80
+
81
+ Required Arguments:
82
+
83
+ 1) url (String) - The URL of the GRIB file that will be downloaded.
84
+
85
+ 2) start (Integer) - The starting byte.
86
+
87
+ 3) end (Integer) - The ending byte.
88
+
89
+ 4) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
90
+
91
+ proxies=None ---> proxies={
92
+ 'http':'http://your-proxy-address:port',
93
+ 'https':'http://your-proxy-address:port'
94
+ }
95
+
96
+ Optional Arguments: None
97
+
98
+ Returns
99
+ -------
100
+
101
+ The data to be written to a GRIB file.
102
+ """
103
+
104
+ results = {}
105
+ for (v, l), (start, end) in ranges.items():
106
+ results[(v, l)] = fetch_range(
107
+ url,
108
+ start,
109
+ end,
110
+ proxies
111
+ )
112
+
113
+ return results
114
+
115
+
116
+ def download_grib_data_by_byte_range(ranges,
117
+ chunk_size,
118
+ path,
119
+ filename,
120
+ grib_url,
121
+ start,
122
+ end,
123
+ proxies):
124
+ """
125
+ results: dict[(var, level)] = bytes
126
+ Writes all GRIB messages to a single file with polished progress bars.
127
+
128
+ This function writes the GRIB file with a progress bar.
129
+ This is a progress bar for the byte-range requests.
130
+
131
+ Required Arguments:
132
+
133
+ 1) ranges (Tuple List) - A tuple list of byte-ranges.
134
+
135
+ 2) chunk_size (Integer) - The size of the chunks when writing the GRIB/NETCDF data to a file.
136
+
137
+ 3) path (String) - The directory where the file is saved to.
138
+
139
+ 4) filename (String) - The name the user wishes to save the file as.
140
+
141
+ 5) grib_url (String) - The URL of the GRIB file that will be downloaded.
142
+
143
+ 6) start (Integer) - The starting byte.
144
+
145
+ 7) end (Integer) - The ending byte.
146
+
147
+ 8) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
148
+
149
+ proxies=None ---> proxies={
150
+ 'http':'http://your-proxy-address:port',
151
+ 'https':'http://your-proxy-address:port'
152
+ }
153
+
154
+ Optional Arguments: None
155
+
156
+ Returns
157
+ -------
158
+
159
+ A GRIB file of {filename} saved to {path}
160
+ """
161
+
162
+ results = fetch_data(ranges,
163
+ grib_url,
164
+ start,
165
+ end,
166
+ proxies)
167
+
168
+ ordered = sorted(results.items(), key=lambda x: (x[0][0], x[0][1]))
169
+
170
+ # Master progress bar (stays visible)
171
+ master = tqdm(
172
+ total=len(ordered),
173
+ desc="Writing GRIB messages",
174
+ position=0,
175
+ leave=False,
176
+ bar_format="{desc:<25} |{bar:30}| {n}/{total}"
177
+ )
178
+
179
+ with open(f"{path}/{filename}", "wb") as f:
180
+ for (var, level), data in ordered:
181
+
182
+ total_bytes = len(data)
183
+ label = f"{var} @ {level}"
184
+
185
+ # Inner bar (disappears after finishing)
186
+ with tqdm(
187
+ total=total_bytes,
188
+ unit="B",
189
+ unit_scale=True,
190
+ unit_divisor=chunk_size,
191
+ desc=label,
192
+ position=1,
193
+ leave=False,
194
+ bar_format="{desc:<30} |{bar:30}| {percentage:3.0f}% [{n_fmt}/{total_fmt}]"
195
+ ) as pbar:
196
+
197
+ for i in range(0, total_bytes, chunk_size):
198
+ chunk = data[i:i+chunk_size]
199
+ f.write(chunk)
200
+ pbar.update(len(chunk))
201
+
202
+ master.update(1)
203
+
204
+ master.close()
@@ -8,10 +8,12 @@ These functions are compatible with users on VPN/PROXY connections as well as no
8
8
  3) get_excel_data
9
9
  4) get_xmacis_data
10
10
  5) get_open_aws_data
11
+ 6) byte_range_request
11
12
 
12
13
  (C) Eric J. Drewitz 2025-2026
13
14
  """
14
15
 
16
+ import itertools as _itertools
15
17
  import requests as _requests
16
18
  import time as _time
17
19
  import sys as _sys
@@ -27,8 +29,11 @@ from datetime import(
27
29
  timedelta as _timedelta
28
30
  )
29
31
 
30
- from wxdata.utils.progress_bar import progress_bar as _progress_bar
32
+ from wxdata.utils.nomads_gribfilter import key_list as _key_list
33
+ from wxdata.client.level_coords import get_level_expression as _get_level_expression
31
34
  from wxdata.utils.xmacis2_cleanup import clean_pandas_dataframe as _clean_pandas_dataframe
35
+ from wxdata.utils.progress_bar import progress_bar as _progress_bar
36
+ from wxdata.client.byte_range import download_grib_data_by_byte_range as _download_grib_data_by_byte_range
32
37
  from wxdata.utils.recycle_bin import(
33
38
  clear_recycle_bin_windows as _clear_recycle_bin_windows,
34
39
  clear_trash_bin_mac as _clear_trash_bin_mac,
@@ -802,4 +807,165 @@ def get_aws_open_data(url,
802
807
  finally:
803
808
  if r:
804
809
  r.close() # Ensure the connection is closed.
810
+
811
+
812
+ def byte_range_request(grib_url,
813
+ idx_url,
814
+ variables,
815
+ levels,
816
+ level_type,
817
+ path,
818
+ filename,
819
+ proxies=None,
820
+ chunk_size=1024,
821
+ notifications='on',
822
+ clear_recycle_bin=False):
823
+
824
+ """
825
+ This client downloads GRIB data for a specific variable that is defined by the range in bytes in that GRIB file.
826
+ This is useful when the user wants to download a GRIB file where there is no GRIB filter present, especially when the file size is large.
827
+ This will allow users to download the variable they are interested in and filter out all other variables prior to downloading.
828
+
829
+ Required Arguments:
830
+
831
+ 1) grib_url (String) - The URL of the GRIB file that will be downloaded.
832
+
833
+ 2) idx_url (String) - The URL of the index file that corresponds to the GRIB file (ends in .idx).
834
+
835
+ 3) variables (String List) - The list of variables to be downloaded.
836
+
837
+ 4) levels (Float or Integer List) - The list of pressure or height levels.
838
+
839
+ 5) level_type (String) - The type of level.
840
+
841
+ Level Types
842
+ -----------
843
+
844
+ 'hybrid'
845
+ 'entire atmosphere'
846
+ 'surface':'surface',
847
+ 'boundary layer'
848
+ 'pressure'
849
+ 'mean sea level'
850
+ 'height above ground'
851
+ 'height below ground'
852
+ 'height above sea level'
853
+ 'entire atmosphere single layer'
854
+ 'low cloud layer'
855
+ 'middle cloud layer'
856
+ 'high cloud layer'
857
+ 'cloud ceiling'
858
+ 'tropopause'
859
+ 'max wind'
860
+ 'isothermal'
861
+ 'highest tropospheric freezing level'
862
+ 'sigma layer'
863
+ 'sigma level'
864
+ 'potential vorticity surface'
865
+ 'reserved'
866
+
867
+ 6) path (String) - The directory where the file is saved to.
868
+
869
+ 7) filename (String) - The name the user wishes to save the file as.
870
+
871
+ Optional Arguments:
872
+
873
+ 1) proxies (dict or None) - Default=None. If the user is using proxy server(s), the user must change the following:
874
+
875
+ proxies=None ---> proxies={
876
+ 'http':'http://your-proxy-address:port',
877
+ 'https':'http://your-proxy-address:port'
878
+ }
879
+
880
+ 2) chunk_size (Integer) - Default=8192. The size of the chunks when writing the GRIB/NETCDF data to a file.
881
+
882
+ 3) notifications (String) - Default='on'. Notification when a file is downloaded and saved to {path}
883
+
884
+ 4) clear_recycle_bin (Boolean) - (Default=False in WxData >= 1.2.5) (Default=True in WxData < 1.2.5). When set to True,
885
+ the contents in your recycle/trash bin will be deleted with each run of the program you are calling WxData.
886
+ This setting is to help preserve memory on the machine.
887
+
888
+ Returns
889
+ -------
890
+
891
+ Downloads a partial GRIB file consisting of the variable the user specifies.
892
+ """
893
+ if clear_recycle_bin == True:
894
+ _clear_recycle_bin_windows()
895
+ _clear_trash_bin_mac()
896
+ _clear_trash_bin_linux()
897
+ else:
898
+ pass
899
+
900
+ try:
901
+ _os.makedirs(f"{path}")
902
+ except Exception as e:
903
+ pass
904
+
905
+ variables = _key_list(variables)
906
+
907
+ if proxies == None:
908
+ idx_text = _requests.get(idx_url).text
909
+ else:
910
+ idx_text = _requests.get(idx_url, proxies=proxies).text
911
+
912
+ records = []
913
+ for line in idx_text.strip().splitlines():
914
+ parts = line.split(':')
915
+ msg_no = int(parts[0])
916
+ offset = int(parts[1])
917
+ var = parts[3]
918
+ lev = parts[4]
919
+ records.append({
920
+ "msg": msg_no,
921
+ "offset": offset,
922
+ "var": var,
923
+ "lev": lev
924
+ })
925
+
926
+ req_levels, levels = _get_level_expression(levels,
927
+ level_type)
928
+
929
+ if levels is not None:
930
+ reqs = list(_itertools.product(variables, req_levels))
931
+ else:
932
+ reqs = []
933
+ for v in variables:
934
+ req = (v, req_levels)
935
+ reqs.append(req)
936
+
937
+ ranges = {}
938
+ for v, l in reqs:
939
+ matches = [r for r in records if r["var"] == v and r["lev"] == l]
940
+ if not matches:
941
+ print(f"{v} is not a valid variable OR {l} is not a valid level.")
942
+ print(f"Please visit {idx_url} to look at the variables in the GRIB file meta-data")
943
+ _sys.exit(1)
944
+ else:
945
+ pass
946
+
947
+ rec = matches[0]
948
+ start = rec["offset"]
949
+
950
+ idx = records.index(rec)
951
+ if idx < len(records) - 1:
952
+ end = records[idx + 1]["offset"] - 1
953
+ else:
954
+ end = None
955
+
956
+ ranges[(v, l)] = (start, end)
957
+
958
+ _download_grib_data_by_byte_range(ranges,
959
+ chunk_size,
960
+ path,
961
+ filename,
962
+ grib_url,
963
+ start,
964
+ end,
965
+ proxies)
966
+
967
+ if notifications == 'on':
968
+ print(f"{filename} saved to {path}")
969
+
970
+
805
971
 
@@ -0,0 +1,141 @@
1
+ """
2
+ This file hosts the functions that handle the level coordinates for byte-range requests.
3
+
4
+ (C) Eric J. Drewitz 2025-2026
5
+ """
6
+
7
+ def get_level_suffix(level_type):
8
+
9
+ """
10
+ This function returns the level type suffix.
11
+
12
+ Required Arguments:
13
+
14
+ 1) level_type (String) - The type of level the coordinates are on.
15
+
16
+ Level Types
17
+ -----------
18
+
19
+ 'hybrid'
20
+ 'entire atmosphere'
21
+ 'surface':'surface',
22
+ 'boundary layer'
23
+ 'pressure'
24
+ 'mean sea level'
25
+ 'height above ground'
26
+ 'height below ground'
27
+ 'height above sea level'
28
+ 'entire atmosphere single layer'
29
+ 'low cloud layer'
30
+ 'middle cloud layer'
31
+ 'high cloud layer'
32
+ 'cloud ceiling'
33
+ 'tropopause'
34
+ 'max wind'
35
+ 'isothermal'
36
+ 'highest tropospheric freezing level'
37
+ 'sigma layer'
38
+ 'sigma level'
39
+ 'potential vorticity surface'
40
+ 'reserved'
41
+
42
+ Optional Arguments: None
43
+
44
+ Returns
45
+ -------
46
+
47
+ The level type suffix.
48
+ """
49
+
50
+ level_types = {
51
+
52
+ 'hybrid':'hybrid level',
53
+ 'entire atmosphere':'entire atmosphere',
54
+ 'surface':'surface',
55
+ 'boundary layer':'planetary boundary layer',
56
+ 'pressure':'mb',
57
+ 'mean sea level':'mean sea level',
58
+ 'height above ground':'m above ground',
59
+ 'height below ground':'m below ground',
60
+ 'height above sea level':'m above mean sea level',
61
+ 'entire atmosphere single layer':'entire atmosphere (considered as a single layer)',
62
+ 'low cloud layer':'low cloud layer',
63
+ 'middle cloud layer':'middle cloud layer',
64
+ 'high cloud layer':'high cloud layer',
65
+ 'cloud ceiling':'cloud ceiling',
66
+ 'tropopause':'tropopause',
67
+ 'max wind':'max wind',
68
+ 'isothermal':'0C isotherm',
69
+ 'highest tropospheric freezing level':'highest tropospheric freezing level',
70
+ 'sigma layer':'sigma layer',
71
+ 'sigma level':'sigma level',
72
+ 'potential vorticity surface':'PV=2e-06 (Km^2/kg/s) surface',
73
+ 'reserved':'reserved'
74
+ }
75
+
76
+ return level_types[level_type]
77
+
78
+
79
+ def get_level_expression(levels,
80
+ level_type):
81
+
82
+ """
83
+ This function returns the full expression for the level.
84
+
85
+ Required Arguments:
86
+
87
+ 1) levels (Float or Integer List) - The pressure or height levels corresponding to level type.
88
+ (i.e. [700, 500, 250] if level type is 'pressure' or [2, 10] if level type is 'height above ground')
89
+
90
+ 2) level_type (String) - The type of level the coordinates are on.
91
+
92
+ Level Types
93
+ -----------
94
+
95
+ 'hybrid'
96
+ 'entire atmosphere'
97
+ 'surface':'surface',
98
+ 'boundary layer'
99
+ 'pressure'
100
+ 'mean sea level'
101
+ 'height above ground'
102
+ 'height below ground'
103
+ 'height above sea level'
104
+ 'entire atmosphere single layer'
105
+ 'low cloud layer'
106
+ 'middle cloud layer'
107
+ 'high cloud layer'
108
+ 'cloud ceiling'
109
+ 'tropopause'
110
+ 'max wind'
111
+ 'isothermal'
112
+ 'highest tropospheric freezing level'
113
+ 'sigma layer'
114
+ 'sigma level'
115
+ 'potential vorticity surface'
116
+ 'reserved'
117
+
118
+ Optional Arguments: None
119
+
120
+ Returns
121
+ -------
122
+
123
+ The full expression for the level to make the bytes-range request.
124
+ """
125
+ level_type = level_type.lower()
126
+
127
+ suffix = get_level_suffix(level_type)
128
+ if levels is not None:
129
+ expression = []
130
+ for level in levels:
131
+ ex = f"{level} {suffix}"
132
+ expression.append(ex)
133
+ levels = levels
134
+ else:
135
+ expression = suffix
136
+ levels = None
137
+
138
+ return expression, levels
139
+
140
+
141
+
@@ -199,7 +199,7 @@ def _FIX_1D_GRIB_DATA(ds_short,
199
199
 
200
200
  dims = ("step", "latitude", "longitude")
201
201
  short_coords = {
202
- "step": len(ds_list_short),
202
+ "step": ds_short['step'],
203
203
  "latitude": lat1d,
204
204
  "longitude": lon1d,
205
205
  }
@@ -239,7 +239,7 @@ def _FIX_1D_GRIB_DATA(ds_short,
239
239
 
240
240
  dims = ("step", "latitude", "longitude")
241
241
  extended_coords = {
242
- "step": len(ds_list_extended),
242
+ "step": ds_extended['step'],
243
243
  "latitude": lat1d,
244
244
  "longitude": lon1d,
245
245
  }
@@ -317,4 +317,4 @@ def process_rtma_data(path,
317
317
  else:
318
318
 
319
319
  ds = shift_longitude(ds)
320
- return ds
320
+ return ds
@@ -517,4 +517,4 @@ def rtma_comparison(model='rtma',
517
517
 
518
518
 
519
519
 
520
-
520
+
@@ -4,10 +4,7 @@ This file hosts a function that displays a progress bar for downloading a file.
4
4
  (C) Eric J. Drewitz 2025-2026
5
5
  """
6
6
 
7
-
8
- import os
9
-
10
- from tqdm import tqdm
7
+ from tqdm.auto import tqdm
11
8
 
12
9
  def progress_bar(response,
13
10
  path,
@@ -41,4 +38,4 @@ def progress_bar(response,
41
38
  with open(f"{path}/{filename}", "wb") as file:
42
39
  for data in response.iter_content(blocksize):
43
40
  file.write(data)
44
- bar.update(len(data))
41
+ bar.update(len(data))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wxdata
3
- Version: 1.6
3
+ Version: 1.7
4
4
  Summary: A Python package of end-to-end weather data clients & raw data clients with VPN/PROXY support, data processors that decode variable keys from GRIB format into a plain-language format & various tools for assisting Python automated workflows, querying meteorological datasets and filling gaps in meteorological data.
5
5
  Author: Eric J. Drewitz
6
6
  Project-URL: Documentation, https://github.com/edrewitz/wxdata/blob/main/Documentation/Landing%20Page.md
@@ -204,6 +204,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
204
204
  *Advanced Users*
205
205
  1) [Using the `client` module to download the latest HadCRUT5 Analysis netCDF file and open this dataset in xarray](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/hadcrut5.ipynb)
206
206
  2) [Downloading the GFS0P25 for temperature fields and using run_external_scripts() to post-process this GFS0P25 dataset in an external Python script](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/external_scripts.ipynb)
207
+ 3) [Downloading GFS Data Using Byte-Range Requests](https://github.com/edrewitz/WxData-JupyterLab-Examples/blob/main/bytes_range_request.ipynb)
207
208
 
208
209
  ---------------------------------------------------
209
210
 
@@ -340,6 +341,9 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
340
341
  ***AWS Open Data***
341
342
  1. [AWS Open Data](https://github.com/edrewitz/WxData/blob/main/Documentation/get_open_aws_data.md#get-aws-open-data)
342
343
 
344
+ ***Byte-Range Requests***
345
+ 1. [Byte-Range Requests](https://github.com/edrewitz/WxData/blob/main/Documentation/bytes_range_request.md#bytes-range-request)
346
+
343
347
  ---------------------------------------------------------------
344
348
 
345
349
  ### Data Querying Tools
@@ -626,6 +630,7 @@ ii) Set up a new environment with an earlier version of Python (must be Python >
626
630
  # - get_excel_data()
627
631
  # - get_xmacis_data()
628
632
  # - get_aws_open_data()
633
+ # - byte_range_request()
629
634
  import wxdata.client.client as client
630
635
 
631
636
  -------------------------------------------
@@ -25,7 +25,9 @@ src/wxdata/cfs/cfs.py
25
25
  src/wxdata/cfs/file_scanner.py
26
26
  src/wxdata/cfs/url_scanners.py
27
27
  src/wxdata/client/__init__.py
28
+ src/wxdata/client/byte_range.py
28
29
  src/wxdata/client/client.py
30
+ src/wxdata/client/level_coords.py
29
31
  src/wxdata/ecmwf/__init__.py
30
32
  src/wxdata/ecmwf/ecmwf.py
31
33
  src/wxdata/ecmwf/file_funcs.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes