dclimate-client-py 0.1.1__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.
- dclimate_client_py-0.1.1/LICENSE +21 -0
- dclimate_client_py-0.1.1/PKG-INFO +183 -0
- dclimate_client_py-0.1.1/README.md +142 -0
- dclimate_client_py-0.1.1/dclimate_client_py/.pytest_cache/.gitignore +2 -0
- dclimate_client_py-0.1.1/dclimate_client_py/.pytest_cache/CACHEDIR.TAG +4 -0
- dclimate_client_py-0.1.1/dclimate_client_py/.pytest_cache/README.md +8 -0
- dclimate_client_py-0.1.1/dclimate_client_py/.pytest_cache/v/cache/nodeids +1 -0
- dclimate_client_py-0.1.1/dclimate_client_py/.pytest_cache/v/cache/stepwise +1 -0
- dclimate_client_py-0.1.1/dclimate_client_py/__init__.py +32 -0
- dclimate_client_py-0.1.1/dclimate_client_py/cids.json +1 -0
- dclimate_client_py-0.1.1/dclimate_client_py/client.py +203 -0
- dclimate_client_py-0.1.1/dclimate_client_py/concatenate.py +207 -0
- dclimate_client_py-0.1.1/dclimate_client_py/datasets.py +667 -0
- dclimate_client_py-0.1.1/dclimate_client_py/dclimate_client.py +204 -0
- dclimate_client_py-0.1.1/dclimate_client_py/dclimate_zarr_errors.py +73 -0
- dclimate_client_py-0.1.1/dclimate_client_py/encryption_codec.py +109 -0
- dclimate_client_py-0.1.1/dclimate_client_py/geotemporal_data.py +722 -0
- dclimate_client_py-0.1.1/dclimate_client_py/ipfs_retrieval.py +144 -0
- dclimate_client_py-0.1.1/dclimate_client_py/s3_retrieval.py +105 -0
- dclimate_client_py-0.1.1/dclimate_client_py/zarr_metadata.py +107 -0
- dclimate_client_py-0.1.1/pyproject.toml +76 -0
- dclimate_client_py-0.1.1/tests/__init__.py +0 -0
- dclimate_client_py-0.1.1/tests/conftest.py +177 -0
- dclimate_client_py-0.1.1/tests/data/zarr-dev/metadata/Data Catalog.json +39 -0
- dclimate_client_py-0.1.1/tests/data/zarr-dev/metadata/Empty Data Catalog.json +21 -0
- dclimate_client_py-0.1.1/tests/data/zarr-dev/metadata/collections/CHIRPS.json +86 -0
- dclimate_client_py-0.1.1/tests/etc/central_ca_farm.geojson +8 -0
- dclimate_client_py-0.1.1/tests/etc/central_northern_ca_counties.cpg +1 -0
- dclimate_client_py-0.1.1/tests/etc/central_northern_ca_counties.dbf +0 -0
- dclimate_client_py-0.1.1/tests/etc/central_northern_ca_counties.geojson +28 -0
- dclimate_client_py-0.1.1/tests/etc/central_northern_ca_counties.prj +1 -0
- dclimate_client_py-0.1.1/tests/etc/central_northern_ca_counties.shp +0 -0
- dclimate_client_py-0.1.1/tests/etc/central_northern_ca_counties.shx +0 -0
- dclimate_client_py-0.1.1/tests/etc/forecast_retrieval_test.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/northern_ca_counties.geojson +55 -0
- dclimate_client_py-0.1.1/tests/etc/northern_ca_points.geojson +60 -0
- dclimate_client_py-0.1.1/tests/etc/retrieval_test.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/sample_zarrs/bafyreibh56lc4n5mqaj25m7q5dkcwda7qt5hyygd7vu32idupejaoan2li.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/sample_zarrs/bafyreicbu5gbtj5kgyzf2qmsvqhlog7fj5yrnksjqmkqmcinn2yrcsaynq.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/sample_zarrs/bafyreiccejigv2gura4mqitpnrfg2moarvjx4jrcgkompqeuzpejhtc4ra.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/sample_zarrs/bafyreiglm3xvfcwkjbdqlwg3mc6zgngxuyfj6tkgfb6qobtmlzobpp63sq.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/sample_zarrs/bafyreiheqhx2wbdx5p3mbxeawlif7g72ixfgwoonuy3bhghsodc45xwjbi.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/sample_zarrs/gfs_temp_max_test.zip +0 -0
- dclimate_client_py-0.1.1/tests/etc/stac_metadata/bafyreibtdfcfyyineq7pv2xunl4sxq6w6ziibswflmelaiydgbqwjk2sku.json +77 -0
- dclimate_client_py-0.1.1/tests/etc/stac_metadata/bafyreidduui6mg33udlulg3kyc4nlujbdf7tqztj35twq7bm3xj4izibdq.json +77 -0
- dclimate_client_py-0.1.1/tests/etc/stac_metadata/bafyreieker5cwplgsz3wdoeuwhsbcoqajri5dz5jrzxayw5qzaafd3z5ay.json +66 -0
- dclimate_client_py-0.1.1/tests/etc/stac_metadata/bafyreifihizlrcxvxtwj7k3dfy6efy67g7sjdvgspxorzni3xthrlfxle4.json +77 -0
- dclimate_client_py-0.1.1/tests/test_client.py +369 -0
- dclimate_client_py-0.1.1/tests/test_dataset_catalog.py +670 -0
- dclimate_client_py-0.1.1/tests/test_geotemporal_data.py +235 -0
- dclimate_client_py-0.1.1/tests/test_s3_retrieval.py +106 -0
- dclimate_client_py-0.1.1/tests/test_zarr_encryption_ipfs.py +204 -0
- dclimate_client_py-0.1.1/tests/test_zarr_metadata.py +320 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 dClimate
|
|
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,183 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dclimate-client-py
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python client library for accessing dClimate weather and climate data
|
|
5
|
+
Keywords: climate,weather,data,dclimate,meteorology,climate-data
|
|
6
|
+
Author-Email: Phil Van Meppelen Scheppink <phil@dclimate.net>, Evan Schechter <evan@arbol.io>, Chris Rossi <chris@christophermrossi.com>, Robert Banick <robert.banick@arbol.io>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Project-URL: Homepage, https://dclimate.net/
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: aiobotocore
|
|
19
|
+
Requires-Dist: xarray>=2025.3.0
|
|
20
|
+
Requires-Dist: rioxarray
|
|
21
|
+
Requires-Dist: zarr>=3.0.8
|
|
22
|
+
Requires-Dist: numpy>=2.1.3
|
|
23
|
+
Requires-Dist: py_hamt>=3.2.0
|
|
24
|
+
Requires-Dist: requests
|
|
25
|
+
Requires-Dist: pyarrow
|
|
26
|
+
Requires-Dist: geopandas
|
|
27
|
+
Requires-Dist: pandas
|
|
28
|
+
Requires-Dist: s3fs
|
|
29
|
+
Requires-Dist: shapely
|
|
30
|
+
Requires-Dist: scipy
|
|
31
|
+
Requires-Dist: pycryptodome>=3.21.0
|
|
32
|
+
Requires-Dist: pytest-asyncio>=1.3.0
|
|
33
|
+
Provides-Extra: testing
|
|
34
|
+
Requires-Dist: pytest; extra == "testing"
|
|
35
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
36
|
+
Requires-Dist: pytest-mock; extra == "testing"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pre-commit>=4.1.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.9.5; extra == "dev"
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://dclimate.net/" target="_blank" rel="noopener noreferrer">
|
|
44
|
+
<img width="50%" src="https://user-images.githubusercontent.com/41392423/173133333-79ef15d0-6671-4be3-ac97-457344e9e958.svg" alt="dClimate logo">
|
|
45
|
+
</a>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
# dClimate-Client-Py
|
|
49
|
+
[](https://codecov.io/gh/dClimate/dClimate-Zarr-Client)
|
|
50
|
+
|
|
51
|
+
Retrieve dClimate GIS zarr datasets stored on IPFS
|
|
52
|
+
|
|
53
|
+
Uses [py-hamt](https://github.com/dClimate/py-hamt) to access Zarr data structures stored efficiently on IPFS.
|
|
54
|
+
|
|
55
|
+
Filtering and aggregation are packaged into convenience functions optimized for flexibility and performance.
|
|
56
|
+
|
|
57
|
+
> **Looking for JavaScript?** Check out our [JavaScript client](https://www.npmjs.com/package/@dclimate/dclimate-client-js) for Node.js and browser environments.
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from datetime import datetime
|
|
63
|
+
import dclimate_client_py as client
|
|
64
|
+
from dclimate_client_py import dClimateClient
|
|
65
|
+
|
|
66
|
+
# --- Recommended: Using dClimateClient (async context manager) ---
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
# The client manages IPFS connections automatically
|
|
70
|
+
# No need to import or configure KuboCAS directly!
|
|
71
|
+
async with dClimateClient() as dclimate:
|
|
72
|
+
# Load datasets by name from the internal catalog
|
|
73
|
+
# For datasets with multiple variants, you must specify which variant
|
|
74
|
+
dataset = await dclimate.load_dataset(
|
|
75
|
+
dataset="2m_temperature",
|
|
76
|
+
collection="era5",
|
|
77
|
+
variant="finalized", # Required for multi-variant datasets
|
|
78
|
+
return_xarray=False # Returns GeotemporalData wrapper (default)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Apply queries using the GeotemporalData interface
|
|
82
|
+
dataset_filtered = dataset.point(latitude=40.875, longitude=-104.875)
|
|
83
|
+
dataset_filtered = dataset_filtered.time_range(
|
|
84
|
+
datetime(2023, 1, 1),
|
|
85
|
+
datetime(2023, 1, 5)
|
|
86
|
+
)
|
|
87
|
+
data_dict = dataset_filtered.as_dict()
|
|
88
|
+
print(data_dict['data'])
|
|
89
|
+
|
|
90
|
+
# Custom IPFS endpoints (optional)
|
|
91
|
+
async def main_custom_ipfs():
|
|
92
|
+
async with dClimateClient(
|
|
93
|
+
gateway_base_url="https://ipfs.io",
|
|
94
|
+
rpc_base_url="http://localhost:5001"
|
|
95
|
+
) as dclimate:
|
|
96
|
+
dataset = await dclimate.load_dataset(
|
|
97
|
+
dataset="2m_temperature",
|
|
98
|
+
collection="era5",
|
|
99
|
+
variant="finalized"
|
|
100
|
+
)
|
|
101
|
+
# Query dataset...
|
|
102
|
+
|
|
103
|
+
# Get raw xarray.Dataset directly
|
|
104
|
+
async def main_xarray():
|
|
105
|
+
async with dClimateClient() as dclimate:
|
|
106
|
+
xr_dataset = await dclimate.load_dataset(
|
|
107
|
+
dataset="2m_temperature",
|
|
108
|
+
collection="era5",
|
|
109
|
+
variant="finalized",
|
|
110
|
+
return_xarray=True # Returns xarray.Dataset
|
|
111
|
+
)
|
|
112
|
+
print(xr_dataset)
|
|
113
|
+
|
|
114
|
+
# List available datasets in the catalog (synchronous helper)
|
|
115
|
+
catalog = client.list_dataset_catalog()
|
|
116
|
+
for collection in catalog:
|
|
117
|
+
print(f"Collection: {collection['collection']}")
|
|
118
|
+
for dataset in collection['datasets']:
|
|
119
|
+
print(f" Dataset: {dataset['dataset']}")
|
|
120
|
+
for variant in dataset['variants']:
|
|
121
|
+
print(f" Variant: {variant['variant']}")
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> More examples can be found at [dClimate Jupyter Notebooks](https://github.com/dClimate/jupyter-notebooks/tree/main/notebooks). To run your own IPFS gateway follow the instructions for [installing ipfs](https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions). For additional assistance find us on [Discord](https://discord.com/invite/bYWVdNDMpe ), if you are an organization or business reach out to us at community at dclimate dot net.
|
|
126
|
+
|
|
127
|
+
## Create and activate a virtual environment:
|
|
128
|
+
|
|
129
|
+
``` shell
|
|
130
|
+
uv venv .venv
|
|
131
|
+
source .venv/bin/activate # macOS/Linux
|
|
132
|
+
.\.venv\Scripts\activate # Windows
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Install Dependencies
|
|
136
|
+
|
|
137
|
+
```shell
|
|
138
|
+
uv sync --extra dev --extra testing
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Run tests for your local environment
|
|
142
|
+
```shell
|
|
143
|
+
uv run pytest tests/
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Use Coverage
|
|
147
|
+
|
|
148
|
+
```shell
|
|
149
|
+
uv run pytest --cov=dclimate_client_py tests/ --cov-report=xml
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Environment requirements
|
|
153
|
+
|
|
154
|
+
- Optionally you can run your own IPFS Server to host your own datasets or connect to others.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
## File breakdown:
|
|
158
|
+
|
|
159
|
+
### client.py
|
|
160
|
+
|
|
161
|
+
Entrypoint to code, contains `geo_temporal_query`, which combines all possible subsetting
|
|
162
|
+
and aggregation logic in a single function. Can output the data as either a `dict`
|
|
163
|
+
or `bytes` representing an `xarray` dataset.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### dclimate_zarr_errors.py
|
|
168
|
+
|
|
169
|
+
Various exceptions to be raised for bad or invalid user input.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### geo_utils.py
|
|
174
|
+
|
|
175
|
+
Functions to manipulate `xarray` datasets. Contains polygon, rectangle, circle and point spatial
|
|
176
|
+
subsetting options, as well as temporal subsetting. Also allows for both spatial and temporal
|
|
177
|
+
aggregations.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
### ipfs_retrieval.py
|
|
182
|
+
|
|
183
|
+
Functions for resolving IPNS names, traversing the dClimate STAC catalog stored on IPFS, and loading Zarr datasets using `py-hamt`. Handles interaction with IPFS gateways and RPC endpoints.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://dclimate.net/" target="_blank" rel="noopener noreferrer">
|
|
3
|
+
<img width="50%" src="https://user-images.githubusercontent.com/41392423/173133333-79ef15d0-6671-4be3-ac97-457344e9e958.svg" alt="dClimate logo">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
# dClimate-Client-Py
|
|
8
|
+
[](https://codecov.io/gh/dClimate/dClimate-Zarr-Client)
|
|
9
|
+
|
|
10
|
+
Retrieve dClimate GIS zarr datasets stored on IPFS
|
|
11
|
+
|
|
12
|
+
Uses [py-hamt](https://github.com/dClimate/py-hamt) to access Zarr data structures stored efficiently on IPFS.
|
|
13
|
+
|
|
14
|
+
Filtering and aggregation are packaged into convenience functions optimized for flexibility and performance.
|
|
15
|
+
|
|
16
|
+
> **Looking for JavaScript?** Check out our [JavaScript client](https://www.npmjs.com/package/@dclimate/dclimate-client-js) for Node.js and browser environments.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
import dclimate_client_py as client
|
|
23
|
+
from dclimate_client_py import dClimateClient
|
|
24
|
+
|
|
25
|
+
# --- Recommended: Using dClimateClient (async context manager) ---
|
|
26
|
+
|
|
27
|
+
async def main():
|
|
28
|
+
# The client manages IPFS connections automatically
|
|
29
|
+
# No need to import or configure KuboCAS directly!
|
|
30
|
+
async with dClimateClient() as dclimate:
|
|
31
|
+
# Load datasets by name from the internal catalog
|
|
32
|
+
# For datasets with multiple variants, you must specify which variant
|
|
33
|
+
dataset = await dclimate.load_dataset(
|
|
34
|
+
dataset="2m_temperature",
|
|
35
|
+
collection="era5",
|
|
36
|
+
variant="finalized", # Required for multi-variant datasets
|
|
37
|
+
return_xarray=False # Returns GeotemporalData wrapper (default)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Apply queries using the GeotemporalData interface
|
|
41
|
+
dataset_filtered = dataset.point(latitude=40.875, longitude=-104.875)
|
|
42
|
+
dataset_filtered = dataset_filtered.time_range(
|
|
43
|
+
datetime(2023, 1, 1),
|
|
44
|
+
datetime(2023, 1, 5)
|
|
45
|
+
)
|
|
46
|
+
data_dict = dataset_filtered.as_dict()
|
|
47
|
+
print(data_dict['data'])
|
|
48
|
+
|
|
49
|
+
# Custom IPFS endpoints (optional)
|
|
50
|
+
async def main_custom_ipfs():
|
|
51
|
+
async with dClimateClient(
|
|
52
|
+
gateway_base_url="https://ipfs.io",
|
|
53
|
+
rpc_base_url="http://localhost:5001"
|
|
54
|
+
) as dclimate:
|
|
55
|
+
dataset = await dclimate.load_dataset(
|
|
56
|
+
dataset="2m_temperature",
|
|
57
|
+
collection="era5",
|
|
58
|
+
variant="finalized"
|
|
59
|
+
)
|
|
60
|
+
# Query dataset...
|
|
61
|
+
|
|
62
|
+
# Get raw xarray.Dataset directly
|
|
63
|
+
async def main_xarray():
|
|
64
|
+
async with dClimateClient() as dclimate:
|
|
65
|
+
xr_dataset = await dclimate.load_dataset(
|
|
66
|
+
dataset="2m_temperature",
|
|
67
|
+
collection="era5",
|
|
68
|
+
variant="finalized",
|
|
69
|
+
return_xarray=True # Returns xarray.Dataset
|
|
70
|
+
)
|
|
71
|
+
print(xr_dataset)
|
|
72
|
+
|
|
73
|
+
# List available datasets in the catalog (synchronous helper)
|
|
74
|
+
catalog = client.list_dataset_catalog()
|
|
75
|
+
for collection in catalog:
|
|
76
|
+
print(f"Collection: {collection['collection']}")
|
|
77
|
+
for dataset in collection['datasets']:
|
|
78
|
+
print(f" Dataset: {dataset['dataset']}")
|
|
79
|
+
for variant in dataset['variants']:
|
|
80
|
+
print(f" Variant: {variant['variant']}")
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> More examples can be found at [dClimate Jupyter Notebooks](https://github.com/dClimate/jupyter-notebooks/tree/main/notebooks). To run your own IPFS gateway follow the instructions for [installing ipfs](https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions). For additional assistance find us on [Discord](https://discord.com/invite/bYWVdNDMpe ), if you are an organization or business reach out to us at community at dclimate dot net.
|
|
85
|
+
|
|
86
|
+
## Create and activate a virtual environment:
|
|
87
|
+
|
|
88
|
+
``` shell
|
|
89
|
+
uv venv .venv
|
|
90
|
+
source .venv/bin/activate # macOS/Linux
|
|
91
|
+
.\.venv\Scripts\activate # Windows
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Install Dependencies
|
|
95
|
+
|
|
96
|
+
```shell
|
|
97
|
+
uv sync --extra dev --extra testing
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Run tests for your local environment
|
|
101
|
+
```shell
|
|
102
|
+
uv run pytest tests/
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Use Coverage
|
|
106
|
+
|
|
107
|
+
```shell
|
|
108
|
+
uv run pytest --cov=dclimate_client_py tests/ --cov-report=xml
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Environment requirements
|
|
112
|
+
|
|
113
|
+
- Optionally you can run your own IPFS Server to host your own datasets or connect to others.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## File breakdown:
|
|
117
|
+
|
|
118
|
+
### client.py
|
|
119
|
+
|
|
120
|
+
Entrypoint to code, contains `geo_temporal_query`, which combines all possible subsetting
|
|
121
|
+
and aggregation logic in a single function. Can output the data as either a `dict`
|
|
122
|
+
or `bytes` representing an `xarray` dataset.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### dclimate_zarr_errors.py
|
|
127
|
+
|
|
128
|
+
Various exceptions to be raised for bad or invalid user input.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### geo_utils.py
|
|
133
|
+
|
|
134
|
+
Functions to manipulate `xarray` datasets. Contains polygon, rectangle, circle and point spatial
|
|
135
|
+
subsetting options, as well as temporal subsetting. Also allows for both spatial and temporal
|
|
136
|
+
aggregations.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### ipfs_retrieval.py
|
|
141
|
+
|
|
142
|
+
Functions for resolving IPNS names, traversing the dClimate STAC catalog stored on IPFS, and loading Zarr datasets using `py-hamt`. Handles interaction with IPFS gateways and RPC endpoints.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# pytest cache directory #
|
|
2
|
+
|
|
3
|
+
This directory contains data from the pytest's cache plugin,
|
|
4
|
+
which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
|
|
5
|
+
|
|
6
|
+
**Do not** commit this to version control.
|
|
7
|
+
|
|
8
|
+
See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# public API
|
|
2
|
+
from .client import (
|
|
3
|
+
load_s3,
|
|
4
|
+
geo_temporal_query,
|
|
5
|
+
)
|
|
6
|
+
from .dclimate_client import dClimateClient
|
|
7
|
+
from .geotemporal_data import GeotemporalData
|
|
8
|
+
from .encryption_codec import (
|
|
9
|
+
EncryptionCodec,
|
|
10
|
+
)
|
|
11
|
+
from .datasets import (
|
|
12
|
+
list_dataset_catalog,
|
|
13
|
+
fetch_cid_from_url,
|
|
14
|
+
DatasetCatalog,
|
|
15
|
+
CatalogCollection,
|
|
16
|
+
CatalogDataset,
|
|
17
|
+
DatasetVariantConfig,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"dClimateClient",
|
|
22
|
+
"load_s3",
|
|
23
|
+
"geo_temporal_query",
|
|
24
|
+
"list_dataset_catalog",
|
|
25
|
+
"fetch_cid_from_url",
|
|
26
|
+
"GeotemporalData",
|
|
27
|
+
"EncryptionCodec",
|
|
28
|
+
"DatasetCatalog",
|
|
29
|
+
"CatalogCollection",
|
|
30
|
+
"CatalogDataset",
|
|
31
|
+
"DatasetVariantConfig",
|
|
32
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"cpc-precip-conus": "bafyr4iff6vuvpkb4zexkx7exoafsjvfnno4ofidyc37gsto2aaxixg5zia", "cpc-precip-global": "bafyr4ihe35o55qz47y2dbau6ikvfkbrthqn57pdhem5jdxhvqf5u7qlrbm", "chirps-final-p05": "bafyr4ic2n63fkxdvwgh2uzealcrbeubncivcqaptttfrr4vp5ecbe5e65a"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions that will map to endpoints in the flask app
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import typing
|
|
7
|
+
import xarray as xr
|
|
8
|
+
|
|
9
|
+
from .dclimate_zarr_errors import (
|
|
10
|
+
ConflictingGeoRequestError,
|
|
11
|
+
ConflictingAggregationRequestError,
|
|
12
|
+
InvalidExportFormatError,
|
|
13
|
+
InvalidSelectionError,
|
|
14
|
+
)
|
|
15
|
+
from .geotemporal_data import GeotemporalData, DEFAULT_POINT_LIMIT
|
|
16
|
+
from .s3_retrieval import get_dataset_from_s3
|
|
17
|
+
from .ipfs_retrieval import (
|
|
18
|
+
_get_dataset_by_ipfs_cid,
|
|
19
|
+
)
|
|
20
|
+
from .datasets import (
|
|
21
|
+
resolve_dataset_source,
|
|
22
|
+
get_concatenable_variants,
|
|
23
|
+
find_dataset_by_name,
|
|
24
|
+
find_collection_by_name,
|
|
25
|
+
fetch_cid_from_url,
|
|
26
|
+
DATASET_CATALOG_INTERNAL,
|
|
27
|
+
DatasetCatalog,
|
|
28
|
+
)
|
|
29
|
+
from .concatenate import concatenate_datasets
|
|
30
|
+
|
|
31
|
+
def load_s3(
|
|
32
|
+
dataset_name: str,
|
|
33
|
+
bucket_name: str,
|
|
34
|
+
) -> GeotemporalData:
|
|
35
|
+
"""
|
|
36
|
+
Load a Geotemporal dataset from an S3 bucket.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
|
|
41
|
+
dataset_name: str
|
|
42
|
+
The name of the dataset in the bucket.
|
|
43
|
+
bucket_name: str
|
|
44
|
+
S3 bucket name where the dataset is going to be fetched
|
|
45
|
+
"""
|
|
46
|
+
ds = get_dataset_from_s3(dataset_name, bucket_name)
|
|
47
|
+
return GeotemporalData(ds, dataset_name=dataset_name)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def geo_temporal_query(
|
|
51
|
+
dataset_name: str,
|
|
52
|
+
source: typing.Literal["s3"] = "s3",
|
|
53
|
+
bucket_name: str = None,
|
|
54
|
+
var_name: str = None,
|
|
55
|
+
gateway_uri_stem: str | None = None,
|
|
56
|
+
rpc_uri_stem: str | None = None,
|
|
57
|
+
forecast_reference_time: str = None,
|
|
58
|
+
point_kwargs: dict = None,
|
|
59
|
+
circle_kwargs: dict = None,
|
|
60
|
+
rectangle_kwargs: dict = None,
|
|
61
|
+
polygon_kwargs: dict = None,
|
|
62
|
+
multiple_points_kwargs: dict = None,
|
|
63
|
+
spatial_agg_kwargs: dict = None,
|
|
64
|
+
temporal_agg_kwargs: dict = None,
|
|
65
|
+
rolling_agg_kwargs: dict = None,
|
|
66
|
+
time_range: typing.Optional[typing.List[datetime.datetime]] = None,
|
|
67
|
+
# as_of: typing.Optional[datetime.datetime] = None, # Removed as_of
|
|
68
|
+
point_limit: int = DEFAULT_POINT_LIMIT,
|
|
69
|
+
output_format: str = "array",
|
|
70
|
+
) -> typing.Union[dict, bytes]:
|
|
71
|
+
"""Filter an XArray dataset
|
|
72
|
+
|
|
73
|
+
Filter an XArray dataset by specified spatial and/or temporal bounds and aggregate
|
|
74
|
+
according to spatial and/or temporal logic, if desired. Before aggregating check
|
|
75
|
+
that the filtered data fits within specified point and area maximums to avoid
|
|
76
|
+
computationally expensive retrieval and processing operations. When bounds or
|
|
77
|
+
aggregation logic are not provided, pass the dataset along untouched.
|
|
78
|
+
|
|
79
|
+
Return either a numpy array of data values or a NetCDF file.
|
|
80
|
+
|
|
81
|
+
Only one of point, circle, rectangle, or polygon kwargs may be provided. Only one of
|
|
82
|
+
temporal or rolling aggregation kwargs may be provided, although they can be chained
|
|
83
|
+
with spatial aggregations if desired.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
dataset_name (str): Name used to identify the dataset within the STAC catalog (for IPFS)
|
|
87
|
+
or the dataset name in the bucket (for S3).
|
|
88
|
+
source: (typing.Literal["ipfs", "s3"]): how to pull data. Defaults to "ipfs".
|
|
89
|
+
bucket_name (str): S3 bucket name where the datasets are going to be fetched (required if source="s3").
|
|
90
|
+
var_name (str, optional): Specific data variable to use within the dataset.
|
|
91
|
+
gateway_uri_stem (str | None, optional): Custom IPFS HTTP Gateway URI stem for IPFS source.
|
|
92
|
+
rpc_uri_stem (str | None, optional): Custom IPFS RPC API URI stem for IPFS source.
|
|
93
|
+
forecast_reference_time (str): Isoformatted string representing the desire date
|
|
94
|
+
to return all available forecasts for
|
|
95
|
+
circle_kwargs (dict, optional): a dictionary of parameters relevant to a
|
|
96
|
+
circular query
|
|
97
|
+
rectangle_kwargs (dict, optional): a dictionary of parameters relevant to a
|
|
98
|
+
rectangular query
|
|
99
|
+
polygon_kwargs (dict, optional): a dictionary of parameters relevant to a
|
|
100
|
+
polygonal query
|
|
101
|
+
multiple_points_kwargs (dict, optional): Parameters for querying multiple specific points.
|
|
102
|
+
point_kwargs (dict, optional): Parameters for querying a single point.
|
|
103
|
+
spatial_agg_kwargs (dict, optional): a dictionary of parameters relevant to a
|
|
104
|
+
spatial aggregation operation
|
|
105
|
+
temporal_agg_kwargs (dict, optional): a dictionary of parameters relevant to a
|
|
106
|
+
temporal aggregation operation
|
|
107
|
+
rolling_agg_kwargs (dict, optional): a dictionary of parameters relevant to a
|
|
108
|
+
rolling aggregation operation
|
|
109
|
+
time_range (typing.Optional[typing.List[datetime.datetime]], optional):
|
|
110
|
+
time range in which to subset data.
|
|
111
|
+
Defaults to None.
|
|
112
|
+
# REMOVED as_of (typing.Optional[datetime.datetime], optional):
|
|
113
|
+
# pull in most recent data created before this time. If None, just get most
|
|
114
|
+
# recent. Defaults to None.
|
|
115
|
+
point_limit (int, optional): maximum number of data points user can request.
|
|
116
|
+
Defaults to DEFAULT_POINT_LIMIT.
|
|
117
|
+
output_format (str, optional): Current supported formats are `array` and
|
|
118
|
+
`netcdf`. Defaults to "array", which provides a dict of data
|
|
119
|
+
values and coordinates.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
typing.Union[dict, bytes]: Output data as dict (default) or NetCDF bytes.
|
|
123
|
+
"""
|
|
124
|
+
# Check for incompatible request parameters
|
|
125
|
+
if (
|
|
126
|
+
len(
|
|
127
|
+
[
|
|
128
|
+
kwarg_dict
|
|
129
|
+
for kwarg_dict in [
|
|
130
|
+
circle_kwargs,
|
|
131
|
+
rectangle_kwargs,
|
|
132
|
+
polygon_kwargs,
|
|
133
|
+
multiple_points_kwargs,
|
|
134
|
+
point_kwargs,
|
|
135
|
+
]
|
|
136
|
+
if kwarg_dict is not None
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
> 1
|
|
140
|
+
):
|
|
141
|
+
raise ConflictingGeoRequestError(
|
|
142
|
+
"User requested more than one type of geographic query, but only one can "
|
|
143
|
+
"be submitted at a time"
|
|
144
|
+
)
|
|
145
|
+
if spatial_agg_kwargs and point_kwargs:
|
|
146
|
+
raise ConflictingGeoRequestError(
|
|
147
|
+
"User requested spatial aggregation methods on a single point, "
|
|
148
|
+
"but these are mutually exclusive parameters. Only one may be requested at "
|
|
149
|
+
"a time."
|
|
150
|
+
)
|
|
151
|
+
if temporal_agg_kwargs and rolling_agg_kwargs:
|
|
152
|
+
raise ConflictingAggregationRequestError(
|
|
153
|
+
"User requested both rolling and temporal aggregation, but these are "
|
|
154
|
+
"mutually exclusive operations. Only one may be requested at a time."
|
|
155
|
+
)
|
|
156
|
+
if output_format not in ["array", "netcdf"]:
|
|
157
|
+
raise InvalidExportFormatError(
|
|
158
|
+
"User requested an invalid export format. Only 'array' or 'netcdf' "
|
|
159
|
+
"permitted."
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Set defaults to avoid Nones accidentally passed by users causing a TypeError
|
|
163
|
+
if not point_limit:
|
|
164
|
+
point_limit = DEFAULT_POINT_LIMIT
|
|
165
|
+
|
|
166
|
+
# Load the dataset based on the source
|
|
167
|
+
if source == "s3":
|
|
168
|
+
if not bucket_name:
|
|
169
|
+
raise ValueError("bucket_name is required when source is 's3'")
|
|
170
|
+
data = load_s3(dataset_name, bucket_name)
|
|
171
|
+
else:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"Invalid source specified. Must be 's3'. "
|
|
174
|
+
"IPFS source is deprecated - use dClimateClient instead."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# If specific variable is requested, use that
|
|
178
|
+
if var_name is not None:
|
|
179
|
+
data = data.use(var_name)
|
|
180
|
+
|
|
181
|
+
# Filter data down temporally, then spatially, and check that the size of resulting
|
|
182
|
+
# dataset fits within the limit. While a user can get the entire DS by providing no
|
|
183
|
+
# filters, this will almost certainly cause the size checks to fail
|
|
184
|
+
|
|
185
|
+
data = data.query(
|
|
186
|
+
forecast_reference_time=forecast_reference_time,
|
|
187
|
+
point_kwargs=point_kwargs,
|
|
188
|
+
circle_kwargs=circle_kwargs,
|
|
189
|
+
rectangle_kwargs=rectangle_kwargs,
|
|
190
|
+
polygon_kwargs=polygon_kwargs,
|
|
191
|
+
multiple_points_kwargs=multiple_points_kwargs,
|
|
192
|
+
spatial_agg_kwargs=spatial_agg_kwargs,
|
|
193
|
+
temporal_agg_kwargs=temporal_agg_kwargs,
|
|
194
|
+
rolling_agg_kwargs=rolling_agg_kwargs,
|
|
195
|
+
time_range=time_range,
|
|
196
|
+
point_limit=point_limit,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Export
|
|
200
|
+
if output_format == "netcdf":
|
|
201
|
+
return data.to_netcdf()
|
|
202
|
+
else: # "array"
|
|
203
|
+
return data.as_dict()
|