luml_api 0.1.0__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.
- luml_api/__init__.py +51 -0
- luml_api/_base_client.py +284 -0
- luml_api/_client.py +576 -0
- luml_api/_exceptions.py +203 -0
- luml_api/_types.py +257 -0
- luml_api/_utils.py +21 -0
- luml_api/handlers/__init__.py +0 -0
- luml_api/handlers/azure_file_handler.py +139 -0
- luml_api/handlers/base_file_handler.py +115 -0
- luml_api/handlers/file_handler_factory.py +15 -0
- luml_api/handlers/model_artifacts.py +121 -0
- luml_api/handlers/progress.py +0 -0
- luml_api/handlers/s3_file_handler.py +166 -0
- luml_api/py.typed +0 -0
- luml_api/resources/__init__.py +0 -0
- luml_api/resources/_listed_resource.py +60 -0
- luml_api/resources/_validators.py +42 -0
- luml_api/resources/artifacts.py +2066 -0
- luml_api/resources/bucket_secrets.py +827 -0
- luml_api/resources/collections.py +935 -0
- luml_api/resources/orbits.py +576 -0
- luml_api/resources/organizations.py +219 -0
- luml_api/services/__init__.py +0 -0
- luml_api/services/upload_service.py +103 -0
- luml_api/utils/__init__.py +0 -0
- luml_api/utils/progress.py +89 -0
- luml_api-0.1.0.dist-info/METADATA +10 -0
- luml_api-0.1.0.dist-info/RECORD +29 -0
- luml_api-0.1.0.dist-info/WHEEL +4 -0
luml_api/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
2
|
+
__title__ = "luml-sdk"
|
|
3
|
+
|
|
4
|
+
from luml_api._client import AsyncLumlClient, LumlClient
|
|
5
|
+
from luml_api._exceptions import (
|
|
6
|
+
APIError,
|
|
7
|
+
APIResponseValidationError,
|
|
8
|
+
APIStatusError,
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
BadRequestError,
|
|
11
|
+
ConflictError,
|
|
12
|
+
InternalServerError,
|
|
13
|
+
LumlAPIError,
|
|
14
|
+
NotFoundError,
|
|
15
|
+
PermissionDeniedError,
|
|
16
|
+
UnprocessableEntityError,
|
|
17
|
+
)
|
|
18
|
+
from luml_api._types import (
|
|
19
|
+
ArtifactSortBy,
|
|
20
|
+
ArtifactStatus,
|
|
21
|
+
ArtifactType,
|
|
22
|
+
CollectionSortBy,
|
|
23
|
+
CollectionType,
|
|
24
|
+
CollectionTypeFilter,
|
|
25
|
+
SortOrder,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"__version__",
|
|
30
|
+
"__title__",
|
|
31
|
+
"LumlClient",
|
|
32
|
+
"AsyncLumlClient",
|
|
33
|
+
"LumlAPIError",
|
|
34
|
+
"APIError",
|
|
35
|
+
"APIResponseValidationError",
|
|
36
|
+
"APIStatusError",
|
|
37
|
+
"BadRequestError",
|
|
38
|
+
"AuthenticationError",
|
|
39
|
+
"PermissionDeniedError",
|
|
40
|
+
"NotFoundError",
|
|
41
|
+
"ConflictError",
|
|
42
|
+
"UnprocessableEntityError",
|
|
43
|
+
"InternalServerError",
|
|
44
|
+
"ArtifactSortBy",
|
|
45
|
+
"ArtifactStatus",
|
|
46
|
+
"ArtifactType",
|
|
47
|
+
"CollectionSortBy",
|
|
48
|
+
"CollectionType",
|
|
49
|
+
"CollectionTypeFilter",
|
|
50
|
+
"SortOrder",
|
|
51
|
+
]
|
luml_api/_base_client.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from httpx import URL
|
|
6
|
+
|
|
7
|
+
from luml_api._exceptions import (
|
|
8
|
+
APIStatusError,
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
BadRequestError,
|
|
11
|
+
ConflictError,
|
|
12
|
+
InternalServerError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
PermissionDeniedError,
|
|
15
|
+
UnprocessableEntityError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseClient(ABC):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
base_url: str | URL,
|
|
23
|
+
timeout: float = 30.0,
|
|
24
|
+
) -> None:
|
|
25
|
+
self._base_url: URL = URL(base_url) if isinstance(base_url, str) else base_url
|
|
26
|
+
self._timeout: float = timeout
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def base_url(self) -> URL:
|
|
30
|
+
return self._base_url
|
|
31
|
+
|
|
32
|
+
@base_url.setter
|
|
33
|
+
def base_url(self, url: URL) -> None:
|
|
34
|
+
self._base_url = url
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def timeout(self) -> float:
|
|
38
|
+
return self._timeout
|
|
39
|
+
|
|
40
|
+
@timeout.setter
|
|
41
|
+
def timeout(self, timeout: float) -> None:
|
|
42
|
+
self._timeout = timeout
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def auth_headers(self) -> dict[str, str]:
|
|
46
|
+
return {}
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def default_headers(self) -> dict[str, str]:
|
|
50
|
+
return {
|
|
51
|
+
"Accept": "application/json",
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
"User-Agent": "luml-sdk/0.1.0",
|
|
54
|
+
**self.auth_headers,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _make_status_error(
|
|
59
|
+
err_msg: str,
|
|
60
|
+
*,
|
|
61
|
+
body: object,
|
|
62
|
+
response: httpx.Response,
|
|
63
|
+
) -> APIStatusError:
|
|
64
|
+
if response.status_code == 400:
|
|
65
|
+
return BadRequestError(err_msg, response=response, body=body)
|
|
66
|
+
|
|
67
|
+
if response.status_code == 401:
|
|
68
|
+
return AuthenticationError(err_msg, response=response, body=body)
|
|
69
|
+
|
|
70
|
+
if response.status_code == 403:
|
|
71
|
+
return PermissionDeniedError(err_msg, response=response, body=body)
|
|
72
|
+
|
|
73
|
+
if response.status_code == 404:
|
|
74
|
+
return NotFoundError(err_msg, response=response, body=body)
|
|
75
|
+
|
|
76
|
+
if response.status_code == 409:
|
|
77
|
+
return ConflictError(err_msg, response=response, body=body)
|
|
78
|
+
|
|
79
|
+
if response.status_code == 422:
|
|
80
|
+
return UnprocessableEntityError(err_msg, response=response, body=body)
|
|
81
|
+
|
|
82
|
+
if response.status_code >= 500:
|
|
83
|
+
return InternalServerError(err_msg, response=response, body=body)
|
|
84
|
+
return APIStatusError(err_msg, response=response, body=body)
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def _process_response(self, response: httpx.Response) -> Any: # noqa: ANN401
|
|
88
|
+
raise NotImplementedError()
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def request(
|
|
92
|
+
self,
|
|
93
|
+
method: str,
|
|
94
|
+
url: str,
|
|
95
|
+
*,
|
|
96
|
+
json: dict[str, Any] | None = None,
|
|
97
|
+
params: dict[str, Any] | None = None,
|
|
98
|
+
headers: dict[str, str] | None = None,
|
|
99
|
+
**kwargs: Any, # noqa: ANN401
|
|
100
|
+
) -> Any: # noqa: ANN401
|
|
101
|
+
raise NotImplementedError()
|
|
102
|
+
|
|
103
|
+
def get(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
104
|
+
raise NotImplementedError()
|
|
105
|
+
|
|
106
|
+
def post(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
107
|
+
raise NotImplementedError()
|
|
108
|
+
|
|
109
|
+
def patch(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
110
|
+
raise NotImplementedError()
|
|
111
|
+
|
|
112
|
+
def delete(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
113
|
+
raise NotImplementedError()
|
|
114
|
+
|
|
115
|
+
def put(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
116
|
+
raise NotImplementedError()
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def filter_none(data: dict[str, Any]) -> dict[str, Any]: # noqa: ANN401
|
|
120
|
+
return {key: value for key, value in data.items() if value is not None}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class SyncBaseClient(BaseClient):
|
|
124
|
+
_client: httpx.Client
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
base_url: str | URL,
|
|
129
|
+
timeout: float = 30.0,
|
|
130
|
+
) -> None:
|
|
131
|
+
self._client = httpx.Client(base_url=base_url, timeout=timeout)
|
|
132
|
+
super().__init__(base_url=base_url, timeout=timeout)
|
|
133
|
+
|
|
134
|
+
def _process_response(self, response: httpx.Response) -> dict | None:
|
|
135
|
+
if response.status_code >= 400:
|
|
136
|
+
try:
|
|
137
|
+
body = response.json()
|
|
138
|
+
except Exception:
|
|
139
|
+
body = response.text if response.content else None
|
|
140
|
+
|
|
141
|
+
error_detail = ""
|
|
142
|
+
if isinstance(body, dict) and "detail" in body:
|
|
143
|
+
error_detail = f": {body['detail']}"
|
|
144
|
+
|
|
145
|
+
raise self._make_status_error(
|
|
146
|
+
f"\nError response {response.status_code} "
|
|
147
|
+
f"while requesting {response.request.method} "
|
|
148
|
+
f"{response.url}\n{error_detail}",
|
|
149
|
+
body=body,
|
|
150
|
+
response=response,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if response.status_code == 204 or not response.content:
|
|
154
|
+
return None
|
|
155
|
+
try:
|
|
156
|
+
return response.json()
|
|
157
|
+
except Exception as e:
|
|
158
|
+
raise InternalServerError(
|
|
159
|
+
f"Failed to parse response from "
|
|
160
|
+
f"{response.request.method} {response.url}: {e}",
|
|
161
|
+
response=response,
|
|
162
|
+
body=response.text,
|
|
163
|
+
) from e
|
|
164
|
+
|
|
165
|
+
def request(
|
|
166
|
+
self,
|
|
167
|
+
method: str,
|
|
168
|
+
url: str,
|
|
169
|
+
*,
|
|
170
|
+
json: dict[str, Any] | None = None,
|
|
171
|
+
params: dict[str, Any] | None = None,
|
|
172
|
+
headers: dict[str, str] | None = None,
|
|
173
|
+
**kwargs: Any, # noqa: ANN401
|
|
174
|
+
) -> Any: # noqa: ANN401
|
|
175
|
+
final_headers = {**self.default_headers}
|
|
176
|
+
if headers:
|
|
177
|
+
final_headers.update(headers)
|
|
178
|
+
|
|
179
|
+
response = self._client.request(
|
|
180
|
+
method=method,
|
|
181
|
+
url=url,
|
|
182
|
+
headers=final_headers,
|
|
183
|
+
json=json,
|
|
184
|
+
params=params,
|
|
185
|
+
**kwargs,
|
|
186
|
+
)
|
|
187
|
+
return self._process_response(response)
|
|
188
|
+
|
|
189
|
+
def get(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
190
|
+
return self.request("GET", url, **kwargs)
|
|
191
|
+
|
|
192
|
+
def post(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
193
|
+
return self.request("POST", url, **kwargs)
|
|
194
|
+
|
|
195
|
+
def patch(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
196
|
+
return self.request("PATCH", url, **kwargs)
|
|
197
|
+
|
|
198
|
+
def delete(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
199
|
+
return self.request("DELETE", url, **kwargs)
|
|
200
|
+
|
|
201
|
+
def put(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
202
|
+
return self.request("PUT", url, **kwargs)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class AsyncBaseClient(BaseClient):
|
|
206
|
+
_client: httpx.AsyncClient
|
|
207
|
+
|
|
208
|
+
def __init__(
|
|
209
|
+
self,
|
|
210
|
+
base_url: str | URL,
|
|
211
|
+
timeout: float = 30.0,
|
|
212
|
+
) -> None:
|
|
213
|
+
self._client = httpx.AsyncClient(base_url=base_url, timeout=timeout)
|
|
214
|
+
super().__init__(base_url=base_url, timeout=timeout)
|
|
215
|
+
|
|
216
|
+
async def _process_response(self, response: httpx.Response) -> dict | None:
|
|
217
|
+
if response.status_code >= 400:
|
|
218
|
+
try:
|
|
219
|
+
body = response.json()
|
|
220
|
+
except Exception:
|
|
221
|
+
body = response.text if response.content else None
|
|
222
|
+
|
|
223
|
+
error_detail = ""
|
|
224
|
+
if isinstance(body, dict) and "detail" in body:
|
|
225
|
+
error_detail = f": {body['detail']}"
|
|
226
|
+
|
|
227
|
+
raise self._make_status_error(
|
|
228
|
+
f"\nError response {response.status_code} "
|
|
229
|
+
f"while requesting {response.request.method} "
|
|
230
|
+
f"{response.url}\n{error_detail}",
|
|
231
|
+
body=body,
|
|
232
|
+
response=response,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if response.status_code == 204 or not response.content:
|
|
236
|
+
return None
|
|
237
|
+
try:
|
|
238
|
+
return response.json()
|
|
239
|
+
except Exception as e:
|
|
240
|
+
raise InternalServerError(
|
|
241
|
+
f"Failed to parse response from "
|
|
242
|
+
f"{response.request.method} {response.url}: {e}",
|
|
243
|
+
response=response,
|
|
244
|
+
body=response.text,
|
|
245
|
+
) from e
|
|
246
|
+
|
|
247
|
+
async def request(
|
|
248
|
+
self,
|
|
249
|
+
method: str,
|
|
250
|
+
url: str,
|
|
251
|
+
*,
|
|
252
|
+
json: dict[str, Any] | None = None,
|
|
253
|
+
params: dict[str, Any] | None = None,
|
|
254
|
+
headers: dict[str, str] | None = None,
|
|
255
|
+
**kwargs: Any, # noqa: ANN401
|
|
256
|
+
) -> Any: # noqa: ANN401
|
|
257
|
+
final_headers = {**self.default_headers}
|
|
258
|
+
if headers:
|
|
259
|
+
final_headers.update(headers)
|
|
260
|
+
|
|
261
|
+
response = await self._client.request(
|
|
262
|
+
method=method,
|
|
263
|
+
url=url,
|
|
264
|
+
headers=final_headers,
|
|
265
|
+
json=json,
|
|
266
|
+
params=params,
|
|
267
|
+
**kwargs,
|
|
268
|
+
)
|
|
269
|
+
return await self._process_response(response)
|
|
270
|
+
|
|
271
|
+
async def get(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
272
|
+
return await self.request("GET", url, **kwargs)
|
|
273
|
+
|
|
274
|
+
async def post(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
275
|
+
return await self.request("POST", url, **kwargs)
|
|
276
|
+
|
|
277
|
+
async def patch(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
278
|
+
return await self.request("PATCH", url, **kwargs)
|
|
279
|
+
|
|
280
|
+
async def delete(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
281
|
+
return await self.request("DELETE", url, **kwargs)
|
|
282
|
+
|
|
283
|
+
async def put(self, url: str, **kwargs) -> Any: # noqa: ANN401
|
|
284
|
+
return await self.request("PUT", url, **kwargs)
|