eo-fetch-api 1.0.0__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 (70) hide show
  1. eo_fetch_api-1.0.0/LICENSE +21 -0
  2. eo_fetch_api-1.0.0/PKG-INFO +230 -0
  3. eo_fetch_api-1.0.0/README.md +248 -0
  4. eo_fetch_api-1.0.0/packaging/README_PYPI.md +180 -0
  5. eo_fetch_api-1.0.0/pyproject.toml +92 -0
  6. eo_fetch_api-1.0.0/setup.cfg +4 -0
  7. eo_fetch_api-1.0.0/src/eo_fetch/__init__.py +48 -0
  8. eo_fetch_api-1.0.0/src/eo_fetch/__main__.py +3 -0
  9. eo_fetch_api-1.0.0/src/eo_fetch/api.py +288 -0
  10. eo_fetch_api-1.0.0/src/eo_fetch/backends/__init__.py +71 -0
  11. eo_fetch_api-1.0.0/src/eo_fetch/backends/base.py +28 -0
  12. eo_fetch_api-1.0.0/src/eo_fetch/backends/cds.py +174 -0
  13. eo_fetch_api-1.0.0/src/eo_fetch/backends/credentials.py +31 -0
  14. eo_fetch_api-1.0.0/src/eo_fetch/backends/ecmwf.py +135 -0
  15. eo_fetch_api-1.0.0/src/eo_fetch/backends/esgf.py +231 -0
  16. eo_fetch_api-1.0.0/src/eo_fetch/backends/eumetsat.py +172 -0
  17. eo_fetch_api-1.0.0/src/eo_fetch/backends/http_index.py +161 -0
  18. eo_fetch_api-1.0.0/src/eo_fetch/backends/local.py +45 -0
  19. eo_fetch_api-1.0.0/src/eo_fetch/backends/marine.py +158 -0
  20. eo_fetch_api-1.0.0/src/eo_fetch/backends/nasa.py +240 -0
  21. eo_fetch_api-1.0.0/src/eo_fetch/backends/s3.py +136 -0
  22. eo_fetch_api-1.0.0/src/eo_fetch/backends/stac.py +237 -0
  23. eo_fetch_api-1.0.0/src/eo_fetch/bridge.py +53 -0
  24. eo_fetch_api-1.0.0/src/eo_fetch/catalog/__init__.py +5 -0
  25. eo_fetch_api-1.0.0/src/eo_fetch/catalog/copernicus_ecmwf.py +130 -0
  26. eo_fetch_api-1.0.0/src/eo_fetch/catalog/descriptions.py +116 -0
  27. eo_fetch_api-1.0.0/src/eo_fetch/catalog/dwd.py +50 -0
  28. eo_fetch_api-1.0.0/src/eo_fetch/catalog/eumetsat.py +42 -0
  29. eo_fetch_api-1.0.0/src/eo_fetch/catalog/nasa.py +37 -0
  30. eo_fetch_api-1.0.0/src/eo_fetch/catalog/noaa.py +67 -0
  31. eo_fetch_api-1.0.0/src/eo_fetch/catalog/stac.py +88 -0
  32. eo_fetch_api-1.0.0/src/eo_fetch/checkreaders.py +142 -0
  33. eo_fetch_api-1.0.0/src/eo_fetch/cli.py +469 -0
  34. eo_fetch_api-1.0.0/src/eo_fetch/discovery.py +111 -0
  35. eo_fetch_api-1.0.0/src/eo_fetch/downloader.py +112 -0
  36. eo_fetch_api-1.0.0/src/eo_fetch/manifest.py +73 -0
  37. eo_fetch_api-1.0.0/src/eo_fetch/models.py +56 -0
  38. eo_fetch_api-1.0.0/src/eo_fetch/plotting.py +178 -0
  39. eo_fetch_api-1.0.0/src/eo_fetch/readers/__init__.py +188 -0
  40. eo_fetch_api-1.0.0/src/eo_fetch/readers/generic.py +83 -0
  41. eo_fetch_api-1.0.0/src/eo_fetch/readers/radolan.py +112 -0
  42. eo_fetch_api-1.0.0/src/eo_fetch/readers/satellite.py +170 -0
  43. eo_fetch_api-1.0.0/src/eo_fetch/readers/unpack.py +81 -0
  44. eo_fetch_api-1.0.0/src/eo_fetch/registry.py +70 -0
  45. eo_fetch_api-1.0.0/src/eo_fetch/regrid.py +125 -0
  46. eo_fetch_api-1.0.0/src/eo_fetch/styles.py +223 -0
  47. eo_fetch_api-1.0.0/src/eo_fetch/timeguess.py +63 -0
  48. eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/PKG-INFO +230 -0
  49. eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/SOURCES.txt +68 -0
  50. eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/dependency_links.txt +1 -0
  51. eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/entry_points.txt +2 -0
  52. eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/requires.txt +30 -0
  53. eo_fetch_api-1.0.0/src/eo_fetch_api.egg-info/top_level.txt +1 -0
  54. eo_fetch_api-1.0.0/tests/test_api.py +102 -0
  55. eo_fetch_api-1.0.0/tests/test_bridge.py +46 -0
  56. eo_fetch_api-1.0.0/tests/test_core.py +203 -0
  57. eo_fetch_api-1.0.0/tests/test_discovery.py +212 -0
  58. eo_fetch_api-1.0.0/tests/test_ecmwf_cds.py +185 -0
  59. eo_fetch_api-1.0.0/tests/test_esgf.py +205 -0
  60. eo_fetch_api-1.0.0/tests/test_eumetsat.py +146 -0
  61. eo_fetch_api-1.0.0/tests/test_http_index.py +146 -0
  62. eo_fetch_api-1.0.0/tests/test_login.py +53 -0
  63. eo_fetch_api-1.0.0/tests/test_login_cdse.py +52 -0
  64. eo_fetch_api-1.0.0/tests/test_marine.py +130 -0
  65. eo_fetch_api-1.0.0/tests/test_nasa.py +202 -0
  66. eo_fetch_api-1.0.0/tests/test_plotting.py +199 -0
  67. eo_fetch_api-1.0.0/tests/test_readers.py +266 -0
  68. eo_fetch_api-1.0.0/tests/test_s3.py +216 -0
  69. eo_fetch_api-1.0.0/tests/test_stac.py +202 -0
  70. eo_fetch_api-1.0.0/tests/test_unpack.py +116 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Umut Kirdemir
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: eo-fetch-api
3
+ Version: 1.0.0
4
+ Summary: One search-and-download interface, with verified downloads and manifests, for Earth-observation and climate data providers.
5
+ Author: Umut Kirdemir
6
+ License-Expression: MIT
7
+ Keywords: earth observation,climate,satellite,download,meteorology,EUMETSAT,NASA,Copernicus,ERA5,CMIP
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
16
+ Classifier: Topic :: Scientific/Engineering :: GIS
17
+ Requires-Python: <3.14,>=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: requests
21
+ Requires-Dist: truststore
22
+ Requires-Dist: eumdac
23
+ Requires-Dist: earthaccess
24
+ Requires-Dist: pystac-client
25
+ Requires-Dist: planetary-computer
26
+ Requires-Dist: s3fs
27
+ Requires-Dist: cdsapi
28
+ Requires-Dist: ecmwf-opendata
29
+ Requires-Dist: copernicusmarine
30
+ Requires-Dist: intake-esgf
31
+ Requires-Dist: numpy
32
+ Requires-Dist: xarray
33
+ Requires-Dist: netcdf4
34
+ Requires-Dist: h5netcdf
35
+ Requires-Dist: h5py
36
+ Requires-Dist: zarr
37
+ Requires-Dist: rioxarray
38
+ Requires-Dist: pyproj
39
+ Requires-Dist: pyresample
40
+ Requires-Dist: satpy
41
+ Requires-Dist: eccodes
42
+ Requires-Dist: earthkit-data
43
+ Requires-Dist: earthkit-plots
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest; extra == "dev"
46
+ Requires-Dist: ruff; extra == "dev"
47
+ Requires-Dist: build; extra == "dev"
48
+ Requires-Dist: twine; extra == "dev"
49
+ Dynamic: license-file
50
+
51
+ # eo-fetch
52
+
53
+ One search-and-download interface, with verified downloads and reproducible manifests, on top of the libraries that already talk to Earth-observation and climate data providers. Readers and map plots for what you download are included.
54
+
55
+ ```python
56
+ import eo_fetch as eof
57
+
58
+ found = eof.search("dwd", "radolan-yw", limit=2) # same query model for every provider
59
+ eof.download(found, "data/dwd") # verified files + manifest.json
60
+ ds = eof.open(f"data/dwd/{found[0].filename}") # xarray Dataset
61
+ eof.plot(ds, "plots/radar.png") # map in the shared style registry
62
+ ```
63
+
64
+ Every provider below was tested on a real download; each provider card states the date it was last verified (`eof.providers()`).
65
+
66
+ ## What it does
67
+
68
+ - **One query model** for every provider: product, time range, area, keyword, plus provider-specific options.
69
+ - **Verified, resumable downloads**: files are written under a temporary name, checked for size and checksum when the provider announces them, retried with back-off, and never exceed the number of connections a service allows.
70
+ - **A manifest** for every download run: what was fetched, from where, when, sizes, SHA-256 checksums, and the provider's licence and attribution. `eof.verify` checks the files against it later.
71
+ - **A provider registry** with access type (none, account, licence), latency, licence, attribution and "last verified" date.
72
+ - **Readers**: NetCDF, HDF5, Zarr, GRIB, GeoTIFF/JPEG 2000/ESRI ASCII, gzip/bzip2/zip containers, and dedicated readers for MSG SEVIRI (native and level-2 GRIB), MTG FCI level-1c, GOES ABI and DWD radar composites.
73
+ - **Maps**: one style registry on top of earthkit-plots; projected data (radar, geostationary) is regridded to latitude/longitude first. NetCDF/Zarr conversion and an earthkit-data bridge.
74
+ - Backends wrap existing libraries (`eumdac`, `earthaccess`, `pystac-client`, `earthkit-data`, `copernicusmarine`, `intake-esgf`, `cdsapi`, `ecmwf-opendata`). eo-fetch does not replace them.
75
+
76
+ ## Install
77
+
78
+ Python 3.12 or 3.13 (the earthkit packages have no wheels for 3.14 yet).
79
+
80
+ ```
81
+ pip install eo-fetch-api
82
+ ```
83
+
84
+ One install includes every provider, the readers and the plots. The package is imported as `eo_fetch`: `import eo_fetch as eof`.
85
+
86
+ ## Python API
87
+
88
+ ```python
89
+ import eo_fetch as eof
90
+ ```
91
+
92
+ Options that are specific to a provider are keyword arguments.
93
+
94
+ ### Find product names
95
+
96
+ ```python
97
+ for match in eof.products("precipitation"): # aliases of all providers
98
+ print(match.provider, match.product, "-", match.description)
99
+ eof.products(provider="dwd") # one provider (+ top-level folders)
100
+ eof.products("soil moisture", "nasa") # with a provider: live catalogue too
101
+ ```
102
+
103
+ `eof.products(text="", provider=None, *, live=None, browse=None)` returns `eof.ProductMatch` objects (`provider`, `product`, `description`, `source`); pass `match.product` to `eof.search`.
104
+
105
+ ### Search
106
+
107
+ ```python
108
+ found = eof.search(
109
+ "nasa",
110
+ "imerg-late-daily",
111
+ start="2026-10-01",
112
+ end="2026-10-03", # ISO strings, dates or datetimes (end exclusive)
113
+ bbox=(5, 47, 16, 55), # west, south, east, north
114
+ )
115
+ print(len(found), f"{found.megabytes:.1f} MB")
116
+ for item in found:
117
+ print(item.time, item.key, item.size)
118
+ ```
119
+
120
+ `eof.search(provider, product="", *, start, end, bbox, text, limit, params, **options)` returns `eof.Results`, a normal list of `eof.Item` that also remembers how to download itself. Provider options go in as keywords or in a `params` dict; lists become comma-separated values:
121
+
122
+ ```python
123
+ eof.search(
124
+ "cdse",
125
+ "sentinel-2",
126
+ start="2026-10-28",
127
+ end="2026-10-30",
128
+ bbox=(8.5, 53.4, 9.5, 53.8),
129
+ assets=["B04_60m", "SCL_60m"],
130
+ limit=2,
131
+ )
132
+ eof.search(
133
+ "esgf",
134
+ "CMIP6",
135
+ variable_id="tas",
136
+ experiment_id="historical",
137
+ source_id="CanESM5",
138
+ member_id="r1i1p1f1",
139
+ table_id="Amon",
140
+ )
141
+ eof.search(
142
+ "cds",
143
+ "era5-single-levels",
144
+ start="2026-01-01",
145
+ end="2026-02-01",
146
+ variable="2m_temperature",
147
+ bbox=(5, 47, 16, 55),
148
+ )
149
+ ```
150
+
151
+ An unknown option raises `ValueError` with the accepted names. Filter or slice the results freely (`found[:3]`, `found.where(lambda item: item.size < 5e6)`); they keep working with `eof.download`.
152
+
153
+ ### Download and verify
154
+
155
+ ```python
156
+ results = eof.download(found, "data/imerg") # also writes data/imerg/manifest.json
157
+ failed = [r for r in results if r.status == "failed"] # downloaded | skipped | failed
158
+ problems = eof.verify("data/imerg/manifest.json") # [] when every file matches
159
+ ```
160
+
161
+ `eof.download(results, dest, *, workers=None, retries=3, manifest=None, on_result=None)` returns a list of `eof.Result` (`item`, `path`, `status`, `size`, `sha256`, `seconds`, `error`). Failures do not raise. Files that already exist with the right size and checksum are skipped, so the same call resumes an interrupted run. `on_result` is called as each file finishes (for progress output).
162
+
163
+ ### Open, plot, convert
164
+
165
+ ```python
166
+ ds = eof.open(results[0].path) # path of the first downloaded file
167
+ path = eof.plot(ds, "plots/imerg.png", bbox=(5, 47, 16, 55), style="precip-daily")
168
+ eof.convert("data/era5/era5.grib", "data/era5/era5.nc") # or .zarr
169
+ ekd = eof.to_earthkit("data/era5/era5.grib") # earthkit-data object
170
+ ```
171
+
172
+ `eof.open(path, format=None, **options)` returns an `xarray.Dataset`; the format is detected from the name and content (see below). `eof.plot(source, output=None, *, variable, style, domain, resolution, bbox, title)` accepts a file or a Dataset and returns the image path. The style names are the keys of `eo_fetch.styles.STYLES`.
173
+
174
+ ### Providers, credentials, diagnostics
175
+
176
+ ```python
177
+ for info in eof.providers(): # ProviderInfo cards
178
+ print(info.name, info.auth, info.verified)
179
+ eof.provider("cds").notes # how to use the provider, options, limits
180
+ eof.collections("copernicus-marine", "sea surface temperature")
181
+ eof.login("cds") # hidden prompts; saves to your user folder
182
+ eof.doctor("nasa") # prints what is missing; True when all is well
183
+ ```
184
+
185
+ Lower-level pieces (`eo_fetch.backends`, `eo_fetch.readers`, `eo_fetch.regrid`) are importable too and are what the functions above are built on. To add a provider, implement the `Backend` protocol (`search(query)`, `fetch(item, target)`, `max_connections`) and register a `ProviderInfo` card.
186
+
187
+ ## Providers
188
+
189
+ `eof.providers()` always shows the current list and verification dates.
190
+
191
+ | Provider | What | Access |
192
+ | --- | --- | --- |
193
+ | `eumetsat` | EUMETSAT Data Store: Meteosat, MTG, Metop, Sentinel-3/5P/6, CM SAF (all collections) | licence per collection |
194
+ | `noaa` | GOES-R (16, 18, 19) and other public NOAA buckets on AWS | none |
195
+ | `dwd` | DWD open data tree: radar, ICON models, climate data centre | none |
196
+ | `nasa` | every collection of NASA's CMR (IMERG, MERRA-2, OMI, SMAP, ...) | free Earthdata account |
197
+ | `planetary-computer`, `earth-search` | STAC catalogues: Sentinel, Landsat, land cover, climate | none |
198
+ | `cdse` | Copernicus Data Space (Sentinel) via STAC and S3 | free account + S3 keys |
199
+ | `ecmwf-open-data` | IFS and ensemble forecasts by parameter and lead time | none |
200
+ | `cds` | ERA5, ERA5-Land and other Climate Data Store datasets | free account, licence per dataset |
201
+ | `copernicus-marine` | ocean physics, biogeochemistry, sea ice, waves | free account |
202
+ | `esgf` | CMIP climate-model output (CMIP5 and CMIP6 tested; CMIP3/7, CORDEX-CMIP6, obs4MIPs as far as the indices serve them) | none |
203
+
204
+ Each card (`eof.provider(name)`) lists licence, attribution, latency and the options of that provider. Respect the licence and attribution when you publish results; the manifest records both.
205
+
206
+ ## Credentials
207
+
208
+ Providers with accounts have a one-time `eof.login("NAME")` (hidden prompts; the login is saved as a plain-text file in your user folder, and deleting the file logs you out). `eof.doctor("NAME")` tells you what is missing. You can also set the environment variables, which take priority, or write the file yourself. eo-fetch never stores credentials anywhere else, never writes them into manifests or download folders, and sends them only to the provider.
209
+
210
+ | Provider | `eof.login` writes | Or use |
211
+ | --- | --- | --- |
212
+ | `nasa` (Earthdata) | `~/.netrc` | `EARTHDATA_USERNAME` and `EARTHDATA_PASSWORD`, or `EARTHDATA_TOKEN`; approve the provider's application once at urs.earthdata.nasa.gov |
213
+ | `eumetsat` | `~/.eumdac/credentials` | `EUMETSAT_KEY` and `EUMETSAT_SECRET`; a licence per collection |
214
+ | `cds` (ERA5) | `~/.cdsapirc` | `CDSAPI_URL` and `CDSAPI_KEY`; accept each dataset's licence on its page |
215
+ | `copernicus-marine` | `~/.copernicusmarine/.copernicusmarine-credentials` (written by the toolbox) | `COPERNICUSMARINE_SERVICE_USERNAME` and `COPERNICUSMARINE_SERVICE_PASSWORD` |
216
+ | `cdse` | `~/.eo-fetch/cdse` (`key,secret`) | `CDSE_S3_KEY` and `CDSE_S3_SECRET` from the Copernicus Data Space S3 key manager (searching needs no keys) |
217
+ | `dwd`, `noaa`, `earth-search`, `planetary-computer`, `ecmwf-open-data`, `esgf` | none needed | |
218
+
219
+ ## Which files can be opened?
220
+
221
+ `eof.open` detects the format from the file name and content. In the last full test, which tried the smallest recent file of every product alias of every provider, these opened: all GOES-18/19 products, SEVIRI (L1.5, IODC, RSS, cloud mask, cloud top height), FCI level 1c and cloud mask, MTG precipitation and lightning, CM SAF products, DWD radar composites, ICON-EU, DWD satellite radiation and NOAA GFS GRIB. Sentinel-2 JPEG 2000, ERA5 GRIB/NetCDF, ECMWF open data, Copernicus Marine and CMIP6 NetCDF were opened in the provider tests. Unknown formats raise a clear "no reader" error that lists the supported formats.
222
+
223
+ Known limits:
224
+
225
+ - Not readable yet: BUFR (ASCAT, RAP), DWD station zips and radar `.tar` composites, NEXRAD.
226
+ - ICON icosahedral grids open only as a flat list of values (they need DWD's grid files for coordinates).
227
+ - OMI and SMAP HDF5 files open but are not plotted (2-D latitude/longitude arrays).
228
+ - Plots draw the first entry of extra dimensions (time, level, band). Categorical rasters such as Sentinel-2 `SCL` have no dedicated style yet.
229
+ - CMIP6 searches may warn about a publishing error in the new STAC indices of ESGF; the warning is from `intake-esgf`. Switch those indices off with `indices="-discovery.east.esgf.io,-discovery.west.esgf.io"` in `eof.search`. Older projects (CMIP5, CMIP3) are only in the legacy indices: add `indices="all"`; some legacy nodes may be offline and are reported as warnings.
230
+ - On Windows, paths are limited to 260 characters; use a short destination folder. A harmless `Error in sys.excepthook` message can appear at exit after reading GeoTIFF/JPEG 2000 files.
@@ -0,0 +1,248 @@
1
+ # eo-fetch
2
+
3
+ One search-and-download interface, with verified downloads and reproducible manifests, on top of the libraries that already talk to Earth-observation and climate data providers. Readers and map plots for what you download are included.
4
+
5
+ ```python
6
+ import eo_fetch as eof
7
+
8
+ found = eof.search("dwd", "radolan-yw", limit=2) # same query model for every provider
9
+ eof.download(found, "data/dwd") # verified files + manifest.json
10
+ ds = eof.open(f"data/dwd/{found[0].filename}") # xarray Dataset
11
+ eof.plot(ds, "plots/radar.png") # map in the shared style registry
12
+ ```
13
+
14
+ Status: pre-1.0. Every provider below was tested on a real download; each provider card states the date it was last verified (`eo-fetch providers`).
15
+
16
+ ## What it does
17
+
18
+ - **One query model** for every provider: product, time range, area, keyword, plus provider-specific options.
19
+ - **Verified, resumable downloads**: files are written under a temporary name, checked for size and checksum when the provider announces them, retried with back-off, and never exceed the number of connections a service allows.
20
+ - **A manifest** for every download run: what was fetched, from where, when, sizes, SHA-256 checksums, and the provider's licence and attribution. `verify` checks the files against it later.
21
+ - **A provider registry** with access type (none, account, licence), latency, licence, attribution and "last verified" date.
22
+ - **Readers**: NetCDF, HDF5, Zarr, GRIB, GeoTIFF/JPEG 2000/ESRI ASCII, gzip/bzip2/zip containers, and dedicated readers for MSG SEVIRI (native and level-2 GRIB), MTG FCI level-1c, GOES ABI and DWD radar composites.
23
+ - **Maps**: one style registry (`eo-fetch styles`) on top of earthkit-plots; projected data (radar, geostationary) is regridded to latitude/longitude first. NetCDF/Zarr conversion and an earthkit-data bridge.
24
+ - Backends wrap existing libraries (`eumdac`, `earthaccess`, `pystac-client`, `earthkit-data`, `copernicusmarine`, `intake-esgf`, `cdsapi`, `ecmwf-opendata`). eo-fetch does not replace them.
25
+
26
+ ## Install
27
+
28
+ Python 3.12 or 3.13 (the earthkit packages have no wheels for 3.14 yet).
29
+
30
+ ```
31
+ pip install eo-fetch-api
32
+ ```
33
+
34
+ One install includes every provider, the readers and the plots. The package is imported as `eo_fetch` (`import eo_fetch as eof`); the command line tool is called `eo-fetch`. From a clone of the repository (for development): `pip install -e ".[dev]"`. Check the setup:
35
+
36
+ ```
37
+ pytest
38
+ eo-fetch providers
39
+ eo-fetch doctor --provider dwd
40
+ ```
41
+
42
+ ## First five minutes (command line)
43
+
44
+ ```
45
+ eo-fetch providers # which providers exist
46
+ eo-fetch products precipitation # which product names fit
47
+ eo-fetch search --provider dwd --product radolan-yw --limit 3
48
+ eo-fetch download --provider dwd --product radolan-yw --limit 2 --dest data/dwd
49
+ eo-fetch verify data/dwd/manifest.json --root data/dwd
50
+ eo-fetch open data/dwd/raa01-yw_10000-2611011130-dwd---bin.hdf5
51
+ eo-fetch plot data/dwd/raa01-yw_10000-2611011130-dwd---bin.hdf5 --bbox 5 47 16 55.5
52
+ ```
53
+
54
+ All commands: `providers`, `products`, `search`, `download` (`--dry-run` lists only), `verify`, `doctor`, `login`, `collections`, `open`, `plot`, `convert`, `styles`, `check-readers`. Common options of `search` and `download`:
55
+
56
+ | Option | Meaning |
57
+ | --- | --- |
58
+ | `--provider NAME` | a provider from `eo-fetch providers` |
59
+ | `--product` | a product alias of the card, or the provider's own id |
60
+ | `--start`, `--end` | ISO date or date-time, UTC; `--end` is exclusive |
61
+ | `--bbox W S E N` | area in degrees |
62
+ | `--text` | keyword, glob or id, depending on the provider |
63
+ | `--limit` | at most this many items |
64
+ | `--param KEY=VALUE` | provider-specific option (repeatable); the card's notes list them |
65
+
66
+ ## Finding product names
67
+
68
+ `--product` takes either a short alias from the provider card (`radolan-yw`, `imerg-late-daily`, `sentinel-2`, ...) or the provider's own id. You do not have to remember them:
69
+
70
+ ```
71
+ eo-fetch products # every alias of every provider, with a description
72
+ eo-fetch products precipitation # aliases whose name or description contains the word(s)
73
+ eo-fetch products radar dwd # words may include the provider name
74
+ eo-fetch products --provider dwd # one provider (file trees also list their top-level folders)
75
+ eo-fetch products --provider nasa soil # with --provider and words, the live catalogue is searched too
76
+ eo-fetch products --live "sea surface" # live catalogues of all providers (needs internet/credentials)
77
+ ```
78
+
79
+ Output lines are `provider/product description`; the part after the slash is what you pass to `--product`. Live results are marked `[catalogue]`, folders `[folder]`. Live catalogue search works for `eumetsat`, `nasa`, `planetary-computer`, `earth-search`, `cdse` and `copernicus-marine` (`eo-fetch collections` lists them without aliases). For `esgf` the product is the project (`cmip3`, `cmip5`, `cmip6`, `cmip7`, `cordex-cmip6`, `input4mips`, `obs4mips`) and the facets go into `--param` (facet names differ by project; CMIP6 and CMIP5 are tested, CMIP5 needs `--param indices=all`); for `cds` and `ecmwf-open-data` read the provider notes (`eo-fetch doctor --provider NAME` shows the access details). If a product name is mistyped and nothing is found, `search` and `download` suggest the closest aliases.
80
+
81
+ ## Python API
82
+
83
+ ```python
84
+ import eo_fetch as eof
85
+ ```
86
+
87
+ The API does everything the command line does, with the same names and rules. Options that are `--param KEY=VALUE` on the command line are keyword arguments.
88
+
89
+ ### Find product names
90
+
91
+ ```python
92
+ for match in eof.products("precipitation"): # aliases of all providers
93
+ print(match.provider, match.product, "-", match.description)
94
+ eof.products(provider="dwd") # one provider (+ top-level folders)
95
+ eof.products("soil moisture", "nasa") # with a provider: live catalogue too
96
+ ```
97
+
98
+ `eof.products(text="", provider=None, *, live=None, browse=None)` returns `eof.ProductMatch` objects (`provider`, `product`, `description`, `source`); pass `match.product` to `eof.search`.
99
+
100
+ ### Search
101
+
102
+ ```python
103
+ found = eof.search(
104
+ "nasa",
105
+ "imerg-late-daily",
106
+ start="2026-10-01",
107
+ end="2026-10-03", # ISO strings, dates or datetimes (end exclusive)
108
+ bbox=(5, 47, 16, 55), # west, south, east, north
109
+ )
110
+ print(len(found), f"{found.megabytes:.1f} MB")
111
+ for item in found:
112
+ print(item.time, item.key, item.size)
113
+ ```
114
+
115
+ `eof.search(provider, product="", *, start, end, bbox, text, limit, params, **options)` returns `eof.Results`, a normal list of `eof.Item` that also remembers how to download itself. Provider options go in as keywords or in a `params` dict; lists become comma-separated values:
116
+
117
+ ```python
118
+ eof.search(
119
+ "cdse",
120
+ "sentinel-2",
121
+ start="2026-10-28",
122
+ end="2026-10-30",
123
+ bbox=(8.5, 53.4, 9.5, 53.8),
124
+ assets=["B04_60m", "SCL_60m"],
125
+ limit=2,
126
+ )
127
+ eof.search(
128
+ "esgf",
129
+ "CMIP6",
130
+ variable_id="tas",
131
+ experiment_id="historical",
132
+ source_id="CanESM5",
133
+ member_id="r1i1p1f1",
134
+ table_id="Amon",
135
+ )
136
+ eof.search(
137
+ "cds",
138
+ "era5-single-levels",
139
+ start="2026-01-01",
140
+ end="2026-02-01",
141
+ variable="2m_temperature",
142
+ bbox=(5, 47, 16, 55),
143
+ )
144
+ ```
145
+
146
+ An unknown option raises `ValueError` with the accepted names. Filter or slice the results freely (`found[:3]`, `found.where(lambda item: item.size < 5e6)`); they keep working with `eof.download`.
147
+
148
+ ### Download and verify
149
+
150
+ ```python
151
+ results = eof.download(found, "data/imerg") # also writes data/imerg/manifest.json
152
+ failed = [r for r in results if r.status == "failed"] # downloaded | skipped | failed
153
+ problems = eof.verify("data/imerg/manifest.json") # [] when every file matches
154
+ ```
155
+
156
+ `eof.download(results, dest, *, workers=None, retries=3, manifest=None, on_result=None)` returns a list of `eof.Result` (`item`, `path`, `status`, `size`, `sha256`, `seconds`, `error`). Failures do not raise. Files that already exist with the right size and checksum are skipped, so the same call resumes an interrupted run. `on_result` is called as each file finishes (for progress output).
157
+
158
+ ### Open, plot, convert
159
+
160
+ ```python
161
+ ds = eof.open(results[0].path) # path of the first downloaded file
162
+ path = eof.plot(ds, "plots/imerg.png", bbox=(5, 47, 16, 55), style="precip-daily")
163
+ eof.convert("data/era5/era5.grib", "data/era5/era5.nc") # or .zarr
164
+ ekd = eof.to_earthkit("data/era5/era5.grib") # earthkit-data object
165
+ ```
166
+
167
+ `eof.open(path, format=None, **options)` returns an `xarray.Dataset`; the format is detected from the name and content (see below). `eof.plot(source, output=None, *, variable, style, domain, resolution, bbox, title)` accepts a file or a Dataset and returns the image path. See `eo-fetch styles` for style names.
168
+
169
+ ### Providers, credentials, diagnostics
170
+
171
+ ```python
172
+ for info in eof.providers(): # ProviderInfo cards
173
+ print(info.name, info.auth, info.verified)
174
+ eof.provider("cds").notes # how to use the provider, options, limits
175
+ eof.collections("copernicus-marine", "sea surface temperature")
176
+ eof.login("cds") # hidden prompts; saves to your user folder
177
+ eof.doctor("nasa") # prints what is missing; True when all is well
178
+ ```
179
+
180
+ Lower-level pieces (`eo_fetch.backends`, `eo_fetch.readers`, `eo_fetch.regrid`) are importable too and are what the functions above are built on. To add a provider, implement the `Backend` protocol (`search(query)`, `fetch(item, target)`, `max_connections`) and register a `ProviderInfo` card.
181
+
182
+ ## Providers
183
+
184
+ `eo-fetch providers` always shows the current list and verification dates.
185
+
186
+ | Provider | What | Access |
187
+ | --- | --- | --- |
188
+ | `eumetsat` | EUMETSAT Data Store: Meteosat, MTG, Metop, Sentinel-3/5P/6, CM SAF (all collections) | licence per collection |
189
+ | `noaa` | GOES-R (16, 18, 19) and other public NOAA buckets on AWS | none |
190
+ | `dwd` | DWD open data tree: radar, ICON models, climate data centre | none |
191
+ | `nasa` | every collection of NASA's CMR (IMERG, MERRA-2, OMI, SMAP, ...) | free Earthdata account |
192
+ | `planetary-computer`, `earth-search` | STAC catalogues: Sentinel, Landsat, land cover, climate | none |
193
+ | `cdse` | Copernicus Data Space (Sentinel) via STAC and S3 | free account + S3 keys |
194
+ | `ecmwf-open-data` | IFS and ensemble forecasts by parameter and lead time | none |
195
+ | `cds` | ERA5, ERA5-Land and other Climate Data Store datasets | free account, licence per dataset |
196
+ | `copernicus-marine` | ocean physics, biogeochemistry, sea ice, waves | free account |
197
+ | `esgf` | CMIP climate-model output (CMIP5 and CMIP6 tested; CMIP3/7, CORDEX-CMIP6, obs4MIPs as far as the indices serve them) | none |
198
+
199
+ Each card (`eof.provider(name)` or `eo-fetch doctor --provider NAME`) lists licence, attribution, latency and the options of that provider. Respect the licence and attribution when you publish results; the manifest records both.
200
+
201
+ ## Credentials
202
+
203
+ Providers with accounts have a one-time `eo-fetch login --provider NAME` (hidden prompts; the login is saved as a plain-text file in your user folder, and deleting the file logs you out). `eo-fetch doctor --provider NAME` tells you what is missing. You can also set the environment variables, which take priority, or write the file yourself. eo-fetch never stores credentials anywhere else, never writes them into manifests or download folders, and sends them only to the provider.
204
+
205
+ | Provider | `login` writes | Or use |
206
+ | --- | --- | --- |
207
+ | `nasa` (Earthdata) | `~/.netrc` | `EARTHDATA_USERNAME` and `EARTHDATA_PASSWORD`, or `EARTHDATA_TOKEN`; approve the provider's application once at urs.earthdata.nasa.gov |
208
+ | `eumetsat` | `~/.eumdac/credentials` | `EUMETSAT_KEY` and `EUMETSAT_SECRET`; a licence per collection |
209
+ | `cds` (ERA5) | `~/.cdsapirc` | `CDSAPI_URL` and `CDSAPI_KEY`; accept each dataset's licence on its page |
210
+ | `copernicus-marine` | `~/.copernicusmarine/.copernicusmarine-credentials` (written by the toolbox) | `COPERNICUSMARINE_SERVICE_USERNAME` and `COPERNICUSMARINE_SERVICE_PASSWORD` |
211
+ | `cdse` | `~/.eo-fetch/cdse` (`key,secret`) | `CDSE_S3_KEY` and `CDSE_S3_SECRET` from the Copernicus Data Space S3 key manager (searching needs no keys) |
212
+ | `dwd`, `noaa`, `earth-search`, `planetary-computer`, `ecmwf-open-data`, `esgf` | none needed | |
213
+
214
+ ## Which files can be opened?
215
+
216
+ `eo-fetch check-readers` downloads the smallest recent file of every product alias of every provider and tries to open it, and prints ok, no reader, fail or skip for each. In the last full run: all GOES-18/19 products, SEVIRI (L1.5, IODC, RSS, cloud mask, cloud top height), FCI level 1c and cloud mask, MTG precipitation and lightning, CM SAF products, DWD radar composites, ICON-EU, DWD satellite radiation and NOAA GFS GRIB opened. Sentinel-2 JPEG 2000, ERA5 GRIB/NetCDF, ECMWF open data, Copernicus Marine and CMIP6 NetCDF were opened in the provider tests.
217
+
218
+ Known limits:
219
+
220
+ - Not readable yet: BUFR (ASCAT, RAP), DWD station zips and radar `.tar` composites, NEXRAD.
221
+ - ICON icosahedral grids open only as a flat list of values (they need DWD's grid files for coordinates).
222
+ - OMI and SMAP HDF5 files open but are not plotted (2-D latitude/longitude arrays).
223
+ - Plots draw the first entry of extra dimensions (time, level, band). Categorical rasters such as Sentinel-2 `SCL` have no dedicated style yet.
224
+ - CMIP6 searches may warn about a publishing error in the new STAC indices of ESGF; the warning is from `intake-esgf`. Switch those indices off with `--param indices=-discovery.east.esgf.io,-discovery.west.esgf.io`. Older projects (CMIP5, CMIP3) are only in the legacy indices: add `--param indices=all`; some legacy nodes may be offline and are reported as warnings.
225
+ - On Windows, paths are limited to 260 characters; use a short `--dest`. A harmless `Error in sys.excepthook` message can appear at exit after reading GeoTIFF/JPEG 2000 files.
226
+
227
+ ## Docker
228
+
229
+ ```
230
+ docker build -t eo-fetch .
231
+ docker run --rm eo-fetch providers
232
+ docker run --rm -v "${PWD}/data:/data" eo-fetch download --provider dwd --product radolan-yw --limit 2 --dest /data/dwd
233
+ ```
234
+
235
+ (`${PWD}` is PowerShell; use `$(pwd)` in bash.) The image contains no credentials. For providers with accounts, either pass the environment variables from the table above (`-e EARTHDATA_USERNAME -e EARTHDATA_PASSWORD`, ...) or mount the credential file read-only, for example `-v "$HOME/.cdsapirc:/home/eo/.cdsapirc:ro"`.
236
+ ## Roadmap
237
+
238
+ Version 2 providers: CHIRPS and other precipitation products, river flow, agriculture and land use, soil moisture. Optional: GRIB subsetting through `herbie`, a `wetterdienst` backend for DWD station data, readers for BUFR and DWD station zips.
239
+
240
+ ## Development
241
+
242
+ ```
243
+ ruff format .
244
+ ruff check .
245
+ pytest -m "not network and not account"
246
+ ```
247
+
248
+ Tests that need the internet or a provider account are marked `network` or `account` and are skipped in CI.