thestage 0.6.6__py3-none-any.whl → 0.6.8__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 (194) hide show
  1. thestage/__init__.py +1 -1
  2. thestage/cli_command_helper.py +2 -2
  3. thestage/config/__init__.py +1 -1
  4. thestage/{services → config/business}/app_config_service.py +3 -4
  5. thestage/{services/config_provider → config/business}/config_provider.py +6 -5
  6. thestage/config/{config_storage.py → business/config_storage.py} +1 -1
  7. thestage/{services → config/business}/validation_service.py +9 -10
  8. thestage/{controllers/config_controller.py → config/communication/config_command.py} +7 -7
  9. thestage/{services/connect → connect/business}/connect_service.py +25 -22
  10. thestage/{services → connect/business}/remote_server_service.py +4 -5
  11. thestage/connect/communication/connect_api_client.py +84 -0
  12. thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_response.py +0 -1
  13. thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_response.py +1 -4
  14. thestage/{services/clients/thestage_api/dtos/base_controller → connect/dto}/connect_resolve_response.py +1 -1
  15. thestage/controllers/base_controller.py +2 -2
  16. thestage/debug_main.dist.py +16 -14
  17. thestage/{services/container → docker_container/business}/container_service.py +120 -40
  18. thestage/{services/container → docker_container/business}/mapper/container_mapper.py +3 -3
  19. thestage/docker_container/communication/__init__.py +0 -0
  20. thestage/{controllers/container_controller.py → docker_container/communication/docker_command.py} +27 -86
  21. thestage/docker_container/communication/docker_container_api_client.py +99 -0
  22. thestage/docker_container/dto/__init__.py +0 -0
  23. thestage/docker_container/dto/container_action_request.py +11 -0
  24. thestage/{services/clients/thestage_api/dtos → docker_container/dto}/container_response.py +4 -4
  25. thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_response.py +3 -5
  26. thestage/docker_container/dto/enum/__init__.py +0 -0
  27. thestage/git/__init__.py +0 -0
  28. thestage/git/business/__init__.py +0 -0
  29. thestage/git/communication/__init__.py +0 -0
  30. thestage/{services/clients/git → git/communication}/git_client.py +5 -5
  31. thestage/global_dto/__init__.py +0 -0
  32. thestage/global_dto/enums/__init__.py +0 -0
  33. thestage/helpers/error_handler.py +5 -5
  34. thestage/helpers/logger/app_logger.py +1 -3
  35. thestage/i18n/en_GB/messages.po +14 -14
  36. thestage/inference_model/__init__.py +0 -0
  37. thestage/inference_model/business/__init__.py +0 -0
  38. thestage/inference_model/business/inference_model_service.py +281 -0
  39. thestage/inference_model/business/mapper/__init__.py +0 -0
  40. thestage/{services/project/mapper/project_inference_simulator_model_mapper.py → inference_model/business/mapper/inference_model_mapper.py} +5 -5
  41. thestage/inference_model/communication/__init__.py +0 -0
  42. thestage/inference_model/communication/inference_model_api_client.py +139 -0
  43. thestage/inference_model/communication/inference_model_command.py +246 -0
  44. thestage/inference_model/dto/__init__.py +0 -0
  45. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_request.py +0 -2
  46. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_response.py +2 -1
  47. thestage/inference_model/dto/enum/__init__.py +0 -0
  48. thestage/{services/project/dto/inference_simulator_model_dto.py → inference_model/dto/inference_model.py} +1 -1
  49. thestage/{entities/project_inference_simulator_model.py → inference_model/dto/inference_model_entity.py} +2 -2
  50. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_response.py +2 -3
  51. thestage/{services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py → inference_model/dto/push_inference_simulator_model_request.py} +1 -1
  52. thestage/{services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py → inference_model/dto/push_inference_simulator_model_response.py} +1 -1
  53. thestage/inference_simulator/__init__.py +0 -0
  54. thestage/inference_simulator/business/__init__.py +0 -0
  55. thestage/inference_simulator/business/inference_simulator_service.py +338 -0
  56. thestage/inference_simulator/business/mapper/__init__.py +0 -0
  57. thestage/{services/project/mapper/project_inference_simulator_mapper.py → inference_simulator/business/mapper/inference_simulator_mapper.py} +5 -5
  58. thestage/inference_simulator/communication/__init__.py +0 -0
  59. thestage/inference_simulator/communication/inference_simulator_api_client.py +114 -0
  60. thestage/inference_simulator/communication/inference_simulator_command.py +347 -0
  61. thestage/inference_simulator/dto/__init__.py +0 -0
  62. thestage/inference_simulator/dto/enum/__init__.py +0 -0
  63. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/get_inference_simulator_request.py +1 -1
  64. thestage/inference_simulator/dto/get_inference_simulator_response.py +12 -0
  65. thestage/{services/project/dto/inference_simulator_dto.py → inference_simulator/dto/inference_simulator.py} +1 -1
  66. thestage/{entities/project_inference_simulator.py → inference_simulator/dto/inference_simulator_entity.py} +1 -1
  67. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_response.py +2 -2
  68. thestage/{services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py → inference_simulator/dto/start_inference_simulator_request.py} +1 -1
  69. thestage/inference_simulator/dto/start_inference_simulator_response.py +10 -0
  70. thestage/instance/__init__.py +0 -0
  71. thestage/instance/business/__init__.py +0 -0
  72. thestage/{services/instance → instance/business}/instance_service.py +26 -27
  73. thestage/instance/business/mapper/__init__.py +0 -0
  74. thestage/{services/instance/mapper/instance_mapper.py → instance/business/mapper/rented_instance_mapper.py} +3 -3
  75. thestage/{services/instance/mapper/selfhosted_mapper.py → instance/business/mapper/selfhosted_instance_mapper.py} +5 -7
  76. thestage/instance/communication/__init__.py +0 -0
  77. thestage/instance/communication/instance_api_client.py +150 -0
  78. thestage/{controllers/instance_controller.py → instance/communication/instance_command.py} +9 -9
  79. thestage/instance/dto/__init__.py +0 -0
  80. thestage/instance/dto/enum/__init__.py +0 -0
  81. thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_detected_gpus.py +1 -2
  82. thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_rented_response.py +2 -2
  83. thestage/{services/clients/thestage_api/dtos → instance/dto}/selfhosted_instance_response.py +2 -3
  84. thestage/logging/__init__.py +0 -0
  85. thestage/logging/business/__init__.py +0 -0
  86. thestage/{services/logging → logging/business}/logging_service.py +45 -36
  87. thestage/logging/communication/__init__.py +0 -0
  88. thestage/logging/communication/logging_api_client.py +63 -0
  89. thestage/logging/dto/__init__.py +0 -0
  90. thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_response.py +2 -2
  91. thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_response.py +2 -2
  92. thestage/main.py +48 -9
  93. thestage/project/__init__.py +0 -0
  94. thestage/project/business/__init__.py +0 -0
  95. thestage/project/business/project_service.py +480 -0
  96. thestage/project/communication/__init__.py +0 -0
  97. thestage/project/communication/project_api_client.py +46 -0
  98. thestage/project/communication/project_command.py +284 -0
  99. thestage/project/dto/__init__.py +0 -0
  100. thestage/{services/project → project}/dto/project_config.py +1 -2
  101. thestage/services/clients/thestage_api/core/api_client_core.py +1 -1
  102. thestage/services/clients/thestage_api/dtos/entity_filter_request.py +1 -1
  103. thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +1 -1
  104. thestage/services/filesystem_service.py +2 -2
  105. thestage/services/service_factory.py +130 -43
  106. thestage/task/__init__.py +0 -0
  107. thestage/task/business/__init__.py +0 -0
  108. thestage/task/business/mapper/__init__.py +0 -0
  109. thestage/{services/project/mapper/project_task_mapper.py → task/business/mapper/task_mapper.py} +5 -5
  110. thestage/task/business/task_service.py +304 -0
  111. thestage/task/communication/__init__.py +0 -0
  112. thestage/task/communication/task_api_client.py +122 -0
  113. thestage/task/communication/task_command.py +212 -0
  114. thestage/task/dto/__init__.py +0 -0
  115. thestage/task/dto/enum/__init__.py +0 -0
  116. thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py → task/dto/list_for_project_response.py} +2 -2
  117. thestage/{services/clients/thestage_api/dtos/project_controller/project_run_task_request.py → task/dto/run_task_request.py} +1 -1
  118. thestage/task/dto/run_task_response.py +13 -0
  119. thestage/{services/task/dto/task_dto.py → task/dto/task.py} +1 -4
  120. thestage/{entities/project_task.py → task/dto/task_entity.py} +1 -1
  121. thestage/{services/clients/thestage_api/dtos/task_controller/task_view_response.py → task/dto/view_response.py} +2 -2
  122. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/METADATA +2 -1
  123. thestage-0.6.8.dist-info/RECORD +219 -0
  124. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/WHEEL +1 -1
  125. thestage/controllers/project_controller.py +0 -1058
  126. thestage/services/clients/thestage_api/api_client.py +0 -753
  127. thestage/services/clients/thestage_api/dtos/container_param_request.py +0 -11
  128. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +0 -13
  129. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +0 -10
  130. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +0 -10
  131. thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +0 -12
  132. thestage/services/project/project_service.py +0 -1283
  133. thestage-0.6.6.dist-info/RECORD +0 -167
  134. /thestage/{entities → color_scheme}/__init__.py +0 -0
  135. /thestage/{entities/enums → config/business}/__init__.py +0 -0
  136. /thestage/{services/clients/git → config/communication}/__init__.py +0 -0
  137. /thestage/{services/clients/thestage_api/dtos/enums → config/dto}/__init__.py +0 -0
  138. /thestage/{services/core_files → config/dto}/config_entity.py +0 -0
  139. /thestage/{services/connect → config}/dto/remote_server_config.py +0 -0
  140. /thestage/{services/config_provider → connect}/__init__.py +0 -0
  141. /thestage/{services/container → connect/business}/__init__.py +0 -0
  142. /thestage/{services/container/mapper → connect/communication}/__init__.py +0 -0
  143. /thestage/{services/instance → connect/dto}/__init__.py +0 -0
  144. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_request.py +0 -0
  145. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_request.py +0 -0
  146. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_request.py +0 -0
  147. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_response.py +0 -0
  148. /thestage/{services/instance/mapper → docker_container}/__init__.py +0 -0
  149. /thestage/{services/project → docker_container/business}/__init__.py +0 -0
  150. /thestage/{services/project → docker_container/business}/mapper/__init__.py +0 -0
  151. /thestage/{entities/container.py → docker_container/dto/container_entity.py} +0 -0
  152. /thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_request.py +0 -0
  153. /thestage/{services/clients/thestage_api/dtos → docker_container/dto}/docker_container_mapping.py +0 -0
  154. /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_pending_action.py +0 -0
  155. /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_status.py +0 -0
  156. /thestage/{services/logging/exception → exceptions}/log_polling_exception.py +0 -0
  157. /thestage/git/{ProgressPrinter.py → business/ProgressPrinter.py} +0 -0
  158. /thestage/{entities → global_dto}/enums/order_direction_type.py +0 -0
  159. /thestage/{entities → global_dto}/enums/shell_type.py +0 -0
  160. /thestage/{entities → global_dto}/enums/tail_output_type.py +0 -0
  161. /thestage/{entities → global_dto}/enums/yes_no_response.py +0 -0
  162. /thestage/{entities → global_dto}/file_item.py +0 -0
  163. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_request.py +0 -0
  164. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_response.py +0 -0
  165. /thestage/{services/clients/thestage_api/dtos/enums → inference_model/dto/enum}/inference_model_status.py +0 -0
  166. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_request.py +0 -0
  167. /thestage/{services/clients/thestage_api/dtos → inference_model/dto}/inference_simulator_model_response.py +0 -0
  168. /thestage/{services/clients/thestage_api/dtos/enums → inference_simulator/dto/enum}/inference_simulator_status.py +0 -0
  169. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_request.py +0 -0
  170. /thestage/{services/clients/thestage_api/dtos → inference_simulator/dto}/inference_simulator_response.py +0 -0
  171. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/cpu_type.py +0 -0
  172. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/gpu_name.py +0 -0
  173. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/instance_rented_status.py +0 -0
  174. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/provider_name.py +0 -0
  175. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/selfhosted_status.py +0 -0
  176. /thestage/{entities → instance/dto}/rented_instance.py +0 -0
  177. /thestage/{entities → instance/dto}/self_hosted_instance.py +0 -0
  178. /thestage/{services/logging → logging}/byte_print_style.py +0 -0
  179. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/docker_container_log_stream_request.py +0 -0
  180. /thestage/{services/logging → logging}/dto/log_message.py +0 -0
  181. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_request.py +0 -0
  182. /thestage/{services/logging → logging}/dto/log_type.py +0 -0
  183. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/task_log_stream_request.py +0 -0
  184. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_request.py +0 -0
  185. /thestage/{services/logging → logging}/logging_constants.py +0 -0
  186. /thestage/{services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py → project/dto/get_deploy_ssh_key_request.py} +0 -0
  187. /thestage/{services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py → project/dto/get_deploy_ssh_key_response.py} +0 -0
  188. /thestage/{services/clients/thestage_api/dtos → project/dto}/project_response.py +0 -0
  189. /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_execution_status.py +0 -0
  190. /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_status.py +0 -0
  191. /thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py → task/dto/list_for_project_request.py} +0 -0
  192. /thestage/{services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py → task/dto/status_localized_map_response.py} +0 -0
  193. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/entry_points.txt +0 -0
  194. {thestage-0.6.6.dist-info → thestage-0.6.8.dist-info}/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,246 @@
1
+ import time
2
+ import re
3
+ from typing import Optional, List
4
+
5
+ import typer
6
+
7
+ from thestage.cli_command import CliCommand
8
+ from thestage.cli_command_helper import get_command_metadata, check_command_permission
9
+ from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
10
+ from thestage.helpers.logger.app_logger import app_logger
11
+ from thestage.i18n.translation import __
12
+ from thestage.inference_model.business.inference_model_service import InferenceModelService
13
+ from thestage.logging.business.logging_service import LoggingService
14
+ from thestage.project.business.project_service import ProjectService
15
+
16
+ app = typer.Typer(no_args_is_help=True, help="Manage project inference simulator models")
17
+ @app.command("ls", help=__("List inference simulator models"), **get_command_metadata(CliCommand.PROJECT_MODEL_LS))
18
+ def list_inference_simulator_models(
19
+ project_public_id: Optional[str] = typer.Option(
20
+ None,
21
+ '--project-id',
22
+ '-pid',
23
+ help=__("Project ID. By default, project info is taken from the current directory"),
24
+ is_eager=False,
25
+ ),
26
+ project_slug: Optional[str] = typer.Option(
27
+ None,
28
+ '--project-name',
29
+ '-pn',
30
+ help=__("Project name. By default, project info is taken from the current directory"),
31
+ is_eager=False,
32
+ ),
33
+ row: int = typer.Option(
34
+ 5,
35
+ '--row',
36
+ '-r',
37
+ help=__("Set number of rows displayed per page"),
38
+ is_eager=False,
39
+ ),
40
+ page: int = typer.Option(
41
+ 1,
42
+ '--page',
43
+ '-p',
44
+ help=__("Set starting page for displaying output"),
45
+ is_eager=False,
46
+ ),
47
+ statuses: List[str] = typer.Option(
48
+ None,
49
+ '--status',
50
+ '-s',
51
+ help=__("Filter by status, use --status all to list all inference simulator models"),
52
+ is_eager=False,
53
+ ),
54
+ ):
55
+ command_name = CliCommand.PROJECT_MODEL_LS
56
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
57
+ check_command_permission(command_name)
58
+
59
+ if sum(v is not None for v in [project_public_id, project_slug]) > 1:
60
+ typer.echo("Provide a single identifier for project - ID or name.")
61
+ raise typer.Exit(1)
62
+
63
+ service_factory = validate_config_and_get_service_factory()
64
+ inference_model_service: InferenceModelService = service_factory.get_inference_model_service()
65
+
66
+ inference_model_service.print_inference_simulator_model_list(
67
+ project_public_id=project_public_id,
68
+ project_slug=project_slug,
69
+ statuses=statuses,
70
+ row=row,
71
+ page=page
72
+ )
73
+
74
+ typer.echo(__("Inference simulator models listing complete"))
75
+ raise typer.Exit(0)
76
+
77
+
78
+ @app.command("deploy-instance", no_args_is_help=True, help=__("Deploy inference simulator model to an instance"), **get_command_metadata(CliCommand.PROJECT_MODEL_DEPLOY_INSTANCE))
79
+ def deploy_inference_simulator_model_to_instance(
80
+ model_public_id: Optional[str] = typer.Option(
81
+ None,
82
+ '--model-id',
83
+ '-mid',
84
+ help="Inference simulator model ID",
85
+ is_eager=False,
86
+ ),
87
+ model_slug: Optional[str] = typer.Option(
88
+ None,
89
+ '--model-name',
90
+ '-mn',
91
+ help="Inference simulator model name",
92
+ is_eager=False,
93
+ ),
94
+ rented_instance_public_id: Optional[str] = typer.Option(
95
+ None,
96
+ '--rented-instance-id',
97
+ '-rid',
98
+ help=__("Rented instance ID on which the inference simulator will run"),
99
+ is_eager=False,
100
+ ),
101
+ rented_instance_slug: Optional[str] = typer.Option(
102
+ None,
103
+ '--rented-instance-name',
104
+ '-rn',
105
+ help=__("Rented instance name on which the inference simulator will run"),
106
+ is_eager=False,
107
+ ),
108
+ self_hosted_instance_public_id: Optional[str] = typer.Option(
109
+ None,
110
+ '--self-hosted-instance-id',
111
+ '-sid',
112
+ help=__("Self-hosted instance ID on which the inference simulator will run"),
113
+ is_eager=False,
114
+ ),
115
+ self_hosted_instance_slug: Optional[str] = typer.Option(
116
+ None,
117
+ '--self-hosted-instance-name',
118
+ '-sn',
119
+ help=__("Self-hosted instance name on which the inference simulator will run"),
120
+ is_eager=False,
121
+ ),
122
+ working_directory: Optional[str] = typer.Option(
123
+ None,
124
+ "--working-directory",
125
+ "-wd",
126
+ help=__("Full path to working directory. By default, the current directory is used"),
127
+ show_default=False,
128
+ is_eager=False,
129
+ ),
130
+ enable_log_stream: Optional[bool] = typer.Option(
131
+ True,
132
+ "--no-logs",
133
+ "-nl",
134
+ help=__("Disable real-time log streaming"),
135
+ is_eager=False,
136
+ ),
137
+ ):
138
+ command_name = CliCommand.PROJECT_MODEL_DEPLOY_INSTANCE
139
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
140
+ check_command_permission(command_name)
141
+
142
+ if model_slug and not re.match(r"^[a-zA-Z0-9-]+$", model_slug):
143
+ raise typer.BadParameter(__("Invalid UID format. UID can only contain letters, numbers, and hyphens."))
144
+
145
+ new_inference_simulator_slug = f"{model_slug}-{int(time.time())}"
146
+
147
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
148
+ inference_model_service: InferenceModelService = service_factory.get_inference_model_service()
149
+
150
+ inference_simulator_public_id=inference_model_service.project_deploy_inference_simulator_model_to_instance(
151
+ model_public_id=model_public_id,
152
+ model_slug=model_slug,
153
+ new_inference_simulator_slug=new_inference_simulator_slug,
154
+ rented_instance_public_id=rented_instance_public_id,
155
+ rented_instance_slug=rented_instance_slug,
156
+ self_hosted_instance_public_id=self_hosted_instance_public_id,
157
+ self_hosted_instance_slug=self_hosted_instance_slug,
158
+ )
159
+
160
+ if enable_log_stream:
161
+ logging_service: LoggingService = service_factory.get_logging_service()
162
+
163
+ logging_service.stream_inference_simulator_logs_with_controls(
164
+ public_id=inference_simulator_public_id
165
+ )
166
+ raise typer.Exit(0)
167
+
168
+
169
+ @app.command("deploy-sagemaker", no_args_is_help=True, help=__("Deploy inference simulator model to SageMaker"), **get_command_metadata(CliCommand.PROJECT_MODEL_DEPLOY_SAGEMAKER))
170
+ def deploy_inference_simulator_model_to_sagemaker(
171
+ model_public_id: Optional[str] = typer.Option(
172
+ None,
173
+ '--model-id',
174
+ '-mid',
175
+ help="Inference simulator model ID",
176
+ is_eager=False,
177
+ ),
178
+ model_slug: Optional[str] = typer.Option(
179
+ None,
180
+ '--model-name',
181
+ '-mn',
182
+ help="Inference simulator model name",
183
+ is_eager=False,
184
+ ),
185
+ arn: Optional[str] = typer.Option(
186
+ None,
187
+ '--amazon-resource-name',
188
+ '-arn',
189
+ help=__("Amazon Resource Name of the IAM Role to use, e.g., arn:aws:iam::{aws_account_id}:role/{role}"),
190
+ is_eager=False,
191
+ ),
192
+ working_directory: Optional[str] = typer.Option(
193
+ None,
194
+ "--working-directory",
195
+ "-wd",
196
+ help=__("Full path to working directory. By default, the current directory is used"),
197
+ show_default=False,
198
+ is_eager=False,
199
+ ),
200
+ instance_type: Optional[str] = typer.Option(
201
+ None,
202
+ '--instance-type',
203
+ '-it',
204
+ help=__("Instance type on which the inference simulator model will be deployed"),
205
+ is_eager=False,
206
+ ),
207
+ initial_variant_weight: Optional[float] = typer.Option(
208
+ None,
209
+ "--initial-variant-weight",
210
+ "-ivw",
211
+ help=__("Initial Variant Weight. By default 1.0"),
212
+ show_default=False,
213
+ is_eager=False,
214
+ ),
215
+ initial_instance_count: Optional[int] = typer.Option(
216
+ None,
217
+ "--initial-instance-count",
218
+ "-iic",
219
+ help=__("Initial Instance Count"),
220
+ show_default=False,
221
+ is_eager=False,
222
+ ),
223
+
224
+ ):
225
+ command_name = CliCommand.PROJECT_MODEL_DEPLOY_SAGEMAKER
226
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
227
+ check_command_permission(command_name)
228
+
229
+ if sum(v is not None for v in [model_public_id, model_slug]) != 1:
230
+ typer.echo("Provide a single identifier for inference simulator model - ID or name.")
231
+ raise typer.Exit(1)
232
+
233
+ if model_slug and not re.match(r"^[a-zA-Z0-9-]+$", model_slug):
234
+ raise typer.BadParameter(__("Invalid UID format. UID can only contain letters, numbers, and hyphens."))
235
+
236
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
237
+ inference_model_service: InferenceModelService = service_factory.get_inference_model_service()
238
+
239
+ inference_model_service.project_deploy_inference_simulator_model_to_sagemaker(
240
+ model_public_id=model_public_id,
241
+ model_slug=model_slug,
242
+ arn=arn,
243
+ instance_type=instance_type,
244
+ initial_variant_weight=initial_variant_weight,
245
+ initial_instance_count=initial_instance_count,
246
+ )
File without changes
@@ -16,5 +16,3 @@ class DeployInferenceModelToInstanceRequest(BaseModel):
16
16
 
17
17
  selfhostedInstancePublicId: Optional[str] = Field(None, alias='selfhostedInstancePublicId')
18
18
  selfhostedInstanceSlug: Optional[str] = Field(None, alias='selfhostedInstanceSlug')
19
-
20
- inferenceSimulatorSlug: Optional[str] = Field(None, alias='inferenceSimulatorSlug')
@@ -3,10 +3,11 @@ from typing import Optional
3
3
  from pydantic import Field, ConfigDict
4
4
 
5
5
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
6
- from thestage.services.project.dto.inference_simulator_model_dto import InferenceSimulatorModelDto
7
6
 
8
7
 
9
8
  class DeployInferenceModelToInstanceResponse(TheStageBaseResponse):
10
9
  model_config = ConfigDict(use_enum_values=True)
11
10
 
11
+ inferenceSimulatorPublicId: Optional[str] = Field(None, alias='inferenceSimulatorPublicId')
12
+
12
13
 
File without changes
@@ -1,7 +1,7 @@
1
1
  from typing import Optional, Any, Dict
2
2
  from pydantic import BaseModel, ConfigDict, Field
3
3
 
4
- class InferenceSimulatorModelDto(BaseModel):
4
+ class InferenceModel(BaseModel):
5
5
  model_config = ConfigDict(use_enum_values=True)
6
6
 
7
7
  public_id: Optional[str] = Field(None, alias='publicId')
@@ -3,7 +3,7 @@ from typing import Optional, Any, Dict
3
3
  from pydantic import BaseModel, ConfigDict, Field
4
4
 
5
5
 
6
- class ProjectInferenceSimulatorModelEntity(BaseModel):
6
+ class InferenceModelEntity(BaseModel):
7
7
 
8
8
  model_config = ConfigDict(
9
9
  populate_by_name=True,
@@ -11,7 +11,7 @@ class ProjectInferenceSimulatorModelEntity(BaseModel):
11
11
  )
12
12
 
13
13
  public_id: Optional[str] = Field(None, alias='ID')
14
- slug: Optional[str] = Field(None, alias='UNIQUE_ID')
14
+ slug: Optional[str] = Field(None, alias='NAME')
15
15
  status: Optional[str] = Field(None, alias='STATUS')
16
16
  commit_hash: Optional[str] = Field(None, alias='COMMIT_HASH')
17
17
  environment_metadata: Optional[Dict[str, Any]] = Field(None, alias='ENVIRONMENT_METADATA')
@@ -1,13 +1,12 @@
1
1
 
2
2
  from pydantic import Field, ConfigDict
3
3
 
4
+ from thestage.inference_model.dto.inference_model import InferenceModel
4
5
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
5
6
  from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
6
- from thestage.services.project.dto.inference_simulator_dto import InferenceSimulatorDto
7
- from thestage.services.project.dto.inference_simulator_model_dto import InferenceSimulatorModelDto
8
7
 
9
8
 
10
9
  class InferenceSimulatorModelListForProjectResponse(TheStageBaseResponse):
11
10
  model_config = ConfigDict(use_enum_values=True)
12
11
 
13
- inferenceSimulatorModels: PaginatedEntityList[InferenceSimulatorModelDto] = Field(None, alias='inferenceSimulatorModels')
12
+ inferenceSimulatorModels: PaginatedEntityList[InferenceModel] = Field(None, alias='inferenceSimulatorModels')
@@ -3,7 +3,7 @@ from typing import Optional
3
3
  from pydantic import Field, ConfigDict, BaseModel
4
4
 
5
5
 
6
- class ProjectPushInferenceSimulatorModelRequest(BaseModel):
6
+ class PushInferenceSimulatorModelRequest(BaseModel):
7
7
  model_config = ConfigDict(use_enum_values=True)
8
8
 
9
9
  inferenceSimulatorPublicId: Optional[str] = Field(None, alias='inferenceSimulatorPublicId')
@@ -3,5 +3,5 @@ from pydantic import ConfigDict
3
3
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
4
4
 
5
5
 
6
- class ProjectPushInferenceSimulatorModelResponse(TheStageBaseResponse):
6
+ class PushInferenceSimulatorModelResponse(TheStageBaseResponse):
7
7
  model_config = ConfigDict(use_enum_values=True)
File without changes
File without changes
@@ -0,0 +1,338 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Optional, List
5
+
6
+ import click
7
+ import typer
8
+ from git import Commit
9
+ from rich import print
10
+
11
+ from thestage.color_scheme.color_scheme import ColorScheme
12
+ from thestage.config.business.config_provider import ConfigProvider
13
+ from thestage.git.communication.git_client import GitLocalClient
14
+ from thestage.global_dto.enums.yes_no_response import YesOrNoResponse
15
+ from thestage.helpers.error_handler import error_handler
16
+ from thestage.i18n.translation import __
17
+ from thestage.inference_model.communication.inference_model_api_client import InferenceModelApiClient
18
+ from thestage.inference_model.dto.push_inference_simulator_model_response import \
19
+ PushInferenceSimulatorModelResponse
20
+ from thestage.inference_simulator.business.mapper.inference_simulator_mapper import InferenceSimulatorMapper
21
+ from thestage.inference_simulator.communication.inference_simulator_api_client import InferenceSimulatorApiClient
22
+ from thestage.inference_simulator.dto.enum.inference_simulator_status import InferenceSimulatorStatus
23
+ from thestage.inference_simulator.dto.get_inference_simulator_response import \
24
+ GetInferenceSimulatorResponse
25
+ from thestage.inference_simulator.dto.inference_simulator import InferenceSimulator
26
+ from thestage.inference_simulator.dto.inference_simulator_entity import InferenceSimulatorEntity
27
+ from thestage.inference_simulator.dto.start_inference_simulator_response import \
28
+ StartInferenceSimulatorResponse
29
+ from thestage.project.business.project_service import ProjectService
30
+ from thestage.project.dto.project_config import ProjectConfig
31
+ from thestage.services.abstract_service import AbstractService
32
+ from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
33
+
34
+
35
+ class InferenceSimulatorService(AbstractService):
36
+ def __init__(
37
+ self,
38
+ inference_simulator_api_client: InferenceSimulatorApiClient,
39
+ inference_model_api_client: InferenceModelApiClient,
40
+ config_provider: ConfigProvider,
41
+ git_local_client: GitLocalClient,
42
+ project_service: ProjectService,
43
+ ):
44
+ self.__inference_simulator_api_client = inference_simulator_api_client
45
+ self.__inference_model_api_client = inference_model_api_client
46
+ self.__config_provider = config_provider
47
+ self.__git_local_client = git_local_client
48
+ self.__project_service = project_service
49
+
50
+ @error_handler()
51
+ def project_run_inference_simulator(
52
+ self,
53
+ commit_hash: Optional[str] = None,
54
+ rented_instance_public_id: Optional[str] = None,
55
+ rented_instance_slug: Optional[str] = None,
56
+ self_hosted_instance_public_id: Optional[str] = None,
57
+ self_hosted_instance_slug: Optional[str] = None,
58
+ inference_dir: Optional[str] = None,
59
+ is_skip_installation: Optional[bool] = False,
60
+ files_to_add: Optional[str] = None,
61
+ is_skip_auto_commit: Optional[bool] = False,
62
+ ) -> Optional[InferenceSimulator]:
63
+ config = self.__config_provider.get_config()
64
+ project_config: ProjectConfig = self.__project_service.get_fixed_project_config()
65
+
66
+ if not project_config:
67
+ typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first. Or provide path to project using --working-directory option.",
68
+ {"path": config.runtime.working_directory}))
69
+ raise typer.Exit(1)
70
+
71
+ instance_args_count = sum(v is not None for v in [rented_instance_public_id, rented_instance_slug, self_hosted_instance_public_id, self_hosted_instance_slug])
72
+ if instance_args_count != 1:
73
+ typer.echo("Please provide a single instance (rented or self-hosted) identifier - name or ID.")
74
+ raise typer.Exit(1)
75
+
76
+ has_wrong_args = files_to_add and commit_hash or is_skip_auto_commit and commit_hash or files_to_add and is_skip_auto_commit
77
+ if has_wrong_args:
78
+ warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] You can provide only one of the following arguments: --commit-hash, --files-add, --skip-autocommit[{ColorScheme.WARNING.value}]"
79
+ print(warning_msg)
80
+ raise typer.Exit(1)
81
+
82
+ if not is_skip_auto_commit and not commit_hash:
83
+ is_git_folder = self.__git_local_client.is_present_local_git(path=config.runtime.working_directory)
84
+ if not is_git_folder:
85
+ typer.echo("Error: Working directory is not a git repository.")
86
+ raise typer.Exit(1)
87
+
88
+ is_commit_allowed: bool = True
89
+ has_changes = self.__git_local_client.has_changes_with_untracked(
90
+ path=config.runtime.working_directory,
91
+ )
92
+
93
+ if self.__git_local_client.is_head_detached(path=config.runtime.working_directory):
94
+ print(f"[{ColorScheme.GIT_HEADLESS.value}]HEAD is detached[{ColorScheme.GIT_HEADLESS.value}]")
95
+
96
+ is_headless_commits_present = self.__git_local_client.is_head_committed_in_headless_state(
97
+ path=config.runtime.working_directory)
98
+ if is_headless_commits_present:
99
+ print(
100
+ f"[{ColorScheme.GIT_HEADLESS.value}]Current commit was made in detached head state. Cannot use it to start the inference simulator. Consider using 'project checkout' command to return to a valid reference.[{ColorScheme.GIT_HEADLESS.value}]")
101
+ raise typer.Exit(1)
102
+
103
+ if has_changes:
104
+ print(
105
+ f"[{ColorScheme.GIT_HEADLESS.value}]Local changes detected in detached head state. They will not impact the inference simulator.[{ColorScheme.GIT_HEADLESS.value}]")
106
+ is_commit_allowed = False
107
+ response: YesOrNoResponse = typer.prompt(
108
+ text=__('Continue?'),
109
+ show_choices=True,
110
+ default=YesOrNoResponse.YES.value,
111
+ type=click.Choice([r.value for r in YesOrNoResponse]),
112
+ show_default=True,
113
+ )
114
+ if response == YesOrNoResponse.NO:
115
+ raise typer.Exit(0)
116
+
117
+ if is_commit_allowed:
118
+ if not self.__git_local_client.add_files_with_size_limit_or_warn(config.runtime.working_directory, files_to_add):
119
+ warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] Inference simulator was not started [{ColorScheme.WARNING.value}]"
120
+ print(warning_msg)
121
+ raise typer.Exit(1)
122
+
123
+ diff_stat = self.__git_local_client.git_diff_stat(repo_path=config.runtime.working_directory)
124
+
125
+ if has_changes and diff_stat:
126
+ branch_name = self.__git_local_client.get_active_branch_name(config.runtime.working_directory)
127
+ typer.echo(__('Active branch [%branch_name%] has uncommitted changes: %diff_stat_bottomline%', {
128
+ 'diff_stat_bottomline': diff_stat,
129
+ 'branch_name': branch_name,
130
+ }))
131
+
132
+ response: str = typer.prompt(
133
+ text=__('Commit changes?'),
134
+ show_choices=True,
135
+ default=YesOrNoResponse.YES.value,
136
+ type=click.Choice([r.value for r in YesOrNoResponse]),
137
+ show_default=True,
138
+ )
139
+ if response == YesOrNoResponse.NO.value:
140
+ typer.echo("inference simulator cannot use uncommitted changes - aborting")
141
+ raise typer.Exit(0)
142
+
143
+ commit_name = typer.prompt(
144
+ text=__('Please provide commit message'),
145
+ show_choices=False,
146
+ type=str,
147
+ show_default=False,
148
+ )
149
+
150
+ if commit_name:
151
+ commit_result = self.__git_local_client.commit_local_changes(
152
+ path=config.runtime.working_directory,
153
+ name=commit_name
154
+ )
155
+
156
+ if commit_result:
157
+ # in docs not Commit object, on real - str
158
+ if isinstance(commit_result, str):
159
+ typer.echo(commit_result)
160
+
161
+ self.__git_local_client.push_changes(
162
+ path=config.runtime.working_directory,
163
+ deploy_key_path=project_config.deploy_key_path
164
+ )
165
+ typer.echo(__("Pushed changes to remote repository"))
166
+ else:
167
+ typer.echo(__('Cannot commit with empty commit name, your code will run without last changes.'))
168
+ else:
169
+ pass
170
+ # possible to push new empty branch - only that there's a wrong place to do so
171
+
172
+ if not commit_hash:
173
+ commit = self.__git_local_client.get_current_commit(path=config.runtime.working_directory)
174
+ if commit and isinstance(commit, Commit):
175
+ commit_hash = commit.hexsha
176
+
177
+ start_inference_simulator_response: StartInferenceSimulatorResponse = self.__inference_simulator_api_client.start_project_inference_simulator(
178
+ project_public_id=project_config.public_id,
179
+ commit_hash=commit_hash,
180
+ rented_instance_public_id=rented_instance_public_id,
181
+ rented_instance_slug=rented_instance_slug,
182
+ self_hosted_instance_public_id=self_hosted_instance_public_id,
183
+ self_hosted_instance_slug=self_hosted_instance_slug,
184
+ inference_dir=inference_dir,
185
+ is_skip_installation=is_skip_installation,
186
+ )
187
+ if start_inference_simulator_response:
188
+ if start_inference_simulator_response.message:
189
+ typer.echo(start_inference_simulator_response.message)
190
+ if start_inference_simulator_response.is_success and start_inference_simulator_response.inferenceSimulator:
191
+ typer.echo("Inference simulator has been scheduled to run successfully.")
192
+ return start_inference_simulator_response.inferenceSimulator
193
+ else:
194
+ typer.echo(__(
195
+ 'Failed to start inference simulator: %server_massage%',
196
+ {'server_massage': start_inference_simulator_response.message or ""}
197
+ ))
198
+ raise typer.Exit(1)
199
+ else:
200
+ typer.echo(__("Failed to start inference simulator"))
201
+ raise typer.Exit(1)
202
+
203
+
204
+ @error_handler()
205
+ def project_push_inference_simulator(
206
+ self,
207
+ public_id: Optional[str] = None,
208
+ slug: Optional[str] = None,
209
+ ):
210
+
211
+ push_inference_simulator_model_response: PushInferenceSimulatorModelResponse = self.__inference_model_api_client.push_project_inference_simulator_model(
212
+ public_id=public_id,
213
+ slug=slug,
214
+ )
215
+ if push_inference_simulator_model_response:
216
+ if push_inference_simulator_model_response.message:
217
+ typer.echo(push_inference_simulator_model_response.message)
218
+ if push_inference_simulator_model_response.is_success:
219
+ typer.echo("Inference simulator has been successfully scheduled to be pushed to S3 and ECR.")
220
+ else:
221
+ typer.echo(__(
222
+ 'Failed to push inference simulator: %server_massage%',
223
+ {'server_massage': push_inference_simulator_model_response.message or ""}
224
+ ))
225
+ raise typer.Exit(1)
226
+ else:
227
+ typer.echo(__("Failed to push inference simulator"))
228
+ raise typer.Exit(1)
229
+
230
+ @error_handler()
231
+ def project_get_and_save_inference_simulator_metadata(
232
+ self,
233
+ inference_simulator_public_id: Optional[str] = None,
234
+ inference_simulator_slug: Optional[str] = None,
235
+ file_path: Optional[str] = None,
236
+ ):
237
+ get_inference_metadata_response: GetInferenceSimulatorResponse = self.__inference_simulator_api_client.get_inference_simulator(
238
+ public_id=inference_simulator_public_id,
239
+ slug=inference_simulator_slug,
240
+ )
241
+
242
+ metadata = get_inference_metadata_response.inferenceSimulator.qlip_serve_metadata
243
+
244
+ if metadata:
245
+ typer.echo("qlip_serve_metadata:")
246
+ typer.echo(json.dumps(metadata, indent=4))
247
+
248
+ if not file_path:
249
+ file_path = Path(os.getcwd()) / "metadata.json"
250
+ typer.echo(__("No file path provided. Saving metadata to %file_path%", {"file_path": str(file_path)}))
251
+
252
+ try:
253
+ parsed_metadata = metadata
254
+
255
+ output_file = Path(file_path)
256
+ output_file.parent.mkdir(parents=True, exist_ok=True)
257
+ with output_file.open("w", encoding="utf-8") as file:
258
+ json.dump(parsed_metadata, file, indent=4)
259
+ typer.echo(__("Metadata successfully saved to %file_path%", {"file_path": str(file_path)}))
260
+ except Exception as e:
261
+ typer.echo(__("Failed to save metadata to %file_path%. Error: %error%",
262
+ {"file_path": file_path, "error": str(e)}))
263
+ raise typer.Exit(1)
264
+ else:
265
+ typer.echo(__("No qlip_serve_metadata found"))
266
+ raise typer.Exit(1)
267
+
268
+
269
+ @error_handler()
270
+ def get_project_inference_simulator_list(
271
+ self,
272
+ project_public_id: Optional[str],
273
+ project_slug: Optional[str],
274
+ statuses: List[str],
275
+ row: int = 5,
276
+ page: int = 1,
277
+ ) -> PaginatedEntityList[InferenceSimulator]:
278
+ data: Optional[PaginatedEntityList[InferenceSimulator]] = self.__inference_simulator_api_client.get_inference_simulator_list(
279
+ statuses=statuses,
280
+ project_public_id=project_public_id,
281
+ project_slug=project_slug,
282
+ page=page,
283
+ limit=row,
284
+ )
285
+
286
+ return data
287
+
288
+ @error_handler()
289
+ def print_inference_simulator_list(self, project_public_id, project_slug, statuses, row, page):
290
+ if not project_public_id and not project_slug:
291
+ project_config: ProjectConfig = self.__config_provider.read_project_config()
292
+ if not project_config:
293
+ typer.echo(__("Provide the project unique ID or run this command from within an initialized project directory"))
294
+ raise typer.Exit(1)
295
+ project_public_id = project_config.public_id
296
+
297
+ inference_simulator_status_map = self.__inference_simulator_api_client.get_inference_simulator_business_status_map()
298
+
299
+ if not statuses:
300
+ statuses = ({key: inference_simulator_status_map[key] for key in [
301
+ InferenceSimulatorStatus.SCHEDULED,
302
+ InferenceSimulatorStatus.CREATING,
303
+ InferenceSimulatorStatus.RUNNING,
304
+ ]}).values()
305
+
306
+ if "all" in statuses:
307
+ statuses = inference_simulator_status_map.values()
308
+
309
+ for input_status_item in statuses:
310
+ if input_status_item not in inference_simulator_status_map.values():
311
+ typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
312
+ 'invalid_status': input_status_item,
313
+ 'valid_statuses': str(list(inference_simulator_status_map.values()))
314
+ }))
315
+ raise typer.Exit(1)
316
+
317
+ typer.echo(__(
318
+ "Listing inference simulators with the following statuses: %statuses%, to view all inference simulators, use --status all",
319
+ placeholders={
320
+ 'statuses': ', '.join([status_item for status_item in statuses])
321
+ }))
322
+
323
+ backend_statuses: List[str] = [key for key, value in inference_simulator_status_map.items() if value in statuses]
324
+
325
+ self.print(
326
+ func_get_data=self.get_project_inference_simulator_list,
327
+ func_special_params={
328
+ 'project_public_id': project_public_id,
329
+ 'project_slug': project_slug,
330
+ 'statuses': backend_statuses,
331
+ },
332
+ mapper=InferenceSimulatorMapper(),
333
+ headers=list(map(lambda x: x.alias, InferenceSimulatorEntity.model_fields.values())),
334
+ row=row,
335
+ page=page,
336
+ max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
337
+ show_index="never",
338
+ )