cecil 0.0.7__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/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .client import Client
cecil/client.py ADDED
@@ -0,0 +1,131 @@
1
+ import os
2
+ from pydantic import BaseModel
3
+ from typing import Dict, List
4
+ import requests
5
+
6
+ from requests import auth
7
+
8
+ import snowflake.connector
9
+
10
+ from .models import (
11
+ AOI,
12
+ AOICreate,
13
+ DataRequest,
14
+ DataRequestCreate,
15
+ Reprojection,
16
+ ReprojectionCreate,
17
+ SnowflakeCredentials,
18
+ )
19
+
20
+ # TODO: Documentation (Google style)
21
+ # TODO: Add HTTP retries
22
+
23
+
24
+ class Client:
25
+ def __init__(self, env="dev"):
26
+ self._api_auth = None
27
+ self._base_url = f"https://{env}-api.cecil.earth" # TODO: hard-code to prod URL
28
+ self._snowflake_creds = None
29
+
30
+ def create_aoi(self, name: str, geometry: Dict) -> AOI:
31
+ # TODO: validate geometry
32
+ res = self._post(url="/v0/aois", model=AOICreate(name=name, geometry=geometry))
33
+ return AOI(**res)
34
+
35
+ def get_aoi(self, id: str) -> AOI:
36
+ res = self._get(url=f"/v0/aois/{id}")
37
+ return AOI(**res)
38
+
39
+ def list_aois(self) -> List[AOI]:
40
+ res = self._get(url="/v0/aois")
41
+ return [AOI(**record) for record in res["records"]]
42
+
43
+ def create_data_request(self, aoi_id: str, dataset_id: str) -> DataRequest:
44
+ res = self._post(
45
+ url="/v0/data-requests",
46
+ model=DataRequestCreate(aoi_id=aoi_id, dataset_id=dataset_id),
47
+ )
48
+ return DataRequest(**res)
49
+
50
+ def get_data_request(self, id: str) -> DataRequest:
51
+ res = self._get(url=f"/v0/data-requests/{id}")
52
+ return DataRequest(**res)
53
+
54
+ def list_data_requests(self):
55
+ res = self._get(url="/v0/data-requests")
56
+ return [DataRequest(**record) for record in res["records"]]
57
+
58
+ def create_reprojection(
59
+ self, data_request_id: str, crs: str, resolution: float
60
+ ) -> Reprojection:
61
+ # TODO: check if data request is completed before creating reprojection
62
+ res = self._post(
63
+ url="/v0/reprojections",
64
+ model=ReprojectionCreate(
65
+ data_request_id=data_request_id,
66
+ crs=crs,
67
+ resolution=resolution,
68
+ ),
69
+ )
70
+ return Reprojection(**res)
71
+
72
+ def get_reprojection(self, id: str) -> Reprojection:
73
+ res = self._get(url=f"/v0/reprojections/{id}")
74
+ return Reprojection(**res)
75
+
76
+ def list_reprojections(self) -> List[Reprojection]:
77
+ res = self._get(url="/v0/reprojections")
78
+ return [Reprojection(**record) for record in res["records"]]
79
+
80
+ def query(self, sql):
81
+ if self._snowflake_creds is None:
82
+ res = self._get(url="/v0/data-access-credentials")
83
+ self._snowflake_creds = SnowflakeCredentials(**res)
84
+
85
+ with snowflake.connector.connect(
86
+ account=self._snowflake_creds.account.get_secret_value(),
87
+ user=self._snowflake_creds.user.get_secret_value(),
88
+ password=self._snowflake_creds.password.get_secret_value(),
89
+ ) as conn:
90
+ df = conn.cursor().execute(sql).fetch_pandas_all()
91
+ df.columns = [x.lower() for x in df.columns]
92
+
93
+ return df
94
+
95
+ def _request(self, method: str, url: str, **kwargs) -> Dict:
96
+
97
+ self._set_auth()
98
+
99
+ try:
100
+ r = requests.request(
101
+ method=method,
102
+ url=self._base_url + url,
103
+ auth=self._api_auth,
104
+ timeout=None,
105
+ **kwargs,
106
+ )
107
+ r.raise_for_status()
108
+ return r.json()
109
+
110
+ except requests.exceptions.ConnectionError as err:
111
+ raise ValueError("Connection error") from err
112
+ except requests.exceptions.HTTPError as err:
113
+ if err.response.status_code == 403:
114
+ raise ValueError("Authentication error") from err
115
+ else:
116
+ raise
117
+
118
+ def _get(self, url: str, **kwargs) -> Dict:
119
+ return self._request(method="get", url=url, **kwargs)
120
+
121
+ def _post(self, url: str, model: BaseModel, **kwargs) -> Dict:
122
+ return self._request(
123
+ method="post", url=url, json=model.model_dump(by_alias=True), **kwargs
124
+ )
125
+
126
+ def _set_auth(self) -> None:
127
+ try:
128
+ api_key = os.environ["CECIL_API_KEY"]
129
+ self._api_auth = auth.HTTPBasicAuth(username=api_key, password="")
130
+ except KeyError:
131
+ raise ValueError("environment variable CECIL_API_KEY not set") from None
cecil/models.py ADDED
@@ -0,0 +1,80 @@
1
+ import datetime
2
+ from enum import Enum
3
+ from typing import Dict, List, Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, SecretStr
6
+ from pydantic.alias_generators import to_camel
7
+
8
+
9
+ class SubRequestStatus(str, Enum):
10
+ COMPLETED = "completed"
11
+ FAILED = "failed"
12
+ PROCESSING = "processing"
13
+
14
+
15
+ class DataRequestStatus(str, Enum):
16
+ COMPLETED = "completed"
17
+ FAILED = "failed"
18
+ PROCESSING = "processing"
19
+
20
+
21
+ class AOI(BaseModel):
22
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
23
+ id: str
24
+ name: str
25
+ geometry: Dict
26
+ hectares: float
27
+ created: datetime.datetime
28
+
29
+
30
+ class AOICreate(BaseModel):
31
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
32
+ name: str
33
+ geometry: Dict
34
+
35
+
36
+ class SubRequest(BaseModel):
37
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
38
+ external_id: str
39
+ description: str
40
+ status: SubRequestStatus
41
+ error_message: Optional[str]
42
+
43
+
44
+ class DataRequest(BaseModel):
45
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
46
+ id: str
47
+ aoi_id: str
48
+ dataset_id: str
49
+ sub_requests: List[SubRequest]
50
+ status: DataRequestStatus
51
+ created: datetime.datetime
52
+
53
+
54
+ class DataRequestCreate(BaseModel):
55
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
56
+ aoi_id: str
57
+ dataset_id: str
58
+
59
+
60
+ class Reprojection(BaseModel):
61
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
62
+ id: str
63
+ data_request_id: str
64
+ crs: str
65
+ resolution: float
66
+ created: datetime.datetime
67
+
68
+
69
+ class ReprojectionCreate(BaseModel):
70
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
71
+ data_request_id: str
72
+ crs: str
73
+ resolution: float
74
+
75
+
76
+ class SnowflakeCredentials(BaseModel):
77
+ model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
78
+ account: SecretStr
79
+ user: SecretStr
80
+ password: SecretStr
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.3
2
+ Name: cecil
3
+ Version: 0.0.7
4
+ Summary: Python SDK for Cecil Earth
5
+ License-Expression: MIT
6
+ License-File: LICENSE.txt
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.8
12
+ Requires-Dist: pydantic
13
+ Requires-Dist: requests
14
+ Requires-Dist: snowflake-connector-python[pandas]
15
+ Description-Content-Type: text/markdown
16
+
17
+ # Cecil SDK
18
+
19
+ [![PyPI - Version](https://img.shields.io/pypi/v/cecil-sdk.svg)](https://pypi.org/project/cecil-sdk)
20
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cecil-sdk.svg)](https://pypi.org/project/cecil-sdk)
21
+
22
+ -----
23
+
24
+ ## Table of Contents
25
+
26
+ - [Installation](#installation)
27
+ - [Authentication](#authentication)
28
+ - [License](#license)
29
+ - [Examples](#examples)
30
+
31
+ ## Installation
32
+
33
+ ```shell
34
+ pip install cecil
35
+ ```
36
+
37
+ ## Authentication
38
+
39
+ Set `CECIL_API_KEY` environment variable to your Cecil API key.
40
+
41
+ ## Examples
42
+
43
+ ### Create an AOI and data request using the Cecil client
44
+
45
+ ```python
46
+ import cecil
47
+
48
+ client = cecil.Client()
49
+
50
+ my_aoi = client.create_aoi(
51
+ name="My AOI",
52
+ geometry={
53
+ "type": "Polygon",
54
+ "coordinates": [
55
+ [
56
+ [145.410408835, -42.004083838],
57
+ [145.410408835, -42.004203978],
58
+ [145.410623191, -42.004203978],
59
+ [145.410623191, -42.004083838],
60
+ [145.410408835, -42.004083838],
61
+ ]
62
+ ],
63
+ },
64
+ )
65
+
66
+ # Get dataset ID from docs.cecil.earth -> Datasets
67
+ planet_forest_carbon_diligence_id = "c2dd4f55-56f6-4d05-aae3-ba7c1dcd812f"
68
+
69
+ my_data_request = client.create_data_request(
70
+ aoi_id=my_aoi.id,
71
+ dataset_id=planet_forest_carbon_diligence_id,
72
+ )
73
+
74
+ print(client.get_data_request(my_data_request.id).status)
75
+ ```
76
+
77
+ ### Create a reprojection using the Cecil client (once data request is completed)
78
+
79
+ ```python
80
+ my_reprojection = client.create_reprojection(
81
+ data_request_id=my_data_request.id,
82
+ crs="EPSG:4326",
83
+ resolution=0.005,
84
+ )
85
+
86
+ print(client.get_reprojection(my_reprojection.id).status)
87
+ ```
88
+
89
+ ### Query data (once reprojection is completed)
90
+
91
+ ```python
92
+ df = client.query(f'''
93
+ SELECT *
94
+ FROM
95
+ planet.forest_carbon_diligence
96
+ WHERE
97
+ reprojection_id = '{my_reprojection.id}'
98
+ ''')
99
+ ```
100
+
101
+ ### Other client methods:
102
+
103
+ ```python
104
+ client.list_aois()
105
+
106
+ client.get_aoi(my_aoi.id)
107
+
108
+ client.list_data_requests()
109
+
110
+ client.get_data_request(my_data_request.id)
111
+
112
+ client.list_reprojections()
113
+
114
+ client.get_reprojection(my_reprojection.id)
115
+ ```
116
+
117
+ ## License
118
+
119
+ `cecil` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,7 @@
1
+ cecil/__init__.py,sha256=MF64bwUCd4sm3dvcxZnkb4ujHtxT_KeuXdD0nVieEt4,27
2
+ cecil/client.py,sha256=VEWL90sGYxcj4lkGBwT9k6H0v4rNPdvZy6iGHby7N40,4285
3
+ cecil/models.py,sha256=srsNT0RKe_mIaodVlnKo71nKPGSc5_o9Y-r988B8Zsw,1958
4
+ cecil-0.0.7.dist-info/METADATA,sha256=g2E1P1MyxoT2feDVBhQvndfgS7jAuXOWCLijLgWCxq0,2676
5
+ cecil-0.0.7.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
6
+ cecil-0.0.7.dist-info/licenses/LICENSE.txt,sha256=mUexcmfYx3bG1VIzAdQTOf_NzStYw6-QkKVdUY_d4i4,1066
7
+ cecil-0.0.7.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 cecilearth
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.