gmicloud 0.1.4__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 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
- :rtype: Artifact
34
- """
35
- result = self.client.get(f"/get_artifact", self.iam_client.get_custom_headers(), {"artifact_id": artifact_id})
36
-
37
- return Artifact.model_validate(result)
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
- result = self.client.get("/get_all_artifacts", self.iam_client.get_custom_headers())
48
- if not result:
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
- :rtype: CreateArtifactResponse
60
- """
61
- result = self.client.post("/create_artifact", self.iam_client.get_custom_headers(), request.model_dump())
62
-
63
- return CreateArtifactResponse.model_validate(result)
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
- :rtype: CreateArtifactFromTemplateResponse
73
- """
74
- result = self.client.post("/create_artifact_from_template", self.iam_client.get_custom_headers(),
75
- {"artifact_template_id": artifact_template_id})
76
-
77
- return CreateArtifactFromTemplateResponse.model_validate(result)
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
- :rtype: RebuildArtifactResponse
87
- """
88
- result = self.client.post("/rebuild_artifact", self.iam_client.get_custom_headers(),
89
- {"artifact_id": artifact_id})
90
-
91
- return CreateArtifactResponse.model_validate(result)
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
- :rtype: DeleteArtifactResponse
101
- """
102
- result = self.client.delete("/delete_artifact", self.iam_client.get_custom_headers(),
103
- {"artifact_id": artifact_id})
104
-
105
- return DeleteArtifactResponse.model_validate(result)
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
- result = self.client.post("/get_bigfile_upload_url", self.iam_client.get_custom_headers(), request.model_dump())
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
- return GetBigFileUploadUrlResponse.model_validate(result)
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
- result = self.client.delete("/delete_bigfile", self.iam_client.get_custom_headers(), request.dict())
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
- return DeleteBigfileResponse.model_validate(result)
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 get_artifact_templates(self) -> GetArtifactTemplatesResponse:
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
- result = self.client.get("/get_artifact_templates", self.iam_client.get_custom_headers())
142
- return GetArtifactTemplatesResponse.model_validate(result)
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,12 @@
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__)
9
+
6
10
 
7
11
  class HTTPClient:
8
12
  """
@@ -51,6 +55,7 @@ class HTTPClient:
51
55
  response = None
52
56
  try:
53
57
  response = requests.request(method, url, params=params, json=data, headers=headers)
58
+ logger.debug(response.text)
54
59
  if response.status_code == 401:
55
60
  raise UnauthorizedError("Access token expired or invalid.")
56
61
  elif response.status_code != 200 and response.status_code != 201:
@@ -61,7 +66,6 @@ class HTTPClient:
61
66
  raise APIError(f"HTTP Request failed: {error_message}")
62
67
  # Raise for HTTP errors
63
68
  response.raise_for_status()
64
- print(response.text)
65
69
 
66
70
  except requests.exceptions.RequestException as e:
67
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,58 +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 username and password.
37
+ Logs in a user with the given email and password.
38
+ Returns True if login is successful, otherwise False.
33
39
  """
34
- custom_headers = {
35
- CLIENT_ID_HEADER: self._client_id
36
- }
37
- req = AuthTokenRequest(email=self._email, password=self._password)
38
- auth_tokens_result = self.client.post("/me/auth-tokens", custom_headers, req.model_dump())
39
- auth_tokens_resp = AuthTokenResponse.model_validate(auth_tokens_result)
40
-
41
- create_session_result = None
42
- if auth_tokens_resp.is2FARequired:
43
- max_attempts = 3
44
- for attempt in range(max_attempts):
45
- code = input(f"Attempt {attempt + 1}/{max_attempts}: Please enter the 2FA code: ")
46
-
47
- create_session_req = CreateSessionRequest(
48
- type="native", authToken=auth_tokens_resp.authToken, otpCode=code
49
- )
50
- try:
51
- create_session_result = self.client.post(
52
- "/me/sessions", custom_headers, create_session_req.model_dump()
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
53
57
  )
54
- break
55
- except Exception as e:
56
- print("Invalid 2FA code, please try again.")
57
- if attempt == max_attempts - 1:
58
- raise Exception("Failed to create session after 3 incorrect 2FA attempts.") from e
59
- else:
60
- create_session_req = CreateSessionRequest(type="native", authToken=auth_tokens_resp.authToken, otpCode=None)
61
- create_session_result = self.client.post("/me/sessions", custom_headers, create_session_req.model_dump())
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)
62
74
 
63
- create_session_resp = CreateSessionResponse.model_validate(create_session_result)
64
- self._access_token = create_session_resp.accessToken
65
- self._refresh_token = create_session_resp.refreshToken
66
- 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()
67
78
 
68
- def refresh_token(self):
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:
69
94
  """
70
- Refreshes a user's token using a refresh token.
95
+ Refreshes the access token. Returns True on success, False otherwise.
71
96
  """
72
- custom_headers = {
73
- CLIENT_ID_HEADER: self._client_id
74
- }
75
- result = self.client.patch("/me/sessions", custom_headers, {"refreshToken": self._refresh_token})
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
76
104
 
77
- resp = CreateSessionResponse.model_validate(result)
78
- self._access_token = resp.accessToken
79
- self._refresh_token = resp.refreshToken
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
80
139
 
81
140
  def parse_user_id(self) -> str:
82
141
  """
@@ -114,6 +173,12 @@ class IAMClient:
114
173
  """
115
174
  return self._client_id
116
175
 
176
+ def get_organization_id(self) -> str:
177
+ """
178
+ Gets the current organization ID.
179
+ """
180
+ return self._organization_id
181
+
117
182
  def get_custom_headers(self) -> dict:
118
183
  """
119
184
  Gets the custom headers for the IAM client.