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