cecil 0.0.16__py3-none-any.whl → 0.0.18__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 +70 -23
- cecil/errors.py +12 -0
- cecil/models.py +35 -2
- cecil/version.py +1 -1
- {cecil-0.0.16.dist-info → cecil-0.0.18.dist-info}/METADATA +1 -1
- cecil-0.0.18.dist-info/RECORD +9 -0
- cecil-0.0.16.dist-info/RECORD +0 -9
- {cecil-0.0.16.dist-info → cecil-0.0.18.dist-info}/WHEEL +0 -0
- {cecil-0.0.16.dist-info → cecil-0.0.18.dist-info}/licenses/LICENSE.txt +0 -0
cecil/client.py
CHANGED
|
@@ -1,45 +1,48 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
2
5
|
import requests
|
|
3
6
|
import snowflake.connector
|
|
4
|
-
|
|
5
7
|
from pydantic import BaseModel
|
|
6
8
|
from requests import auth
|
|
7
|
-
from
|
|
9
|
+
from cryptography.hazmat.primitives import serialization
|
|
8
10
|
|
|
9
11
|
from .errors import (
|
|
10
12
|
Error,
|
|
11
13
|
_handle_bad_request,
|
|
12
14
|
_handle_not_found,
|
|
15
|
+
_handle_too_many_requests,
|
|
13
16
|
_handle_unprocessable_entity,
|
|
14
17
|
)
|
|
15
|
-
|
|
16
18
|
from .models import (
|
|
17
19
|
AOI,
|
|
18
20
|
AOICreate,
|
|
19
21
|
DataRequest,
|
|
20
22
|
DataRequestCreate,
|
|
21
|
-
|
|
22
|
-
TransformationCreate,
|
|
23
|
+
OrganisationCreate,
|
|
23
24
|
RecoverAPIKey,
|
|
24
25
|
RecoverAPIKeyRequest,
|
|
25
26
|
RotateAPIKey,
|
|
26
27
|
RotateAPIKeyRequest,
|
|
27
|
-
|
|
28
|
+
SignUpRequest,
|
|
29
|
+
SignUpResponse,
|
|
30
|
+
SnowflakeUserCredentials,
|
|
31
|
+
Transformation,
|
|
32
|
+
TransformationCreate,
|
|
33
|
+
User,
|
|
34
|
+
UserCreate,
|
|
28
35
|
)
|
|
29
|
-
|
|
30
36
|
from .version import __version__
|
|
31
37
|
|
|
32
|
-
# TODO: Documentation (Google style)
|
|
33
|
-
# TODO: Add HTTP retries
|
|
34
|
-
|
|
35
38
|
|
|
36
39
|
class Client:
|
|
37
|
-
def __init__(self, env=None):
|
|
40
|
+
def __init__(self, env: str = None) -> None:
|
|
38
41
|
self._api_auth = None
|
|
39
42
|
self._base_url = (
|
|
40
43
|
"https://api.cecil.earth" if env is None else f"https://{env}.cecil.earth"
|
|
41
44
|
)
|
|
42
|
-
self.
|
|
45
|
+
self._snowflake_user_creds = None
|
|
43
46
|
|
|
44
47
|
def create_aoi(self, name: str, geometry: Dict) -> AOI:
|
|
45
48
|
# TODO: validate geometry
|
|
@@ -65,14 +68,13 @@ class Client:
|
|
|
65
68
|
res = self._get(url=f"/v0/data-requests/{id}")
|
|
66
69
|
return DataRequest(**res)
|
|
67
70
|
|
|
68
|
-
def list_data_requests(self):
|
|
71
|
+
def list_data_requests(self) -> List[DataRequest]:
|
|
69
72
|
res = self._get(url="/v0/data-requests")
|
|
70
73
|
return [DataRequest(**record) for record in res["records"]]
|
|
71
74
|
|
|
72
75
|
def create_transformation(
|
|
73
76
|
self, data_request_id: str, crs: str, spatial_resolution: float
|
|
74
77
|
) -> Transformation:
|
|
75
|
-
# TODO: check if data request is completed before creating transformation
|
|
76
78
|
res = self._post(
|
|
77
79
|
url="/v0/transformations",
|
|
78
80
|
model=TransformationCreate(
|
|
@@ -91,15 +93,20 @@ class Client:
|
|
|
91
93
|
res = self._get(url="/v0/transformations")
|
|
92
94
|
return [Transformation(**record) for record in res["records"]]
|
|
93
95
|
|
|
94
|
-
def query(self, sql):
|
|
95
|
-
if self.
|
|
96
|
-
res = self._get(url="/v0/
|
|
97
|
-
self.
|
|
96
|
+
def query(self, sql: str) -> pd.DataFrame:
|
|
97
|
+
if self._snowflake_user_creds is None:
|
|
98
|
+
res = self._get(url="/v0/snowflake-user-credentials")
|
|
99
|
+
self._snowflake_user_creds = SnowflakeUserCredentials(**res)
|
|
100
|
+
|
|
101
|
+
private_key = serialization.load_pem_private_key(
|
|
102
|
+
self._snowflake_user_creds.private_key.get_secret_value().encode(),
|
|
103
|
+
password=None,
|
|
104
|
+
)
|
|
98
105
|
|
|
99
106
|
with snowflake.connector.connect(
|
|
100
|
-
account=self.
|
|
101
|
-
user=self.
|
|
102
|
-
|
|
107
|
+
account=self._snowflake_user_creds.account.get_secret_value(),
|
|
108
|
+
user=self._snowflake_user_creds.user.get_secret_value(),
|
|
109
|
+
private_key=private_key,
|
|
103
110
|
) as conn:
|
|
104
111
|
df = conn.cursor().execute(sql).fetch_pandas_all()
|
|
105
112
|
df.columns = [x.lower() for x in df.columns]
|
|
@@ -108,7 +115,7 @@ class Client:
|
|
|
108
115
|
|
|
109
116
|
def recover_api_key(self, email: str) -> RecoverAPIKey:
|
|
110
117
|
res = self._post(
|
|
111
|
-
url=
|
|
118
|
+
url="/v0/api-key/recover",
|
|
112
119
|
model=RecoverAPIKeyRequest(email=email),
|
|
113
120
|
skip_auth=True,
|
|
114
121
|
)
|
|
@@ -116,10 +123,43 @@ class Client:
|
|
|
116
123
|
return RecoverAPIKey(**res)
|
|
117
124
|
|
|
118
125
|
def rotate_api_key(self) -> RotateAPIKey:
|
|
119
|
-
res = self._post(url=f"/v0/
|
|
126
|
+
res = self._post(url=f"/v0/api-key/rotate", model=RotateAPIKeyRequest())
|
|
120
127
|
|
|
121
128
|
return RotateAPIKey(**res)
|
|
122
129
|
|
|
130
|
+
def sign_up(
|
|
131
|
+
self, organisation: Dict[str, str], user: Dict[str, str]
|
|
132
|
+
) -> SignUpResponse:
|
|
133
|
+
res = self._post(
|
|
134
|
+
url="/v0/sign-up",
|
|
135
|
+
model=SignUpRequest(
|
|
136
|
+
organisation=OrganisationCreate(**organisation),
|
|
137
|
+
user=UserCreate(**user),
|
|
138
|
+
),
|
|
139
|
+
skip_auth=True,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return SignUpResponse(**res)
|
|
143
|
+
|
|
144
|
+
def create_user(self, first_name: str, last_name: str, email: str) -> User:
|
|
145
|
+
res = self._post(
|
|
146
|
+
url="/v0/users",
|
|
147
|
+
model=UserCreate(
|
|
148
|
+
first_name=first_name,
|
|
149
|
+
last_name=last_name,
|
|
150
|
+
email=email,
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
return User(**res)
|
|
154
|
+
|
|
155
|
+
def get_user(self, id: str) -> User:
|
|
156
|
+
res = self._get(url=f"/v0/users/{id}")
|
|
157
|
+
return User(**res)
|
|
158
|
+
|
|
159
|
+
def list_users(self) -> List[User]:
|
|
160
|
+
res = self._get(url="/v0/users")
|
|
161
|
+
return [User(**record) for record in res["records"]]
|
|
162
|
+
|
|
123
163
|
def _request(self, method: str, url: str, skip_auth=False, **kwargs) -> Dict:
|
|
124
164
|
|
|
125
165
|
if skip_auth is False:
|
|
@@ -141,7 +181,12 @@ class Client:
|
|
|
141
181
|
|
|
142
182
|
except requests.exceptions.ConnectionError:
|
|
143
183
|
raise Error("failed to connect to the Cecil Platform")
|
|
184
|
+
|
|
144
185
|
except requests.exceptions.HTTPError as err:
|
|
186
|
+
message = f"Request failed with status code {err.response.status_code}"
|
|
187
|
+
if err.response.text != "":
|
|
188
|
+
message += f": {err.response.text}"
|
|
189
|
+
|
|
145
190
|
match err.response.status_code:
|
|
146
191
|
case 400:
|
|
147
192
|
_handle_bad_request(err.response)
|
|
@@ -151,6 +196,8 @@ class Client:
|
|
|
151
196
|
_handle_not_found(err.response)
|
|
152
197
|
case 422:
|
|
153
198
|
_handle_unprocessable_entity(err.response)
|
|
199
|
+
case 429:
|
|
200
|
+
_handle_too_many_requests(err.response)
|
|
154
201
|
case 500:
|
|
155
202
|
raise Error("internal server error")
|
|
156
203
|
case _:
|
cecil/errors.py
CHANGED
|
@@ -47,6 +47,18 @@ def _handle_not_found(response):
|
|
|
47
47
|
raise Error("resource not found", details)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def _handle_too_many_requests(response):
|
|
51
|
+
if not _is_json(response.text):
|
|
52
|
+
raise Error("too many requests")
|
|
53
|
+
|
|
54
|
+
details = {}
|
|
55
|
+
|
|
56
|
+
for key, value in response.json().items():
|
|
57
|
+
details[_format_json_key(key)] = value
|
|
58
|
+
|
|
59
|
+
raise Error("too many requests", details)
|
|
60
|
+
|
|
61
|
+
|
|
50
62
|
def _handle_unprocessable_entity(response):
|
|
51
63
|
if not _is_json(response.text):
|
|
52
64
|
raise Error(f"failed to process request")
|
cecil/models.py
CHANGED
|
@@ -36,6 +36,11 @@ class DataRequestCreate(BaseModel):
|
|
|
36
36
|
dataset_id: str
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
class OrganisationCreate(BaseModel):
|
|
40
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
41
|
+
name: str
|
|
42
|
+
|
|
43
|
+
|
|
39
44
|
class RecoverAPIKey(BaseModel):
|
|
40
45
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
41
46
|
message: str
|
|
@@ -72,8 +77,36 @@ class TransformationCreate(BaseModel):
|
|
|
72
77
|
spatial_resolution: float
|
|
73
78
|
|
|
74
79
|
|
|
75
|
-
class
|
|
80
|
+
class SnowflakeUserCredentials(BaseModel):
|
|
76
81
|
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
77
82
|
account: SecretStr
|
|
78
83
|
user: SecretStr
|
|
79
|
-
|
|
84
|
+
private_key: SecretStr
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class User(BaseModel):
|
|
88
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
89
|
+
id: str
|
|
90
|
+
first_name: str
|
|
91
|
+
last_name: str
|
|
92
|
+
email: str
|
|
93
|
+
created_at: datetime.datetime
|
|
94
|
+
created_by: str
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class UserCreate(BaseModel):
|
|
98
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
99
|
+
first_name: str
|
|
100
|
+
last_name: str
|
|
101
|
+
email: str
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SignUpRequest(BaseModel):
|
|
105
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
106
|
+
organisation: OrganisationCreate
|
|
107
|
+
user: UserCreate
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SignUpResponse(BaseModel):
|
|
111
|
+
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
|
|
112
|
+
message: str
|
cecil/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.18"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cecil/__init__.py,sha256=AEcRl73BDSAQe6W0d1PDD87IEcumARtREl7dCVa_YQY,86
|
|
2
|
+
cecil/client.py,sha256=S6j7jwDdp02eFB3c3IlQ2b6h3qaVb7UCUR7Dh_wr66o,7288
|
|
3
|
+
cecil/errors.py,sha256=ZNiSTYH2MgNZ7tNIgV07-Ge3KtmdncfzWiBi9yjURGs,1818
|
|
4
|
+
cecil/models.py,sha256=BjSJlcRBSuJjUoTHa4y-2i-9dIGct9giLvg7DQPgerQ,2841
|
|
5
|
+
cecil/version.py,sha256=qgKF-lRlzaBqf95e1sodHCZkSUCbz7ECKSeYHwXfAvI,23
|
|
6
|
+
cecil-0.0.18.dist-info/METADATA,sha256=vrVGp0w4-sX9iz5wwW1eEGLkdHK6ciqrpZ8-YjoRl_w,2659
|
|
7
|
+
cecil-0.0.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
cecil-0.0.18.dist-info/licenses/LICENSE.txt,sha256=mUexcmfYx3bG1VIzAdQTOf_NzStYw6-QkKVdUY_d4i4,1066
|
|
9
|
+
cecil-0.0.18.dist-info/RECORD,,
|
cecil-0.0.16.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
cecil/__init__.py,sha256=AEcRl73BDSAQe6W0d1PDD87IEcumARtREl7dCVa_YQY,86
|
|
2
|
-
cecil/client.py,sha256=hykvGQrpuAEQR_TawLKCwc_mWsjD72uxAZHaOTZjf1M,5764
|
|
3
|
-
cecil/errors.py,sha256=r9NNRtv_oO_hblnh-VOlAf767cJqmlnbJfHB63OA_zY,1538
|
|
4
|
-
cecil/models.py,sha256=xkjrLAOenJboRtzRPihT-C8jpadngePz4KzLNJoemHM,2012
|
|
5
|
-
cecil/version.py,sha256=8ss7zPyQ3YfaQJw9IIGX35NOL7ASZ7-LErAxKnQDN7c,23
|
|
6
|
-
cecil-0.0.16.dist-info/METADATA,sha256=7f2D9tc10goXMoeFgZqjHWeENrVyUq6H0OXmma75eWY,2659
|
|
7
|
-
cecil-0.0.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
cecil-0.0.16.dist-info/licenses/LICENSE.txt,sha256=mUexcmfYx3bG1VIzAdQTOf_NzStYw6-QkKVdUY_d4i4,1066
|
|
9
|
-
cecil-0.0.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|