thestage 0.5.471__py3-none-any.whl → 0.6.2__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 (179) hide show
  1. thestage/.env +5 -5
  2. thestage/__init__.py +3 -4
  3. thestage/__main__.py +9 -9
  4. thestage/cli_command.py +56 -56
  5. thestage/cli_command_helper.py +51 -51
  6. thestage/color_scheme/color_scheme.py +7 -7
  7. thestage/config/__init__.py +18 -18
  8. thestage/config/config_storage.py +5 -5
  9. thestage/config/env_base.py +7 -7
  10. thestage/controllers/__init__.py +0 -0
  11. thestage/controllers/base_controller.py +67 -67
  12. thestage/controllers/config_controller.py +137 -137
  13. thestage/controllers/container_controller.py +389 -389
  14. thestage/controllers/instance_controller.py +183 -183
  15. thestage/controllers/project_controller.py +810 -783
  16. thestage/controllers/utils_controller.py +32 -32
  17. thestage/debug_main.dist.py +28 -28
  18. thestage/entities/__init__.py +0 -0
  19. thestage/entities/container.py +17 -17
  20. thestage/entities/enums/__init__.py +0 -0
  21. thestage/entities/enums/order_direction_type.py +6 -6
  22. thestage/entities/enums/shell_type.py +7 -7
  23. thestage/entities/enums/tail_output_type.py +6 -6
  24. thestage/entities/enums/yes_no_response.py +7 -7
  25. thestage/entities/file_item.py +27 -27
  26. thestage/entities/project_inference_simulator.py +18 -18
  27. thestage/entities/project_inference_simulator_model.py +16 -16
  28. thestage/entities/project_task.py +19 -19
  29. thestage/entities/rented_instance.py +19 -19
  30. thestage/entities/self_hosted_instance.py +18 -18
  31. thestage/exceptions/__init__.py +0 -0
  32. thestage/exceptions/auth_exception.py +6 -6
  33. thestage/exceptions/base_exception.py +13 -13
  34. thestage/exceptions/business_logic_exception.py +6 -6
  35. thestage/exceptions/config_exception.py +6 -6
  36. thestage/exceptions/file_system_exception.py +6 -6
  37. thestage/exceptions/git_access_exception.py +17 -17
  38. thestage/exceptions/remote_server_exception.py +24 -24
  39. thestage/git/ProgressPrinter.py +22 -22
  40. thestage/helpers/__init__.py +0 -0
  41. thestage/helpers/error_handler.py +115 -115
  42. thestage/helpers/exception_hook.py +14 -14
  43. thestage/helpers/logger/__init__.py +0 -0
  44. thestage/helpers/logger/app_logger.py +50 -50
  45. thestage/helpers/ssh_util.py +38 -38
  46. thestage/i18n/en_GB/messages.po +947 -947
  47. thestage/i18n/translation.py +9 -9
  48. thestage/main.py +36 -36
  49. thestage/services/.env +6 -6
  50. thestage/services/__init__.py +0 -0
  51. thestage/services/abstract_mapper.py +9 -9
  52. thestage/services/abstract_service.py +87 -87
  53. thestage/services/app_config_service.py +52 -52
  54. thestage/services/clients/__init__.py +0 -0
  55. thestage/services/clients/git/__init__.py +0 -0
  56. thestage/services/clients/git/git_client.py +436 -331
  57. thestage/services/clients/thestage_api/__init__.py +0 -0
  58. thestage/services/clients/thestage_api/api_client.py +718 -720
  59. thestage/services/clients/thestage_api/core/api_client_core.py +108 -108
  60. thestage/services/clients/thestage_api/core/http_client_exception.py +12 -12
  61. thestage/services/clients/thestage_api/dtos/__init__.py +0 -0
  62. thestage/services/clients/thestage_api/dtos/base_response.py +13 -13
  63. thestage/services/clients/thestage_api/dtos/cloud_provider_region.py +19 -19
  64. thestage/services/clients/thestage_api/dtos/container_param_request.py +11 -11
  65. thestage/services/clients/thestage_api/dtos/container_response.py +67 -67
  66. thestage/services/clients/thestage_api/dtos/docker_container_assigned_device.py +10 -10
  67. thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_request.py +13 -13
  68. thestage/services/clients/thestage_api/dtos/docker_container_controller/docker_container_list_response.py +13 -13
  69. thestage/services/clients/thestage_api/dtos/docker_container_mapping.py +10 -10
  70. thestage/services/clients/thestage_api/dtos/entity_filter_request.py +14 -14
  71. thestage/services/clients/thestage_api/dtos/enums/__init__.py +0 -0
  72. thestage/services/clients/thestage_api/dtos/enums/container_pending_action.py +10 -10
  73. thestage/services/clients/thestage_api/dtos/enums/container_status.py +17 -17
  74. thestage/services/clients/thestage_api/dtos/enums/cpu_type.py +8 -8
  75. thestage/services/clients/thestage_api/dtos/enums/currency_type.py +10 -10
  76. thestage/services/clients/thestage_api/dtos/enums/daemon_status.py +9 -9
  77. thestage/services/clients/thestage_api/dtos/enums/disk_type.py +7 -7
  78. thestage/services/clients/thestage_api/dtos/enums/drive_type.py +7 -7
  79. thestage/services/clients/thestage_api/dtos/enums/gpu_name.py +8 -8
  80. thestage/services/clients/thestage_api/dtos/enums/inference_model_status.py +9 -9
  81. thestage/services/clients/thestage_api/dtos/enums/inference_simulator_status.py +15 -15
  82. thestage/services/clients/thestage_api/dtos/enums/instance_rented_status.py +17 -17
  83. thestage/services/clients/thestage_api/dtos/enums/instance_type.py +7 -7
  84. thestage/services/clients/thestage_api/dtos/enums/location_region.py +11 -11
  85. thestage/services/clients/thestage_api/dtos/enums/power_status.py +10 -10
  86. thestage/services/clients/thestage_api/dtos/enums/provider_name.py +11 -11
  87. thestage/services/clients/thestage_api/dtos/enums/selfhosted_status.py +10 -10
  88. thestage/services/clients/thestage_api/dtos/enums/task_execution_status.py +12 -12
  89. thestage/services/clients/thestage_api/dtos/enums/task_status.py +12 -12
  90. thestage/services/clients/thestage_api/dtos/frontend_status.py +10 -10
  91. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_request.py +13 -13
  92. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_instance_response.py +13 -13
  93. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_request.py +12 -12
  94. thestage/services/clients/thestage_api/dtos/inference_controller/deploy_inference_model_to_sagemaker_response.py +12 -12
  95. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_request.py +10 -10
  96. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +13 -13
  97. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_for_project_request.py +14 -14
  98. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_list_for_project_response.py +12 -12
  99. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_request.py +12 -12
  100. thestage/services/clients/thestage_api/dtos/inference_controller/inference_simulator_model_list_for_project_response.py +13 -13
  101. thestage/services/clients/thestage_api/dtos/inference_simulator_model_response.py +11 -11
  102. thestage/services/clients/thestage_api/dtos/inference_simulator_response.py +11 -11
  103. thestage/services/clients/thestage_api/dtos/installed_service.py +17 -17
  104. thestage/services/clients/thestage_api/dtos/instance_detected_gpus.py +20 -20
  105. thestage/services/clients/thestage_api/dtos/instance_rented_response.py +71 -71
  106. thestage/services/clients/thestage_api/dtos/logging_controller/docker_container_log_stream_request.py +7 -7
  107. thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +13 -13
  108. thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_response.py +14 -14
  109. thestage/services/clients/thestage_api/dtos/logging_controller/task_log_stream_request.py +7 -7
  110. thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_request.py +21 -21
  111. thestage/services/clients/thestage_api/dtos/logging_controller/user_logs_query_response.py +14 -14
  112. thestage/services/clients/thestage_api/dtos/paginated_entity_list.py +11 -11
  113. thestage/services/clients/thestage_api/dtos/pagination_data.py +10 -10
  114. thestage/services/clients/thestage_api/dtos/price_definition.py +14 -14
  115. thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py +7 -7
  116. thestage/services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py +10 -10
  117. thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py +8 -8
  118. thestage/services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py +6 -6
  119. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_request.py +15 -15
  120. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +10 -10
  121. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py +13 -14
  122. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +10 -10
  123. thestage/services/clients/thestage_api/dtos/project_response.py +32 -32
  124. thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +56 -56
  125. thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +13 -13
  126. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_request.py +8 -8
  127. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_key_to_user_response.py +11 -11
  128. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_request.py +8 -8
  129. thestage/services/clients/thestage_api/dtos/ssh_key_controller/add_ssh_public_key_to_instance_response.py +11 -11
  130. thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_request.py +7 -7
  131. thestage/services/clients/thestage_api/dtos/ssh_key_controller/is_user_has_public_ssh_key_response.py +12 -12
  132. thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py +10 -10
  133. thestage/services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py +12 -12
  134. thestage/services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py +9 -9
  135. thestage/services/clients/thestage_api/dtos/task_controller/task_view_response.py +12 -12
  136. thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +12 -12
  137. thestage/services/clients/thestage_api/dtos/validate_token_response.py +11 -11
  138. thestage/services/config_provider/__init__.py +0 -0
  139. thestage/services/config_provider/config_provider.py +237 -237
  140. thestage/services/connect/connect_service.py +196 -196
  141. thestage/services/connect/dto/remote_server_config.py +9 -9
  142. thestage/services/container/__init__.py +0 -0
  143. thestage/services/container/container_service.py +374 -374
  144. thestage/services/container/mapper/__init__.py +0 -0
  145. thestage/services/container/mapper/container_mapper.py +30 -30
  146. thestage/services/core_files/config_entity.py +26 -26
  147. thestage/services/filesystem_service.py +133 -133
  148. thestage/services/instance/__init__.py +0 -0
  149. thestage/services/instance/instance_service.py +303 -303
  150. thestage/services/instance/mapper/__init__.py +0 -0
  151. thestage/services/instance/mapper/instance_mapper.py +24 -24
  152. thestage/services/instance/mapper/selfhosted_mapper.py +33 -33
  153. thestage/services/logging/byte_print_style.py +5 -5
  154. thestage/services/logging/dto/log_message.py +15 -15
  155. thestage/services/logging/dto/log_type.py +6 -6
  156. thestage/services/logging/exception/log_polling_exception.py +6 -6
  157. thestage/services/logging/logging_constants.py +3 -3
  158. thestage/services/logging/logging_service.py +367 -367
  159. thestage/services/project/__init__.py +0 -0
  160. thestage/services/project/dto/inference_simulator_dto.py +22 -22
  161. thestage/services/project/dto/inference_simulator_model_dto.py +20 -20
  162. thestage/services/project/dto/project_config.py +14 -14
  163. thestage/services/project/mapper/__init__.py +0 -0
  164. thestage/services/project/mapper/project_inference_simulator_mapper.py +21 -21
  165. thestage/services/project/mapper/project_inference_simulator_model_mapper.py +21 -21
  166. thestage/services/project/mapper/project_task_mapper.py +22 -22
  167. thestage/services/project/project_service.py +1253 -1241
  168. thestage/services/remote_server_service.py +609 -609
  169. thestage/services/service_factory.py +97 -97
  170. thestage/services/task/dto/task_dto.py +40 -40
  171. thestage/services/validation_service.py +61 -61
  172. {thestage-0.5.471.dist-info → thestage-0.6.2.dist-info}/LICENSE.txt +12 -12
  173. {thestage-0.5.471.dist-info → thestage-0.6.2.dist-info}/METADATA +1 -1
  174. thestage-0.6.2.dist-info/RECORD +176 -0
  175. {thestage-0.5.471.dist-info → thestage-0.6.2.dist-info}/WHEEL +1 -1
  176. thestage/debug_tests.py +0 -12
  177. thestage/services/clients/.DS_Store +0 -0
  178. thestage-0.5.471.dist-info/RECORD +0 -178
  179. {thestage-0.5.471.dist-info → thestage-0.6.2.dist-info}/entry_points.txt +0 -0
@@ -1,783 +1,810 @@
1
- import time
2
- from pathlib import Path
3
- from typing import Optional, List
4
-
5
- import re
6
-
7
- import typer
8
- from typing_extensions import Annotated
9
-
10
- from thestage.cli_command import CliCommand
11
- from thestage.cli_command_helper import get_command_group_help_panel, get_command_metadata, check_command_permission
12
- from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
13
- from thestage.helpers.logger.app_logger import app_logger
14
- from thestage.i18n.translation import __
15
- from thestage.services.clients.thestage_api.dtos.inference_controller.get_inference_simulator_response import \
16
- GetInferenceSimulatorResponse
17
- from thestage.services.logging.logging_service import LoggingService
18
- from thestage.services.project.project_service import ProjectService
19
- from thestage.services.task.dto.task_dto import TaskDto
20
-
21
- app = typer.Typer(no_args_is_help=True, help=__("Manage projects"))
22
- inference_simulators_app = typer.Typer(no_args_is_help=True, help="Manage project inference simulators")
23
- inference_simulator_model_app = typer.Typer(no_args_is_help=True, help="Manage project inference simulator models")
24
- task_app = typer.Typer(no_args_is_help=True, help=__("Manage project tasks"))
25
- config_app = typer.Typer(no_args_is_help=True, help=__("Manage project config"))
26
-
27
- app.add_typer(inference_simulators_app, name="inference-simulator", rich_help_panel=get_command_group_help_panel())
28
- app.add_typer(inference_simulator_model_app, name="model", rich_help_panel=get_command_group_help_panel())
29
- app.add_typer(task_app, name="task", rich_help_panel=get_command_group_help_panel())
30
- app.add_typer(config_app, name="config", rich_help_panel=get_command_group_help_panel())
31
-
32
-
33
- @app.command(name='clone', no_args_is_help=True, help=__("Clone project repository to empty directory"), **get_command_metadata(CliCommand.PROJECT_CLONE))
34
- def clone(
35
- project_uid: str = typer.Argument(
36
- help=__("Project unique ID"),
37
- ),
38
- working_directory: Optional[str] = typer.Option(
39
- None,
40
- "--working-directory",
41
- "-wd",
42
- help=__("Full path to the working directory: current directory used by default"),
43
- is_eager=False,
44
- ),
45
- ):
46
- command_name = CliCommand.PROJECT_CLONE
47
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
48
- check_command_permission(command_name)
49
-
50
- if not working_directory:
51
- working_directory = get_current_directory().joinpath(project_uid)
52
-
53
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
54
- project_service = service_factory.get_project_service()
55
-
56
- project_service.clone_project(
57
- project_slug=project_uid,
58
- )
59
-
60
- raise typer.Exit(0)
61
-
62
-
63
- @app.command(name='init', no_args_is_help=True, help=__("Initialize project repository with existing files"), **get_command_metadata(CliCommand.PROJECT_INIT))
64
- def init(
65
- project_uid: Optional[str] = typer.Argument(
66
- help=__("Project unique ID"),
67
- ),
68
- working_directory: Optional[str] = typer.Option(
69
- None,
70
- "--working-directory",
71
- "-wd",
72
- help=__("Full path to working directory"),
73
- is_eager=False,
74
- ),
75
- ):
76
- command_name = CliCommand.PROJECT_INIT
77
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
78
- check_command_permission(command_name)
79
-
80
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
81
- project_service = service_factory.get_project_service()
82
- project_config = service_factory.get_config_provider().read_project_config()
83
-
84
- if project_config:
85
- typer.echo(__("Directory is initialized and already contains working project"))
86
- raise typer.Exit(1)
87
-
88
- project_service.init_project(
89
- project_slug=project_uid,
90
- )
91
-
92
- raise typer.Exit(0)
93
-
94
-
95
- @app.command(name='run', no_args_is_help=True, help=__("Run a task within the project. By default, it uses the latest commit from the main branch and streams real-time task logs."), **get_command_metadata(CliCommand.PROJECT_RUN))
96
- def run(
97
- command: Annotated[List[str], typer.Argument(
98
- help=__("Command to run (required)"),
99
- )],
100
- commit_hash: Optional[str] = typer.Option(
101
- None,
102
- '--commit-hash',
103
- '-hash',
104
- help=__("Commit hash to use. By default, the current HEAD commit is used."),
105
- is_eager=False,
106
- ),
107
- docker_container_slug: Optional[str] = typer.Option(
108
- None,
109
- '--container-uid',
110
- '-cid',
111
- help=__("Docker container unique ID"),
112
- is_eager=False,
113
- ),
114
- working_directory: Optional[str] = typer.Option(
115
- None,
116
- "--working-directory",
117
- "-wd",
118
- help=__("Full path to working directory"),
119
- show_default=False,
120
- is_eager=False,
121
- ),
122
- enable_log_stream: Optional[bool] = typer.Option(
123
- True,
124
- " /--no-logs",
125
- " /-nl",
126
- help=__("Disable real-time log streaming"),
127
- is_eager=False,
128
- ),
129
- task_title: Optional[str] = typer.Option(
130
- None,
131
- "--title",
132
- "-t",
133
- help=__("Provide a custom task title. Git commit message is used by default."),
134
- is_eager=False,
135
- ),
136
- ):
137
- command_name = CliCommand.PROJECT_RUN
138
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
139
- check_command_permission(command_name)
140
-
141
- if not command:
142
- typer.echo(__('Command is required'))
143
- raise typer.Exit(1)
144
-
145
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
146
- project_service = service_factory.get_project_service()
147
-
148
- task: Optional[TaskDto] = project_service.project_run_task(
149
- run_command=" ".join(command),
150
- commit_hash=commit_hash,
151
- docker_container_slug=docker_container_slug,
152
- task_title=task_title,
153
- )
154
-
155
- if enable_log_stream:
156
- logging_service: LoggingService = service_factory.get_logging_service()
157
- logging_service.stream_task_logs_with_controls(task_id=task.id)
158
-
159
- raise typer.Exit(0)
160
-
161
-
162
- @task_app.command(name='cancel', no_args_is_help=True, help=__("Cancel a task by ID"), **get_command_metadata(CliCommand.PROJECT_TASK_CANCEL))
163
- def cancel_task(
164
- task_id: Annotated[int, typer.Argument(
165
- help=__("Task ID (required)"),
166
- )],
167
- ):
168
- command_name = CliCommand.PROJECT_TASK_CANCEL
169
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
170
- check_command_permission(command_name)
171
-
172
- if not task_id:
173
- typer.echo(__('Task ID is required'))
174
- raise typer.Exit(1)
175
-
176
- service_factory = validate_config_and_get_service_factory()
177
- project_service = service_factory.get_project_service()
178
-
179
- project_service.cancel_task(
180
- task_id=task_id
181
- )
182
-
183
- raise typer.Exit(0)
184
-
185
-
186
- @task_app.command("ls", help=__("List tasks"), **get_command_metadata(CliCommand.PROJECT_TASK_LS))
187
- def list_runs(
188
- project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
189
- row: int = typer.Option(
190
- 5,
191
- '--row',
192
- '-r',
193
- help=__("Set number of rows displayed per page"),
194
- is_eager=False,
195
- ),
196
- page: int = typer.Option(
197
- 1,
198
- '--page',
199
- '-p',
200
- help=__("Set starting page for displaying output"),
201
- is_eager=False,
202
- ),
203
- ):
204
- command_name = CliCommand.PROJECT_TASK_LS
205
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
206
- check_command_permission(command_name)
207
-
208
- service_factory = validate_config_and_get_service_factory()
209
- project_service: ProjectService = service_factory.get_project_service()
210
-
211
- project_service.print_task_list(project_uid, row, page)
212
-
213
- typer.echo(__("Tasks listing complete"))
214
- raise typer.Exit(0)
215
-
216
-
217
- @task_app.command(name="logs", no_args_is_help=True, help=__("Stream real-time task logs or view last logs for a task"), **get_command_metadata(CliCommand.PROJECT_TASK_LOGS))
218
- def task_logs(
219
- task_id: Optional[int] = typer.Argument(help=__("Task ID"),),
220
- logs_number: Optional[int] = typer.Option(
221
- None,
222
- '--number',
223
- '-n',
224
- help=__("Display a number of latest log entries. No real-time stream if provided."),
225
- is_eager=False,
226
- ),
227
- ):
228
- command_name = CliCommand.PROJECT_TASK_LOGS
229
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
230
- check_command_permission(command_name)
231
-
232
- if not task_id:
233
- typer.echo(__('Task ID is required'))
234
- raise typer.Exit(1)
235
-
236
- service_factory = validate_config_and_get_service_factory()
237
- logging_service: LoggingService = service_factory.get_logging_service()
238
-
239
- if logs_number is None:
240
- logging_service.stream_task_logs_with_controls(task_id=task_id)
241
- else:
242
- logging_service.print_last_task_logs(task_id=task_id, logs_number=logs_number)
243
-
244
- app_logger.info(f'Task logs - end')
245
- raise typer.Exit(0)
246
-
247
-
248
- @app.command(name='checkout', no_args_is_help=True, help=__("Checkout project repository to a specific reference"), **get_command_metadata(CliCommand.PROJECT_CHECKOUT))
249
- def checkout_project(
250
- reference: Optional[str] = typer.Argument(
251
- help=__("Task ID or branch name to checkout. '/' will try to identify the main branch."),
252
- ),
253
- working_directory: Optional[str] = typer.Option(
254
- None,
255
- "--working-directory",
256
- "-wd",
257
- help=__("Full path to working directory"),
258
- is_eager=False,
259
- ),
260
- ):
261
- command_name = CliCommand.PROJECT_CHECKOUT
262
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
263
- check_command_permission(command_name)
264
-
265
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
266
- project_service = service_factory.get_project_service()
267
-
268
- task_id: Optional[int] = None
269
- branch_name: Optional[str] = None
270
-
271
- if reference == "/":
272
- pass
273
- elif reference.isdigit():
274
- task_id = int(reference)
275
- else:
276
- branch_name = reference
277
-
278
- project_service.checkout_project(
279
- task_id=task_id,
280
- branch_name=branch_name
281
- )
282
-
283
- raise typer.Exit(0)
284
-
285
-
286
- @app.command(name='pull', help=__("Pulls the changes from the remote project repository. Equivalent to 'git pull'."), **get_command_metadata(CliCommand.PROJECT_PULL))
287
- def pull_project(
288
- working_directory: Optional[str] = typer.Option(
289
- None,
290
- "--working-directory",
291
- "-wd",
292
- help=__("Full path to working directory"),
293
- is_eager=False,
294
- ),
295
- ):
296
- command_name = CliCommand.PROJECT_PULL
297
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
298
- check_command_permission(command_name)
299
-
300
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
301
- project_service = service_factory.get_project_service()
302
-
303
- project_service.pull_project()
304
-
305
- raise typer.Exit(0)
306
-
307
-
308
- @app.command(name='reset', help=__("Resets the current project branch to remote counterpart. All working tree changes will be lost. Equivalent to 'git fetch && git reset --hard origin/{ref}'."), **get_command_metadata(CliCommand.PROJECT_RESET))
309
- def reset_project(
310
- working_directory: Optional[str] = typer.Option(
311
- None,
312
- "--working-directory",
313
- "-wd",
314
- help=__("Full path to working directory"),
315
- is_eager=False,
316
- ),
317
- ):
318
- command_name = CliCommand.PROJECT_RESET
319
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
320
- check_command_permission(command_name)
321
-
322
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
323
- project_service = service_factory.get_project_service()
324
-
325
- project_service.reset_project()
326
-
327
- raise typer.Exit(0)
328
-
329
-
330
- @config_app.command(name='set-default-container', no_args_is_help=True, help=__("Set default docker container for a project installation"), **get_command_metadata(CliCommand.PROJECT_CONFIG_SET_DEFAULT_CONTAINER))
331
- def set_default_container(
332
- container_uid: Annotated[Optional[str], typer.Argument(
333
- help=__("Unique ID of the container to use by default for running tasks"),
334
- )] = None,
335
- unset_default_container: Optional[bool] = typer.Option(
336
- False,
337
- "--unset",
338
- "-u",
339
- help=__("Unsets the default docker container"),
340
- is_eager=False,
341
- ),
342
- working_directory: Optional[str] = typer.Option(
343
- None,
344
- "--working-directory",
345
- "-wd",
346
- help=__("Full path to working directory"),
347
- is_eager=False,
348
- ),
349
- ):
350
- command_name = CliCommand.PROJECT_CONFIG_SET_DEFAULT_CONTAINER
351
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
352
- check_command_permission(command_name)
353
-
354
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
355
-
356
- if unset_default_container and container_uid:
357
- typer.echo("Container unique ID is provided along with unset flag. Please pick one.")
358
- raise typer.Exit(1)
359
-
360
- if not unset_default_container and not container_uid:
361
- typer.echo("Provide container unique ID or use '--unset' flag")
362
- raise typer.Exit(1)
363
-
364
- project_service = service_factory.get_project_service()
365
-
366
- project_service.set_default_container(
367
- container_uid=container_uid,
368
- )
369
-
370
- raise typer.Exit(0)
371
-
372
-
373
- @config_app.command(name='get', no_args_is_help=False, help=__("View config for a local project installation"), **get_command_metadata(CliCommand.PROJECT_CONFIG_GET))
374
- def get_project_config(
375
- working_directory: Optional[str] = typer.Option(
376
- None,
377
- "--working-directory",
378
- "-wd",
379
- help=__("Full path to working directory"),
380
- is_eager=False,
381
- ),
382
- ):
383
- command_name = CliCommand.PROJECT_CONFIG_GET
384
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
385
- check_command_permission(command_name)
386
-
387
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
388
- project_service = service_factory.get_project_service()
389
-
390
- project_service.print_project_config()
391
-
392
- raise typer.Exit(0)
393
-
394
-
395
- @inference_simulators_app.command(name='run', no_args_is_help=True, help="Run an inference simulator within the project", **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_RUN))
396
- def run_inference_simulator(
397
- unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"), ),
398
- rented_instance_unique_id: Optional[str] = typer.Option(
399
- None,
400
- '--rented-instance-unique-id',
401
- '-ruid',
402
- help=__("The rented instance unique ID on which the inference simulator will run"),
403
- is_eager=False,
404
- ),
405
- self_hosted_instance_unique_id: Optional[str] = typer.Option(
406
- None,
407
- '--self-hosted-instance-unique-id',
408
- '-suid',
409
- help=__("The self-hosted instance unique ID on which the inference simulator will run"),
410
- is_eager=False,
411
- ),
412
- commit_hash: Optional[str] = typer.Option(
413
- None,
414
- '--commit-hash',
415
- '-hash',
416
- help=__("Commit hash to use. By default, the current HEAD commit is used."),
417
- is_eager=False,
418
- ),
419
- working_directory: Optional[str] = typer.Option(
420
- None,
421
- "--working-directory",
422
- "-wd",
423
- help=__("Full path to working directory. By default, the current directory is used"),
424
- show_default=False,
425
- is_eager=False,
426
- ),
427
- enable_log_stream: Optional[bool] = typer.Option(
428
- True,
429
- " /--no-logs",
430
- " /-nl",
431
- help=__("Disable real-time log streaming"),
432
- is_eager=False,
433
- ),
434
- is_skip_installation: Optional[bool] = typer.Option(
435
- False,
436
- "--skip-installation",
437
- "-si",
438
- help=__("Skip installing dependencies from requirements.txt and install.sh"),
439
- is_eager=False,
440
- ),
441
- ):
442
- command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_RUN
443
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
444
- check_command_permission(command_name)
445
-
446
- if unique_id and not re.match(r"^[a-zA-Z0-9-]+$", unique_id):
447
- raise typer.BadParameter(__("Invalid UID format. The UID can only contain letters, numbers, and hyphens."))
448
-
449
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
450
- config = service_factory.get_config_provider().get_config()
451
-
452
- working_dir_path = Path(working_directory) if working_directory else Path(config.runtime.working_directory)
453
- inference_files = list(working_dir_path.rglob("inference.py"))
454
- if not inference_files:
455
- typer.echo("No inference.py file found in the project directory.")
456
- raise typer.Exit(1)
457
- elif len(inference_files) == 1:
458
- selected_inference = inference_files[0]
459
- else:
460
- choices = [str(path.relative_to(working_dir_path)) for path in inference_files]
461
- typer.echo("Multiple inference.py files found:")
462
- for idx, choice in enumerate(choices, start=1):
463
- typer.echo(f"{idx}) {choice}")
464
- choice_str = typer.prompt("Choose which inference.py to use")
465
- try:
466
- choice_index = int(choice_str)
467
- except ValueError:
468
- raise typer.BadParameter("Invalid input. Please enter a number.")
469
- if not (1 <= choice_index <= len(choices)):
470
- raise typer.BadParameter("Choice out of range.")
471
- selected_inference = inference_files[choice_index - 1]
472
-
473
- relative_inference = selected_inference.relative_to(working_dir_path)
474
- parent_dir = relative_inference.parent
475
- if parent_dir == Path("."):
476
- inference_dir = "/"
477
- else:
478
- inference_dir = f"{parent_dir.as_posix()}/"
479
- typer.echo(f"Selected inference file relative path: {inference_dir}")
480
-
481
- project_service = service_factory.get_project_service()
482
-
483
- project_service.project_run_inference_simulator(
484
- commit_hash=commit_hash,
485
- slug=unique_id,
486
- rented_instance_unique_id=rented_instance_unique_id,
487
- self_hosted_instance_unique_id=self_hosted_instance_unique_id,
488
- inference_dir=inference_dir,
489
- is_skip_installation=is_skip_installation,
490
- )
491
-
492
- if enable_log_stream:
493
- logging_service: LoggingService = service_factory.get_logging_service()
494
-
495
- logging_service.stream_inference_simulator_logs_with_controls(
496
- slug=unique_id
497
- )
498
- raise typer.Exit(0)
499
-
500
-
501
- @inference_simulators_app.command(name='save-metadata', no_args_is_help=True, help="Get and save inference simulator metadata", **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_SAVE_METADATA))
502
- def get_and_save_inference_simulator_metadata(
503
- unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"),),
504
- file_path: Optional[str] = typer.Option(
505
- None,
506
- "--file-path",
507
- "-fp",
508
- help=__("Full path to a new file. By default metadata is saved to the current directory as metadata.json"),
509
- show_default=False,
510
- is_eager=False,
511
- ),
512
- ):
513
- command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_SAVE_METADATA
514
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
515
- check_command_permission(command_name)
516
-
517
- service_factory = validate_config_and_get_service_factory()
518
- project_service = service_factory.get_project_service()
519
-
520
- project_service.project_get_and_save_inference_simulator_metadata(
521
- file_path=file_path,
522
- slug=unique_id,
523
- )
524
-
525
- raise typer.Exit(0)
526
-
527
-
528
- @inference_simulators_app.command(name='push', no_args_is_help=True, help="Push an inference simulator within the project to model registry", **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_PUSH))
529
- def push_inference_simulator(
530
- unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"),),
531
- ):
532
- command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_PUSH
533
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
534
- check_command_permission(command_name)
535
-
536
- service_factory = validate_config_and_get_service_factory()
537
- project_service = service_factory.get_project_service()
538
-
539
- project_service.project_push_inference_simulator(
540
- slug=unique_id,
541
- )
542
-
543
- raise typer.Exit(0)
544
-
545
-
546
- @inference_simulators_app.command("ls", help=__("List inference simulators"), **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_LS))
547
- def list_inference_simulators(
548
- project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
549
- row: int = typer.Option(
550
- 5,
551
- '--row',
552
- '-r',
553
- help=__("Set number of rows displayed per page"),
554
- is_eager=False,
555
- ),
556
- page: int = typer.Option(
557
- 1,
558
- '--page',
559
- '-p',
560
- help=__("Set starting page for displaying output"),
561
- is_eager=False,
562
- ),
563
- statuses: List[str] = typer.Option(
564
- None,
565
- '--status',
566
- '-s',
567
- help=__("Filter by status, use --status all to list all inference simulators"),
568
- is_eager=False,
569
- ),
570
- ):
571
- command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_LS
572
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
573
- check_command_permission(command_name)
574
-
575
- service_factory = validate_config_and_get_service_factory()
576
- project_service: ProjectService = service_factory.get_project_service()
577
-
578
- project_service.print_inference_simulator_list(project_uid, statuses, row, page)
579
-
580
- typer.echo(__("Inference simulators listing complete"))
581
- raise typer.Exit(0)
582
-
583
-
584
- @inference_simulator_model_app.command("ls", help=__("List inference simulator models"), **get_command_metadata(CliCommand.PROJECT_MODEL_LS))
585
- def list_inference_simulator_models(
586
- project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
587
- row: int = typer.Option(
588
- 5,
589
- '--row',
590
- '-r',
591
- help=__("Set number of rows displayed per page"),
592
- is_eager=False,
593
- ),
594
- page: int = typer.Option(
595
- 1,
596
- '--page',
597
- '-p',
598
- help=__("Set starting page for displaying output"),
599
- is_eager=False,
600
- ),
601
- statuses: List[str] = typer.Option(
602
- None,
603
- '--status',
604
- '-s',
605
- help=__("Filter by status, use --status all to list all inference simulator models"),
606
- is_eager=False,
607
- ),
608
- ):
609
- command_name = CliCommand.PROJECT_MODEL_LS
610
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
611
- check_command_permission(command_name)
612
-
613
- service_factory = validate_config_and_get_service_factory()
614
- project_service: ProjectService = service_factory.get_project_service()
615
-
616
- project_service.print_inference_simulator_model_list(project_uid, statuses, row, page)
617
-
618
- typer.echo(__("Inference simulator models listing complete"))
619
- raise typer.Exit(0)
620
-
621
-
622
- @inference_simulators_app.command(name="logs", no_args_is_help=True, help=__("Stream real-time task logs or view last logs for an inference simulator"), **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_LOGS))
623
- def inference_simulator_logs(
624
- unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"),),
625
- logs_number: Optional[int] = typer.Option(
626
- None,
627
- '--number',
628
- '-n',
629
- help=__("Display a number of latest log entries. No real-time stream if provided."),
630
- is_eager=False,
631
- ),
632
- ):
633
- command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_LOGS
634
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
635
- check_command_permission(command_name)
636
-
637
- if not unique_id:
638
- typer.echo(__('Inference simulator UID is required'))
639
- raise typer.Exit(1)
640
-
641
- service_factory = validate_config_and_get_service_factory()
642
- logging_service: LoggingService = service_factory.get_logging_service()
643
-
644
- if logs_number is None:
645
- logging_service.stream_inference_simulator_logs_with_controls(
646
- slug=unique_id
647
- )
648
- else:
649
- get_inference_simulator_response: Optional[GetInferenceSimulatorResponse] = service_factory.get_thestage_api_client().get_inference_simulator(
650
- slug=unique_id,
651
- )
652
- if not get_inference_simulator_response:
653
- typer.echo(__("Inference simulator with UID %uid% not found", {"uid": unique_id}))
654
- raise typer.Exit(1)
655
- else:
656
- inference_simulator_id = get_inference_simulator_response.inferenceSimulator.id
657
- logging_service.print_last_inference_simulator_logs(inference_simulator_id=inference_simulator_id, logs_number=logs_number)
658
-
659
- app_logger.info(f'Inference simulator logs - end')
660
- raise typer.Exit(0)
661
-
662
-
663
- @inference_simulator_model_app.command("deploy-instance", no_args_is_help=True, help=__("Deploy an inference simulator model to an instance"), **get_command_metadata(CliCommand.PROJECT_MODEL_DEPLOY_INSTANCE))
664
- def deploy_inference_simulator_model_to_instance(
665
- unique_id: Optional[str] = typer.Argument(help=__("Inference simulator model unique ID"), ),
666
- rented_instance_unique_id: Optional[str] = typer.Option(
667
- None,
668
- '--rented-instance-unique-id',
669
- '-ruid',
670
- help=__("The rented instance unique ID on which the inference simulator model will be deployed"),
671
- is_eager=False,
672
- ),
673
- self_hosted_instance_unique_id: Optional[str] = typer.Option(
674
- None,
675
- '--self-hosted-instance-unique-id',
676
- '-suid',
677
- help=__("The self-hosted instance unique ID on which the inference simulator model will be deployed"),
678
- is_eager=False,
679
- ),
680
- working_directory: Optional[str] = typer.Option(
681
- None,
682
- "--working-directory",
683
- "-wd",
684
- help=__("Full path to working directory. By default, the current directory is used"),
685
- show_default=False,
686
- is_eager=False,
687
- ),
688
- enable_log_stream: Optional[bool] = typer.Option(
689
- True,
690
- " /--no-logs",
691
- " /-nl",
692
- help=__("Disable real-time log streaming"),
693
- is_eager=False,
694
- ),
695
- ):
696
- command_name = CliCommand.PROJECT_MODEL_DEPLOY_INSTANCE
697
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
698
- check_command_permission(command_name)
699
-
700
- if unique_id and not re.match(r"^[a-zA-Z0-9-]+$", unique_id):
701
- raise typer.BadParameter(__("Invalid UID format. The UID can only contain letters, numbers, and hyphens."))
702
-
703
- unique_id_with_timestamp = f"{unique_id}-{int(time.time())}"
704
-
705
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
706
- project_service = service_factory.get_project_service()
707
-
708
- project_service.project_deploy_inference_simulator_model_to_instance(
709
- unique_id=unique_id,
710
- unique_id_with_timestamp=unique_id_with_timestamp,
711
- rented_instance_unique_id=rented_instance_unique_id,
712
- self_hosted_instance_unique_id=self_hosted_instance_unique_id,
713
- )
714
-
715
- if enable_log_stream:
716
- logging_service: LoggingService = service_factory.get_logging_service()
717
-
718
- logging_service.stream_inference_simulator_logs_with_controls(
719
- slug=unique_id_with_timestamp
720
- )
721
- raise typer.Exit(0)
722
-
723
-
724
- @inference_simulator_model_app.command("deploy-sagemaker", no_args_is_help=True, help=__("Deploy an inference simulator model to SageMaker"), **get_command_metadata(CliCommand.PROJECT_MODEL_DEPLOY_SAGEMAKER))
725
- def deploy_inference_simulator_model_to_sagemaker(
726
- unique_id: Optional[str] = typer.Argument(help=__("Inference simulator model unique ID"), ),
727
- arn: Optional[str] = typer.Option(
728
- None,
729
- '--amazon-resource-name',
730
- '-arn',
731
- help=__("The Amazon Resource Name of the IAM Role to use, e.g., arn:aws:iam::{aws_account_id}:role/{role}"),
732
- is_eager=False,
733
- ),
734
- working_directory: Optional[str] = typer.Option(
735
- None,
736
- "--working-directory",
737
- "-wd",
738
- help=__("Full path to working directory. By default, the current directory is used"),
739
- show_default=False,
740
- is_eager=False,
741
- ),
742
- instance_type: Optional[str] = typer.Option(
743
- None,
744
- '--instance-type',
745
- '-it',
746
- help=__("Instance type on which the inference simulator model will be deployed"),
747
- is_eager=False,
748
- ),
749
- initial_variant_weight: Optional[float] = typer.Option(
750
- None,
751
- "--initial-variant-weight",
752
- "-ivw",
753
- help=__("Initial Variant Weight. By default 1.0"),
754
- show_default=False,
755
- is_eager=False,
756
- ),
757
- initial_instance_count: Optional[int] = typer.Option(
758
- None,
759
- "--initial-instance-count",
760
- "-iic",
761
- help=__("Initial Instance Count"),
762
- show_default=False,
763
- is_eager=False,
764
- ),
765
-
766
- ):
767
- command_name = CliCommand.PROJECT_MODEL_DEPLOY_SAGEMAKER
768
- app_logger.info(f'Running {command_name} from {get_current_directory()}')
769
- check_command_permission(command_name)
770
-
771
- if unique_id and not re.match(r"^[a-zA-Z0-9-]+$", unique_id):
772
- raise typer.BadParameter(__("Invalid UID format. The UID can only contain letters, numbers, and hyphens."))
773
-
774
- service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
775
- project_service = service_factory.get_project_service()
776
-
777
- project_service.project_deploy_inference_simulator_model_to_sagemaker(
778
- unique_id=unique_id,
779
- arn=arn,
780
- instance_type=instance_type,
781
- initial_variant_weight=initial_variant_weight,
782
- initial_instance_count=initial_instance_count,
783
- )
1
+ import time
2
+ from pathlib import Path
3
+ from typing import Optional, List
4
+
5
+ import re
6
+
7
+ import typer
8
+ from typing_extensions import Annotated
9
+
10
+ from thestage.cli_command import CliCommand
11
+ from thestage.cli_command_helper import get_command_group_help_panel, get_command_metadata, check_command_permission
12
+ from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
13
+ from thestage.helpers.logger.app_logger import app_logger
14
+ from thestage.i18n.translation import __
15
+ from thestage.services.clients.thestage_api.dtos.inference_controller.get_inference_simulator_response import \
16
+ GetInferenceSimulatorResponse
17
+ from thestage.services.logging.logging_service import LoggingService
18
+ from thestage.services.project.project_service import ProjectService
19
+ from thestage.services.task.dto.task_dto import TaskDto
20
+
21
+ app = typer.Typer(no_args_is_help=True, help=__("Manage projects"))
22
+ inference_simulators_app = typer.Typer(no_args_is_help=True, help="Manage project inference simulators")
23
+ inference_simulator_model_app = typer.Typer(no_args_is_help=True, help="Manage project inference simulator models")
24
+ task_app = typer.Typer(no_args_is_help=True, help=__("Manage project tasks"))
25
+ config_app = typer.Typer(no_args_is_help=True, help=__("Manage project config"))
26
+
27
+ app.add_typer(inference_simulators_app, name="inference-simulator", rich_help_panel=get_command_group_help_panel())
28
+ app.add_typer(inference_simulator_model_app, name="model", rich_help_panel=get_command_group_help_panel())
29
+ app.add_typer(task_app, name="task", rich_help_panel=get_command_group_help_panel())
30
+ app.add_typer(config_app, name="config", rich_help_panel=get_command_group_help_panel())
31
+
32
+
33
+ @app.command(name='clone', no_args_is_help=True, help=__("Clone project repository to empty directory"), **get_command_metadata(CliCommand.PROJECT_CLONE))
34
+ def clone(
35
+ project_uid: str = typer.Argument(
36
+ help=__("Project unique ID"),
37
+ ),
38
+ working_directory: Optional[str] = typer.Option(
39
+ None,
40
+ "--working-directory",
41
+ "-wd",
42
+ help=__("Full path to the working directory: current directory used by default"),
43
+ is_eager=False,
44
+ ),
45
+ ):
46
+ command_name = CliCommand.PROJECT_CLONE
47
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
48
+ check_command_permission(command_name)
49
+
50
+ if not working_directory:
51
+ working_directory = get_current_directory().joinpath(project_uid)
52
+
53
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
54
+ project_service = service_factory.get_project_service()
55
+
56
+ project_service.clone_project(
57
+ project_slug=project_uid,
58
+ )
59
+
60
+ raise typer.Exit(0)
61
+
62
+
63
+ @app.command(name='init', no_args_is_help=True, help=__("Initialize project repository with existing files"), **get_command_metadata(CliCommand.PROJECT_INIT))
64
+ def init(
65
+ project_uid: Optional[str] = typer.Argument(
66
+ help=__("Project unique ID"),
67
+ ),
68
+ working_directory: Optional[str] = typer.Option(
69
+ None,
70
+ "--working-directory",
71
+ "-wd",
72
+ help=__("Full path to working directory"),
73
+ is_eager=False,
74
+ ),
75
+ ):
76
+ command_name = CliCommand.PROJECT_INIT
77
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
78
+ check_command_permission(command_name)
79
+
80
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
81
+ project_service = service_factory.get_project_service()
82
+ project_config = service_factory.get_config_provider().read_project_config()
83
+
84
+ if project_config:
85
+ typer.echo(__("Directory is initialized and already contains working project"))
86
+ raise typer.Exit(1)
87
+
88
+ project_service.init_project(
89
+ project_slug=project_uid,
90
+ )
91
+
92
+ raise typer.Exit(0)
93
+
94
+
95
+ @app.command(name='run', no_args_is_help=True, help=__("Run a task within the project. By default, it uses the latest commit from the main branch and streams real-time task logs."), **get_command_metadata(CliCommand.PROJECT_RUN))
96
+ def run(
97
+ command: Annotated[List[str], typer.Argument(
98
+ help=__("Command to run (required)"),
99
+ )],
100
+ commit_hash: Optional[str] = typer.Option(
101
+ None,
102
+ '--commit-hash',
103
+ '-hash',
104
+ help=__("Commit hash to use. By default, the current HEAD commit is used."),
105
+ is_eager=False,
106
+ ),
107
+ docker_container_slug: Optional[str] = typer.Option(
108
+ None,
109
+ '--container-uid',
110
+ '-cid',
111
+ help=__("Docker container unique ID"),
112
+ is_eager=False,
113
+ ),
114
+ working_directory: Optional[str] = typer.Option(
115
+ None,
116
+ "--working-directory",
117
+ "-wd",
118
+ help=__("Full path to working directory"),
119
+ show_default=False,
120
+ is_eager=False,
121
+ ),
122
+ enable_log_stream: Optional[bool] = typer.Option(
123
+ True,
124
+ " /--no-logs",
125
+ " /-nl",
126
+ help=__("Disable real-time log streaming"),
127
+ is_eager=False,
128
+ ),
129
+ task_title: Optional[str] = typer.Option(
130
+ None,
131
+ "--title",
132
+ "-t",
133
+ help=__("Provide a custom task title. Git commit message is used by default."),
134
+ is_eager=False,
135
+ ),
136
+ files_to_add: Optional[str] = typer.Option(
137
+ None,
138
+ "--files-add",
139
+ "-fa",
140
+ help=__("Files to add to the commit. You can add files by their relative path from the working directory with a comma as a separator."),
141
+ is_eager=False,
142
+ ),
143
+ is_skip_auto_commit: Optional[bool] = typer.Option(
144
+ False,
145
+ "--skip-autocommit",
146
+ "-sa",
147
+ help=__("Skip automatic commit of the changes"),
148
+ is_eager=False,
149
+ ),
150
+ ):
151
+ command_name = CliCommand.PROJECT_RUN
152
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
153
+ check_command_permission(command_name)
154
+
155
+ if not command:
156
+ typer.echo(__('Command is required'))
157
+ raise typer.Exit(1)
158
+
159
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
160
+ project_service = service_factory.get_project_service()
161
+
162
+ task: Optional[TaskDto] = project_service.project_run_task(
163
+ run_command=" ".join(command),
164
+ commit_hash=commit_hash,
165
+ docker_container_slug=docker_container_slug,
166
+ task_title=task_title,
167
+ files_to_add=files_to_add,
168
+ is_skip_auto_commit=is_skip_auto_commit,
169
+ )
170
+
171
+ if enable_log_stream:
172
+ logging_service: LoggingService = service_factory.get_logging_service()
173
+ logging_service.stream_task_logs_with_controls(task_id=task.id)
174
+
175
+ raise typer.Exit(0)
176
+
177
+
178
+ @task_app.command(name='cancel', no_args_is_help=True, help=__("Cancel a task by ID"), **get_command_metadata(CliCommand.PROJECT_TASK_CANCEL))
179
+ def cancel_task(
180
+ task_id: Annotated[int, typer.Argument(
181
+ help=__("Task ID (required)"),
182
+ )],
183
+ ):
184
+ command_name = CliCommand.PROJECT_TASK_CANCEL
185
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
186
+ check_command_permission(command_name)
187
+
188
+ if not task_id:
189
+ typer.echo(__('Task ID is required'))
190
+ raise typer.Exit(1)
191
+
192
+ service_factory = validate_config_and_get_service_factory()
193
+ project_service = service_factory.get_project_service()
194
+
195
+ project_service.cancel_task(
196
+ task_id=task_id
197
+ )
198
+
199
+ raise typer.Exit(0)
200
+
201
+
202
+ @task_app.command("ls", help=__("List tasks"), **get_command_metadata(CliCommand.PROJECT_TASK_LS))
203
+ def list_runs(
204
+ project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
205
+ row: int = typer.Option(
206
+ 5,
207
+ '--row',
208
+ '-r',
209
+ help=__("Set number of rows displayed per page"),
210
+ is_eager=False,
211
+ ),
212
+ page: int = typer.Option(
213
+ 1,
214
+ '--page',
215
+ '-p',
216
+ help=__("Set starting page for displaying output"),
217
+ is_eager=False,
218
+ ),
219
+ ):
220
+ command_name = CliCommand.PROJECT_TASK_LS
221
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
222
+ check_command_permission(command_name)
223
+
224
+ service_factory = validate_config_and_get_service_factory()
225
+ project_service: ProjectService = service_factory.get_project_service()
226
+
227
+ project_service.print_task_list(project_uid, row, page)
228
+
229
+ typer.echo(__("Tasks listing complete"))
230
+ raise typer.Exit(0)
231
+
232
+
233
+ @task_app.command(name="logs", no_args_is_help=True, help=__("Stream real-time task logs or view last logs for a task"), **get_command_metadata(CliCommand.PROJECT_TASK_LOGS))
234
+ def task_logs(
235
+ task_id: Optional[int] = typer.Argument(help=__("Task ID"),),
236
+ logs_number: Optional[int] = typer.Option(
237
+ None,
238
+ '--number',
239
+ '-n',
240
+ help=__("Display a number of latest log entries. No real-time stream if provided."),
241
+ is_eager=False,
242
+ ),
243
+ ):
244
+ command_name = CliCommand.PROJECT_TASK_LOGS
245
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
246
+ check_command_permission(command_name)
247
+
248
+ if not task_id:
249
+ typer.echo(__('Task ID is required'))
250
+ raise typer.Exit(1)
251
+
252
+ service_factory = validate_config_and_get_service_factory()
253
+ logging_service: LoggingService = service_factory.get_logging_service()
254
+
255
+ if logs_number is None:
256
+ logging_service.stream_task_logs_with_controls(task_id=task_id)
257
+ else:
258
+ logging_service.print_last_task_logs(task_id=task_id, logs_number=logs_number)
259
+
260
+ app_logger.info(f'Task logs - end')
261
+ raise typer.Exit(0)
262
+
263
+
264
+ @app.command(name='checkout', no_args_is_help=True, help=__("Checkout project repository to a specific reference"), **get_command_metadata(CliCommand.PROJECT_CHECKOUT))
265
+ def checkout_project(
266
+ reference: Optional[str] = typer.Argument(
267
+ help=__("Task ID or branch name to checkout. '/' will try to identify the main branch."),
268
+ ),
269
+ working_directory: Optional[str] = typer.Option(
270
+ None,
271
+ "--working-directory",
272
+ "-wd",
273
+ help=__("Full path to working directory"),
274
+ is_eager=False,
275
+ ),
276
+ ):
277
+ command_name = CliCommand.PROJECT_CHECKOUT
278
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
279
+ check_command_permission(command_name)
280
+
281
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
282
+ project_service = service_factory.get_project_service()
283
+
284
+ task_id: Optional[int] = None
285
+ branch_name: Optional[str] = None
286
+
287
+ if reference == "/":
288
+ pass
289
+ elif reference.isdigit():
290
+ task_id = int(reference)
291
+ else:
292
+ branch_name = reference
293
+
294
+ project_service.checkout_project(
295
+ task_id=task_id,
296
+ branch_name=branch_name,
297
+ )
298
+
299
+ raise typer.Exit(0)
300
+
301
+
302
+ @app.command(name='pull', help=__("Pulls the changes from the remote project repository. Equivalent to 'git pull'."), **get_command_metadata(CliCommand.PROJECT_PULL))
303
+ def pull_project(
304
+ working_directory: Optional[str] = typer.Option(
305
+ None,
306
+ "--working-directory",
307
+ "-wd",
308
+ help=__("Full path to working directory"),
309
+ is_eager=False,
310
+ ),
311
+ ):
312
+ command_name = CliCommand.PROJECT_PULL
313
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
314
+ check_command_permission(command_name)
315
+
316
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
317
+ project_service = service_factory.get_project_service()
318
+
319
+ project_service.pull_project()
320
+
321
+ raise typer.Exit(0)
322
+
323
+
324
+ @app.command(name='reset', help=__("Resets the current project branch to remote counterpart. All working tree changes will be lost. Equivalent to 'git fetch && git reset --hard origin/{ref}'."), **get_command_metadata(CliCommand.PROJECT_RESET))
325
+ def reset_project(
326
+ working_directory: Optional[str] = typer.Option(
327
+ None,
328
+ "--working-directory",
329
+ "-wd",
330
+ help=__("Full path to working directory"),
331
+ is_eager=False,
332
+ ),
333
+ ):
334
+ command_name = CliCommand.PROJECT_RESET
335
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
336
+ check_command_permission(command_name)
337
+
338
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
339
+ project_service = service_factory.get_project_service()
340
+
341
+ project_service.reset_project()
342
+
343
+ raise typer.Exit(0)
344
+
345
+
346
+ @config_app.command(name='set-default-container', no_args_is_help=True, help=__("Set default docker container for a project installation"), **get_command_metadata(CliCommand.PROJECT_CONFIG_SET_DEFAULT_CONTAINER))
347
+ def set_default_container(
348
+ container_uid: Annotated[Optional[str], typer.Argument(
349
+ help=__("Unique ID of the container to use by default for running tasks"),
350
+ )] = None,
351
+ unset_default_container: Optional[bool] = typer.Option(
352
+ False,
353
+ "--unset",
354
+ "-u",
355
+ help=__("Unsets the default docker container"),
356
+ is_eager=False,
357
+ ),
358
+ working_directory: Optional[str] = typer.Option(
359
+ None,
360
+ "--working-directory",
361
+ "-wd",
362
+ help=__("Full path to working directory"),
363
+ is_eager=False,
364
+ ),
365
+ ):
366
+ command_name = CliCommand.PROJECT_CONFIG_SET_DEFAULT_CONTAINER
367
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
368
+ check_command_permission(command_name)
369
+
370
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
371
+
372
+ if unset_default_container and container_uid:
373
+ typer.echo("Container unique ID is provided along with unset flag. Please pick one.")
374
+ raise typer.Exit(1)
375
+
376
+ if not unset_default_container and not container_uid:
377
+ typer.echo("Provide container unique ID or use '--unset' flag")
378
+ raise typer.Exit(1)
379
+
380
+ project_service = service_factory.get_project_service()
381
+
382
+ project_service.set_default_container(
383
+ container_uid=container_uid,
384
+ )
385
+
386
+ raise typer.Exit(0)
387
+
388
+
389
+ @config_app.command(name='get', no_args_is_help=False, help=__("View config for a local project installation"), **get_command_metadata(CliCommand.PROJECT_CONFIG_GET))
390
+ def get_project_config(
391
+ working_directory: Optional[str] = typer.Option(
392
+ None,
393
+ "--working-directory",
394
+ "-wd",
395
+ help=__("Full path to working directory"),
396
+ is_eager=False,
397
+ ),
398
+ ):
399
+ command_name = CliCommand.PROJECT_CONFIG_GET
400
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
401
+ check_command_permission(command_name)
402
+
403
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
404
+ project_service = service_factory.get_project_service()
405
+
406
+ project_service.print_project_config()
407
+
408
+ raise typer.Exit(0)
409
+
410
+
411
+ @inference_simulators_app.command(name='run', no_args_is_help=True, help="Run an inference simulator within the project", **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_RUN))
412
+ def run_inference_simulator(
413
+ rented_instance_unique_id: Optional[str] = typer.Option(
414
+ None,
415
+ '--rented-instance-unique-id',
416
+ '-ruid',
417
+ help=__("The rented instance unique ID on which the inference simulator will run"),
418
+ is_eager=False,
419
+ ),
420
+ self_hosted_instance_unique_id: Optional[str] = typer.Option(
421
+ None,
422
+ '--self-hosted-instance-unique-id',
423
+ '-suid',
424
+ help=__("The self-hosted instance unique ID on which the inference simulator will run"),
425
+ is_eager=False,
426
+ ),
427
+ commit_hash: Optional[str] = typer.Option(
428
+ None,
429
+ '--commit-hash',
430
+ '-hash',
431
+ help=__("Commit hash to use. By default, the current HEAD commit is used."),
432
+ is_eager=False,
433
+ ),
434
+ working_directory: Optional[str] = typer.Option(
435
+ None,
436
+ "--working-directory",
437
+ "-wd",
438
+ help=__("Full path to working directory. By default, the current directory is used"),
439
+ show_default=False,
440
+ is_eager=False,
441
+ ),
442
+ enable_log_stream: Optional[bool] = typer.Option(
443
+ True,
444
+ " /--no-logs",
445
+ " /-nl",
446
+ help=__("Disable real-time log streaming"),
447
+ is_eager=False,
448
+ ),
449
+ is_skip_installation: Optional[bool] = typer.Option(
450
+ False,
451
+ "--skip-installation",
452
+ "-si",
453
+ help=__("Skip installing dependencies from requirements.txt and install.sh"),
454
+ is_eager=False,
455
+ ),
456
+ files_to_add: Optional[str] = typer.Option(
457
+ None,
458
+ "--files-add",
459
+ "-fa",
460
+ help=__("Files to add to the commit. You can add files by their relative path from the working directory with a comma as a separator."),
461
+ is_eager=False,
462
+ ),
463
+ is_skip_auto_commit: Optional[bool] = typer.Option(
464
+ False,
465
+ "--skip-autocommit",
466
+ "-sa",
467
+ help=__("Skip automatic commit of the changes"),
468
+ is_eager=False,
469
+ ),
470
+ ):
471
+ command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_RUN
472
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
473
+ check_command_permission(command_name)
474
+
475
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
476
+ config = service_factory.get_config_provider().get_config()
477
+
478
+ working_dir_path = Path(working_directory) if working_directory else Path(config.runtime.working_directory)
479
+ inference_files = list(working_dir_path.rglob("inference.py"))
480
+ if not inference_files:
481
+ typer.echo("No inference.py file found in the project directory.")
482
+ raise typer.Exit(1)
483
+ elif len(inference_files) == 1:
484
+ selected_inference = inference_files[0]
485
+ else:
486
+ choices = [str(path.relative_to(working_dir_path)) for path in inference_files]
487
+ typer.echo("Multiple inference.py files found:")
488
+ for idx, choice in enumerate(choices, start=1):
489
+ typer.echo(f"{idx}) {choice}")
490
+ choice_str = typer.prompt("Choose which inference.py to use")
491
+ try:
492
+ choice_index = int(choice_str)
493
+ except ValueError:
494
+ raise typer.BadParameter("Invalid input. Please enter a number.")
495
+ if not (1 <= choice_index <= len(choices)):
496
+ raise typer.BadParameter("Choice out of range.")
497
+ selected_inference = inference_files[choice_index - 1]
498
+
499
+ relative_inference = selected_inference.relative_to(working_dir_path)
500
+ parent_dir = relative_inference.parent
501
+ if parent_dir == Path("."):
502
+ inference_dir = "/"
503
+ else:
504
+ inference_dir = f"{parent_dir.as_posix()}/"
505
+ typer.echo(f"Selected inference file relative path: {inference_dir}")
506
+
507
+ project_service = service_factory.get_project_service()
508
+
509
+ inference_simulator = project_service.project_run_inference_simulator(
510
+ commit_hash=commit_hash,
511
+ rented_instance_unique_id=rented_instance_unique_id,
512
+ self_hosted_instance_unique_id=self_hosted_instance_unique_id,
513
+ inference_dir=inference_dir,
514
+ is_skip_installation=is_skip_installation,
515
+ files_to_add=files_to_add,
516
+ is_skip_auto_commit=is_skip_auto_commit,
517
+ )
518
+
519
+ if enable_log_stream:
520
+ logging_service: LoggingService = service_factory.get_logging_service()
521
+
522
+ logging_service.stream_inference_simulator_logs_with_controls(
523
+ slug=inference_simulator.slug
524
+ )
525
+ raise typer.Exit(0)
526
+
527
+
528
+ @inference_simulators_app.command(name='save-metadata', no_args_is_help=True, help="Get and save inference simulator metadata", **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_SAVE_METADATA))
529
+ def get_and_save_inference_simulator_metadata(
530
+ unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"),),
531
+ file_path: Optional[str] = typer.Option(
532
+ None,
533
+ "--file-path",
534
+ "-fp",
535
+ help=__("Full path to a new file. By default metadata is saved to the current directory as metadata.json"),
536
+ show_default=False,
537
+ is_eager=False,
538
+ ),
539
+ ):
540
+ command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_SAVE_METADATA
541
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
542
+ check_command_permission(command_name)
543
+
544
+ service_factory = validate_config_and_get_service_factory()
545
+ project_service = service_factory.get_project_service()
546
+
547
+ project_service.project_get_and_save_inference_simulator_metadata(
548
+ file_path=file_path,
549
+ slug=unique_id,
550
+ )
551
+
552
+ raise typer.Exit(0)
553
+
554
+
555
+ @inference_simulators_app.command(name='push', no_args_is_help=True, help="Push an inference simulator within the project to model registry", **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_PUSH))
556
+ def push_inference_simulator(
557
+ unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"),),
558
+ ):
559
+ command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_PUSH
560
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
561
+ check_command_permission(command_name)
562
+
563
+ service_factory = validate_config_and_get_service_factory()
564
+ project_service = service_factory.get_project_service()
565
+
566
+ project_service.project_push_inference_simulator(
567
+ slug=unique_id,
568
+ )
569
+
570
+ raise typer.Exit(0)
571
+
572
+
573
+ @inference_simulators_app.command("ls", help=__("List inference simulators"), **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_LS))
574
+ def list_inference_simulators(
575
+ project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
576
+ row: int = typer.Option(
577
+ 5,
578
+ '--row',
579
+ '-r',
580
+ help=__("Set number of rows displayed per page"),
581
+ is_eager=False,
582
+ ),
583
+ page: int = typer.Option(
584
+ 1,
585
+ '--page',
586
+ '-p',
587
+ help=__("Set starting page for displaying output"),
588
+ is_eager=False,
589
+ ),
590
+ statuses: List[str] = typer.Option(
591
+ None,
592
+ '--status',
593
+ '-s',
594
+ help=__("Filter by status, use --status all to list all inference simulators"),
595
+ is_eager=False,
596
+ ),
597
+ ):
598
+ command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_LS
599
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
600
+ check_command_permission(command_name)
601
+
602
+ service_factory = validate_config_and_get_service_factory()
603
+ project_service: ProjectService = service_factory.get_project_service()
604
+
605
+ project_service.print_inference_simulator_list(project_uid, statuses, row, page)
606
+
607
+ typer.echo(__("Inference simulators listing complete"))
608
+ raise typer.Exit(0)
609
+
610
+
611
+ @inference_simulator_model_app.command("ls", help=__("List inference simulator models"), **get_command_metadata(CliCommand.PROJECT_MODEL_LS))
612
+ def list_inference_simulator_models(
613
+ project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
614
+ row: int = typer.Option(
615
+ 5,
616
+ '--row',
617
+ '-r',
618
+ help=__("Set number of rows displayed per page"),
619
+ is_eager=False,
620
+ ),
621
+ page: int = typer.Option(
622
+ 1,
623
+ '--page',
624
+ '-p',
625
+ help=__("Set starting page for displaying output"),
626
+ is_eager=False,
627
+ ),
628
+ statuses: List[str] = typer.Option(
629
+ None,
630
+ '--status',
631
+ '-s',
632
+ help=__("Filter by status, use --status all to list all inference simulator models"),
633
+ is_eager=False,
634
+ ),
635
+ ):
636
+ command_name = CliCommand.PROJECT_MODEL_LS
637
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
638
+ check_command_permission(command_name)
639
+
640
+ service_factory = validate_config_and_get_service_factory()
641
+ project_service: ProjectService = service_factory.get_project_service()
642
+
643
+ project_service.print_inference_simulator_model_list(project_uid, statuses, row, page)
644
+
645
+ typer.echo(__("Inference simulator models listing complete"))
646
+ raise typer.Exit(0)
647
+
648
+
649
+ @inference_simulators_app.command(name="logs", no_args_is_help=True, help=__("Stream real-time task logs or view last logs for an inference simulator"), **get_command_metadata(CliCommand.PROJECT_INFERENCE_SIMULATOR_LOGS))
650
+ def inference_simulator_logs(
651
+ unique_id: Optional[str] = typer.Argument(help=__("Inference simulator unique ID"),),
652
+ logs_number: Optional[int] = typer.Option(
653
+ None,
654
+ '--number',
655
+ '-n',
656
+ help=__("Display a number of latest log entries. No real-time stream if provided."),
657
+ is_eager=False,
658
+ ),
659
+ ):
660
+ command_name = CliCommand.PROJECT_INFERENCE_SIMULATOR_LOGS
661
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
662
+ check_command_permission(command_name)
663
+
664
+ if not unique_id:
665
+ typer.echo(__('Inference simulator UID is required'))
666
+ raise typer.Exit(1)
667
+
668
+ service_factory = validate_config_and_get_service_factory()
669
+ logging_service: LoggingService = service_factory.get_logging_service()
670
+
671
+ if logs_number is None:
672
+ logging_service.stream_inference_simulator_logs_with_controls(
673
+ slug=unique_id
674
+ )
675
+ else:
676
+ get_inference_simulator_response: Optional[GetInferenceSimulatorResponse] = service_factory.get_thestage_api_client().get_inference_simulator(
677
+ slug=unique_id,
678
+ )
679
+ if not get_inference_simulator_response:
680
+ typer.echo(__("Inference simulator with UID %uid% not found", {"uid": unique_id}))
681
+ raise typer.Exit(1)
682
+ else:
683
+ inference_simulator_id = get_inference_simulator_response.inferenceSimulator.id
684
+ logging_service.print_last_inference_simulator_logs(inference_simulator_id=inference_simulator_id, logs_number=logs_number)
685
+
686
+ app_logger.info(f'Inference simulator logs - end')
687
+ raise typer.Exit(0)
688
+
689
+
690
+ @inference_simulator_model_app.command("deploy-instance", no_args_is_help=True, help=__("Deploy an inference simulator model to an instance"), **get_command_metadata(CliCommand.PROJECT_MODEL_DEPLOY_INSTANCE))
691
+ def deploy_inference_simulator_model_to_instance(
692
+ unique_id: Optional[str] = typer.Argument(help=__("Inference simulator model unique ID"), ),
693
+ rented_instance_unique_id: Optional[str] = typer.Option(
694
+ None,
695
+ '--rented-instance-unique-id',
696
+ '-ruid',
697
+ help=__("The rented instance unique ID on which the inference simulator model will be deployed"),
698
+ is_eager=False,
699
+ ),
700
+ self_hosted_instance_unique_id: Optional[str] = typer.Option(
701
+ None,
702
+ '--self-hosted-instance-unique-id',
703
+ '-suid',
704
+ help=__("The self-hosted instance unique ID on which the inference simulator model will be deployed"),
705
+ is_eager=False,
706
+ ),
707
+ working_directory: Optional[str] = typer.Option(
708
+ None,
709
+ "--working-directory",
710
+ "-wd",
711
+ help=__("Full path to working directory. By default, the current directory is used"),
712
+ show_default=False,
713
+ is_eager=False,
714
+ ),
715
+ enable_log_stream: Optional[bool] = typer.Option(
716
+ True,
717
+ " /--no-logs",
718
+ " /-nl",
719
+ help=__("Disable real-time log streaming"),
720
+ is_eager=False,
721
+ ),
722
+ ):
723
+ command_name = CliCommand.PROJECT_MODEL_DEPLOY_INSTANCE
724
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
725
+ check_command_permission(command_name)
726
+
727
+ if unique_id and not re.match(r"^[a-zA-Z0-9-]+$", unique_id):
728
+ raise typer.BadParameter(__("Invalid UID format. The UID can only contain letters, numbers, and hyphens."))
729
+
730
+ unique_id_with_timestamp = f"{unique_id}-{int(time.time())}"
731
+
732
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
733
+ project_service = service_factory.get_project_service()
734
+
735
+ project_service.project_deploy_inference_simulator_model_to_instance(
736
+ unique_id=unique_id,
737
+ unique_id_with_timestamp=unique_id_with_timestamp,
738
+ rented_instance_unique_id=rented_instance_unique_id,
739
+ self_hosted_instance_unique_id=self_hosted_instance_unique_id,
740
+ )
741
+
742
+ if enable_log_stream:
743
+ logging_service: LoggingService = service_factory.get_logging_service()
744
+
745
+ logging_service.stream_inference_simulator_logs_with_controls(
746
+ slug=unique_id_with_timestamp
747
+ )
748
+ raise typer.Exit(0)
749
+
750
+
751
+ @inference_simulator_model_app.command("deploy-sagemaker", no_args_is_help=True, help=__("Deploy an inference simulator model to SageMaker"), **get_command_metadata(CliCommand.PROJECT_MODEL_DEPLOY_SAGEMAKER))
752
+ def deploy_inference_simulator_model_to_sagemaker(
753
+ unique_id: Optional[str] = typer.Argument(help=__("Inference simulator model unique ID"), ),
754
+ arn: Optional[str] = typer.Option(
755
+ None,
756
+ '--amazon-resource-name',
757
+ '-arn',
758
+ help=__("The Amazon Resource Name of the IAM Role to use, e.g., arn:aws:iam::{aws_account_id}:role/{role}"),
759
+ is_eager=False,
760
+ ),
761
+ working_directory: Optional[str] = typer.Option(
762
+ None,
763
+ "--working-directory",
764
+ "-wd",
765
+ help=__("Full path to working directory. By default, the current directory is used"),
766
+ show_default=False,
767
+ is_eager=False,
768
+ ),
769
+ instance_type: Optional[str] = typer.Option(
770
+ None,
771
+ '--instance-type',
772
+ '-it',
773
+ help=__("Instance type on which the inference simulator model will be deployed"),
774
+ is_eager=False,
775
+ ),
776
+ initial_variant_weight: Optional[float] = typer.Option(
777
+ None,
778
+ "--initial-variant-weight",
779
+ "-ivw",
780
+ help=__("Initial Variant Weight. By default 1.0"),
781
+ show_default=False,
782
+ is_eager=False,
783
+ ),
784
+ initial_instance_count: Optional[int] = typer.Option(
785
+ None,
786
+ "--initial-instance-count",
787
+ "-iic",
788
+ help=__("Initial Instance Count"),
789
+ show_default=False,
790
+ is_eager=False,
791
+ ),
792
+
793
+ ):
794
+ command_name = CliCommand.PROJECT_MODEL_DEPLOY_SAGEMAKER
795
+ app_logger.info(f'Running {command_name} from {get_current_directory()}')
796
+ check_command_permission(command_name)
797
+
798
+ if unique_id and not re.match(r"^[a-zA-Z0-9-]+$", unique_id):
799
+ raise typer.BadParameter(__("Invalid UID format. The UID can only contain letters, numbers, and hyphens."))
800
+
801
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
802
+ project_service = service_factory.get_project_service()
803
+
804
+ project_service.project_deploy_inference_simulator_model_to_sagemaker(
805
+ unique_id=unique_id,
806
+ arn=arn,
807
+ instance_type=instance_type,
808
+ initial_variant_weight=initial_variant_weight,
809
+ initial_instance_count=initial_instance_count,
810
+ )