gmicloud 0.1.3__py3-none-any.whl → 0.1.5__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.
- gmicloud/__init__.py +12 -1
- gmicloud/_internal/_client/_artifact_client.py +126 -56
- gmicloud/_internal/_client/_http_client.py +5 -2
- gmicloud/_internal/_client/_iam_client.py +108 -20
- gmicloud/_internal/_client/_task_client.py +75 -30
- gmicloud/_internal/_enums.py +8 -0
- gmicloud/_internal/_manager/_artifact_manager.py +17 -5
- gmicloud/_internal/_manager/_iam_manager.py +36 -0
- gmicloud/_internal/_manager/_task_manager.py +19 -12
- gmicloud/_internal/_models.py +129 -11
- gmicloud/client.py +26 -5
- gmicloud/tests/test_artifacts.py +14 -15
- gmicloud/tests/test_tasks.py +1 -1
- {gmicloud-0.1.3.dist-info → gmicloud-0.1.5.dist-info}/METADATA +88 -56
- gmicloud-0.1.5.dist-info/RECORD +27 -0
- {gmicloud-0.1.3.dist-info → gmicloud-0.1.5.dist-info}/WHEEL +1 -1
- gmicloud-0.1.3.dist-info/RECORD +0 -26
- {gmicloud-0.1.3.dist-info → gmicloud-0.1.5.dist-info}/top_level.txt +0 -0
gmicloud/__init__.py
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
|
1
4
|
from ._internal._models import (
|
2
5
|
Artifact,
|
3
6
|
ArtifactData,
|
@@ -16,7 +19,8 @@ from ._internal._models import (
|
|
16
19
|
)
|
17
20
|
from ._internal._enums import (
|
18
21
|
BuildStatus,
|
19
|
-
TaskEndpointStatus
|
22
|
+
TaskEndpointStatus,
|
23
|
+
TaskStatus
|
20
24
|
)
|
21
25
|
from .client import Client
|
22
26
|
|
@@ -39,3 +43,10 @@ __all__ = [
|
|
39
43
|
"BuildStatus",
|
40
44
|
"TaskEndpointStatus",
|
41
45
|
]
|
46
|
+
|
47
|
+
# Configure logging
|
48
|
+
log_level = os.getenv("GMI_CLOUD_LOG_LEVEL", "INFO").upper()
|
49
|
+
logging.basicConfig(
|
50
|
+
level=log_level,
|
51
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
52
|
+
)
|
@@ -1,4 +1,6 @@
|
|
1
1
|
from typing import List
|
2
|
+
import logging
|
3
|
+
from requests.exceptions import RequestException
|
2
4
|
|
3
5
|
from ._http_client import HTTPClient
|
4
6
|
from ._iam_client import IAMClient
|
@@ -6,6 +8,8 @@ from ._decorator import handle_refresh_token
|
|
6
8
|
from .._models import *
|
7
9
|
from .._config import ARTIFACT_SERVICE_BASE_URL
|
8
10
|
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
9
13
|
|
10
14
|
class ArtifactClient:
|
11
15
|
"""
|
@@ -24,119 +28,185 @@ class ArtifactClient:
|
|
24
28
|
self.iam_client = iam_client
|
25
29
|
|
26
30
|
@handle_refresh_token
|
27
|
-
def get_artifact(self, artifact_id: str) -> Artifact:
|
31
|
+
def get_artifact(self, artifact_id: str) -> Optional[Artifact]:
|
28
32
|
"""
|
29
33
|
Fetches an artifact by its ID.
|
30
34
|
|
31
35
|
:param artifact_id: The ID of the artifact to fetch.
|
32
|
-
:return: The Artifact object.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
:return: The Artifact object or None if an error occurs.
|
37
|
+
"""
|
38
|
+
try:
|
39
|
+
response = self.client.get(
|
40
|
+
"/get_artifact",
|
41
|
+
self.iam_client.get_custom_headers(),
|
42
|
+
{"artifact_id": artifact_id}
|
43
|
+
)
|
44
|
+
return Artifact.model_validate(response) if response else None
|
45
|
+
except (RequestException, ValueError) as e:
|
46
|
+
logger.error(f"Failed to fetch artifact {artifact_id}: {e}")
|
47
|
+
return None
|
38
48
|
|
39
49
|
@handle_refresh_token
|
40
50
|
def get_all_artifacts(self) -> List[Artifact]:
|
41
51
|
"""
|
42
52
|
Fetches all artifacts.
|
43
53
|
|
44
|
-
:return: A list of Artifact objects.
|
45
|
-
:rtype: List[Artifact]
|
54
|
+
:return: A list of Artifact objects. If an error occurs, returns an empty list.
|
46
55
|
"""
|
47
|
-
|
48
|
-
|
56
|
+
try:
|
57
|
+
response = self.client.get("/get_all_artifacts", self.iam_client.get_custom_headers())
|
58
|
+
if not response:
|
59
|
+
logger.error("Empty response from /get_all_artifacts")
|
60
|
+
return []
|
61
|
+
return [Artifact.model_validate(item) for item in response]
|
62
|
+
except (RequestException, ValueError) as e:
|
63
|
+
logger.error(f"Failed to fetch all artifacts: {e}")
|
49
64
|
return []
|
50
|
-
return [Artifact.model_validate(item) for item in result]
|
51
65
|
|
52
66
|
@handle_refresh_token
|
53
|
-
def create_artifact(self, request: CreateArtifactRequest) -> CreateArtifactResponse:
|
67
|
+
def create_artifact(self, request: CreateArtifactRequest) -> Optional[CreateArtifactResponse]:
|
54
68
|
"""
|
55
69
|
Creates a new artifact in the service.
|
56
70
|
|
57
71
|
:param request: The request object containing artifact details.
|
58
|
-
:return: The response object containing the created artifact details.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
72
|
+
:return: The response object containing the created artifact details, or None on error.
|
73
|
+
"""
|
74
|
+
try:
|
75
|
+
response = self.client.post(
|
76
|
+
"/create_artifact",
|
77
|
+
self.iam_client.get_custom_headers(),
|
78
|
+
request.model_dump()
|
79
|
+
)
|
80
|
+
return CreateArtifactResponse.model_validate(response) if response else None
|
81
|
+
except (RequestException, ValueError) as e:
|
82
|
+
logger.error(f"Failed to create artifact: {e}")
|
83
|
+
return None
|
64
84
|
|
65
85
|
@handle_refresh_token
|
66
|
-
def create_artifact_from_template(self, artifact_template_id: str) -> CreateArtifactFromTemplateResponse:
|
86
|
+
def create_artifact_from_template(self, artifact_template_id: str) -> Optional[CreateArtifactFromTemplateResponse]:
|
67
87
|
"""
|
68
88
|
Creates a new artifact in the service.
|
69
89
|
|
70
90
|
:param artifact_template_id: The ID of the artifact template to use.
|
71
|
-
:return: The response object containing the created artifact details.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
91
|
+
:return: The response object containing the created artifact details or None if an error occurs.
|
92
|
+
"""
|
93
|
+
try:
|
94
|
+
response = self.client.post(
|
95
|
+
"/create_artifact_from_template",
|
96
|
+
self.iam_client.get_custom_headers(),
|
97
|
+
{"artifact_template_id": artifact_template_id}
|
98
|
+
)
|
99
|
+
return CreateArtifactFromTemplateResponse.model_validate(response) if response else None
|
100
|
+
except (RequestException, ValueError) as e:
|
101
|
+
logger.error(f"Failed to create artifact from template {artifact_template_id}: {e}")
|
102
|
+
return None
|
78
103
|
|
79
104
|
@handle_refresh_token
|
80
|
-
def rebuild_artifact(self, artifact_id: str) -> RebuildArtifactResponse:
|
105
|
+
def rebuild_artifact(self, artifact_id: str) -> Optional[RebuildArtifactResponse]:
|
81
106
|
"""
|
82
107
|
Rebuilds an artifact in the service.
|
83
108
|
|
84
109
|
:param artifact_id: The ID of the artifact to rebuild.
|
85
|
-
:return: The response object containing the rebuilt artifact details.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
110
|
+
:return: The response object containing the rebuilt artifact details or None if an error occurs.
|
111
|
+
"""
|
112
|
+
try:
|
113
|
+
response = self.client.post(
|
114
|
+
"/rebuild_artifact",
|
115
|
+
self.iam_client.get_custom_headers(),
|
116
|
+
{"artifact_id": artifact_id}
|
117
|
+
)
|
118
|
+
return RebuildArtifactResponse.model_validate(response) if response else None
|
119
|
+
except (RequestException, ValueError) as e:
|
120
|
+
logger.error(f"Failed to rebuild artifact {artifact_id}: {e}")
|
121
|
+
return None
|
92
122
|
|
93
123
|
@handle_refresh_token
|
94
|
-
def delete_artifact(self, artifact_id: str) -> DeleteArtifactResponse:
|
124
|
+
def delete_artifact(self, artifact_id: str) -> Optional[DeleteArtifactResponse]:
|
95
125
|
"""
|
96
126
|
Deletes an artifact by its ID.
|
97
127
|
|
98
128
|
:param artifact_id: The ID of the artifact to delete.
|
99
|
-
:return: The response object containing the deleted artifact details.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
129
|
+
:return: The response object containing the deleted artifact details or None if an error occurs.
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
response = self.client.delete(
|
133
|
+
"/delete_artifact",
|
134
|
+
self.iam_client.get_custom_headers(),
|
135
|
+
{"artifact_id": artifact_id}
|
136
|
+
)
|
137
|
+
return DeleteArtifactResponse.model_validate(response) if response else None
|
138
|
+
except (RequestException, ValueError) as e:
|
139
|
+
logger.error(f"Failed to delete artifact {artifact_id}: {e}")
|
140
|
+
return None
|
106
141
|
|
107
142
|
@handle_refresh_token
|
108
|
-
def get_bigfile_upload_url(self, request: GetBigFileUploadUrlRequest) -> GetBigFileUploadUrlResponse:
|
143
|
+
def get_bigfile_upload_url(self, request: GetBigFileUploadUrlRequest) -> Optional[GetBigFileUploadUrlResponse]:
|
109
144
|
"""
|
110
145
|
Generates a pre-signed URL for uploading a large file.
|
111
146
|
|
112
147
|
:param request: The request object containing the artifact ID, file name, and file type.
|
113
|
-
:return: The response object containing the pre-signed URL and upload details.
|
114
|
-
:rtype: GetBigFileUploadUrlResponse
|
148
|
+
:return: The response object containing the pre-signed URL and upload details, or None if an error occurs.
|
115
149
|
"""
|
116
|
-
|
150
|
+
try:
|
151
|
+
response = self.client.post("/get_bigfile_upload_url",
|
152
|
+
self.iam_client.get_custom_headers(),
|
153
|
+
request.model_dump())
|
154
|
+
|
155
|
+
if not response:
|
156
|
+
logger.error("Empty response from /get_bigfile_upload_url")
|
157
|
+
return None
|
158
|
+
|
159
|
+
return GetBigFileUploadUrlResponse.model_validate(response)
|
117
160
|
|
118
|
-
|
161
|
+
except (RequestException, ValueError) as e:
|
162
|
+
logger.error(f"Failed to generate upload URL: {e}")
|
163
|
+
return None
|
119
164
|
|
120
165
|
@handle_refresh_token
|
121
|
-
def delete_bigfile(self, request: DeleteBigfileRequest) -> DeleteBigfileResponse:
|
166
|
+
def delete_bigfile(self, request: DeleteBigfileRequest) -> Optional[DeleteBigfileResponse]:
|
122
167
|
"""
|
123
168
|
Deletes a large file associated with an artifact.
|
124
169
|
|
125
170
|
:param request: The request object containing the artifact ID and file name.
|
126
|
-
:return: The response object containing the deletion status.
|
127
|
-
:rtype: DeleteBigfileResponse
|
171
|
+
:return: The response object containing the deletion status, or None if an error occurs.
|
128
172
|
"""
|
129
|
-
|
173
|
+
try:
|
174
|
+
response = self.client.delete("/delete_bigfile",
|
175
|
+
self.iam_client.get_custom_headers(),
|
176
|
+
request.model_dump())
|
177
|
+
|
178
|
+
if not response:
|
179
|
+
logger.error("Empty response from /delete_bigfile")
|
180
|
+
return None
|
181
|
+
|
182
|
+
return DeleteBigfileResponse.model_validate(response)
|
130
183
|
|
131
|
-
|
184
|
+
except (RequestException, ValueError) as e:
|
185
|
+
logger.error(f"Failed to delete big file: {e}")
|
186
|
+
return None
|
132
187
|
|
133
188
|
@handle_refresh_token
|
134
|
-
def
|
189
|
+
def get_public_templates(self) -> List[ArtifactTemplate]:
|
135
190
|
"""
|
136
191
|
Fetches all artifact templates.
|
137
192
|
|
138
193
|
:return: A list of ArtifactTemplate objects.
|
139
194
|
:rtype: List[ArtifactTemplate]
|
140
195
|
"""
|
141
|
-
|
142
|
-
|
196
|
+
try:
|
197
|
+
response = self.client.get("/get_public_templates", self.iam_client.get_custom_headers())
|
198
|
+
|
199
|
+
if not response:
|
200
|
+
logger.error("Empty response received from /get_public_templates API")
|
201
|
+
return []
|
202
|
+
|
203
|
+
try:
|
204
|
+
result = GetPublicTemplatesResponse.model_validate(response)
|
205
|
+
return result.artifact_templates
|
206
|
+
except ValueError as ve:
|
207
|
+
logger.error(f"Failed to validate response data: {ve}")
|
208
|
+
return []
|
209
|
+
|
210
|
+
except RequestException as e:
|
211
|
+
logger.error(f"Request to /get_public_templates failed: {e}")
|
212
|
+
return []
|
@@ -1,8 +1,11 @@
|
|
1
|
+
import logging
|
2
|
+
|
1
3
|
import requests
|
2
4
|
from .._exceptions import APIError
|
3
5
|
from .._exceptions import UnauthorizedError
|
4
6
|
from .._constants import *
|
5
|
-
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
6
9
|
|
7
10
|
|
8
11
|
class HTTPClient:
|
@@ -52,6 +55,7 @@ class HTTPClient:
|
|
52
55
|
response = None
|
53
56
|
try:
|
54
57
|
response = requests.request(method, url, params=params, json=data, headers=headers)
|
58
|
+
logger.debug(response.text)
|
55
59
|
if response.status_code == 401:
|
56
60
|
raise UnauthorizedError("Access token expired or invalid.")
|
57
61
|
elif response.status_code != 200 and response.status_code != 201:
|
@@ -62,7 +66,6 @@ class HTTPClient:
|
|
62
66
|
raise APIError(f"HTTP Request failed: {error_message}")
|
63
67
|
# Raise for HTTP errors
|
64
68
|
response.raise_for_status()
|
65
|
-
print(response.text)
|
66
69
|
|
67
70
|
except requests.exceptions.RequestException as e:
|
68
71
|
raise APIError(f"HTTP Request failed: {str(e)}")
|
@@ -1,10 +1,14 @@
|
|
1
1
|
import jwt
|
2
|
+
import logging
|
3
|
+
from requests.exceptions import RequestException
|
2
4
|
|
3
5
|
from ._http_client import HTTPClient
|
4
6
|
from .._config import IAM_SERVICE_BASE_URL
|
5
7
|
from .._models import *
|
6
8
|
from .._constants import CLIENT_ID_HEADER, AUTHORIZATION_HEADER
|
7
9
|
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
8
12
|
|
9
13
|
class IAMClient:
|
10
14
|
"""
|
@@ -25,35 +29,113 @@ class IAMClient:
|
|
25
29
|
self._access_token = ""
|
26
30
|
self._refresh_token = ""
|
27
31
|
self._user_id = ""
|
32
|
+
self._organization_id = ""
|
28
33
|
self.client = HTTPClient(IAM_SERVICE_BASE_URL)
|
29
34
|
|
30
|
-
def login(self):
|
35
|
+
def login(self) -> bool:
|
31
36
|
"""
|
32
|
-
Logs in a user with the given
|
37
|
+
Logs in a user with the given email and password.
|
38
|
+
Returns True if login is successful, otherwise False.
|
33
39
|
"""
|
34
|
-
|
35
|
-
CLIENT_ID_HEADER: self._client_id
|
36
|
-
|
37
|
-
|
38
|
-
|
40
|
+
try:
|
41
|
+
custom_headers = {CLIENT_ID_HEADER: self._client_id}
|
42
|
+
req = AuthTokenRequest(email=self._email, password=self._password)
|
43
|
+
auth_tokens_result = self.client.post("/me/auth-tokens", custom_headers, req.model_dump())
|
44
|
+
|
45
|
+
if not auth_tokens_result:
|
46
|
+
logger.error("Login failed: Received empty response from auth-tokens endpoint")
|
47
|
+
return False
|
48
|
+
|
49
|
+
auth_tokens_resp = AuthTokenResponse.model_validate(auth_tokens_result)
|
50
|
+
|
51
|
+
# Handle 2FA
|
52
|
+
if auth_tokens_resp.is2FARequired:
|
53
|
+
for attempt in range(3):
|
54
|
+
code = input(f"Attempt {attempt + 1}/3: Please enter the 2FA code: ")
|
55
|
+
create_session_req = CreateSessionRequest(
|
56
|
+
type="native", authToken=auth_tokens_resp.authToken, otpCode=code
|
57
|
+
)
|
58
|
+
try:
|
59
|
+
session_result = self.client.post("/me/sessions", custom_headers,
|
60
|
+
create_session_req.model_dump())
|
61
|
+
if session_result:
|
62
|
+
break
|
63
|
+
except RequestException:
|
64
|
+
logger.warning("Invalid 2FA code, please try again.")
|
65
|
+
if attempt == 2:
|
66
|
+
logger.error("Failed to create session after 3 incorrect 2FA attempts.")
|
67
|
+
return False
|
68
|
+
else:
|
69
|
+
create_session_req = CreateSessionRequest(type="native", authToken=auth_tokens_resp.authToken,
|
70
|
+
otpCode=None)
|
71
|
+
session_result = self.client.post("/me/sessions", custom_headers, create_session_req.model_dump())
|
72
|
+
|
73
|
+
create_session_resp = CreateSessionResponse.model_validate(session_result)
|
39
74
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
self._user_id = self.parse_user_id()
|
75
|
+
self._access_token = create_session_resp.accessToken
|
76
|
+
self._refresh_token = create_session_resp.refreshToken
|
77
|
+
self._user_id = self.parse_user_id()
|
44
78
|
|
45
|
-
|
79
|
+
# Fetch profile to get organization ID
|
80
|
+
profile_result = self.client.get("/me/profile", self.get_custom_headers())
|
81
|
+
if not profile_result:
|
82
|
+
logger.error("Failed to fetch user profile data.")
|
83
|
+
return False
|
84
|
+
|
85
|
+
profile_resp = ProfileResponse.model_validate(profile_result)
|
86
|
+
self._organization_id = profile_resp.organization.id
|
87
|
+
|
88
|
+
return True
|
89
|
+
except (RequestException, ValueError, KeyError) as e:
|
90
|
+
logger.error(f"Login failed due to exception: {e}")
|
91
|
+
return False
|
92
|
+
|
93
|
+
def refresh_token(self) -> bool:
|
46
94
|
"""
|
47
|
-
Refreshes
|
95
|
+
Refreshes the access token. Returns True on success, False otherwise.
|
48
96
|
"""
|
49
|
-
|
50
|
-
CLIENT_ID_HEADER: self._client_id
|
51
|
-
|
52
|
-
|
97
|
+
try:
|
98
|
+
custom_headers = {CLIENT_ID_HEADER: self._client_id}
|
99
|
+
result = self.client.patch("/me/sessions", custom_headers, {"refreshToken": self._refresh_token})
|
100
|
+
|
101
|
+
if not result:
|
102
|
+
logger.error("Failed to refresh token: Empty response received")
|
103
|
+
return False
|
53
104
|
|
54
|
-
|
55
|
-
|
56
|
-
|
105
|
+
resp = CreateSessionResponse.model_validate(result)
|
106
|
+
self._access_token = resp.accessToken
|
107
|
+
self._refresh_token = resp.refreshToken
|
108
|
+
|
109
|
+
return True
|
110
|
+
except (RequestException, ValueError) as e:
|
111
|
+
logger.error(f"Token refresh failed: {e}")
|
112
|
+
return False
|
113
|
+
|
114
|
+
def create_org_api_key(self, request: CreateAPIKeyRequest) -> Optional[str]:
|
115
|
+
"""
|
116
|
+
Creates a new API key for the current user.
|
117
|
+
"""
|
118
|
+
try:
|
119
|
+
result = self.client.post(f"/organizations/{self.get_organization_id()}/api-keys",
|
120
|
+
self.get_custom_headers(), request.model_dump())
|
121
|
+
|
122
|
+
return CreateAPIKeyResponse.model_validate(result).key if result else None
|
123
|
+
except (RequestException, ValueError) as e:
|
124
|
+
logger.error(f"Failed to create API key: {e}")
|
125
|
+
return None
|
126
|
+
|
127
|
+
def get_org_api_keys(self) -> Optional[GetAPIKeysResponse]:
|
128
|
+
"""
|
129
|
+
Fetches all API keys for the current user.
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
result = self.client.get(f"/organizations/{self.get_organization_id()}/api-keys",
|
133
|
+
self.get_custom_headers())
|
134
|
+
|
135
|
+
return GetAPIKeysResponse.model_validate(result) if result else None
|
136
|
+
except (RequestException, ValueError) as e:
|
137
|
+
logger.error(f"Failed to retrieve organization API keys: {e}")
|
138
|
+
return None
|
57
139
|
|
58
140
|
def parse_user_id(self) -> str:
|
59
141
|
"""
|
@@ -91,6 +173,12 @@ class IAMClient:
|
|
91
173
|
"""
|
92
174
|
return self._client_id
|
93
175
|
|
176
|
+
def get_organization_id(self) -> str:
|
177
|
+
"""
|
178
|
+
Gets the current organization ID.
|
179
|
+
"""
|
180
|
+
return self._organization_id
|
181
|
+
|
94
182
|
def get_custom_headers(self) -> dict:
|
95
183
|
"""
|
96
184
|
Gets the custom headers for the IAM client.
|
@@ -1,9 +1,14 @@
|
|
1
|
+
import logging
|
2
|
+
from requests.exceptions import RequestException
|
3
|
+
|
1
4
|
from ._http_client import HTTPClient
|
2
5
|
from ._decorator import handle_refresh_token
|
3
6
|
from ._iam_client import IAMClient
|
4
7
|
from .._config import TASK_SERVICE_BASE_URL
|
5
8
|
from .._models import *
|
6
9
|
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
7
12
|
|
8
13
|
class TaskClient:
|
9
14
|
"""
|
@@ -21,17 +26,19 @@ class TaskClient:
|
|
21
26
|
self.iam_client = iam_client
|
22
27
|
|
23
28
|
@handle_refresh_token
|
24
|
-
def get_task(self, task_id: str) -> Task:
|
29
|
+
def get_task(self, task_id: str) -> Optional[Task]:
|
25
30
|
"""
|
26
31
|
Retrieves a task from the task service using the given task ID.
|
27
32
|
|
28
33
|
:param task_id: The ID of the task to be retrieved.
|
29
|
-
:return: An instance of Task containing the details of the retrieved task.
|
30
|
-
:rtype: Task
|
34
|
+
:return: An instance of Task containing the details of the retrieved task, or None if an error occurs.
|
31
35
|
"""
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
try:
|
37
|
+
response = self.client.get("/get_task", self.iam_client.get_custom_headers(), {"task_id": task_id})
|
38
|
+
return Task.model_validate(response) if response else None
|
39
|
+
except (RequestException, ValueError) as e:
|
40
|
+
logger.error(f"Failed to retrieve task {task_id}: {e}")
|
41
|
+
return None
|
35
42
|
|
36
43
|
@handle_refresh_token
|
37
44
|
def get_all_tasks(self) -> GetAllTasksResponse:
|
@@ -39,70 +46,108 @@ class TaskClient:
|
|
39
46
|
Retrieves all tasks from the task service.
|
40
47
|
|
41
48
|
:return: An instance of GetAllTasksResponse containing the retrieved tasks.
|
42
|
-
:rtype: GetAllTasksResponse
|
43
49
|
"""
|
44
|
-
|
45
|
-
|
50
|
+
try:
|
51
|
+
response = self.client.get("/get_tasks", self.iam_client.get_custom_headers())
|
52
|
+
if not response:
|
53
|
+
logger.error("Empty response from /get_tasks")
|
54
|
+
return GetAllTasksResponse(tasks=[])
|
55
|
+
return GetAllTasksResponse.model_validate(response)
|
56
|
+
except (RequestException, ValueError) as e:
|
57
|
+
logger.error(f"Failed to retrieve all tasks: {e}")
|
46
58
|
return GetAllTasksResponse(tasks=[])
|
47
59
|
|
48
|
-
return GetAllTasksResponse.model_validate(result)
|
49
|
-
|
50
60
|
@handle_refresh_token
|
51
|
-
def create_task(self, task: Task) -> CreateTaskResponse:
|
61
|
+
def create_task(self, task: Task) -> Optional[CreateTaskResponse]:
|
52
62
|
"""
|
53
63
|
Creates a new task using the provided task object.
|
54
64
|
|
55
65
|
:param task: The Task object containing the details of the task to be created.
|
66
|
+
:return: The response object containing created task details, or None if an error occurs.
|
56
67
|
"""
|
57
|
-
|
58
|
-
|
59
|
-
|
68
|
+
try:
|
69
|
+
response = self.client.post("/create_task", self.iam_client.get_custom_headers(), task.model_dump())
|
70
|
+
return CreateTaskResponse.model_validate(response) if response else None
|
71
|
+
except (RequestException, ValueError) as e:
|
72
|
+
logger.error(f"Failed to create task: {e}")
|
73
|
+
return None
|
60
74
|
|
61
75
|
@handle_refresh_token
|
62
|
-
def update_task_schedule(self, task: Task):
|
76
|
+
def update_task_schedule(self, task: Task) -> bool:
|
63
77
|
"""
|
64
78
|
Updates the schedule of an existing task.
|
65
79
|
|
66
80
|
:param task: The Task object containing the updated task details.
|
81
|
+
:return: True if update is successful, False otherwise.
|
67
82
|
"""
|
68
|
-
|
83
|
+
try:
|
84
|
+
response = self.client.put("/update_schedule", self.iam_client.get_custom_headers(), task.model_dump())
|
85
|
+
return response is not None
|
86
|
+
except RequestException as e:
|
87
|
+
logger.error(f"Failed to update schedule for task {task.task_id}: {e}")
|
88
|
+
return False
|
69
89
|
|
70
90
|
@handle_refresh_token
|
71
|
-
def start_task(self, task_id: str):
|
91
|
+
def start_task(self, task_id: str) -> bool:
|
72
92
|
"""
|
73
93
|
Starts a task using the given task ID.
|
74
94
|
|
75
95
|
:param task_id: The ID of the task to be started.
|
96
|
+
:return: True if start is successful, False otherwise.
|
76
97
|
"""
|
77
|
-
|
98
|
+
try:
|
99
|
+
response = self.client.post("/start_task", self.iam_client.get_custom_headers(), {"task_id": task_id})
|
100
|
+
return response is not None
|
101
|
+
except RequestException as e:
|
102
|
+
logger.error(f"Failed to start task {task_id}: {e}")
|
103
|
+
return False
|
78
104
|
|
79
105
|
@handle_refresh_token
|
80
|
-
def stop_task(self, task_id: str):
|
106
|
+
def stop_task(self, task_id: str) -> bool:
|
81
107
|
"""
|
82
108
|
Stops a running task using the given task ID.
|
83
109
|
|
84
110
|
:param task_id: The ID of the task to be stopped.
|
111
|
+
:return: True if stop is successful, False otherwise.
|
85
112
|
"""
|
86
|
-
|
113
|
+
try:
|
114
|
+
response = self.client.post("/stop_task", self.iam_client.get_custom_headers(), {"task_id": task_id})
|
115
|
+
return response is not None
|
116
|
+
except RequestException as e:
|
117
|
+
logger.error(f"Failed to stop task {task_id}: {e}")
|
118
|
+
return False
|
87
119
|
|
88
120
|
@handle_refresh_token
|
89
|
-
def get_usage_data(self, start_timestamp: str, end_timestamp: str) -> GetUsageDataResponse:
|
121
|
+
def get_usage_data(self, start_timestamp: str, end_timestamp: str) -> Optional[GetUsageDataResponse]:
|
90
122
|
"""
|
91
123
|
Retrieves the usage data of a task using the given task ID.
|
92
124
|
|
93
125
|
:param start_timestamp: The start timestamp of the usage data.
|
94
126
|
:param end_timestamp: The end timestamp of the usage data.
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
127
|
+
:return: An instance of GetUsageDataResponse, or None if an error occurs.
|
128
|
+
"""
|
129
|
+
try:
|
130
|
+
response = self.client.get(
|
131
|
+
"/get_usage_data",
|
132
|
+
self.iam_client.get_custom_headers(),
|
133
|
+
{"start_timestamp": start_timestamp, "end_timestamp": end_timestamp}
|
134
|
+
)
|
135
|
+
return GetUsageDataResponse.model_validate(response) if response else None
|
136
|
+
except (RequestException, ValueError) as e:
|
137
|
+
logger.error(f"Failed to retrieve usage data from {start_timestamp} to {end_timestamp}: {e}")
|
138
|
+
return None
|
100
139
|
|
101
140
|
@handle_refresh_token
|
102
|
-
def archive_task(self, task_id: str):
|
141
|
+
def archive_task(self, task_id: str) -> bool:
|
103
142
|
"""
|
104
143
|
Archives a task using the given task ID.
|
105
144
|
|
106
145
|
:param task_id: The ID of the task to be archived.
|
107
|
-
|
108
|
-
|
146
|
+
:return: True if archiving is successful, False otherwise.
|
147
|
+
"""
|
148
|
+
try:
|
149
|
+
response = self.client.post("/archive_task", self.iam_client.get_custom_headers(), {"task_id": task_id})
|
150
|
+
return response is not None
|
151
|
+
except RequestException as e:
|
152
|
+
logger.error(f"Failed to archive task {task_id}: {e}")
|
153
|
+
return False
|
gmicloud/_internal/_enums.py
CHANGED
@@ -23,3 +23,11 @@ class TaskEndpointStatus(str, Enum):
|
|
23
23
|
READY = "ready"
|
24
24
|
UNREADY = "unready"
|
25
25
|
NEW = "new"
|
26
|
+
|
27
|
+
class TaskStatus(str, Enum):
|
28
|
+
IDLE = "idle"
|
29
|
+
STARTING = "starting"
|
30
|
+
IN_QUEUE = "in-queue"
|
31
|
+
RUNNING = "running"
|
32
|
+
NEEDSTOP = "needstop"
|
33
|
+
ARCHIVED = "archived"
|