cecil 0.0.16__tar.gz → 0.0.18__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.

Potentially problematic release.


This version of cecil might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cecil
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: Python SDK for Cecil Earth
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE.txt
@@ -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 typing import Dict, List
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
- Transformation,
22
- TransformationCreate,
23
+ OrganisationCreate,
23
24
  RecoverAPIKey,
24
25
  RecoverAPIKeyRequest,
25
26
  RotateAPIKey,
26
27
  RotateAPIKeyRequest,
27
- SnowflakeCredentials,
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._snowflake_creds = None
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._snowflake_creds is None:
96
- res = self._get(url="/v0/data-access-credentials")
97
- self._snowflake_creds = SnowflakeCredentials(**res)
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._snowflake_creds.account.get_secret_value(),
101
- user=self._snowflake_creds.user.get_secret_value(),
102
- password=self._snowflake_creds.password.get_secret_value(),
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=f"/v0/recover-api-key",
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/rotate-api-key", model=RotateAPIKeyRequest())
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 _:
@@ -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")
@@ -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 SnowflakeCredentials(BaseModel):
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
- password: SecretStr
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
@@ -0,0 +1 @@
1
+ __version__ = "0.0.18"
@@ -1 +0,0 @@
1
- __version__ = "0.0.16"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes