thestage 0.6.5__py3-none-any.whl → 0.6.7__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.
Files changed (75) hide show
  1. thestage/__init__.py +1 -1
  2. thestage/color_scheme/color_scheme.py +1 -0
  3. thestage/controllers/base_controller.py +4 -3
  4. thestage/controllers/config_controller.py +16 -4
  5. thestage/controllers/container_controller.py +151 -106
  6. thestage/controllers/instance_controller.py +35 -9
  7. thestage/controllers/project_controller.py +335 -89
  8. thestage/entities/container.py +5 -3
  9. thestage/entities/project_inference_simulator.py +2 -1
  10. thestage/entities/project_inference_simulator_model.py +2 -2
  11. thestage/entities/project_task.py +2 -3
  12. thestage/entities/rented_instance.py +2 -2
  13. thestage/entities/self_hosted_instance.py +2 -2
  14. thestage/helpers/error_handler.py +1 -1
  15. thestage/main.py +1 -1
  16. thestage/services/clients/git/git_client.py +1 -1
  17. thestage/services/clients/thestage_api/api_client.py +142 -109
  18. thestage/services/clients/thestage_api/dtos/base_controller/connect_resolve_response.py +22 -0
  19. thestage/services/clients/thestage_api/dtos/container_param_request.py +1 -1
  20. thestage/services/clients/thestage_api/dtos/container_response.py +1 -21
  21. thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_request.py +2 -1
  22. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_request.py +5 -1
  23. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_response.py +2 -1
  24. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_request.py +1 -0
  25. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_request.py +2 -1
  26. thestage/services/clients/thestage_api/dtos/inference_controller/{inference_simulator_list_for_project_request.py → inference_simulator_list_request.py} +3 -2
  27. thestage/services/clients/thestage_api/dtos/inference_controller/{inference_simulator_list_for_project_response.py → inference_simulator_list_response.py} +1 -1
  28. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_request.py +2 -1
  29. thestage/services/clients/thestage_api/dtos/instance_rented_response.py +4 -37
  30. thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +3 -3
  31. thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_request.py +3 -11
  32. thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py +1 -0
  33. thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py +2 -1
  34. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_request.py +2 -4
  35. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +3 -0
  36. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py +5 -3
  37. thestage/services/clients/thestage_api/dtos/project_response.py +3 -15
  38. thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +2 -20
  39. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_response.py +1 -1
  40. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_request.py +4 -2
  41. thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_response.py +1 -1
  42. thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py +4 -1
  43. thestage/services/clients/thestage_api/dtos/task_controller/task_view_response.py +0 -2
  44. thestage/services/config_provider/config_provider.py +2 -2
  45. thestage/services/connect/connect_service.py +77 -74
  46. thestage/services/container/container_service.py +120 -41
  47. thestage/services/container/mapper/container_mapper.py +2 -1
  48. thestage/services/instance/instance_service.py +13 -20
  49. thestage/services/instance/mapper/instance_mapper.py +1 -3
  50. thestage/services/instance/mapper/selfhosted_mapper.py +3 -4
  51. thestage/services/logging/logging_service.py +45 -48
  52. thestage/services/project/dto/inference_simulator_dto.py +1 -10
  53. thestage/services/project/dto/inference_simulator_model_dto.py +2 -10
  54. thestage/services/project/dto/project_config.py +2 -2
  55. thestage/services/project/mapper/project_inference_simulator_mapper.py +1 -0
  56. thestage/services/project/mapper/project_inference_simulator_model_mapper.py +2 -2
  57. thestage/services/project/mapper/project_task_mapper.py +2 -3
  58. thestage/services/project/project_service.py +174 -140
  59. thestage/services/remote_server_service.py +1 -0
  60. thestage/services/task/dto/task_dto.py +3 -23
  61. {thestage-0.6.5.dist-info → thestage-0.6.7.dist-info}/METADATA +4 -2
  62. {thestage-0.6.5.dist-info → thestage-0.6.7.dist-info}/RECORD +65 -74
  63. {thestage-0.6.5.dist-info → thestage-0.6.7.dist-info}/WHEEL +1 -1
  64. thestage/services/clients/thestage_api/dtos/cloud_provider_region.py +0 -19
  65. thestage/services/clients/thestage_api/dtos/docker_container_assigned_device.py +0 -10
  66. thestage/services/clients/thestage_api/dtos/enums/currency_type.py +0 -10
  67. thestage/services/clients/thestage_api/dtos/enums/daemon_status.py +0 -9
  68. thestage/services/clients/thestage_api/dtos/enums/disk_type.py +0 -7
  69. thestage/services/clients/thestage_api/dtos/enums/drive_type.py +0 -7
  70. thestage/services/clients/thestage_api/dtos/enums/instance_type.py +0 -7
  71. thestage/services/clients/thestage_api/dtos/enums/location_region.py +0 -11
  72. thestage/services/clients/thestage_api/dtos/enums/power_status.py +0 -10
  73. thestage/services/clients/thestage_api/dtos/price_definition.py +0 -14
  74. {thestage-0.6.5.dist-info → thestage-0.6.7.dist-info}/entry_points.txt +0 -0
  75. {thestage-0.6.5.dist-info → thestage-0.6.7.dist-info/licenses}/LICENSE.txt +0 -0
@@ -43,9 +43,9 @@ class LoggingService:
43
43
 
44
44
 
45
45
  @error_handler()
46
- def print_last_task_logs(self, task_id: int, logs_number: Optional[int]):
46
+ def print_last_task_logs(self, task_public_id: str, logs_number: Optional[int]):
47
47
  logs = self.__thestage_api_client.query_user_logs(
48
- task_id=task_id,
48
+ task_public_id=task_public_id,
49
49
  limit=logs_number
50
50
  )
51
51
  for log_message in reversed(logs.queryResult):
@@ -53,9 +53,9 @@ class LoggingService:
53
53
 
54
54
 
55
55
  @error_handler()
56
- def print_last_inference_simulator_logs(self, inference_simulator_id: int, logs_number: Optional[int]):
56
+ def print_last_inference_simulator_logs(self, inference_simulator_public_id: str, logs_number: Optional[int]):
57
57
  logs = self.__thestage_api_client.query_user_logs(
58
- inference_simulator_id=inference_simulator_id,
58
+ inference_simulator_public_id=inference_simulator_public_id,
59
59
  limit=logs_number
60
60
  )
61
61
  for log_message in reversed(logs.queryResult):
@@ -63,9 +63,10 @@ class LoggingService:
63
63
 
64
64
 
65
65
  @error_handler()
66
- def print_last_container_logs(self, container_uid: str, logs_number: Optional[int]):
66
+ def print_last_container_logs(self, container_public_id: Optional[str], container_slug: Optional[str], logs_number: Optional[int]):
67
67
  container: Optional[DockerContainerDto] = self.__thestage_api_client.get_container(
68
- container_slug=container_uid,
68
+ container_public_id=container_public_id,
69
+ container_slug=container_slug,
69
70
  )
70
71
 
71
72
  if not container:
@@ -73,7 +74,7 @@ class LoggingService:
73
74
  raise typer.Exit(1)
74
75
 
75
76
  logs = self.__thestage_api_client.query_user_logs(
76
- container_id=container.id,
77
+ container_public_id=container.public_id,
77
78
  limit=logs_number
78
79
  )
79
80
  for log_message in reversed(logs.queryResult):
@@ -81,15 +82,15 @@ class LoggingService:
81
82
 
82
83
 
83
84
  @error_handler()
84
- def stream_task_logs_with_controls(self, task_id: int):
85
+ def stream_task_logs_with_controls(self, task_public_id: str):
85
86
  asyncio.run(
86
- self.__stream_task_logs_with_controls_async(task_id=task_id)
87
+ self.__stream_task_logs_with_controls_async(task_public_id=task_public_id)
87
88
  )
88
89
 
89
90
 
90
91
  @error_handler()
91
- async def __stream_task_logs_with_controls_async(self, task_id: int):
92
- task_view_response: Optional[TaskViewResponse] = self.__thestage_api_client.get_task(task_id=task_id,)
92
+ async def __stream_task_logs_with_controls_async(self, task_public_id: str):
93
+ task_view_response: Optional[TaskViewResponse] = self.__thestage_api_client.get_task(task_public_id=task_public_id,)
93
94
 
94
95
  task_status_map: Dict[str, str] = self.__thestage_api_client.get_task_localized_status_map()
95
96
 
@@ -98,25 +99,20 @@ class LoggingService:
98
99
  if task:
99
100
  if task.frontend_status.status_key not in [TaskStatus.RUNNING, TaskStatus.SCHEDULED]:
100
101
  typer.echo(__("Task must be in status '%required_status%' to stream real-time logs. Task %task_id% status: '%status%'.", {
101
- 'task_id': str(task.id),
102
+ 'task_id': str(task.public_id),
102
103
  'status': task.frontend_status.status_translation,
103
104
  'required_status': task_status_map.get(TaskStatus.RUNNING) or TaskStatus.RUNNING
104
105
  }))
105
106
  raise typer.Exit(1)
106
107
  else:
107
- typer.echo(__("Task with ID %task_id% was not found", {'task_id': task.id}))
108
+ typer.echo(f"Task with ID {task_public_id} was not found")
108
109
  raise typer.Exit(1)
109
110
 
110
- typer.echo(__(
111
- f"Log stream for task %task_id% started",
112
- {
113
- 'task_id': str(task.id),
114
- }
115
- ))
111
+ typer.echo(f"Log stream for task {task.public_id} started")
116
112
 
117
113
  typer.echo(__("CTRL+C to cancel the task. CTRL+D to disconnect from log stream."))
118
114
 
119
- print_logs_task = asyncio.create_task(self.print_realtime_logs(task_id=task.id))
115
+ print_logs_task = asyncio.create_task(self.print_realtime_logs(task_public_id=task.public_id))
120
116
  input_task = asyncio.create_task(self.read_log_stream_input())
121
117
 
122
118
  def sigint_handler():
@@ -131,22 +127,24 @@ class LoggingService:
131
127
  if input_task in done:
132
128
  print_logs_task.cancel()
133
129
  if not input_task.result(): # result is only expected if ctrl+D triggered EOFError
134
- typer.echo(f"\rTask {task_id} will be canceled")
130
+ typer.echo(f"\rTask {task_public_id} will be canceled")
135
131
  self.__thestage_api_client.cancel_task(
136
- task_id=task.id,
132
+ task_public_id=task.public_id,
137
133
  )
138
134
 
139
135
  @error_handler()
140
- def stream_inference_simulator_logs_with_controls(self, slug: str):
136
+ def stream_inference_simulator_logs_with_controls(self, public_id: Optional[str] = None, slug: Optional[str] = None):
141
137
  asyncio.run(
142
138
  self.__stream_inference_simulator_logs_with_controls_async(
139
+ public_id=public_id,
143
140
  slug=slug
144
141
  )
145
142
  )
146
143
 
147
144
  @error_handler()
148
- async def __stream_inference_simulator_logs_with_controls_async(self, slug: str):
145
+ async def __stream_inference_simulator_logs_with_controls_async(self, public_id: Optional[str], slug: Optional[str]):
149
146
  get_inference_simulator_response: Optional[GetInferenceSimulatorResponse] = self.__thestage_api_client.get_inference_simulator(
147
+ public_id=public_id,
150
148
  slug=slug,
151
149
  )
152
150
 
@@ -164,20 +162,15 @@ class LoggingService:
164
162
  }))
165
163
  raise typer.Exit(1)
166
164
  else:
167
- typer.echo(__("Inference simulator with unique ID %slug% was not found", {'slug': inference_simulator.slug}))
165
+ typer.echo("Inference simulator was not found")
168
166
  raise typer.Exit(1)
169
167
 
170
- typer.echo(__(
171
- f"Log stream for inference simulator %slug% started",
172
- {
173
- 'slug': str(inference_simulator.slug),
174
- }
175
- ))
168
+ typer.echo(f"Log stream for inference simulator '{inference_simulator.public_id}' started")
176
169
 
177
170
  typer.echo(__("CTRL+D to disconnect from log stream."))
178
171
 
179
172
  print_task_or_inference_simulator_logs = asyncio.create_task(
180
- self.print_realtime_logs(inference_simulator_id=inference_simulator.id)
173
+ self.print_realtime_logs(inference_simulator_public_id=inference_simulator.public_id)
181
174
  )
182
175
  input_task = asyncio.create_task(self.read_log_stream_input())
183
176
 
@@ -186,22 +179,25 @@ class LoggingService:
186
179
 
187
180
  if input_task in done:
188
181
  print_task_or_inference_simulator_logs.cancel()
189
- typer.echo(__(f"Disconnected from log stream. You can try to reconnect with 'thestage project inference-simulator logs {slug}'."))
182
+ inference_logs_cmd_id = f"-isn {slug}" if slug else f"-isid {public_id}"
183
+ typer.echo(f"Disconnected from log stream. You can try to reconnect with 'thestage project inference-simulator logs {inference_logs_cmd_id}'")
190
184
 
191
185
 
192
186
  @error_handler()
193
- def stream_container_logs_with_controls(self, container_uid: str):
187
+ def stream_container_logs_with_controls(self, container_public_id: Optional[str], container_slug: Optional[str]):
194
188
  asyncio.run(
195
189
  self.__stream_container_logs_with_controls_async(
196
- container_uid=container_uid
190
+ container_public_id=container_public_id,
191
+ container_slug=container_slug
197
192
  )
198
193
  )
199
194
 
200
195
 
201
196
  @error_handler()
202
- async def __stream_container_logs_with_controls_async(self, container_uid: str):
197
+ async def __stream_container_logs_with_controls_async(self, container_public_id: Optional[str], container_slug: Optional[str]):
203
198
  container: Optional[DockerContainerDto] = self.__thestage_api_client.get_container(
204
- container_slug=container_uid,
199
+ container_public_id=container_public_id,
200
+ container_slug=container_slug,
205
201
  )
206
202
 
207
203
  if container:
@@ -214,7 +210,7 @@ class LoggingService:
214
210
  typer.echo(f"Log stream for Docker container started")
215
211
  typer.echo("CTRL+D to disconnect from log stream.")
216
212
 
217
- print_logs_task = asyncio.create_task(self.print_realtime_logs(docker_container_id=container.id))
213
+ print_logs_task = asyncio.create_task(self.print_realtime_logs(docker_container_public_id=container.public_id))
218
214
  input_task = asyncio.create_task(self.read_log_stream_input())
219
215
 
220
216
  def sigint_handler():
@@ -243,9 +239,9 @@ class LoggingService:
243
239
 
244
240
  async def print_realtime_logs(
245
241
  self,
246
- task_id: Optional[int] = None,
247
- inference_simulator_id: Optional[int] = None,
248
- docker_container_id: Optional[int] = None,
242
+ task_public_id: Optional[str] = None,
243
+ inference_simulator_public_id: Optional[str] = None,
244
+ docker_container_public_id: Optional[str] = None,
249
245
  ):
250
246
  polling_interval_seconds: float = 4 # also adjust polling api method timeout if changed
251
247
  between_logs_sleeping_coef: float = 1 # we emulate delay between logs, but if for any reason code runs for too long - delays will be controlled with this coef
@@ -269,9 +265,9 @@ class LoggingService:
269
265
  # print_nonblocking(f'TDIFF {stream_to_logs_diff.total_seconds()}', writer)
270
266
  try:
271
267
  logs_response = await self.__thestage_api_client.poll_logs_httpx(
272
- task_id=task_id,
273
- inference_simulator_id=inference_simulator_id,
274
- docker_container_id=docker_container_id,
268
+ task_public_id=task_public_id,
269
+ inference_simulator_public_id=inference_simulator_public_id,
270
+ docker_container_public_id=docker_container_public_id,
275
271
  last_log_timestamp=last_iteration_log_timestamp,
276
272
  last_log_id=last_log_id
277
273
  )
@@ -285,6 +281,7 @@ class LoggingService:
285
281
  consecutive_error_count = 0
286
282
  errors_started_at = None
287
283
  log_wait_remaining_limit = 0 # no log delays after reconnect
284
+ print_nonblocking("Reconnected to log stream", writer, BytePrintStyle.GREEN)
288
285
 
289
286
  last_iteration_log_timestamp = logs_response.lastLogTimestamp
290
287
  last_log_id = logs_response.lastLogId
@@ -314,11 +311,11 @@ class LoggingService:
314
311
 
315
312
  if consecutive_error_count > 7:
316
313
  seconds_with_error = (datetime.utcnow() - errors_started_at).total_seconds()
317
- if inference_simulator_id:
314
+ if inference_simulator_public_id:
318
315
  print_nonblocking(f"Log stream: disconnected from server (connectivity issues for {seconds_with_error} seconds). Try 'thestage inference-simulator logs <inference-simulator-UID>' to reconnect.", writer)
319
- elif task_id:
320
- print_nonblocking(f"Log stream: disconnected from server (connectivity issues for {seconds_with_error} seconds). Try 'thestage project task logs {task_id}' to reconnect.", writer)
321
- elif docker_container_id:
316
+ elif task_public_id:
317
+ print_nonblocking(f"Log stream: disconnected from server (connectivity issues for {seconds_with_error} seconds). Try 'thestage project task logs {task_public_id}' to reconnect.", writer)
318
+ elif docker_container_public_id:
322
319
  print_nonblocking(f"Log stream: disconnected from server (connectivity issues for {seconds_with_error} seconds). Try 'thestage container logs <docker-container-UID>' to reconnect.", writer)
323
320
  else:
324
321
  print_nonblocking(f"Log stream: disconnected from server (connectivity issues for {seconds_with_error} seconds)", writer)
@@ -4,20 +4,11 @@ from pydantic import BaseModel, ConfigDict, Field
4
4
  class InferenceSimulatorDto(BaseModel):
5
5
  model_config = ConfigDict(use_enum_values=True)
6
6
 
7
- id: Optional[int] = Field(None, alias="id")
8
- client_id: Optional[int] = Field(None, alias="clientId")
9
- title: Optional[str] = Field(None, alias="title")
7
+ public_id: Optional[str] = Field(None, alias="publicId")
10
8
  slug: Optional[str] = Field(None, alias="slug")
11
- docker_container_id: Optional[int] = Field(None, alias="dockerContainerId")
12
- instance_rented_id: Optional[int] = Field(None, alias="instanceRentedId")
13
- selfhosted_instance_id: Optional[int] = Field(None, alias="selfhostedInstanceId")
14
- project_id: Optional[int] = Field(None, alias="projectId")
15
9
  status: Optional[str] = Field(None, alias="status")
16
- commit_hash: Optional[str] = Field(None, alias="commitHash")
17
10
  http_endpoint: Optional[str] = Field(None, alias="httpEndpoint")
18
11
  grpc_endpoint: Optional[str] = Field(None, alias="grpcEndpoint")
19
12
  metrics_endpoint: Optional[str] = Field(None, alias="metricsEndpoint")
20
- inference_metadata: Optional[Dict[str, Any]] = Field(None, alias="inferenceMetadata")
21
13
  qlip_serve_metadata: Optional[Dict[str, Any]] = Field(None, alias="qlipServeMetadata")
22
14
  created_at: Optional[str] = Field(None, alias="createdAt")
23
- updated_at: Optional[str] = Field(None, alias="updatedAt")
@@ -4,18 +4,10 @@ from pydantic import BaseModel, ConfigDict, Field
4
4
  class InferenceSimulatorModelDto(BaseModel):
5
5
  model_config = ConfigDict(use_enum_values=True)
6
6
 
7
- id: Optional[int] = Field(None, alias='id')
7
+ public_id: Optional[str] = Field(None, alias='publicId')
8
8
  slug: Optional[str] = Field(None, alias='slug')
9
- client_id: Optional[int] = Field(None, alias='clientId')
10
- instance_rented_id: Optional[int] = Field(None, alias='instanceRentedId')
11
- selfhosted_instance_id: Optional[int] = Field(None, alias='selfhostedInstanceId')
12
- project_id: Optional[int] = Field(None, alias='projectId')
13
9
  status: Optional[str] = Field(None, alias='status')
14
10
  environment_metadata: Optional[Dict[str, Any]] = Field(None, alias='environmentMetadata')
15
11
  commit_hash: Optional[str] = Field(None, alias='commitHash')
16
- cpu_architecture: Optional[str] = Field(None, alias='cpuArchitecture')
17
- gpu_model: Optional[str] = Field(None, alias='gpuModel')
18
- ecr_image_url: Optional[str] = Field(None, alias='ecrImageUrl')
19
- s3_artifacts_url: Optional[str] = Field(None, alias='s3ArtifactsUrl')
20
12
  created_at: Optional[str] = Field(None, alias='createdAt')
21
- updated_at: Optional[str] = Field(None, alias='updatedAt')
13
+ updated_at: Optional[str] = Field(None, alias='updatedAt')
@@ -6,9 +6,9 @@ from pydantic import Field, ConfigDict, BaseModel
6
6
  class ProjectConfig(BaseModel):
7
7
  model_config = ConfigDict(use_enum_values=True)
8
8
 
9
- id: int = Field(None, alias='id')
10
9
  slug: str = Field(None, alias='slug')
10
+ public_id: str = Field(None, alias='public_id')
11
11
  git_repository_url: str = Field(None, alias='git_repository_url')
12
12
  deploy_key_path: str = Field(None, alias='deploy_key_path')
13
- default_container_uid: Optional[str] = Field(None, alias='default_container_uid')
13
+ default_container_public_id: Optional[str] = Field(None, alias='default_container_public_id')
14
14
  prompt_for_default_container: bool = Field(True, alias='prompt_for_default_container')
@@ -12,6 +12,7 @@ class ProjectInferenceSimulatorMapper(AbstractMapper):
12
12
  return None
13
13
 
14
14
  return ProjectInferenceSimulatorEntity(
15
+ public_id=item.public_id or '',
15
16
  slug=item.slug or '',
16
17
  status=item.status or '',
17
18
  http_endpoint=item.http_endpoint or '',
@@ -11,11 +11,11 @@ class ProjectInferenceSimulatorModelMapper(AbstractMapper):
11
11
  return None
12
12
 
13
13
  return ProjectInferenceSimulatorModelEntity(
14
- id=item.id,
14
+ public_id=item.public_id,
15
15
  slug=item.slug,
16
16
  status=item.status or '',
17
17
  commit_hash=item.commit_hash or '',
18
18
  environment_metadata=item.environment_metadata or {},
19
19
  started_at=item.created_at or '',
20
- finished_at=item.updated_at or ''
20
+ finished_at=item.updated_at or '' # TODO updated_at cannot be finished_at
21
21
  )
@@ -12,11 +12,10 @@ class ProjectTaskMapper(AbstractMapper):
12
12
  return None
13
13
 
14
14
  return ProjectTaskEntity(
15
- id=item.id or '',
15
+ public_id=item.public_id or '',
16
16
  title=item.title or '',
17
17
  status=item.frontend_status.status_translation or '',
18
- docker_container_id=item.docker_container_id or '',
19
- docker_container_slug=(str(item.docker_container.slug) or '') if item.docker_container else '',
18
+ docker_container_public_id=item.docker_container_public_id,
20
19
  started_at=item.started_at or '',
21
20
  finished_at=item.finished_at or '',
22
21
  )