gmicloud 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.
@@ -0,0 +1,92 @@
1
+ import jwt
2
+
3
+ from ._http_client import HTTPClient
4
+ from .._config import IAM_SERVICE_BASE_URL
5
+ from .._models import *
6
+ from .._constants import CLIENT_ID_HEADER
7
+
8
+
9
+ class IAMClient:
10
+ """
11
+ Client for interacting with the IAM Service API.
12
+ """
13
+
14
+ def __init__(self, client_id: str, email: str, password: str):
15
+ """
16
+ Initialize IAMClient with client credentials and an IAMClient instance.
17
+
18
+ :param client_id: Client ID for login.
19
+ :param email: Email for login.
20
+ :param password: Password for login.
21
+ """
22
+ self._client_id = client_id
23
+ self._email = email
24
+ self._password = password
25
+ self._access_token = ""
26
+ self._refresh_token = ""
27
+ self._user_id = ""
28
+ self.client = HTTPClient(IAM_SERVICE_BASE_URL)
29
+
30
+ def login(self):
31
+ """
32
+ Logs in a user with the given username and password.
33
+ """
34
+ custom_headers = {
35
+ CLIENT_ID_HEADER: self._client_id
36
+ }
37
+ req = LoginRequest(email=self._email, password=self._password)
38
+ result = self.client.post("/me/sessions", custom_headers, req.model_dump())
39
+
40
+ resp = LoginResponse.model_validate(result)
41
+ self._access_token = resp.accessToken
42
+ self._refresh_token = resp.refreshToken
43
+ self._user_id = self.parse_user_id()
44
+
45
+ def refresh_token(self):
46
+ """
47
+ Refreshes a user's token using a refresh token.
48
+ """
49
+ custom_headers = {
50
+ CLIENT_ID_HEADER: self._client_id
51
+ }
52
+ result = self.client.patch("/me/sessions", custom_headers, {"refreshToken": self.refresh_token})
53
+
54
+ resp = LoginResponse.model_validate(result)
55
+ self._access_token = resp.accessToken
56
+ self._refresh_token = resp.refreshToken
57
+
58
+ def parse_user_id(self) -> str:
59
+ """
60
+ Parses the current access token and returns the user ID.
61
+ """
62
+ return self.parse_token()["userId"]
63
+
64
+ def parse_token(self) -> dict:
65
+ """
66
+ Parses the current access token and returns the payload as a dictionary.
67
+ """
68
+ return jwt.decode(self._access_token, options={"verify_signature": False})
69
+
70
+ def get_access_token(self) -> str:
71
+ """
72
+ Gets the current access token.
73
+ """
74
+ return self._access_token
75
+
76
+ def get_refresh_token(self) -> str:
77
+ """
78
+ Gets the current refresh token.
79
+ """
80
+ return self._refresh_token
81
+
82
+ def get_user_id(self) -> str:
83
+ """
84
+ Gets the current user ID.
85
+ """
86
+ return self._user_id
87
+
88
+ def get_client_id(self) -> str:
89
+ """
90
+ Gets the current client ID.
91
+ """
92
+ return self._client_id
@@ -0,0 +1,142 @@
1
+ from ._http_client import HTTPClient
2
+ from ._decorator import handle_refresh_token
3
+ from ._iam_client import IAMClient
4
+ from .._config import TASK_SERVICE_BASE_URL
5
+ from .._models import *
6
+ from .._constants import ACCESS_TOKEN_HEADER, CLIENT_ID_HEADER
7
+
8
+
9
+ class TaskClient:
10
+ """
11
+ A client for interacting with the task service API.
12
+
13
+ This client provides methods to retrieve, create, update, and stop tasks
14
+ through HTTP calls to the task service.
15
+ """
16
+
17
+ def __init__(self, iam_client: IAMClient):
18
+ """
19
+ Initializes the TaskClient with the given base URL for the task service.
20
+ """
21
+ self.client = HTTPClient(TASK_SERVICE_BASE_URL)
22
+ self.iam_client = iam_client
23
+
24
+ @handle_refresh_token
25
+ def get_task(self, task_id: str) -> Task:
26
+ """
27
+ Retrieves a task from the task service using the given task ID.
28
+
29
+ :param task_id: The ID of the task to be retrieved.
30
+ :return: An instance of Task containing the details of the retrieved task.
31
+ :rtype: Task
32
+ """
33
+ custom_headers = {
34
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
35
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
36
+ }
37
+ result = self.client.get("/get_task", custom_headers, {"task_id": task_id})
38
+
39
+ return Task.model_validate(result)
40
+
41
+ @handle_refresh_token
42
+ def get_all_tasks(self, user_id: str) -> GetAllTasksResponse:
43
+ """
44
+ Retrieves all tasks from the task service.
45
+
46
+ :return: An instance of GetAllTasksResponse containing the retrieved tasks.
47
+ :rtype: GetAllTasksResponse
48
+ """
49
+ custom_headers = {
50
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
51
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
52
+ }
53
+ result = self.client.get("/get_tasks", custom_headers, {"user_id": user_id})
54
+ if not result:
55
+ return GetAllTasksResponse(tasks=[])
56
+
57
+ return GetAllTasksResponse.model_validate(result)
58
+
59
+ @handle_refresh_token
60
+ def create_task(self, task: Task) -> CreateTaskResponse:
61
+ """
62
+ Creates a new task using the provided task object.
63
+
64
+ :param task: The Task object containing the details of the task to be created.
65
+ """
66
+ custom_headers = {
67
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
68
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
69
+ }
70
+
71
+ result = self.client.post("/create_task", custom_headers, task.model_dump())
72
+
73
+ return CreateTaskResponse.model_validate(result)
74
+
75
+ @handle_refresh_token
76
+ def update_task_schedule(self, task: Task):
77
+ """
78
+ Updates the schedule of an existing task.
79
+
80
+ :param task: The Task object containing the updated task details.
81
+ """
82
+ custom_headers = {
83
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
84
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
85
+ }
86
+ self.client.put("/update_schedule", custom_headers, task.model_dump())
87
+
88
+ @handle_refresh_token
89
+ def start_task(self, task_id: str):
90
+ """
91
+ Starts a task using the given task ID.
92
+
93
+ :param task_id: The ID of the task to be started.
94
+ """
95
+ custom_headers = {
96
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
97
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
98
+ }
99
+ self.client.post("/start_task", custom_headers, {"task_id": task_id})
100
+
101
+ @handle_refresh_token
102
+ def stop_task(self, task_id: str):
103
+ """
104
+ Stops a running task using the given task ID.
105
+
106
+ :param task_id: The ID of the task to be stopped.
107
+ """
108
+ custom_headers = {
109
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
110
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
111
+ }
112
+ self.client.post("/stop_task", custom_headers, {"task_id": task_id})
113
+
114
+ @handle_refresh_token
115
+ def get_usage_data(self, start_timestamp: str, end_timestamp: str) -> GetUsageDataResponse:
116
+ """
117
+ Retrieves the usage data of a task using the given task ID.
118
+
119
+ :param start_timestamp: The start timestamp of the usage data.
120
+ :param end_timestamp: The end timestamp of the usage data.
121
+ """
122
+ custom_headers = {
123
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
124
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
125
+ }
126
+ result = self.client.get("/get_usage_data", custom_headers,
127
+ {"start_timestamp": start_timestamp, "end_timestamp": end_timestamp})
128
+
129
+ return result
130
+
131
+ @handle_refresh_token
132
+ def archive_task(self, task_id: str):
133
+ """
134
+ Archives a task using the given task ID.
135
+
136
+ :param task_id: The ID of the task to be archived.
137
+ """
138
+ custom_headers = {
139
+ ACCESS_TOKEN_HEADER: self.iam_client.get_access_token(),
140
+ CLIENT_ID_HEADER: self.iam_client.get_client_id()
141
+ }
142
+ self.client.post("/archive_task", custom_headers, {"task_id": task_id})
@@ -0,0 +1,3 @@
1
+ ARTIFACT_SERVICE_BASE_URL = "https://ce-tot.gmicloud-dev.com/api/v1/ie/artifact"
2
+ TASK_SERVICE_BASE_URL = "https://ce-tot.gmicloud-dev.com/api/v1/ie/task"
3
+ IAM_SERVICE_BASE_URL = "https://ce-tot.gmicloud-dev.com/api/v1"
@@ -0,0 +1,13 @@
1
+ ACCEPT_HEADER = 'Accept'
2
+ CONTENT_TYPE_HEADER = 'Content-Type'
3
+ CLIENT_ID_HEADER = 'CE-ClientId'
4
+ ACCESS_TOKEN_HEADER = 'CE-AccessToken'
5
+
6
+ HTTP_METHOD_POST = 'POST'
7
+ HTTP_METHOD_GET = 'GET'
8
+ HTTP_METHOD_PATCH = 'PATCH'
9
+ HTTP_METHOD_DELETE = 'DELETE'
10
+ HTTP_METHOD_PUT = 'PUT'
11
+
12
+ JSON_CONTENT_TYPE = 'application/json'
13
+ TEXT_CONTENT_TYPE = 'text/html'
@@ -0,0 +1,23 @@
1
+ from enum import Enum
2
+
3
+
4
+ class BuildStatus(str, Enum):
5
+ INIT = "INIT"
6
+ CREATED = "CREATED"
7
+ BUILDING = "BUILDING"
8
+ SUCCESS = "SUCCESS"
9
+ FAILURE = "FAILURE"
10
+ TIMEOUT = "TIMEOUT"
11
+ CANCELLED = "CANCELLED"
12
+ WORKING = "WORKING"
13
+ QUEUED = "QUEUED"
14
+
15
+
16
+ class TaskEndpointStatus(str, Enum):
17
+ PENDING = "pending"
18
+ DEPLOYING = "deploying"
19
+ SCALING = "scaling"
20
+ RUNNING = "running"
21
+ ARCHIVED = "archived"
22
+ READY = "ready"
23
+ UNREADY = "unready"
@@ -0,0 +1,19 @@
1
+ class APIError(Exception):
2
+ """
3
+ Generic exception for API-related errors.
4
+ """
5
+ pass
6
+
7
+
8
+ class UploadFileError(Exception):
9
+ """
10
+ Exception for file upload errors.
11
+ """
12
+ pass
13
+
14
+
15
+ class UnauthorizedError(Exception):
16
+ """
17
+ Exception for unauthorized access errors.
18
+ """
19
+ pass
File without changes
@@ -0,0 +1,303 @@
1
+ import os
2
+ from typing import List
3
+ import mimetypes
4
+
5
+ from .._client._iam_client import IAMClient
6
+ from .._client._artifact_client import ArtifactClient
7
+ from .._client._file_upload_client import FileUploadClient
8
+ from .._models import *
9
+
10
+
11
+ class ArtifactManager:
12
+ """
13
+ Artifact Manager handles creation, retrieval, and file upload associated with artifacts.
14
+ """
15
+
16
+ def __init__(self, iam_client: IAMClient):
17
+ """
18
+ Initialize the ArtifactManager instance with the user ID and access token.
19
+
20
+ :param iam_client: The IAMClient instance to use for authentication.
21
+ :raises ValueError: If the `user_id` is None or an empty string.
22
+ """
23
+ self.iam_client = iam_client
24
+ self.artifact_client = ArtifactClient(iam_client)
25
+
26
+ def get_artifact(self, artifact_id: str) -> Artifact:
27
+ """
28
+ Retrieve an artifact by its ID.
29
+
30
+ :param artifact_id: The ID of the artifact to retrieve.
31
+ :return: The Artifact object associated with the ID.
32
+ :rtype: Artifact
33
+ :raises ValueError: If `artifact_id` is None or empty.
34
+ """
35
+ self._validate_artifact_id(artifact_id)
36
+
37
+ return self.artifact_client.get_artifact(artifact_id)
38
+
39
+ def get_all_artifacts(self) -> List[Artifact]:
40
+ """
41
+ Retrieve all artifacts for a given user.
42
+
43
+ :return: A list of Artifact objects associated with the user.
44
+ :rtype: List[Artifact]
45
+ """
46
+ return self.artifact_client.get_all_artifacts(self.iam_client.get_user_id())
47
+
48
+ def create_artifact(
49
+ self,
50
+ artifact_name: str,
51
+ description: Optional[str] = "",
52
+ tags: Optional[List[str]] = None
53
+ ) -> CreateArtifactResponse:
54
+ """
55
+ Create a new artifact for a user.
56
+
57
+ :param artifact_name: The name of the artifact.
58
+ :param description: An optional description for the artifact.
59
+ :param tags: Optional tags associated with the artifact, as a comma-separated string.
60
+ :return: A `CreateArtifactResponse` object containing information about the created artifact.
61
+ :rtype: CreateArtifactResponse
62
+ """
63
+ if not artifact_name or not artifact_name.strip():
64
+ raise ValueError("Artifact name is required and cannot be empty.")
65
+
66
+ req = CreateArtifactRequest(user_id=self.iam_client.get_user_id(), artifact_name=artifact_name,
67
+ artifact_description=description,
68
+ artifact_tags=tags, )
69
+
70
+ return self.artifact_client.create_artifact(req)
71
+
72
+ def create_artifact_from_template(self, artifact_template_id: str) -> CreateArtifactFromTemplateResponse:
73
+ """
74
+ Create a new artifact for a user using a template.
75
+
76
+ :param artifact_template_id: The ID of the template to use for the artifact.
77
+ :return: A `CreateArtifactResponse` object containing information about the created artifact.
78
+ :rtype: CreateArtifactResponse
79
+ """
80
+ if not artifact_template_id or not artifact_template_id.strip():
81
+ raise ValueError("Artifact template ID is required and cannot be empty.")
82
+
83
+ req = CreateArtifactFromTemplateRequest(user_id=self.iam_client.get_user_id(),
84
+ artifact_template_id=artifact_template_id)
85
+
86
+ return self.artifact_client.create_artifact_from_template(req)
87
+
88
+ def rebuild_artifact(self, artifact_id: str) -> RebuildArtifactResponse:
89
+ """
90
+ Rebuild an existing artifact.
91
+
92
+ :param artifact_id: The ID of the artifact to rebuild.
93
+ :return: A `RebuildArtifactResponse` object containing information about the rebuilt artifact.
94
+ :rtype: RebuildArtifactResponse
95
+ :raises ValueError: If `artifact_id` is None or empty
96
+ """
97
+ self._validate_artifact_id(artifact_id)
98
+
99
+ return self.artifact_client.rebuild_artifact(artifact_id)
100
+
101
+ def delete_artifact(self, artifact_id: str) -> DeleteArtifactResponse:
102
+ """
103
+ Delete an existing artifact.
104
+
105
+ :param artifact_id: The ID of the artifact to delete.
106
+ :return: A `DeleteArtifactResponse` object containing information about the deleted artifact.
107
+ :rtype: DeleteArtifactResponse
108
+ :raises ValueError: If `artifact_id` is None or empty
109
+ """
110
+ self._validate_artifact_id(artifact_id)
111
+
112
+ return self.artifact_client.delete_artifact(artifact_id)
113
+
114
+ def upload_artifact_file(self, upload_link: str, artifact_file_path: str) -> None:
115
+ """
116
+ Upload a file associated with an artifact.
117
+
118
+ :param upload_link: The URL to upload the artifact file.
119
+ :param artifact_file_path: The path to the artifact file.
120
+ :raises ValueError: If `file_path` is None or empty.
121
+ :raises FileNotFoundError: If the provided `file_path` does not exist.
122
+ """
123
+ self._validate_artifact_file_path(artifact_file_path)
124
+ artifact_file_type = mimetypes.guess_type(artifact_file_path)[0]
125
+
126
+ FileUploadClient.upload_small_file(upload_link, artifact_file_path, artifact_file_type)
127
+
128
+ def create_artifact_with_file(
129
+ self,
130
+ artifact_name: str,
131
+ artifact_file_path: str,
132
+ description: Optional[str] = "",
133
+ tags: Optional[List[str]] = None
134
+ ) -> str:
135
+ """
136
+ Create a new artifact for a user and upload a file associated with the artifact.
137
+
138
+ :param artifact_name: The name of the artifact.
139
+ :param artifact_file_path: The path to the artifact file(Dockerfile+serve.py).
140
+ :param description: An optional description for the artifact.
141
+ :param tags: Optional tags associated with the artifact, as a comma-separated string.
142
+ :return: The `artifact_id` of the created artifact.
143
+ :rtype: str
144
+ :raises FileNotFoundError: If the provided `file_path` does not exist.
145
+ """
146
+ self._validate_artifact_file_path(artifact_file_path)
147
+
148
+ # Create the artifact
149
+ create_artifact_resp = self.create_artifact(artifact_name, description, tags)
150
+ artifact_id = create_artifact_resp.artifact_id
151
+
152
+ artifact_file_type = mimetypes.guess_type(artifact_file_path)[0]
153
+ FileUploadClient.upload_small_file(create_artifact_resp.upload_link, artifact_file_path, artifact_file_type)
154
+
155
+ return artifact_id
156
+
157
+ def get_bigfile_upload_url(self, artifact_id: str, model_file_path: str) -> str:
158
+ """
159
+ Generate a pre-signed URL for uploading a large file associated with an artifact.
160
+
161
+ :param artifact_id: The ID of the artifact for which the file is being uploaded.
162
+ :param model_file_path: The path to the model file.
163
+ :return: The pre-signed upload URL for the large file.
164
+ :rtype: str
165
+ :raises ValueError: If `artifact_id` is None or empty.
166
+ """
167
+ self._validate_artifact_id(artifact_id)
168
+ self._validate_file_path(model_file_path)
169
+
170
+ model_file_name = os.path.basename(model_file_path)
171
+ model_file_type = mimetypes.guess_type(model_file_path)[0]
172
+
173
+ req = GetBigFileUploadUrlRequest(artifact_id=artifact_id, file_name=model_file_name, file_type=model_file_type)
174
+
175
+ return self.artifact_client.get_bigfile_upload_url(req).upload_link
176
+
177
+ def delete_bigfile(self, artifact_id: str, file_name: str) -> str:
178
+ """
179
+ Delete a large file associated with an artifact.
180
+
181
+ :param artifact_id: The ID of the artifact for which the file is being deleted.
182
+ :param file_name: The name of the file being deleted.
183
+ """
184
+ self._validate_artifact_id(artifact_id)
185
+ self._validate_file_name(file_name)
186
+
187
+ return self.artifact_client.delete_bigfile(artifact_id, file_name).status
188
+
189
+ def upload_large_file(self, upload_link: str, file_path: str) -> None:
190
+ """
191
+ Upload a large file to the specified URL.
192
+
193
+ :param upload_link: The URL to upload the file.
194
+ :param file_path: The path to the file to upload.
195
+ :raises ValueError: If `file_path` is None or empty.
196
+ :raises ValueError: If `upload_link` is None or empty.
197
+ :raises FileNotFoundError: If the provided `file_path` does not exist.
198
+ """
199
+ self._validate_file_path(file_path)
200
+ self._validate_upload_url(upload_link)
201
+
202
+ FileUploadClient.upload_large_file(upload_link, file_path)
203
+
204
+ def create_artifact_with_model_files(
205
+ self,
206
+ artifact_name: str,
207
+ artifact_file_path: str,
208
+ model_file_paths: List[str],
209
+ description: Optional[str] = "",
210
+ tags: Optional[str] = None
211
+ ) -> str:
212
+ """
213
+ Create a new artifact for a user and upload model files associated with the artifact.
214
+
215
+ :param artifact_name: The name of the artifact.
216
+ :param artifact_file_path: The path to the artifact file(Dockerfile+serve.py).
217
+ :param model_file_paths: The paths to the model files.
218
+ :param description: An optional description for the artifact.
219
+ :param tags: Optional tags associated with the artifact, as a comma-separated string.
220
+ :return: The `artifact_id` of the created artifact.
221
+ :raises FileNotFoundError: If the provided `file_path` does not exist.
222
+ """
223
+ artifact_id = self.create_artifact_with_file(artifact_name, artifact_file_path, description, tags)
224
+
225
+ for model_file_path in model_file_paths:
226
+ self._validate_file_path(model_file_path)
227
+ bigfile_upload_url_resp = self.artifact_client.get_bigfile_upload_url(
228
+ GetBigFileUploadUrlRequest(artifact_id=artifact_id, model_file_path=model_file_path)
229
+ )
230
+ FileUploadClient.upload_large_file(bigfile_upload_url_resp.upload_link, model_file_path)
231
+
232
+ return artifact_id
233
+
234
+ def get_artifact_templates(self) -> List[ArtifactTemplate]:
235
+ """
236
+ Fetch all artifact templates.
237
+
238
+ :return: A list of ArtifactTemplate objects.
239
+ :rtype: List[ArtifactTemplate]
240
+ """
241
+ return self.artifact_client.get_artifact_templates().artifact_templates
242
+
243
+ @staticmethod
244
+ def _validate_file_name(file_name: str) -> None:
245
+ """
246
+ Validate the file name.
247
+
248
+ :param file_name: The file name to validate.
249
+ :raises ValueError: If `file_name` is None or empty.
250
+ """
251
+ if not file_name or not file_name.strip():
252
+ raise ValueError("File name is required and cannot be empty.")
253
+
254
+ @staticmethod
255
+ def _validate_artifact_id(artifact_id: str) -> None:
256
+ """
257
+ Validate the artifact ID.
258
+
259
+ :param artifact_id: The artifact ID to validate.
260
+ :raises ValueError: If `artifact_id` is None or empty.
261
+ """
262
+ if not artifact_id or not artifact_id.strip():
263
+ raise ValueError("Artifact ID is required and cannot be empty.")
264
+
265
+ @staticmethod
266
+ def _validate_artifact_file_path(artifact_file_path: str) -> None:
267
+ """
268
+ Validate the artifact file path.
269
+
270
+ :param artifact_file_path: The file path to validate.
271
+ :raises ValueError: If `file_path` is None or empty.
272
+ :raises FileNotFoundError: If the provided `file_path` does not exist.
273
+ """
274
+ ArtifactManager._validate_file_path(artifact_file_path)
275
+
276
+ file_type = mimetypes.guess_type(artifact_file_path)[0]
277
+ if file_type != "application/zip":
278
+ raise ValueError("File type must be application/zip.")
279
+
280
+ @staticmethod
281
+ def _validate_upload_url(upload_link: str) -> None:
282
+ """
283
+ Validate the upload URL.
284
+
285
+ :param upload_link: The upload URL to validate.
286
+ :raises ValueError: If `upload_link` is None or empty.
287
+ """
288
+ if not upload_link or not upload_link.strip():
289
+ raise ValueError("Upload link is required and cannot be empty.")
290
+
291
+ @staticmethod
292
+ def _validate_file_path(file_path: str) -> None:
293
+ """
294
+ Validate the file path.
295
+
296
+ :param file_path: The file path to validate.
297
+ :raises ValueError: If `file_path` is None or empty.
298
+ :raises FileNotFoundError: If the provided `file_path` does not exist.
299
+ """
300
+ if not file_path or not file_path.strip():
301
+ raise ValueError("File path is required and cannot be empty.")
302
+ if not os.path.exists(file_path):
303
+ raise FileNotFoundError(f"File not found: {file_path}")