gmicloud 0.1.7__tar.gz → 0.1.10__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.
Files changed (36) hide show
  1. {gmicloud-0.1.7 → gmicloud-0.1.10}/PKG-INFO +60 -33
  2. {gmicloud-0.1.7 → gmicloud-0.1.10}/README.md +59 -32
  3. gmicloud-0.1.10/gmicloud/_internal/_client/_auth_config.py +78 -0
  4. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/_http_client.py +7 -7
  5. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/_iam_client.py +57 -38
  6. gmicloud-0.1.10/gmicloud/_internal/_client/_video_client.py +111 -0
  7. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_config.py +4 -3
  8. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_enums.py +19 -1
  9. gmicloud-0.1.10/gmicloud/_internal/_exceptions.py +36 -0
  10. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_manager/_artifact_manager.py +27 -8
  11. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_manager/_iam_manager.py +1 -1
  12. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_manager/_task_manager.py +29 -0
  13. gmicloud-0.1.10/gmicloud/_internal/_manager/_video_manager.py +91 -0
  14. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_manager/serve_command_utils.py +10 -6
  15. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_models.py +88 -3
  16. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/client.py +15 -6
  17. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud.egg-info/PKG-INFO +60 -33
  18. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud.egg-info/SOURCES.txt +3 -0
  19. {gmicloud-0.1.7 → gmicloud-0.1.10}/pyproject.toml +1 -1
  20. gmicloud-0.1.7/gmicloud/_internal/_exceptions.py +0 -19
  21. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/__init__.py +0 -0
  22. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/__init__.py +0 -0
  23. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/__init__.py +0 -0
  24. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/_artifact_client.py +0 -0
  25. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/_decorator.py +0 -0
  26. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/_file_upload_client.py +0 -0
  27. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_client/_task_client.py +0 -0
  28. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_constants.py +0 -0
  29. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/_internal/_manager/__init__.py +0 -0
  30. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/tests/__init__.py +0 -0
  31. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/tests/test_artifacts.py +0 -0
  32. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/tests/test_tasks.py +0 -0
  33. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud/utils/uninstall_packages.py +0 -0
  34. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud.egg-info/dependency_links.txt +0 -0
  35. {gmicloud-0.1.7 → gmicloud-0.1.10}/gmicloud.egg-info/top_level.txt +0 -0
  36. {gmicloud-0.1.7 → gmicloud-0.1.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gmicloud
3
- Version: 0.1.7
3
+ Version: 0.1.10
4
4
  Summary: GMI Cloud Python SDK
5
5
  Author-email: GMI <gmi@gmitec.net>
6
6
  License: MIT
@@ -133,7 +133,22 @@ model_checkpoint_save_dir = "files/model_garden"
133
133
  snapshot_download(repo_id=model_name, local_dir=model_checkpoint_save_dir)
134
134
  ```
135
135
 
136
- 2. Find a template of specific SGLang version
136
+ #### Pre-downloaded models
137
+ ```
138
+ "deepseek-ai/DeepSeek-R1"
139
+ "deepseek-ai/DeepSeek-V3-0324"
140
+ "deepseek-ai/DeepSeek-R1-Distill-Llama-70B"
141
+ "deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
142
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B"
143
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
144
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
145
+ "meta-llama/Llama-3.3-70B-Instruct"
146
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct"
147
+ "meta-llama/Llama-4-Scout-17B-16E-Instruct"
148
+ "Qwen/QwQ-32B"
149
+ ```
150
+
151
+ 2. Find a template of specific vllm or SGLang version
137
152
 
138
153
  ```python
139
154
  # export GMI_CLOUD_CLIENT_ID=<YOUR_CLIENT_ID>
@@ -158,55 +173,67 @@ picked_template_name = "gmi_sglang_0.4.5.post1"
158
173
  serve_command = "python3 -m sglang.launch_server --model-path deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B --trust-remote-code --mem-fraction-static 0.8 --tp 2"
159
174
  ```
160
175
 
161
- 4. Create an artifact and upload custom model. The artifact can be reused to create inference tasks later. Artifact also suggests recommended resources for each inference server replica
176
+ 4. Create an artifact. you can pass `pre_download_model` parameter. If you want custom model, upload model checkpoint to the artifactThe artifact can be reused to create inference tasks later. Artifact also suggests recommended resources for each inference server replica
162
177
 
163
178
  ```python
164
- artifact_id, recommended_replica_resources = cli.artifact_manager.create_artifact_from_template_name(
165
- artifact_template_name=picked_template_name,
166
- env_parameters={
167
- "SERVER_COMMAND": serve_command,
168
- "GPU_TYPE": "H100",
169
- }
179
+ artifact_name = "artifact_hello_world"
180
+ artifact_id, recommended_replica_resources = cli.artifact_manager.create_artifact_for_serve_command_and_custom_model(
181
+ template_name=picked_template_name,
182
+ artifact_name=artifact_name,
183
+ serve_command=serve_command,
184
+ gpu_type="H100",
185
+ artifact_description="This is a test artifact",
186
+ pre_download_model=pick_pre_downloaded_model,
170
187
  )
171
188
  print(f"Created artifact {artifact_id} with recommended resources: {recommended_replica_resources}")
189
+ ```
172
190
 
173
- # Upload model files to artifact
191
+ Alternatively, Upload a custom model checkpoint to artifact
192
+ ```python
174
193
  cli.artifact_manager.upload_model_files_to_artifact(artifact_id, model_checkpoint_save_dir)
194
+
195
+ # Maybe Wait 10 minutes for the artifact to be ready
196
+ time.sleep(10 * 60)
175
197
  ```
176
198
 
177
199
  5. Create Inference task (defining min/max inference replica), start and wait
178
200
 
179
201
  ```python
180
- new_task = Task(
181
- config=TaskConfig(
182
- ray_task_config=RayTaskConfig(
183
- artifact_id=artifact_id,
184
- file_path="serve",
185
- deployment_name="app",
186
- replica_resource=recommended_replica_resources,
187
- ),
188
- task_scheduling = TaskScheduling(
189
- scheduling_oneoff=OneOffScheduling(
190
- trigger_timestamp=int(datetime.now().timestamp()),
191
- min_replicas=1,
192
- max_replicas=4,
193
- )
194
- ),
195
- ),
196
- )
197
- task = cli.task_manager.create_task(new_task)
198
- task_id = task.task_id
199
- task = cli.task_manager.get_task(task_id)
202
+ # Create Task based on Artifact
203
+ new_task_id = cli.task_manager.create_task_from_artifact_id(artifact_id, recommended_replica_resources, TaskScheduling(
204
+ scheduling_oneoff=OneOffScheduling(
205
+ trigger_timestamp=int(datetime.now().timestamp()),
206
+ min_replicas=1,
207
+ max_replicas=4,
208
+ )
209
+ ))
210
+ task = cli.task_manager.get_task(new_task_id)
200
211
  print(f"Task created: {task.config.task_name}. You can check details at https://inference-engine.gmicloud.ai/user-console/task")
201
212
 
202
213
  # Start Task and wait for it to be ready
203
- cli.task_manager.start_task_and_wait(task_id)
214
+ cli.task_manager.start_task_and_wait(new_task_id)
204
215
  ```
205
216
 
206
- 6. Test with sample chat completion request
217
+ 6. Test with sample chat completion request with OpenAI client
207
218
 
208
219
  ```python
209
- print(call_chat_completion(cli, task_id))
220
+ pi_key = "<YOUR_API_KEY>"
221
+ endpoint_url = cli.task_manager.get_task_endpoint_url(new_task_id)
222
+ open_ai = OpenAI(
223
+ base_url=os.getenv("OPENAI_API_BASE", f"https://{endpoint_url}/serve/v1/"),
224
+ api_key=api_key
225
+ )
226
+ # Make a chat completion request using the new OpenAI client.
227
+ completion = open_ai.chat.completions.create(
228
+ model=picked_template_name,
229
+ messages=[
230
+ {"role": "system", "content": "You are a helpful assistant."},
231
+ {"role": "user", "content": "Who are you?"},
232
+ ],
233
+ max_tokens=500,
234
+ temperature=0.7
235
+ )
236
+ print(completion.choices[0].message.content)
210
237
  ```
211
238
 
212
239
 
@@ -121,7 +121,22 @@ model_checkpoint_save_dir = "files/model_garden"
121
121
  snapshot_download(repo_id=model_name, local_dir=model_checkpoint_save_dir)
122
122
  ```
123
123
 
124
- 2. Find a template of specific SGLang version
124
+ #### Pre-downloaded models
125
+ ```
126
+ "deepseek-ai/DeepSeek-R1"
127
+ "deepseek-ai/DeepSeek-V3-0324"
128
+ "deepseek-ai/DeepSeek-R1-Distill-Llama-70B"
129
+ "deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
130
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B"
131
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
132
+ "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
133
+ "meta-llama/Llama-3.3-70B-Instruct"
134
+ "meta-llama/Llama-4-Maverick-17B-128E-Instruct"
135
+ "meta-llama/Llama-4-Scout-17B-16E-Instruct"
136
+ "Qwen/QwQ-32B"
137
+ ```
138
+
139
+ 2. Find a template of specific vllm or SGLang version
125
140
 
126
141
  ```python
127
142
  # export GMI_CLOUD_CLIENT_ID=<YOUR_CLIENT_ID>
@@ -146,55 +161,67 @@ picked_template_name = "gmi_sglang_0.4.5.post1"
146
161
  serve_command = "python3 -m sglang.launch_server --model-path deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B --trust-remote-code --mem-fraction-static 0.8 --tp 2"
147
162
  ```
148
163
 
149
- 4. Create an artifact and upload custom model. The artifact can be reused to create inference tasks later. Artifact also suggests recommended resources for each inference server replica
164
+ 4. Create an artifact. you can pass `pre_download_model` parameter. If you want custom model, upload model checkpoint to the artifactThe artifact can be reused to create inference tasks later. Artifact also suggests recommended resources for each inference server replica
150
165
 
151
166
  ```python
152
- artifact_id, recommended_replica_resources = cli.artifact_manager.create_artifact_from_template_name(
153
- artifact_template_name=picked_template_name,
154
- env_parameters={
155
- "SERVER_COMMAND": serve_command,
156
- "GPU_TYPE": "H100",
157
- }
167
+ artifact_name = "artifact_hello_world"
168
+ artifact_id, recommended_replica_resources = cli.artifact_manager.create_artifact_for_serve_command_and_custom_model(
169
+ template_name=picked_template_name,
170
+ artifact_name=artifact_name,
171
+ serve_command=serve_command,
172
+ gpu_type="H100",
173
+ artifact_description="This is a test artifact",
174
+ pre_download_model=pick_pre_downloaded_model,
158
175
  )
159
176
  print(f"Created artifact {artifact_id} with recommended resources: {recommended_replica_resources}")
177
+ ```
160
178
 
161
- # Upload model files to artifact
179
+ Alternatively, Upload a custom model checkpoint to artifact
180
+ ```python
162
181
  cli.artifact_manager.upload_model_files_to_artifact(artifact_id, model_checkpoint_save_dir)
182
+
183
+ # Maybe Wait 10 minutes for the artifact to be ready
184
+ time.sleep(10 * 60)
163
185
  ```
164
186
 
165
187
  5. Create Inference task (defining min/max inference replica), start and wait
166
188
 
167
189
  ```python
168
- new_task = Task(
169
- config=TaskConfig(
170
- ray_task_config=RayTaskConfig(
171
- artifact_id=artifact_id,
172
- file_path="serve",
173
- deployment_name="app",
174
- replica_resource=recommended_replica_resources,
175
- ),
176
- task_scheduling = TaskScheduling(
177
- scheduling_oneoff=OneOffScheduling(
178
- trigger_timestamp=int(datetime.now().timestamp()),
179
- min_replicas=1,
180
- max_replicas=4,
181
- )
182
- ),
183
- ),
184
- )
185
- task = cli.task_manager.create_task(new_task)
186
- task_id = task.task_id
187
- task = cli.task_manager.get_task(task_id)
190
+ # Create Task based on Artifact
191
+ new_task_id = cli.task_manager.create_task_from_artifact_id(artifact_id, recommended_replica_resources, TaskScheduling(
192
+ scheduling_oneoff=OneOffScheduling(
193
+ trigger_timestamp=int(datetime.now().timestamp()),
194
+ min_replicas=1,
195
+ max_replicas=4,
196
+ )
197
+ ))
198
+ task = cli.task_manager.get_task(new_task_id)
188
199
  print(f"Task created: {task.config.task_name}. You can check details at https://inference-engine.gmicloud.ai/user-console/task")
189
200
 
190
201
  # Start Task and wait for it to be ready
191
- cli.task_manager.start_task_and_wait(task_id)
202
+ cli.task_manager.start_task_and_wait(new_task_id)
192
203
  ```
193
204
 
194
- 6. Test with sample chat completion request
205
+ 6. Test with sample chat completion request with OpenAI client
195
206
 
196
207
  ```python
197
- print(call_chat_completion(cli, task_id))
208
+ pi_key = "<YOUR_API_KEY>"
209
+ endpoint_url = cli.task_manager.get_task_endpoint_url(new_task_id)
210
+ open_ai = OpenAI(
211
+ base_url=os.getenv("OPENAI_API_BASE", f"https://{endpoint_url}/serve/v1/"),
212
+ api_key=api_key
213
+ )
214
+ # Make a chat completion request using the new OpenAI client.
215
+ completion = open_ai.chat.completions.create(
216
+ model=picked_template_name,
217
+ messages=[
218
+ {"role": "system", "content": "You are a helpful assistant."},
219
+ {"role": "user", "content": "Who are you?"},
220
+ ],
221
+ max_tokens=500,
222
+ temperature=0.7
223
+ )
224
+ print(completion.choices[0].message.content)
198
225
  ```
199
226
 
200
227
 
@@ -0,0 +1,78 @@
1
+ import os
2
+ import jwt
3
+ import time
4
+ import json
5
+ import logging
6
+ import threading
7
+ from pathlib import Path
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ CONFIG_FILE_NAME = ".gmicloud.config.json"
12
+
13
+ # create the thread lock object56
14
+ lock = threading.Lock()
15
+
16
+ def _read_config_file()->dict|None:
17
+ """Read the config file."""
18
+ base_dir = Path.home()
19
+ config_file_path =os.path.join(base_dir,CONFIG_FILE_NAME)
20
+ if not os.path.exists(config_file_path):
21
+ return None
22
+ with lock:
23
+ # open the config file, read mode with lock
24
+ with open(config_file_path,"r") as fr:
25
+ return json.loads(fr.read())
26
+
27
+
28
+ def _write_config_file(config_file_path:str,config_dic:dict)->None:
29
+ """Write the config file."""
30
+ with lock:
31
+ # open the config file, write mode with lock
32
+ with open(config_file_path,"w") as fw:
33
+ # transform the config dictionary to JSON format and write it to the file
34
+ fw.write(json.dumps(config_dic))
35
+
36
+
37
+ def write_user_refresh_token_to_system_config(email:str,refresh_token:str)->bool:
38
+ """Write the user refresh token to the system config file."""
39
+ base_dir = Path.home()
40
+ config_file_path = os.path.join(base_dir,CONFIG_FILE_NAME)
41
+ try:
42
+ # check the config file is exists. if not, create it, if yes, update the refresh token
43
+ if not os.path.exists(config_file_path):
44
+ config_dic = { email : {"refresh_token": refresh_token} }
45
+ _write_config_file(config_file_path,config_dic)
46
+ else:
47
+ config_dic = _read_config_file()
48
+ if not config_dic.get(email):
49
+ config_dic[email] = dict()
50
+ config_dic[email] = {"refresh_token": refresh_token}
51
+ _write_config_file(config_file_path,config_dic)
52
+ except Exception as e:
53
+ logger.error("write file wrong :", e)
54
+ return False
55
+ return True
56
+
57
+
58
+ def get_user_refresh_token_from_system_config(email:str)->str|None:
59
+ """Get the user refresh token from the system config file."""
60
+ config_dic = _read_config_file()
61
+ if not config_dic or not config_dic.get(email):
62
+ return None
63
+ return config_dic[email]["refresh_token"]
64
+
65
+
66
+ def _parese_refresh_token(refresh_token:str)->dict:
67
+ """Parse the refresh token."""
68
+ return jwt.decode(refresh_token, options={"verify_signature": False})
69
+
70
+
71
+ def is_refresh_token_expired(refresh_token:str)->bool:
72
+ """Check the refresh token is expired. if expired, return True, else return False."""
73
+ try:
74
+ refresh_token_time = _parese_refresh_token(refresh_token)['exp']
75
+ except Exception as e:
76
+ logger.error("parse refresh token wrong :", e)
77
+ return True
78
+ return refresh_token_time < time.time()
@@ -57,28 +57,28 @@ class HTTPClient:
57
57
  response = requests.request(method, url, params=params, json=data, headers=headers)
58
58
  logger.debug(response.text)
59
59
  if response.status_code == 401:
60
- raise UnauthorizedError("Access token expired or invalid.")
60
+ raise UnauthorizedError(f"Unauthorized Error : {response.status_code} - Access token expired or invalid.")
61
61
  elif response.status_code != 200 and response.status_code != 201:
62
- if url.find("ie/artifact") != -1 or url.find("ie/task") != -1:
62
+ if url.find("ie/artifact") != -1 or url.find("ie/task") != -1 or url.find("ie/requestqueue") != -1:
63
63
  error_message = response.json().get('error', 'Unknown error')
64
64
  else:
65
65
  error_message = response.json().get('message', 'Unknown error')
66
- raise APIError(f"HTTP Request failed: {error_message}")
66
+ raise APIError(f"HTTP Request failed: {response.status_code} - {error_message}")
67
67
  # Raise for HTTP errors
68
68
  response.raise_for_status()
69
69
 
70
70
  except requests.exceptions.RequestException as e:
71
- raise APIError(f"HTTP Request failed: {str(e)}")
71
+ raise APIError(f"HTTP Request failed: {response.status_code} - {str(e)}")
72
72
  except ValueError as e:
73
73
  # Fallback if response JSON is invalid
74
- raise APIError(f"Failed to parse JSON response: {response.text}")
74
+ raise APIError(f"Failed to parse JSON response: {response.status_code} - {response.text}")
75
75
 
76
76
  if response.headers.get(CONTENT_TYPE_HEADER).find(JSON_CONTENT_TYPE) != -1:
77
77
  return response.json()
78
78
  elif response.headers.get(CONTENT_TYPE_HEADER).find(TEXT_CONTENT_TYPE) != -1:
79
- raise APIError(f"Got text response: {response.text}")
79
+ raise APIError(f"Got text response: {response.status_code} - {response.text}")
80
80
  else:
81
- raise APIError(f"Unsupported content type: {response.headers.get(CONTENT_TYPE_HEADER)}")
81
+ raise APIError(f"Unsupported content type: {response.status_code} - {response.headers.get(CONTENT_TYPE_HEADER)}")
82
82
 
83
83
  def post(self, endpoint, custom_headers=None, data=None):
84
84
  """
@@ -6,7 +6,11 @@ from ._http_client import HTTPClient
6
6
  from .._config import IAM_SERVICE_BASE_URL
7
7
  from .._models import *
8
8
  from .._constants import CLIENT_ID_HEADER, AUTHORIZATION_HEADER
9
-
9
+ from ._auth_config import (
10
+ get_user_refresh_token_from_system_config,
11
+ write_user_refresh_token_to_system_config,
12
+ is_refresh_token_expired
13
+ )
10
14
  logger = logging.getLogger(__name__)
11
15
 
12
16
 
@@ -38,42 +42,50 @@ class IAMClient:
38
42
  Returns True if login is successful, otherwise False.
39
43
  """
40
44
  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
45
+ # Check config refresh token is available and is not expired, if yes ,refresh it
46
+ temp_refresh_token = get_user_refresh_token_from_system_config(self._email)
47
+ if temp_refresh_token and not is_refresh_token_expired(temp_refresh_token):
48
+ self._refresh_token = temp_refresh_token
49
+ self.refresh_token()
68
50
  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)
74
-
75
- self._access_token = create_session_resp.accessToken
76
- self._refresh_token = create_session_resp.refreshToken
51
+ custom_headers = {CLIENT_ID_HEADER: self._client_id}
52
+ req = AuthTokenRequest(email=self._email, password=self._password)
53
+ auth_tokens_result = self.client.post("/me/auth-tokens", custom_headers, req.model_dump())
54
+
55
+ if not auth_tokens_result:
56
+ logger.error("Login failed: Received empty response from auth-tokens endpoint")
57
+ return False
58
+
59
+ auth_tokens_resp = AuthTokenResponse.model_validate(auth_tokens_result)
60
+
61
+ # Handle 2FA
62
+ if auth_tokens_resp.is2FARequired:
63
+ for attempt in range(3):
64
+ code = input(f"Attempt {attempt + 1}/3: Please enter the 2FA code: ")
65
+ create_session_req = CreateSessionRequest(
66
+ type="native", authToken=auth_tokens_resp.authToken, otpCode=code
67
+ )
68
+ try:
69
+ session_result = self.client.post("/me/sessions", custom_headers,
70
+ create_session_req.model_dump())
71
+ if session_result:
72
+ break
73
+ except RequestException:
74
+ logger.warning("Invalid 2FA code, please try again.")
75
+ if attempt == 2:
76
+ logger.error("Failed to create session after 3 incorrect 2FA attempts.")
77
+ return False
78
+ else:
79
+ create_session_req = CreateSessionRequest(type="native", authToken=auth_tokens_resp.authToken,
80
+ otpCode=None)
81
+ session_result = self.client.post("/me/sessions", custom_headers, create_session_req.model_dump())
82
+
83
+ create_session_resp = CreateSessionResponse.model_validate(session_result)
84
+
85
+ self._access_token = create_session_resp.accessToken
86
+ self._refresh_token = create_session_resp.refreshToken
87
+ # first login write refresh token to system config
88
+ write_user_refresh_token_to_system_config(self._email,self._refresh_token)
77
89
  self._user_id = self.parse_user_id()
78
90
 
79
91
  # Fetch profile to get organization ID
@@ -96,7 +108,12 @@ class IAMClient:
96
108
  """
97
109
  try:
98
110
  custom_headers = {CLIENT_ID_HEADER: self._client_id}
99
- result = self.client.patch("/me/sessions", custom_headers, {"refreshToken": self._refresh_token})
111
+ try:
112
+ result = self.client.patch("/me/sessions", custom_headers, {"refreshToken": self._refresh_token})
113
+ except Exception as err:
114
+ logger.error(f"{str(err)}, please re-login.")
115
+ write_user_refresh_token_to_system_config(self._email,"")
116
+ return False
100
117
 
101
118
  if not result:
102
119
  logger.error("Failed to refresh token: Empty response received")
@@ -105,7 +122,9 @@ class IAMClient:
105
122
  resp = CreateSessionResponse.model_validate(result)
106
123
  self._access_token = resp.accessToken
107
124
  self._refresh_token = resp.refreshToken
108
-
125
+ # the _refresh_token will be updated when call this function
126
+ # so write it to system config file for update the _refresh_token expired time
127
+ write_user_refresh_token_to_system_config(self._email,self._refresh_token)
109
128
  return True
110
129
  except (RequestException, ValueError) as e:
111
130
  logger.error(f"Token refresh failed: {e}")
@@ -0,0 +1,111 @@
1
+
2
+ import logging
3
+ from requests.exceptions import RequestException
4
+
5
+ from ._http_client import HTTPClient
6
+ from ._decorator import handle_refresh_token
7
+ from ._iam_client import IAMClient
8
+ from .._config import IAM_SERVICE_BASE_URL
9
+ from .._models import *
10
+ from .._exceptions import formated_exception
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class VideoClient:
16
+ """
17
+ A client for interacting with the video service API.
18
+
19
+ This client provides methods to retrieve, create, update, and stop video tasks
20
+ through HTTP calls to the video service.
21
+ """
22
+
23
+ def __init__(self, iam_client: IAMClient):
24
+ """
25
+ Initializes the VideoClient with the given base URL for the video service.
26
+ """
27
+ self.client = HTTPClient(IAM_SERVICE_BASE_URL+ "/ie/requestqueue")
28
+ self.iam_client = iam_client
29
+
30
+
31
+ @handle_refresh_token
32
+ def get_request_detail(self, request_id: str) -> GetRequestResponse | dict:
33
+ """
34
+ Retrieves detailed information about a specific request by its ID. This endpoint requires authentication with a bearer token and only returns requests belonging to the authenticated organization.
35
+
36
+ :param request_id: The ID of the request to be retrieved.
37
+ :return: Details of the GetRequestResponse successfully retrieved
38
+ """
39
+ try:
40
+ response = self.client.get(f"/requests/{request_id}", self.iam_client.get_custom_headers())
41
+ return GetRequestResponse.model_validate(response) if response else None
42
+ except Exception as e:
43
+ logger.error(f"An unexpected error occurred while retrieving request details for {request_id}: {e}")
44
+ return formated_exception(e)
45
+
46
+
47
+ @handle_refresh_token
48
+ def get_requests(self, model_id: str) -> List[GetRequestResponse] | dict:
49
+ """
50
+ Retrieves a list of requests submitted by the authenticated user for a specific model. This endpoint requires authentication with a bearer token and filters results by the authenticated organization.
51
+
52
+ :param model_id: The ID of the model to be retrieved.
53
+ :return: List of GetRequestResponse successfully retrieved
54
+ """
55
+ try:
56
+ response = self.client.get("/requests", self.iam_client.get_custom_headers(), {"model_id": model_id})
57
+ requests = response.get('requests', []) if response else []
58
+ return [GetRequestResponse.model_validate(req) for req in requests] if requests else None
59
+ except Exception as e:
60
+ logger.error(f"An unexpected error occurred while retrieving requests for model {model_id}: {e}")
61
+ return formated_exception(e)
62
+
63
+
64
+ @handle_refresh_token
65
+ def create_request(self, request: SubmitRequestRequest) -> SubmitRequestResponse | dict:
66
+ """
67
+ Submits a new asynchronous request to process a specified model with provided parameters. This endpoint requires authentication with a bearer token.
68
+
69
+ :param request: The request data to be created by SubmitRequestRequest model.
70
+ :return: The created request data as SubmitRequestResponse model.
71
+ """
72
+ try:
73
+ response = self.client.post("/requests", self.iam_client.get_custom_headers(), request.model_dump())
74
+ return SubmitRequestResponse.model_validate(response) if response else None
75
+ except Exception as e:
76
+ logger.error(f"An unexpected error occurred while creating a request: {e}")
77
+ return formated_exception(e)
78
+
79
+
80
+ @handle_refresh_token
81
+ def get_model_detail(self, model_id: str) -> GetModelResponse | dict:
82
+ """
83
+ Retrieves detailed information about a specific model by its ID.
84
+
85
+ :param model_id: The ID of the model to be retrieved.
86
+ :return: Details of the GetModelResponse model successfully retrieved.
87
+ """
88
+ try:
89
+ response = self.client.get(f"/models/{model_id}", self.iam_client.get_custom_headers())
90
+ return GetModelResponse.model_validate(response) if response else None
91
+ except Exception as e:
92
+ logger.error(f"An unexpected error occurred while retrieving model details for {model_id}: {e}")
93
+ return formated_exception(e)
94
+
95
+
96
+ @handle_refresh_token
97
+ def get_models(self) -> List[GetModelResponse] | dict:
98
+ """
99
+ Retrieves a list of available models from the video service.
100
+
101
+ :return: A list of GetModelResponse model successfully retrieved.
102
+ """
103
+ try:
104
+ response = self.client.get("/models", self.iam_client.get_custom_headers())
105
+ models = response.get('models', []) if response else []
106
+ return [GetModelResponse.model_validate(model) for model in models] if models else None
107
+ except Exception as e:
108
+ logger.error(f"An unexpected error occurred while retrieving models: {e}")
109
+ return formated_exception(e)
110
+
111
+
@@ -3,7 +3,8 @@
3
3
  # TASK_SERVICE_BASE_URL = "https://ce-tot.gmicloud-dev.com/api/v1/ie/task"
4
4
  # IAM_SERVICE_BASE_URL = "https://ce-tot.gmicloud-dev.com/api/v1"
5
5
 
6
+
6
7
  # Prod environment
7
- ARTIFACT_SERVICE_BASE_URL = "https://inference-engine.gmicloud.ai/api/v1/ie/artifact"
8
- TASK_SERVICE_BASE_URL = "https://inference-engine.gmicloud.ai/api/v1/ie/task"
9
- IAM_SERVICE_BASE_URL = "https://inference-engine.gmicloud.ai/api/v1"
8
+ ARTIFACT_SERVICE_BASE_URL = "https://console.gmicloud.ai/api/v1/ie/artifact"
9
+ TASK_SERVICE_BASE_URL = "https://console.gmicloud.ai/api/v1/ie/task"
10
+ IAM_SERVICE_BASE_URL = "https://console.gmicloud.ai/api/v1"
@@ -24,6 +24,7 @@ class TaskEndpointStatus(str, Enum):
24
24
  UNREADY = "unready"
25
25
  NEW = "new"
26
26
 
27
+
27
28
  class TaskStatus(str, Enum):
28
29
  IDLE = "idle"
29
30
  STARTING = "starting"
@@ -32,7 +33,24 @@ class TaskStatus(str, Enum):
32
33
  NEEDSTOP = "needstop"
33
34
  ARCHIVED = "archived"
34
35
 
36
+
35
37
  class ModelParameterType(str, Enum):
36
38
  NUMERIC = "numeric"
37
39
  TEXT = "text"
38
- BOOLEAN = "boolean"
40
+ BOOLEAN = "boolean"
41
+
42
+
43
+ class RequestStatus(Enum):
44
+ CREATED = "created"
45
+ QUEUED = "queued"
46
+ DISPATCHED = "dispatched"
47
+ PROCESSING = "processing"
48
+ SUCCESS = "success"
49
+ FAILED = "failed"
50
+ CANCELLED = "cancelled"
51
+
52
+
53
+ class HostType(Enum):
54
+ DEFAULT = ""
55
+ INTERNAL = "internal"
56
+ EXTERNAL = "external"