cecil 0.0.21__py3-none-any.whl → 0.0.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cecil might be problematic. Click here for more details.
- cecil/client.py +64 -5
- cecil/errors.py +11 -0
- cecil/models.py +57 -4
- cecil/version.py +1 -1
- cecil/xarray.py +165 -0
- {cecil-0.0.21.dist-info → cecil-0.0.30.dist-info}/METADATA +8 -5
- cecil-0.0.30.dist-info/RECORD +10 -0
- cecil-0.0.21.dist-info/RECORD +0 -9
- {cecil-0.0.21.dist-info → cecil-0.0.30.dist-info}/WHEEL +0 -0
- {cecil-0.0.21.dist-info → cecil-0.0.30.dist-info}/licenses/LICENSE.txt +0 -0
cecil/client.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import Dict, List
|
|
3
2
|
|
|
4
3
|
import pandas as pd
|
|
5
4
|
import requests
|
|
6
5
|
import snowflake.connector
|
|
6
|
+
import xarray
|
|
7
|
+
|
|
7
8
|
from pydantic import BaseModel
|
|
8
9
|
from requests import auth
|
|
9
10
|
from cryptography.hazmat.primitives import serialization
|
|
11
|
+
from typing import Dict, List, Optional
|
|
12
|
+
from warnings import warn
|
|
10
13
|
|
|
11
14
|
from .errors import (
|
|
12
15
|
Error,
|
|
13
16
|
_handle_bad_request,
|
|
17
|
+
_handle_method_not_allowed,
|
|
14
18
|
_handle_not_found,
|
|
15
19
|
_handle_too_many_requests,
|
|
16
20
|
_handle_unprocessable_entity,
|
|
@@ -31,8 +35,13 @@ from .models import (
|
|
|
31
35
|
TransformationCreate,
|
|
32
36
|
User,
|
|
33
37
|
UserCreate,
|
|
38
|
+
DataRequestMetadata,
|
|
39
|
+
DataRequestParquetFiles,
|
|
40
|
+
DataRequestLoadXarray,
|
|
34
41
|
)
|
|
35
42
|
from .version import __version__
|
|
43
|
+
from .xarray import load_xarray
|
|
44
|
+
from .xarray import load_xarray_v2
|
|
36
45
|
|
|
37
46
|
|
|
38
47
|
class Client:
|
|
@@ -43,9 +52,12 @@ class Client:
|
|
|
43
52
|
)
|
|
44
53
|
self._snowflake_user_creds = None
|
|
45
54
|
|
|
46
|
-
def create_aoi(self,
|
|
55
|
+
def create_aoi(self, geometry: Dict, external_ref: Optional[str] = None) -> AOI:
|
|
47
56
|
# TODO: validate geometry
|
|
48
|
-
res = self._post(
|
|
57
|
+
res = self._post(
|
|
58
|
+
url="/v0/aois",
|
|
59
|
+
model=AOICreate(geometry=geometry, external_ref=external_ref),
|
|
60
|
+
)
|
|
49
61
|
return AOI(**res)
|
|
50
62
|
|
|
51
63
|
def get_aoi(self, id: str) -> AOI:
|
|
@@ -56,10 +68,14 @@ class Client:
|
|
|
56
68
|
res = self._get(url="/v0/aois")
|
|
57
69
|
return [AOIRecord(**record) for record in res["records"]]
|
|
58
70
|
|
|
59
|
-
def create_data_request(
|
|
71
|
+
def create_data_request(
|
|
72
|
+
self, aoi_id: str, dataset_id: str, external_ref: Optional[str] = None
|
|
73
|
+
) -> DataRequest:
|
|
60
74
|
res = self._post(
|
|
61
75
|
url="/v0/data-requests",
|
|
62
|
-
model=DataRequestCreate(
|
|
76
|
+
model=DataRequestCreate(
|
|
77
|
+
aoi_id=aoi_id, dataset_id=dataset_id, external_ref=external_ref
|
|
78
|
+
),
|
|
63
79
|
)
|
|
64
80
|
return DataRequest(**res)
|
|
65
81
|
|
|
@@ -71,9 +87,32 @@ class Client:
|
|
|
71
87
|
res = self._get(url="/v0/data-requests")
|
|
72
88
|
return [DataRequest(**record) for record in res["records"]]
|
|
73
89
|
|
|
90
|
+
def load_xarray(self, data_request_id: str) -> xarray.Dataset:
|
|
91
|
+
res = self._get(url=f"/v0/data-requests/{data_request_id}/metadata")
|
|
92
|
+
metadata = DataRequestMetadata(**res)
|
|
93
|
+
return load_xarray(metadata)
|
|
94
|
+
|
|
95
|
+
def load_xarray_v2(self, data_request_id: str) -> xarray.Dataset:
|
|
96
|
+
res = self._get(url=f"/v0/data-requests/{data_request_id}/load-xarray")
|
|
97
|
+
load_xarray_info = DataRequestLoadXarray(**res)
|
|
98
|
+
return load_xarray_v2(load_xarray_info)
|
|
99
|
+
|
|
100
|
+
def load_dataframe(self, data_request_id: str) -> pd.DataFrame:
|
|
101
|
+
res = self._get(url=f"/v0/data-requests/{data_request_id}/parquet-files")
|
|
102
|
+
metadata = DataRequestParquetFiles(**res)
|
|
103
|
+
df = pd.concat((pd.read_parquet(f) for f in metadata.files))
|
|
104
|
+
return df[
|
|
105
|
+
[col for col in df.columns if col not in ("organisation_id", "created_at")]
|
|
106
|
+
]
|
|
107
|
+
|
|
74
108
|
def create_transformation(
|
|
75
109
|
self, data_request_id: str, crs: str, spatial_resolution: float
|
|
76
110
|
) -> Transformation:
|
|
111
|
+
warn(
|
|
112
|
+
"create_transformation() is deprecated, refer to https://github.com/cecilearth/examples",
|
|
113
|
+
DeprecationWarning,
|
|
114
|
+
stacklevel=2,
|
|
115
|
+
)
|
|
77
116
|
res = self._post(
|
|
78
117
|
url="/v0/transformations",
|
|
79
118
|
model=TransformationCreate(
|
|
@@ -85,14 +124,32 @@ class Client:
|
|
|
85
124
|
return Transformation(**res)
|
|
86
125
|
|
|
87
126
|
def get_transformation(self, id: str) -> Transformation:
|
|
127
|
+
warn(
|
|
128
|
+
"get_transformation() is deprecated.",
|
|
129
|
+
DeprecationWarning,
|
|
130
|
+
stacklevel=2,
|
|
131
|
+
)
|
|
88
132
|
res = self._get(url=f"/v0/transformations/{id}")
|
|
89
133
|
return Transformation(**res)
|
|
90
134
|
|
|
91
135
|
def list_transformations(self) -> List[Transformation]:
|
|
136
|
+
warn(
|
|
137
|
+
"list_transformations() is deprecated.",
|
|
138
|
+
DeprecationWarning,
|
|
139
|
+
stacklevel=2,
|
|
140
|
+
)
|
|
92
141
|
res = self._get(url="/v0/transformations")
|
|
93
142
|
return [Transformation(**record) for record in res["records"]]
|
|
94
143
|
|
|
95
144
|
def query(self, sql: str) -> pd.DataFrame:
|
|
145
|
+
warn(
|
|
146
|
+
"query() is deprecated, use load_xarray() or load_dataframe() instead.",
|
|
147
|
+
DeprecationWarning,
|
|
148
|
+
stacklevel=2,
|
|
149
|
+
)
|
|
150
|
+
return self._query(sql)
|
|
151
|
+
|
|
152
|
+
def _query(self, sql: str) -> pd.DataFrame:
|
|
96
153
|
if self._snowflake_user_creds is None:
|
|
97
154
|
res = self._get(url="/v0/snowflake-user-credentials")
|
|
98
155
|
self._snowflake_user_creds = SnowflakeUserCredentials(**res)
|
|
@@ -196,6 +253,8 @@ class Client:
|
|
|
196
253
|
raise Error("unauthorised")
|
|
197
254
|
case 404:
|
|
198
255
|
_handle_not_found(err.response)
|
|
256
|
+
case 405:
|
|
257
|
+
_handle_method_not_allowed(err.response)
|
|
199
258
|
case 422:
|
|
200
259
|
_handle_unprocessable_entity(err.response)
|
|
201
260
|
case 429:
|
cecil/errors.py
CHANGED
|
@@ -36,6 +36,17 @@ def _handle_bad_request(response):
|
|
|
36
36
|
raise Error("bad request", details)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def _handle_method_not_allowed(response):
|
|
40
|
+
if not _is_json(response.text):
|
|
41
|
+
raise Error("method not allowed")
|
|
42
|
+
|
|
43
|
+
details = {}
|
|
44
|
+
for key, value in response.json().items():
|
|
45
|
+
details[_format_json_key(key)] = value
|
|
46
|
+
|
|
47
|
+
raise Error("method not allowed", details)
|
|
48
|
+
|
|
49
|
+
|
|
39
50
|
def _handle_not_found(response):
|
|
40
51
|
if not _is_json(response.text):
|
|
41
52
|
raise Error("resource not found")
|
cecil/models.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from typing import Dict, Optional
|
|
2
|
+
from typing import Dict, Optional, List
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, ConfigDict, SecretStr
|
|
5
5
|
from pydantic.alias_generators import to_camel
|
|
@@ -8,7 +8,7 @@ from pydantic.alias_generators import to_camel
|
|
|
8
8
|
class AOI(BaseModel):
|
|
9
9
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
10
10
|
id: str
|
|
11
|
-
|
|
11
|
+
external_ref: Optional[str]
|
|
12
12
|
geometry: Dict
|
|
13
13
|
hectares: float
|
|
14
14
|
created_at: datetime.datetime
|
|
@@ -18,7 +18,7 @@ class AOI(BaseModel):
|
|
|
18
18
|
class AOIRecord(BaseModel):
|
|
19
19
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
20
20
|
id: str
|
|
21
|
-
|
|
21
|
+
external_ref: Optional[str]
|
|
22
22
|
hectares: float
|
|
23
23
|
created_at: datetime.datetime
|
|
24
24
|
created_by: str
|
|
@@ -26,8 +26,8 @@ class AOIRecord(BaseModel):
|
|
|
26
26
|
|
|
27
27
|
class AOICreate(BaseModel):
|
|
28
28
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
29
|
-
name: str
|
|
30
29
|
geometry: Dict
|
|
30
|
+
external_ref: Optional[str]
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class DataRequest(BaseModel):
|
|
@@ -35,6 +35,7 @@ class DataRequest(BaseModel):
|
|
|
35
35
|
id: str
|
|
36
36
|
aoi_id: str
|
|
37
37
|
dataset_id: str
|
|
38
|
+
external_ref: Optional[str]
|
|
38
39
|
created_at: datetime.datetime
|
|
39
40
|
created_by: str
|
|
40
41
|
|
|
@@ -43,6 +44,7 @@ class DataRequestCreate(BaseModel):
|
|
|
43
44
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
44
45
|
aoi_id: str
|
|
45
46
|
dataset_id: str
|
|
47
|
+
external_ref: Optional[str]
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
class OrganisationSettings(BaseModel):
|
|
@@ -108,3 +110,54 @@ class UserCreate(BaseModel):
|
|
|
108
110
|
first_name: str
|
|
109
111
|
last_name: str
|
|
110
112
|
email: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Band(BaseModel):
|
|
116
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
117
|
+
variable_name: str
|
|
118
|
+
time: str
|
|
119
|
+
time_pattern: str
|
|
120
|
+
number: int
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class File(BaseModel):
|
|
124
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
125
|
+
url: str
|
|
126
|
+
bands: List[Band]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DataRequestMetadata(BaseModel):
|
|
130
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
131
|
+
provider_name: str
|
|
132
|
+
dataset_id: str
|
|
133
|
+
dataset_name: str
|
|
134
|
+
dataset_crs: str
|
|
135
|
+
aoi_id: str
|
|
136
|
+
data_request_id: str
|
|
137
|
+
files: List[File]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Bucket(BaseModel):
|
|
141
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
142
|
+
name: str
|
|
143
|
+
prefix: str
|
|
144
|
+
access_key_id: str
|
|
145
|
+
secret_access_key: str
|
|
146
|
+
session_token: str
|
|
147
|
+
expiration: datetime.datetime
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class DataRequestLoadXarray(BaseModel):
|
|
151
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
152
|
+
provider_name: str
|
|
153
|
+
dataset_id: str
|
|
154
|
+
dataset_name: str
|
|
155
|
+
dataset_crs: str
|
|
156
|
+
aoi_id: str
|
|
157
|
+
data_request_id: str
|
|
158
|
+
bucket: Bucket
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class DataRequestParquetFiles(BaseModel):
|
|
162
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
163
|
+
files: List[str]
|
cecil/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.30"
|
cecil/xarray.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
import rioxarray
|
|
7
|
+
import xarray
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from .errors import Error
|
|
12
|
+
from .models import DataRequestMetadata, DataRequestLoadXarray, Bucket
|
|
13
|
+
|
|
14
|
+
os.environ["GDAL_DISABLE_READDIR_ON_OPEN"] = "TRUE"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def align_pixel_grids(time_series):
|
|
18
|
+
# Use the first timestep as reference
|
|
19
|
+
reference_da = time_series[0]
|
|
20
|
+
aligned_series = [reference_da]
|
|
21
|
+
|
|
22
|
+
# Align all other timesteps to the reference grid
|
|
23
|
+
for i, da in enumerate(time_series[1:], 1):
|
|
24
|
+
try:
|
|
25
|
+
aligned_da = da.rio.reproject_match(reference_da)
|
|
26
|
+
aligned_series.append(aligned_da)
|
|
27
|
+
except Exception:
|
|
28
|
+
raise Error
|
|
29
|
+
|
|
30
|
+
return aligned_series
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def retry_with_exponential_backoff(
|
|
34
|
+
func, retries, start_delay, multiplier, *args, **kwargs
|
|
35
|
+
):
|
|
36
|
+
delay = start_delay
|
|
37
|
+
for attempt in range(1, retries + 1):
|
|
38
|
+
try:
|
|
39
|
+
return func(*args, **kwargs)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
if attempt == retries:
|
|
42
|
+
raise e
|
|
43
|
+
time.sleep(delay)
|
|
44
|
+
delay *= multiplier
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_file(url: str):
|
|
49
|
+
return rioxarray.open_rasterio(
|
|
50
|
+
url,
|
|
51
|
+
chunks={"x": 2000, "y": 2000},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def load_xarray(metadata: DataRequestMetadata) -> xarray.Dataset:
|
|
56
|
+
data_vars = {}
|
|
57
|
+
|
|
58
|
+
for f in metadata.files:
|
|
59
|
+
try:
|
|
60
|
+
dataset = retry_with_exponential_backoff(load_file, 5, 1, 2, f.url)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
raise ValueError(f"failed to load file: {e}")
|
|
63
|
+
|
|
64
|
+
for b in f.bands:
|
|
65
|
+
band = dataset.sel(band=b.number, drop=True)
|
|
66
|
+
|
|
67
|
+
if b.time and b.time_pattern:
|
|
68
|
+
t = datetime.strptime(b.time, b.time_pattern)
|
|
69
|
+
band = band.expand_dims("time")
|
|
70
|
+
band = band.assign_coords(time=[t])
|
|
71
|
+
|
|
72
|
+
band.name = b.variable_name
|
|
73
|
+
|
|
74
|
+
if b.variable_name not in data_vars:
|
|
75
|
+
data_vars[b.variable_name] = []
|
|
76
|
+
|
|
77
|
+
data_vars[b.variable_name].append(band)
|
|
78
|
+
|
|
79
|
+
for variable_name, time_series in data_vars.items():
|
|
80
|
+
if "time" in time_series[0].dims:
|
|
81
|
+
# time_series = align_pixel_grids(time_series)
|
|
82
|
+
data_vars[variable_name] = xarray.concat(
|
|
83
|
+
time_series, dim="time", join="exact"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
data_vars[variable_name] = time_series[0]
|
|
87
|
+
|
|
88
|
+
return xarray.Dataset(
|
|
89
|
+
data_vars=data_vars,
|
|
90
|
+
attrs={
|
|
91
|
+
"provider_name": metadata.provider_name,
|
|
92
|
+
"dataset_id": metadata.dataset_id,
|
|
93
|
+
"dataset_name": metadata.dataset_name,
|
|
94
|
+
"dataset_crs": metadata.dataset_crs,
|
|
95
|
+
"aoi_id": metadata.aoi_id,
|
|
96
|
+
"data_request_id": metadata.data_request_id,
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def load_xarray_v2(load_xarray_info: DataRequestLoadXarray) -> xarray.Dataset:
|
|
102
|
+
data_vars = {}
|
|
103
|
+
|
|
104
|
+
keys = _get_xarray_keys(load_xarray_info.bucket)
|
|
105
|
+
for key in keys:
|
|
106
|
+
timestamp_pattern = re.compile(r"\d{4}/\d{2}/\d{2}/\d{2}/\d{2}/\d{2}")
|
|
107
|
+
timestamp_str = timestamp_pattern.search(key).group()
|
|
108
|
+
|
|
109
|
+
variable_name = key.split("/")[14]
|
|
110
|
+
filename = f"s3://{load_xarray_info.bucket.name}/{key}"
|
|
111
|
+
dataset = rioxarray.open_rasterio(filename, chunks={"x": 2000, "y": 2000})
|
|
112
|
+
band = dataset.sel(band=1, drop=True)
|
|
113
|
+
band.name = variable_name
|
|
114
|
+
|
|
115
|
+
# Dataset without time information
|
|
116
|
+
if timestamp_str != "0000/00/00/00/00/00":
|
|
117
|
+
time = datetime.strptime(timestamp_str, "%Y/%m/%d/%H/%M/%S")
|
|
118
|
+
band = band.expand_dims("time")
|
|
119
|
+
band = band.assign_coords(time=[time])
|
|
120
|
+
|
|
121
|
+
if variable_name not in data_vars:
|
|
122
|
+
data_vars[variable_name] = []
|
|
123
|
+
|
|
124
|
+
data_vars[variable_name].append(band)
|
|
125
|
+
|
|
126
|
+
for variable_name, time_series in data_vars.items():
|
|
127
|
+
if "time" in time_series[0].dims:
|
|
128
|
+
data_vars[variable_name] = xarray.concat(
|
|
129
|
+
time_series, dim="time", join="exact"
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
data_vars[variable_name] = time_series[0]
|
|
133
|
+
|
|
134
|
+
return xarray.Dataset(
|
|
135
|
+
data_vars=data_vars,
|
|
136
|
+
attrs={
|
|
137
|
+
"provider_name": load_xarray_info.provider_name,
|
|
138
|
+
"dataset_id": load_xarray_info.dataset_id,
|
|
139
|
+
"dataset_name": load_xarray_info.dataset_name,
|
|
140
|
+
"dataset_crs": load_xarray_info.dataset_crs,
|
|
141
|
+
"aoi_id": load_xarray_info.aoi_id,
|
|
142
|
+
"data_request_id": load_xarray_info.data_request_id,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _get_xarray_keys(bucket: Bucket) -> list[str]:
|
|
148
|
+
os.environ["AWS_ACCESS_KEY_ID"] = bucket.access_key_id
|
|
149
|
+
os.environ["AWS_SECRET_ACCESS_KEY"] = bucket.secret_access_key
|
|
150
|
+
os.environ["AWS_SESSION_TOKEN"] = bucket.session_token
|
|
151
|
+
|
|
152
|
+
s3_client = boto3.client("s3")
|
|
153
|
+
|
|
154
|
+
paginator = s3_client.get_paginator("list_objects_v2")
|
|
155
|
+
page_iterator = paginator.paginate(
|
|
156
|
+
Bucket=bucket.name,
|
|
157
|
+
Prefix=bucket.prefix,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
keys = []
|
|
161
|
+
for page in page_iterator:
|
|
162
|
+
for obj in page.get("Contents", []):
|
|
163
|
+
keys.append(obj["Key"])
|
|
164
|
+
|
|
165
|
+
return keys
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cecil
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.30
|
|
4
4
|
Summary: Python SDK for Cecil Earth
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE.txt
|
|
@@ -8,10 +8,13 @@ Classifier: Development Status :: 4 - Beta
|
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Requires-Python: >=3.
|
|
12
|
-
Requires-Dist:
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: dask==2025.9.1
|
|
13
|
+
Requires-Dist: pydantic<3.0.0,>=2.11.9
|
|
14
|
+
Requires-Dist: requests<3.0.0,>=2.32.5
|
|
15
|
+
Requires-Dist: rioxarray==0.19.0
|
|
16
|
+
Requires-Dist: snowflake-connector-python[pandas]<4.0.0,>=3.17.4
|
|
17
|
+
Requires-Dist: xarray==2025.6.1
|
|
15
18
|
Description-Content-Type: text/markdown
|
|
16
19
|
|
|
17
20
|
# Cecil SDK
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
cecil/__init__.py,sha256=AEcRl73BDSAQe6W0d1PDD87IEcumARtREl7dCVa_YQY,86
|
|
2
|
+
cecil/client.py,sha256=mhe7l133-uy7hpRWjcb2s4DD4wsnxPqJLoEuBKMut5I,9501
|
|
3
|
+
cecil/errors.py,sha256=EnyYvFfU_JWYTTRax58bdwOndri2f-HzbqyzxtoV8uo,2100
|
|
4
|
+
cecil/models.py,sha256=lI4UulUv-J0Qh4zrm1UBuqS96CDewyL6sGWRP4AQEQs,4163
|
|
5
|
+
cecil/version.py,sha256=8ZeepqkW4DvpVeNm92mx0tIzgvVevS4NKWkTXXHuXNY,23
|
|
6
|
+
cecil/xarray.py,sha256=K3IRfTkdWAJVUK0LgZLjztpeqhC4QptkrdNg9WYoIVk,5024
|
|
7
|
+
cecil-0.0.30.dist-info/METADATA,sha256=-G1QZ40hNHT_Nz8p5G9PFpqZTTnZoYeW6eEslNdlUGw,2800
|
|
8
|
+
cecil-0.0.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
+
cecil-0.0.30.dist-info/licenses/LICENSE.txt,sha256=mUexcmfYx3bG1VIzAdQTOf_NzStYw6-QkKVdUY_d4i4,1066
|
|
10
|
+
cecil-0.0.30.dist-info/RECORD,,
|
cecil-0.0.21.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
cecil/__init__.py,sha256=AEcRl73BDSAQe6W0d1PDD87IEcumARtREl7dCVa_YQY,86
|
|
2
|
-
cecil/client.py,sha256=TXcTdNUF8ER8gs8KQBzHIsoQPqDLj3npFeF0PWEi4EI,7430
|
|
3
|
-
cecil/errors.py,sha256=ZNiSTYH2MgNZ7tNIgV07-Ge3KtmdncfzWiBi9yjURGs,1818
|
|
4
|
-
cecil/models.py,sha256=GpW1pT9NBKS5y4Os0pW8UR3MO9kVJe1r8jOK-MjADLA,2799
|
|
5
|
-
cecil/version.py,sha256=PsqtE_T084MVsMv47JyTQ3DK2CRZJ3Kd9Q_vnw02oZk,23
|
|
6
|
-
cecil-0.0.21.dist-info/METADATA,sha256=2Q9DsNWUMtimtqNDdDqbssyFGT33DgWaVjrYHrkLmrs,2659
|
|
7
|
-
cecil-0.0.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
cecil-0.0.21.dist-info/licenses/LICENSE.txt,sha256=mUexcmfYx3bG1VIzAdQTOf_NzStYw6-QkKVdUY_d4i4,1066
|
|
9
|
-
cecil-0.0.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|