thestage 0.6.7__py3-none-any.whl → 0.6.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. thestage/__init__.py +1 -1
  2. thestage/cli_command_helper.py +2 -2
  3. thestage/config/__init__.py +1 -1
  4. thestage/{services → config/business}/app_config_service.py +3 -4
  5. thestage/{services/config_provider → config/business}/config_provider.py +6 -5
  6. thestage/config/{config_storage.py → business/config_storage.py} +1 -1
  7. thestage/{services → config/business}/validation_service.py +9 -10
  8. thestage/{controllers/config_controller.py → config/communication/config_command.py} +7 -7
  9. thestage/{services/connect → connect/business}/connect_service.py +22 -19
  10. thestage/{services → connect/business}/remote_server_service.py +4 -5
  11. thestage/connect/communication/connect_api_client.py +84 -0
  12. thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_response.py +0 -1
  13. thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_response.py +1 -4
  14. thestage/{services/clients/thestage_api/dtos/base_controller → connect/dto}/connect_resolve_response.py +0 -1
  15. thestage/controllers/base_controller.py +2 -2
  16. thestage/debug_main.dist.py +16 -14
  17. thestage/{services/container → docker_container/business}/container_service.py +27 -22
  18. thestage/{services/container → docker_container/business}/mapper/container_mapper.py +3 -3
  19. thestage/docker_container/communication/__init__.py +0 -0
  20. thestage/{controllers/container_controller.py → docker_container/communication/docker_command.py} +17 -23
  21. thestage/docker_container/communication/docker_container_api_client.py +99 -0
  22. thestage/docker_container/dto/__init__.py +0 -0
  23. thestage/docker_container/dto/container_action_request.py +11 -0
  24. thestage/{services/clients/thestage_api/dtos → docker_container/dto}/container_response.py +4 -4
  25. thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_response.py +3 -5
  26. thestage/docker_container/dto/enum/__init__.py +0 -0
  27. thestage/git/__init__.py +0 -0
  28. thestage/git/business/__init__.py +0 -0
  29. thestage/git/communication/__init__.py +0 -0
  30. thestage/{services/clients/git → git/communication}/git_client.py +4 -4
  31. thestage/global_dto/__init__.py +0 -0
  32. thestage/global_dto/enums/__init__.py +0 -0
  33. thestage/helpers/error_handler.py +3 -3
  34. thestage/helpers/logger/app_logger.py +1 -3
  35. thestage/i18n/en_GB/messages.po +14 -14
  36. thestage/inference_model/__init__.py +0 -0
  37. thestage/inference_model/business/__init__.py +0 -0
  38. thestage/inference_model/business/inference_model_service.py +281 -0
  39. thestage/inference_model/business/mapper/__init__.py +0 -0
  40. thestage/{services/project/mapper/project_inference_simulator_model_mapper.py → inference_model/business/mapper/inference_model_mapper.py} +5 -5
  41. thestage/inference_model/communication/__init__.py +0 -0
  42. thestage/inference_model/communication/inference_model_api_client.py +139 -0
  43. thestage/inference_model/communication/inference_model_command.py +246 -0
  44. thestage/inference_model/dto/__init__.py +0 -0
  45. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_response.py +0 -1
  46. thestage/inference_model/dto/enum/__init__.py +0 -0
  47. thestage/{services/project/dto/inference_simulator_model_dto.py → inference_model/dto/inference_model.py} +1 -1
  48. thestage/{entities/project_inference_simulator_model.py → inference_model/dto/inference_model_entity.py} +1 -1
  49. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_response.py +2 -3
  50. thestage/{services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_request.py → inference_model/dto/push_inference_simulator_model_request.py} +1 -1
  51. thestage/{services/clients/thestage_api/dtos/project_controller/project_push_inference_simulator_model_response.py → inference_model/dto/push_inference_simulator_model_response.py} +1 -1
  52. thestage/inference_simulator/__init__.py +0 -0
  53. thestage/inference_simulator/business/__init__.py +0 -0
  54. thestage/inference_simulator/business/inference_simulator_service.py +338 -0
  55. thestage/inference_simulator/business/mapper/__init__.py +0 -0
  56. thestage/{services/project/mapper/project_inference_simulator_mapper.py → inference_simulator/business/mapper/inference_simulator_mapper.py} +5 -5
  57. thestage/inference_simulator/communication/__init__.py +0 -0
  58. thestage/inference_simulator/communication/inference_simulator_api_client.py +114 -0
  59. thestage/inference_simulator/communication/inference_simulator_command.py +347 -0
  60. thestage/inference_simulator/dto/__init__.py +0 -0
  61. thestage/inference_simulator/dto/enum/__init__.py +0 -0
  62. thestage/inference_simulator/dto/get_inference_simulator_response.py +12 -0
  63. thestage/{services/project/dto/inference_simulator_dto.py → inference_simulator/dto/inference_simulator.py} +1 -1
  64. thestage/{entities/project_inference_simulator.py → inference_simulator/dto/inference_simulator_entity.py} +1 -1
  65. thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_response.py +2 -2
  66. thestage/{services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py → inference_simulator/dto/start_inference_simulator_request.py} +1 -1
  67. thestage/inference_simulator/dto/start_inference_simulator_response.py +10 -0
  68. thestage/instance/__init__.py +0 -0
  69. thestage/instance/business/__init__.py +0 -0
  70. thestage/{services/instance → instance/business}/instance_service.py +26 -27
  71. thestage/instance/business/mapper/__init__.py +0 -0
  72. thestage/{services/instance/mapper/instance_mapper.py → instance/business/mapper/rented_instance_mapper.py} +3 -3
  73. thestage/{services/instance/mapper/selfhosted_mapper.py → instance/business/mapper/selfhosted_instance_mapper.py} +5 -7
  74. thestage/instance/communication/__init__.py +0 -0
  75. thestage/instance/communication/instance_api_client.py +150 -0
  76. thestage/{controllers/instance_controller.py → instance/communication/instance_command.py} +5 -5
  77. thestage/instance/dto/__init__.py +0 -0
  78. thestage/instance/dto/enum/__init__.py +0 -0
  79. thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_detected_gpus.py +1 -2
  80. thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_rented_response.py +2 -2
  81. thestage/{services/clients/thestage_api/dtos → instance/dto}/selfhosted_instance_response.py +2 -3
  82. thestage/logging/__init__.py +0 -0
  83. thestage/logging/business/__init__.py +0 -0
  84. thestage/{services/logging → logging/business}/logging_service.py +40 -28
  85. thestage/logging/communication/__init__.py +0 -0
  86. thestage/logging/communication/logging_api_client.py +63 -0
  87. thestage/logging/dto/__init__.py +0 -0
  88. thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_response.py +2 -2
  89. thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_response.py +2 -2
  90. thestage/main.py +47 -8
  91. thestage/project/__init__.py +0 -0
  92. thestage/project/business/__init__.py +0 -0
  93. thestage/project/business/project_service.py +480 -0
  94. thestage/project/communication/__init__.py +0 -0
  95. thestage/project/communication/project_api_client.py +46 -0
  96. thestage/project/communication/project_command.py +284 -0
  97. thestage/project/dto/__init__.py +0 -0
  98. thestage/services/clients/thestage_api/core/api_client_core.py +1 -1
  99. thestage/services/clients/thestage_api/dtos/entity_filter_request.py +1 -1
  100. thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +1 -1
  101. thestage/services/filesystem_service.py +2 -2
  102. thestage/services/service_factory.py +130 -43
  103. thestage/task/__init__.py +0 -0
  104. thestage/task/business/__init__.py +0 -0
  105. thestage/task/business/mapper/__init__.py +0 -0
  106. thestage/{services/project/mapper/project_task_mapper.py → task/business/mapper/task_mapper.py} +5 -5
  107. thestage/task/business/task_service.py +304 -0
  108. thestage/task/communication/__init__.py +0 -0
  109. thestage/task/communication/task_api_client.py +122 -0
  110. thestage/task/communication/task_command.py +212 -0
  111. thestage/task/dto/__init__.py +0 -0
  112. thestage/task/dto/enum/__init__.py +0 -0
  113. thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py → task/dto/list_for_project_response.py} +2 -2
  114. thestage/{services/clients/thestage_api/dtos/project_controller/project_run_task_request.py → task/dto/run_task_request.py} +1 -1
  115. thestage/task/dto/run_task_response.py +13 -0
  116. thestage/{services/task/dto/task_dto.py → task/dto/task.py} +1 -4
  117. thestage/{entities/project_task.py → task/dto/task_entity.py} +1 -1
  118. thestage/{services/clients/thestage_api/dtos/task_controller/task_view_response.py → task/dto/view_response.py} +2 -2
  119. {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/METADATA +1 -1
  120. thestage-0.6.8.dist-info/RECORD +219 -0
  121. {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/WHEEL +1 -1
  122. thestage/controllers/project_controller.py +0 -1056
  123. thestage/services/clients/thestage_api/api_client.py +0 -751
  124. thestage/services/clients/thestage_api/dtos/container_param_request.py +0 -11
  125. thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +0 -13
  126. thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +0 -13
  127. thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +0 -10
  128. thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +0 -12
  129. thestage/services/project/project_service.py +0 -1287
  130. thestage-0.6.7.dist-info/RECORD +0 -167
  131. /thestage/{entities → color_scheme}/__init__.py +0 -0
  132. /thestage/{entities/enums → config/business}/__init__.py +0 -0
  133. /thestage/{services/clients/git → config/communication}/__init__.py +0 -0
  134. /thestage/{services/clients/thestage_api/dtos/enums → config/dto}/__init__.py +0 -0
  135. /thestage/{services/core_files → config/dto}/config_entity.py +0 -0
  136. /thestage/{services/connect → config}/dto/remote_server_config.py +0 -0
  137. /thestage/{services/config_provider → connect}/__init__.py +0 -0
  138. /thestage/{services/container → connect/business}/__init__.py +0 -0
  139. /thestage/{services/container/mapper → connect/communication}/__init__.py +0 -0
  140. /thestage/{services/instance → connect/dto}/__init__.py +0 -0
  141. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_request.py +0 -0
  142. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_request.py +0 -0
  143. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_request.py +0 -0
  144. /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_response.py +0 -0
  145. /thestage/{services/instance/mapper → docker_container}/__init__.py +0 -0
  146. /thestage/{services/project → docker_container/business}/__init__.py +0 -0
  147. /thestage/{services/project → docker_container/business}/mapper/__init__.py +0 -0
  148. /thestage/{entities/container.py → docker_container/dto/container_entity.py} +0 -0
  149. /thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_request.py +0 -0
  150. /thestage/{services/clients/thestage_api/dtos → docker_container/dto}/docker_container_mapping.py +0 -0
  151. /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_pending_action.py +0 -0
  152. /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_status.py +0 -0
  153. /thestage/{services/logging/exception → exceptions}/log_polling_exception.py +0 -0
  154. /thestage/git/{ProgressPrinter.py → business/ProgressPrinter.py} +0 -0
  155. /thestage/{entities → global_dto}/enums/order_direction_type.py +0 -0
  156. /thestage/{entities → global_dto}/enums/shell_type.py +0 -0
  157. /thestage/{entities → global_dto}/enums/tail_output_type.py +0 -0
  158. /thestage/{entities → global_dto}/enums/yes_no_response.py +0 -0
  159. /thestage/{entities → global_dto}/file_item.py +0 -0
  160. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_request.py +0 -0
  161. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_request.py +0 -0
  162. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_response.py +0 -0
  163. /thestage/{services/clients/thestage_api/dtos/enums → inference_model/dto/enum}/inference_model_status.py +0 -0
  164. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_request.py +0 -0
  165. /thestage/{services/clients/thestage_api/dtos → inference_model/dto}/inference_simulator_model_response.py +0 -0
  166. /thestage/{services/clients/thestage_api/dtos/enums → inference_simulator/dto/enum}/inference_simulator_status.py +0 -0
  167. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/get_inference_simulator_request.py +0 -0
  168. /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_request.py +0 -0
  169. /thestage/{services/clients/thestage_api/dtos → inference_simulator/dto}/inference_simulator_response.py +0 -0
  170. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/cpu_type.py +0 -0
  171. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/gpu_name.py +0 -0
  172. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/instance_rented_status.py +0 -0
  173. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/provider_name.py +0 -0
  174. /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/selfhosted_status.py +0 -0
  175. /thestage/{entities → instance/dto}/rented_instance.py +0 -0
  176. /thestage/{entities → instance/dto}/self_hosted_instance.py +0 -0
  177. /thestage/{services/logging → logging}/byte_print_style.py +0 -0
  178. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/docker_container_log_stream_request.py +0 -0
  179. /thestage/{services/logging → logging}/dto/log_message.py +0 -0
  180. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_request.py +0 -0
  181. /thestage/{services/logging → logging}/dto/log_type.py +0 -0
  182. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/task_log_stream_request.py +0 -0
  183. /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_request.py +0 -0
  184. /thestage/{services/logging → logging}/logging_constants.py +0 -0
  185. /thestage/{services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_request.py → project/dto/get_deploy_ssh_key_request.py} +0 -0
  186. /thestage/{services/clients/thestage_api/dtos/project_controller/project_get_deploy_ssh_key_response.py → project/dto/get_deploy_ssh_key_response.py} +0 -0
  187. /thestage/{services/project → project}/dto/project_config.py +0 -0
  188. /thestage/{services/clients/thestage_api/dtos → project/dto}/project_response.py +0 -0
  189. /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_execution_status.py +0 -0
  190. /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_status.py +0 -0
  191. /thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py → task/dto/list_for_project_request.py} +0 -0
  192. /thestage/{services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py → task/dto/status_localized_map_response.py} +0 -0
  193. {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/entry_points.txt +0 -0
  194. {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,1287 +0,0 @@
1
- import os
2
- import time
3
- from datetime import datetime
4
- from pathlib import Path
5
- from typing import Optional, List
6
-
7
- import json
8
-
9
- import boto3
10
- import click
11
- import typer
12
- from git import Commit
13
- from tabulate import tabulate
14
-
15
- from thestage.entities.project_inference_simulator import ProjectInferenceSimulatorEntity
16
- from thestage.entities.project_inference_simulator_model import ProjectInferenceSimulatorModelEntity
17
- from thestage.entities.project_task import ProjectTaskEntity
18
- from thestage.services.clients.thestage_api.core.http_client_exception import HttpClientException
19
- from thestage.services.clients.thestage_api.dtos.enums.inference_model_status import InferenceModelStatus
20
- from thestage.services.clients.thestage_api.dtos.enums.inference_simulator_status import InferenceSimulatorStatus
21
- from thestage.color_scheme.color_scheme import ColorScheme
22
- from thestage.entities.enums.yes_no_response import YesOrNoResponse
23
- from thestage.exceptions.git_access_exception import GitAccessException
24
- from thestage.i18n.translation import __
25
- from thestage.services.clients.git.git_client import GitLocalClient
26
- from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
27
- from thestage.services.clients.thestage_api.dtos.enums.container_status import DockerContainerStatus
28
- from thestage.services.clients.thestage_api.dtos.inference_controller.deploy_inference_model_to_instance_response import \
29
- DeployInferenceModelToInstanceResponse
30
- from thestage.services.clients.thestage_api.dtos.inference_controller.deploy_inference_model_to_sagemaker_response import \
31
- DeployInferenceModelToSagemakerResponse
32
- from thestage.services.clients.thestage_api.dtos.inference_controller.get_inference_simulator_response import \
33
- GetInferenceSimulatorResponse
34
- from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
35
- from thestage.services.clients.thestage_api.dtos.project_controller.project_push_inference_simulator_model_response import \
36
- ProjectPushInferenceSimulatorModelResponse
37
- from thestage.services.clients.thestage_api.dtos.project_controller.project_run_task_response import \
38
- ProjectRunTaskResponse
39
- from thestage.services.clients.thestage_api.dtos.project_controller.project_start_inference_simulator_response import \
40
- ProjectStartInferenceSimulatorResponse
41
- from thestage.services.clients.thestage_api.dtos.project_response import ProjectDto
42
- from thestage.services.clients.thestage_api.dtos.task_controller.task_view_response import TaskViewResponse
43
- from thestage.services.filesystem_service import FileSystemService
44
- from thestage.services.project.dto.inference_simulator_dto import InferenceSimulatorDto
45
- from thestage.services.project.dto.inference_simulator_model_dto import InferenceSimulatorModelDto
46
- from thestage.services.project.mapper.project_inference_simulator_mapper import ProjectInferenceSimulatorMapper
47
- from thestage.services.project.mapper.project_inference_simulator_model_mapper import \
48
- ProjectInferenceSimulatorModelMapper
49
- from thestage.services.task.dto.task_dto import TaskDto
50
- from thestage.services.project.dto.project_config import ProjectConfig
51
- from thestage.services.project.mapper.project_task_mapper import ProjectTaskMapper
52
- from thestage.services.remote_server_service import RemoteServerService
53
- from thestage.services.abstract_service import AbstractService
54
- from thestage.helpers.error_handler import error_handler
55
- from thestage.services.clients.thestage_api.api_client import TheStageApiClient
56
- from thestage.services.config_provider.config_provider import ConfigProvider
57
- from rich import print
58
-
59
-
60
- class ProjectService(AbstractService):
61
- __thestage_api_client: TheStageApiClient = None
62
- __config_provider: ConfigProvider = None
63
-
64
- def __init__(
65
- self,
66
- thestage_api_client: TheStageApiClient,
67
- config_provider: ConfigProvider,
68
- remote_server_service: RemoteServerService,
69
- file_system_service: FileSystemService,
70
- git_local_client: GitLocalClient,
71
- ):
72
- self.__thestage_api_client = thestage_api_client
73
- self.__remote_server_service = remote_server_service
74
- self.__file_system_service = file_system_service
75
- self.__git_local_client = git_local_client
76
- self.__project_task_mapper = ProjectTaskMapper()
77
- self.__config_provider = config_provider
78
-
79
-
80
- @error_handler()
81
- def init_project(
82
- self,
83
- project_slug: Optional[str] = None,
84
- project_public_id: Optional[str] = None,
85
- ):
86
- config = self.__config_provider.get_config()
87
- project: Optional[ProjectDto] = self.__thestage_api_client.get_project(
88
- slug=project_slug,
89
- public_id=project_public_id,
90
- )
91
-
92
- if not project:
93
- typer.echo('Project not found')
94
- raise typer.Exit(1)
95
-
96
- is_git_folder = self.__git_local_client.is_present_local_git(
97
- path=config.runtime.working_directory,
98
- )
99
- if is_git_folder:
100
- has_remote = self.__git_local_client.has_remote(
101
- path=config.runtime.working_directory,
102
- )
103
- if has_remote:
104
- typer.echo(__('You have local repo with remote, we can not work with this'))
105
- raise typer.Exit(1)
106
-
107
- if not project.git_repository_url:
108
- typer.echo(__('Sketch dont have git repository url'))
109
- raise typer.Exit(1)
110
-
111
- if project.last_commit_hash or project.last_commit_description:
112
- continue_with_non_empty_repo: YesOrNoResponse = typer.prompt(
113
- text=__('Remote repository is probably not empty: latest commit is "{commit_description}" (sha: {commit_hash})\nDo you wish to continue?').format(commit_description=project.last_commit_description, commit_hash=project.last_commit_hash),
114
- show_choices=True,
115
- default=YesOrNoResponse.YES.value,
116
- type=click.Choice([r.value for r in YesOrNoResponse]),
117
- show_default=True,
118
- )
119
- if continue_with_non_empty_repo == YesOrNoResponse.NO:
120
- typer.echo(__('Project init aborted'))
121
- raise typer.Exit(0)
122
-
123
- deploy_ssh_key = self.__thestage_api_client.get_project_deploy_ssh_key(
124
- public_id=project.public_id,
125
- )
126
-
127
- deploy_key_path = self.__config_provider.save_project_deploy_ssh_key(
128
- deploy_ssh_key=deploy_ssh_key,
129
- project_public_id=project.public_id,
130
- )
131
-
132
- if is_git_folder:
133
- has_changes = self.__git_local_client.has_changes_with_untracked(
134
- path=config.runtime.working_directory,
135
- )
136
- if has_changes:
137
- typer.echo(__('You local repo has changes and not empty, please create empty folder'))
138
- raise typer.Exit(1)
139
- else:
140
- repo = self.__git_local_client.init_repository(
141
- path=config.runtime.working_directory,
142
- )
143
-
144
- is_remote_added = self.__git_local_client.add_remote_to_repo(
145
- path=config.runtime.working_directory,
146
- remote_url=project.git_repository_url,
147
- remote_name=project.git_repository_name,
148
- )
149
- if not is_remote_added:
150
- typer.echo(__('We can not add remote, something wrong'))
151
- raise typer.Exit(2)
152
-
153
- self.__git_local_client.git_fetch(path=config.runtime.working_directory, deploy_key_path=deploy_key_path)
154
-
155
- self.__git_local_client.init_gitignore(path=config.runtime.working_directory)
156
-
157
- self.__git_local_client.git_add_all(repo_path=config.runtime.working_directory)
158
-
159
- project_config = ProjectConfig()
160
- project_config.public_id = project.public_id
161
- project_config.slug = project.slug
162
- project_config.git_repository_url = project.git_repository_url
163
- project_config.deploy_key_path = str(deploy_key_path)
164
- self.__config_provider.save_project_config(project_config=project_config)
165
-
166
- typer.echo(__("Project successfully initialized at %path%", {"path": config.runtime.working_directory}))
167
-
168
-
169
- @error_handler()
170
- def clone_project(
171
- self,
172
- project_slug: str,
173
- project_public_id: str
174
- ):
175
- config = self.__config_provider.get_config()
176
- project: Optional[ProjectDto] = self.__thestage_api_client.get_project(
177
- slug=project_slug,
178
- public_id=project_public_id
179
- )
180
-
181
- if not project:
182
- typer.echo('Project not found')
183
- raise typer.Exit(1)
184
-
185
- if not self.__file_system_service.is_folder_empty(folder=config.runtime.working_directory, auto_create=True):
186
- typer.echo(__("Cannot clone: the folder is not empty"))
187
- raise typer.Exit(1)
188
-
189
- is_git_folder = self.__git_local_client.is_present_local_git(
190
- path=config.runtime.working_directory,
191
- )
192
-
193
- if is_git_folder:
194
- typer.echo(__('You have local repo, we can not work with this'))
195
- raise typer.Exit(1)
196
-
197
- if not project.git_repository_url:
198
- typer.echo(__("Unexpected Project error, missing Repository"))
199
- raise typer.Exit(1)
200
-
201
- deploy_ssh_key = self.__thestage_api_client.get_project_deploy_ssh_key(public_id=project.public_id)
202
- deploy_key_path = self.__config_provider.save_project_deploy_ssh_key(deploy_ssh_key=deploy_ssh_key, project_public_id=project.public_id,)
203
-
204
- try:
205
- self.__git_local_client.clone(
206
- url=project.git_repository_url,
207
- path=config.runtime.working_directory,
208
- deploy_key_path=deploy_key_path
209
- )
210
- self.__git_local_client.init_gitignore(path=config.runtime.working_directory)
211
- except GitAccessException as ex:
212
- typer.echo(ex.get_message())
213
- typer.echo(ex.get_dop_message())
214
- typer.echo(__(
215
- "Please check you mail or open this repo url %git_url% and 'Accept invitation'",
216
- {
217
- 'git_url': ex.get_url()
218
- }
219
- ))
220
- raise typer.Exit(1)
221
-
222
- project_config = ProjectConfig()
223
- project_config.public_id = project.public_id
224
- project_config.slug = project.slug
225
- project_config.git_repository_url = project.git_repository_url
226
- project_config.deploy_key_path = str(deploy_key_path)
227
- self.__config_provider.save_project_config(project_config=project_config)
228
- typer.echo(__("Project successfully cloned to %path%", {"path": config.runtime.working_directory}))
229
-
230
-
231
- @error_handler()
232
- def project_run_task(
233
- self,
234
- run_command: str,
235
- docker_container_slug: str,
236
- docker_container_public_id: str,
237
- task_title: Optional[str] = None,
238
- commit_hash: Optional[str] = None,
239
- files_to_add: Optional[str] = None,
240
- is_skip_auto_commit: Optional[bool] = False,
241
- ) -> Optional[TaskDto]:
242
- config = self.__config_provider.get_config()
243
- project_config: ProjectConfig = self.__get_fixed_project_config()
244
- if not project_config:
245
- typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first.", {"path": config.runtime.working_directory}))
246
- raise typer.Exit(1)
247
-
248
- if not docker_container_public_id and not docker_container_slug and not project_config.default_container_public_id:
249
- typer.echo(__('Docker container ID or name is required'))
250
- raise typer.Exit(1)
251
-
252
- final_container_public_id = docker_container_public_id
253
- final_container_slug = docker_container_slug
254
- if not final_container_public_id and not final_container_slug:
255
- final_container_public_id = project_config.default_container_public_id
256
- typer.echo(f"Using default docker container for this project: '{project_config.default_container_public_id}'")
257
-
258
- container: DockerContainerDto = self.__thestage_api_client.get_container(
259
- container_slug=final_container_slug,
260
- container_public_id=final_container_public_id
261
- )
262
-
263
- if container is None:
264
- if final_container_slug:
265
- typer.echo(f"Could not find container with name '{final_container_slug}'")
266
- if final_container_public_id:
267
- typer.echo(f"Could not find container with ID '{final_container_public_id}'")
268
- if project_config.default_container_public_id == final_container_public_id:
269
- project_config.default_container_public_id = None
270
- project_config.prompt_for_default_container = True
271
- self.__config_provider.save_project_config(project_config=project_config)
272
- typer.echo(f"Default container settings were reset")
273
- raise typer.Exit(1)
274
-
275
- if container.project.public_id != project_config.public_id:
276
- typer.echo(f"Provided container '{container.public_id}' is not related to project '{project_config.public_id}'")
277
- raise typer.Exit(1)
278
-
279
- if (project_config.prompt_for_default_container is None or project_config.prompt_for_default_container) and (docker_container_slug or docker_container_public_id) and (project_config.default_container_public_id != container.public_id):
280
- set_default_container_answer: str = typer.prompt(
281
- text=f"Would you like to set the container '{container.slug}' (ID: '{container.public_id}') as default for this project installation?",
282
- show_choices=True,
283
- default=YesOrNoResponse.YES.value,
284
- type=click.Choice([r.value for r in YesOrNoResponse]),
285
- show_default=True,
286
- )
287
- project_config.prompt_for_default_container = False
288
- if set_default_container_answer == YesOrNoResponse.YES.value:
289
- project_config.default_container_public_id = container.public_id
290
-
291
- self.__config_provider.save_project_config(project_config=project_config)
292
-
293
- has_wrong_args = files_to_add and commit_hash or is_skip_auto_commit and commit_hash or files_to_add and is_skip_auto_commit
294
-
295
- if has_wrong_args:
296
- warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] You can provide only one of the following arguments: --commit-hash, --files-add, --skip-autocommit[{ColorScheme.WARNING.value}]"
297
- print(warning_msg)
298
- raise typer.Exit(1)
299
-
300
- if not is_skip_auto_commit and not commit_hash:
301
- is_git_folder = self.__git_local_client.is_present_local_git(path=config.runtime.working_directory)
302
- if not is_git_folder:
303
- typer.echo("Error: working directory does not contain git repository")
304
- raise typer.Exit(1)
305
-
306
- is_commit_allowed: bool = True
307
- has_changes = self.__git_local_client.has_changes_with_untracked(
308
- path=config.runtime.working_directory,
309
- )
310
-
311
- if self.__git_local_client.is_head_detached(path=config.runtime.working_directory):
312
- is_commit_allowed = False
313
- print(f"[{ColorScheme.GIT_HEADLESS.value}]HEAD is detached[{ColorScheme.GIT_HEADLESS.value}]")
314
-
315
- is_headless_commits_present = self.__git_local_client.is_head_committed_in_headless_state(path=config.runtime.working_directory)
316
- if is_headless_commits_present:
317
- print(f"[{ColorScheme.GIT_HEADLESS.value}]Current commit was made in detached head state. Cannot use it to run the task. Consider using 'project checkout' command to return to a valid reference.[{ColorScheme.GIT_HEADLESS.value}]")
318
- raise typer.Exit(1)
319
-
320
- if has_changes:
321
- print(f"[{ColorScheme.GIT_HEADLESS.value}]Local changes detected in detached head state. They will not impact the task execution.[{ColorScheme.GIT_HEADLESS.value}]")
322
- response: YesOrNoResponse = typer.prompt(
323
- text=__('Continue?'),
324
- show_choices=True,
325
- default=YesOrNoResponse.YES.value,
326
- type=click.Choice([r.value for r in YesOrNoResponse]),
327
- show_default=True,
328
- )
329
- if response == YesOrNoResponse.NO:
330
- raise typer.Exit(0)
331
-
332
- if is_commit_allowed:
333
- if not self.__git_local_client.add_files_with_size_limit_or_warn(config.runtime.working_directory, files_to_add):
334
- warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] Task was not started [{ColorScheme.WARNING.value}]"
335
- print(warning_msg)
336
- raise typer.Exit(1)
337
-
338
- diff_stat = self.__git_local_client.git_diff_stat(repo_path=config.runtime.working_directory)
339
-
340
- if has_changes and diff_stat:
341
- branch_name = self.__git_local_client.get_active_branch_name(config.runtime.working_directory)
342
-
343
- typer.echo(__('Active branch [%branch_name%] has uncommitted changes: %diff_stat_bottomline%', {
344
- 'diff_stat_bottomline': diff_stat,
345
- 'branch_name': branch_name,
346
- }))
347
-
348
- response: str = typer.prompt(
349
- text=__('Commit changes?'),
350
- show_choices=True,
351
- default=YesOrNoResponse.YES.value,
352
- type=click.Choice([r.value for r in YesOrNoResponse]),
353
- show_default=True,
354
- )
355
- if response == YesOrNoResponse.NO.value:
356
- typer.echo("Task cannot use uncommitted changes - aborting")
357
- raise typer.Exit(0)
358
-
359
- commit_name = typer.prompt(
360
- text=__('Please provide commit message'),
361
- show_choices=False,
362
- type=str,
363
- show_default=False,
364
- )
365
-
366
- if commit_name:
367
- commit_result = self.__git_local_client.commit_local_changes(
368
- path=config.runtime.working_directory,
369
- name=commit_name
370
- )
371
-
372
- if commit_result:
373
- # in docs not Commit object, on real - str
374
- if isinstance(commit_result, str):
375
- typer.echo(commit_result)
376
- else:
377
- typer.echo(__('Cannot commit with empty commit message'))
378
- raise typer.Exit(0)
379
- else:
380
- pass
381
- # possible to push new empty branch - only that there's a wrong place to do so
382
-
383
- self.__git_local_client.push_changes(
384
- path=config.runtime.working_directory,
385
- deploy_key_path=project_config.deploy_key_path
386
- )
387
- typer.echo(__("Pushed changes to remote repository"))
388
-
389
- if not commit_hash:
390
- commit = self.__git_local_client.get_current_commit(path=config.runtime.working_directory)
391
- if not commit or not isinstance(commit, Commit):
392
- print('[red]Error: No current commit found in the local repository[/red]')
393
- raise typer.Exit(0)
394
- commit_hash = commit.hexsha
395
- else:
396
- commit = self.__git_local_client.get_commit_by_hash(path=config.runtime.working_directory, commit_hash=commit_hash)
397
- if not commit or not isinstance(commit, Commit):
398
- print(f'[red]Error: commit \'{commit_hash}\' was not found in the local repository[/red]')
399
- raise typer.Exit(0)
400
-
401
- if not task_title:
402
- task_title = commit.message.strip() if commit.message else f'Task_{commit_hash}'
403
- if not commit.message:
404
- typer.echo(f'Commit message is empty. Task title is set to "{task_title}"')
405
-
406
- run_task_response: ProjectRunTaskResponse = self.__thestage_api_client.execute_project_task(
407
- project_public_id=project_config.public_id,
408
- docker_container_public_id=container.public_id,
409
- run_command=run_command,
410
- commit_hash=commit_hash,
411
- task_title=task_title,
412
- )
413
- if run_task_response:
414
- if run_task_response.message:
415
- print(f"[{ColorScheme.WARNING.value}]{run_task_response.message}[{ColorScheme.WARNING.value}]")
416
- if run_task_response.is_success and run_task_response.task:
417
- typer.echo(f"Task '{run_task_response.task.title}' has been scheduled successfully. Task ID: {run_task_response.task.public_id}")
418
- if run_task_response.tasksInQueue:
419
- typer.echo(f"There are tasks in queue ahead of this new task:")
420
- for queued_task_item in run_task_response.tasksInQueue:
421
- typer.echo(f"{queued_task_item.public_id} - {queued_task_item.frontend_status.status_translation}")
422
- return run_task_response.task
423
- else:
424
- typer.echo(f'The task failed with an error: {run_task_response.message}')
425
- raise typer.Exit(1)
426
- else:
427
- typer.echo("The task failed with an error")
428
- raise typer.Exit(1)
429
-
430
- @error_handler()
431
- def cancel_task(self, task_public_id: str):
432
- cancel_result = self.__thestage_api_client.cancel_task(
433
- task_public_id=task_public_id,
434
- )
435
-
436
- if cancel_result.is_success:
437
- typer.echo(f'Task {task_public_id} has been canceled')
438
- else:
439
- typer.echo(f'Task {task_public_id} could not be canceled: {cancel_result.message}')
440
-
441
-
442
- @error_handler()
443
- def project_run_inference_simulator(
444
- self,
445
- commit_hash: Optional[str] = None,
446
- rented_instance_public_id: Optional[str] = None,
447
- rented_instance_slug: Optional[str] = None,
448
- self_hosted_instance_public_id: Optional[str] = None,
449
- self_hosted_instance_slug: Optional[str] = None,
450
- inference_dir: Optional[str] = None,
451
- is_skip_installation: Optional[bool] = False,
452
- files_to_add: Optional[str] = None,
453
- is_skip_auto_commit: Optional[bool] = False,
454
- ) -> Optional[InferenceSimulatorDto]:
455
- config = self.__config_provider.get_config()
456
- project_config: ProjectConfig = self.__get_fixed_project_config()
457
- if not project_config:
458
- typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first. Or provide path to project using --working-directory option.",
459
- {"path": config.runtime.working_directory}))
460
- raise typer.Exit(1)
461
-
462
- instance_args_count = sum(v is not None for v in [rented_instance_public_id, rented_instance_slug, self_hosted_instance_public_id, self_hosted_instance_slug])
463
- if instance_args_count != 1:
464
- typer.echo("Please provide a single instance (rented or self-hosted) identifier - name or ID.")
465
- raise typer.Exit(1)
466
-
467
- has_wrong_args = files_to_add and commit_hash or is_skip_auto_commit and commit_hash or files_to_add and is_skip_auto_commit
468
- if has_wrong_args:
469
- warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] You can provide only one of the following arguments: --commit-hash, --files-add, --skip-autocommit[{ColorScheme.WARNING.value}]"
470
- print(warning_msg)
471
- raise typer.Exit(1)
472
-
473
- if not is_skip_auto_commit and not commit_hash:
474
- is_git_folder = self.__git_local_client.is_present_local_git(path=config.runtime.working_directory)
475
- if not is_git_folder:
476
- typer.echo("Error: Working directory does not contain git repository.")
477
- raise typer.Exit(1)
478
-
479
- is_commit_allowed: bool = True
480
- has_changes = self.__git_local_client.has_changes_with_untracked(
481
- path=config.runtime.working_directory,
482
- )
483
-
484
- if self.__git_local_client.is_head_detached(path=config.runtime.working_directory):
485
- print(f"[{ColorScheme.GIT_HEADLESS.value}]HEAD is detached[{ColorScheme.GIT_HEADLESS.value}]")
486
-
487
- is_headless_commits_present = self.__git_local_client.is_head_committed_in_headless_state(
488
- path=config.runtime.working_directory)
489
- if is_headless_commits_present:
490
- print(
491
- f"[{ColorScheme.GIT_HEADLESS.value}]Current commit was made in detached head state. Cannot use it to start the inference simulator. Consider using 'project checkout' command to return to a valid reference.[{ColorScheme.GIT_HEADLESS.value}]")
492
- raise typer.Exit(1)
493
-
494
- if has_changes:
495
- print(
496
- f"[{ColorScheme.GIT_HEADLESS.value}]Local changes detected in detached head state. They will not impact the inference simulator.[{ColorScheme.GIT_HEADLESS.value}]")
497
- is_commit_allowed = False
498
- response: YesOrNoResponse = typer.prompt(
499
- text=__('Continue?'),
500
- show_choices=True,
501
- default=YesOrNoResponse.YES.value,
502
- type=click.Choice([r.value for r in YesOrNoResponse]),
503
- show_default=True,
504
- )
505
- if response == YesOrNoResponse.NO:
506
- raise typer.Exit(0)
507
-
508
- if is_commit_allowed:
509
- if not self.__git_local_client.add_files_with_size_limit_or_warn(config.runtime.working_directory, files_to_add):
510
- warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] Inference simulator was not started [{ColorScheme.WARNING.value}]"
511
- print(warning_msg)
512
- raise typer.Exit(1)
513
-
514
- diff_stat = self.__git_local_client.git_diff_stat(repo_path=config.runtime.working_directory)
515
-
516
- if has_changes and diff_stat:
517
- branch_name = self.__git_local_client.get_active_branch_name(config.runtime.working_directory)
518
- typer.echo(__('Active branch [%branch_name%] has uncommitted changes: %diff_stat_bottomline%', {
519
- 'diff_stat_bottomline': diff_stat,
520
- 'branch_name': branch_name,
521
- }))
522
-
523
- response: str = typer.prompt(
524
- text=__('Commit changes?'),
525
- show_choices=True,
526
- default=YesOrNoResponse.YES.value,
527
- type=click.Choice([r.value for r in YesOrNoResponse]),
528
- show_default=True,
529
- )
530
- if response == YesOrNoResponse.NO.value:
531
- typer.echo("inference simulator cannot use uncommitted changes - aborting")
532
- raise typer.Exit(0)
533
-
534
- commit_name = typer.prompt(
535
- text=__('Please provide commit message'),
536
- show_choices=False,
537
- type=str,
538
- show_default=False,
539
- )
540
-
541
- if commit_name:
542
- commit_result = self.__git_local_client.commit_local_changes(
543
- path=config.runtime.working_directory,
544
- name=commit_name
545
- )
546
-
547
- if commit_result:
548
- # in docs not Commit object, on real - str
549
- if isinstance(commit_result, str):
550
- typer.echo(commit_result)
551
-
552
- self.__git_local_client.push_changes(
553
- path=config.runtime.working_directory,
554
- deploy_key_path=project_config.deploy_key_path
555
- )
556
- typer.echo(__("Pushed changes to remote repository"))
557
- else:
558
- typer.echo(__('Cannot commit with empty commit name, your code will run without last changes.'))
559
- else:
560
- pass
561
- # possible to push new empty branch - only that there's a wrong place to do so
562
-
563
- if not commit_hash:
564
- commit = self.__git_local_client.get_current_commit(path=config.runtime.working_directory)
565
- if commit and isinstance(commit, Commit):
566
- commit_hash = commit.hexsha
567
-
568
- start_inference_simulator_response: ProjectStartInferenceSimulatorResponse = self.__thestage_api_client.start_project_inference_simulator(
569
- project_public_id=project_config.public_id,
570
- commit_hash=commit_hash,
571
- rented_instance_public_id=rented_instance_public_id,
572
- rented_instance_slug=rented_instance_slug,
573
- self_hosted_instance_public_id=self_hosted_instance_public_id,
574
- self_hosted_instance_slug=self_hosted_instance_slug,
575
- inference_dir=inference_dir,
576
- is_skip_installation=is_skip_installation,
577
- )
578
- if start_inference_simulator_response:
579
- if start_inference_simulator_response.message:
580
- typer.echo(start_inference_simulator_response.message)
581
- if start_inference_simulator_response.is_success and start_inference_simulator_response.inferenceSimulator:
582
- typer.echo("Inference simulator has been scheduled to run successfully.")
583
- return start_inference_simulator_response.inferenceSimulator
584
- else:
585
- typer.echo(__(
586
- 'Inference simulator failed to run with an error: %server_massage%',
587
- {'server_massage': start_inference_simulator_response.message or ""}
588
- ))
589
- raise typer.Exit(1)
590
- else:
591
- typer.echo(__("Inference simulator failed to run with an error"))
592
- raise typer.Exit(1)
593
-
594
-
595
- @error_handler()
596
- def project_push_inference_simulator(
597
- self,
598
- public_id: Optional[str] = None,
599
- slug: Optional[str] = None,
600
- ):
601
-
602
- push_inference_simulator_model_response: ProjectPushInferenceSimulatorModelResponse = self.__thestage_api_client.push_project_inference_simulator_model(
603
- public_id=public_id,
604
- slug=slug,
605
- )
606
- if push_inference_simulator_model_response:
607
- if push_inference_simulator_model_response.message:
608
- typer.echo(push_inference_simulator_model_response.message)
609
- if push_inference_simulator_model_response.is_success:
610
- typer.echo("Inference simulator has been successfully scheduled to be pushed to S3 and ECR.")
611
- else:
612
- typer.echo(__(
613
- 'Failed to push inference simulator with an error: %server_massage%',
614
- {'server_massage': push_inference_simulator_model_response.message or ""}
615
- ))
616
- raise typer.Exit(1)
617
- else:
618
- typer.echo(__("Failed to push inference simulator with an error"))
619
- raise typer.Exit(1)
620
-
621
- @error_handler()
622
- def project_get_and_save_inference_simulator_metadata(
623
- self,
624
- inference_simulator_public_id: Optional[str] = None,
625
- inference_simulator_slug: Optional[str] = None,
626
- file_path: Optional[str] = None,
627
- ):
628
- get_inference_metadata_response: GetInferenceSimulatorResponse = self.__thestage_api_client.get_inference_simulator(
629
- public_id=inference_simulator_public_id,
630
- slug=inference_simulator_slug,
631
- )
632
-
633
- metadata = get_inference_metadata_response.inferenceSimulator.qlip_serve_metadata
634
-
635
- if metadata:
636
- typer.echo("qlip_serve_metadata:")
637
- typer.echo(json.dumps(metadata, indent=4))
638
-
639
- if not file_path:
640
- file_path = Path(os.getcwd()) / "metadata.json"
641
- typer.echo(__("No file path provided. Saving metadata to %file_path%", {"file_path": str(file_path)}))
642
-
643
- try:
644
- parsed_metadata = metadata
645
-
646
- output_file = Path(file_path)
647
- output_file.parent.mkdir(parents=True, exist_ok=True)
648
- with output_file.open("w", encoding="utf-8") as file:
649
- json.dump(parsed_metadata, file, indent=4)
650
- typer.echo(__("Metadata successfully saved to %file_path%", {"file_path": str(file_path)}))
651
- except Exception as e:
652
- typer.echo(__("Failed to save metadata to %file_path%. Error: %error%",
653
- {"file_path": file_path, "error": str(e)}))
654
- raise typer.Exit(1)
655
- else:
656
- typer.echo(__("No qlip_serve_metadata found"))
657
- raise typer.Exit(1)
658
-
659
-
660
- @error_handler()
661
- def get_project_inference_simulator_list(
662
- self,
663
- project_public_id: Optional[str],
664
- project_slug: Optional[str],
665
- statuses: List[str],
666
- row: int = 5,
667
- page: int = 1,
668
- ) -> PaginatedEntityList[InferenceSimulatorDto]:
669
- data: Optional[PaginatedEntityList[InferenceSimulatorDto]] = self.__thestage_api_client.get_inference_simulator_list(
670
- statuses=statuses,
671
- project_public_id=project_public_id,
672
- project_slug=project_slug,
673
- page=page,
674
- limit=row,
675
- )
676
-
677
- return data
678
-
679
-
680
- @error_handler()
681
- def get_project_inference_simulator_model_list(
682
- self,
683
- project_public_id: Optional[str],
684
- project_slug: Optional[str],
685
- statuses: List[str],
686
- row: int = 5,
687
- page: int = 1,
688
- ) -> PaginatedEntityList[InferenceSimulatorModelDto]:
689
- data: Optional[
690
- PaginatedEntityList[InferenceSimulatorModelDto]] = self.__thestage_api_client.get_inference_simulator_model_list_for_project(
691
- statuses=statuses,
692
- project_public_id=project_public_id,
693
- project_slug=project_slug,
694
- page=page,
695
- limit=row,
696
- )
697
-
698
- return data
699
-
700
-
701
- @error_handler()
702
- def checkout_project(
703
- self,
704
- task_public_id: Optional[str],
705
- branch_name: Optional[str],
706
- ):
707
- config = self.__config_provider.get_config()
708
- project_config: ProjectConfig = self.__get_fixed_project_config()
709
- if not project_config:
710
- typer.echo(__("This command is only allowed from within an initialized project directory"))
711
- raise typer.Exit(1)
712
-
713
- target_commit_hash: Optional[str] = None
714
- if task_public_id:
715
- task_view_response: Optional[TaskViewResponse] = None
716
- try:
717
- task_view_response = self.__thestage_api_client.get_task(task_public_id=task_public_id)
718
- except HttpClientException as e:
719
- if e.get_status_code() == 400:
720
- typer.echo(f"Task {task_public_id} was not found")
721
- # overriding arguments here
722
- branch_name = str(task_public_id)
723
- task_public_id = None
724
-
725
- if task_view_response and task_view_response.task:
726
- target_commit_hash = task_view_response.task.commit_hash
727
- if not target_commit_hash:
728
- typer.echo(f"Provided task ({task_public_id}) has no commit hash") # possible legacy problems
729
- raise typer.Exit(1)
730
-
731
- is_commit_allowed: bool = True
732
-
733
- if self.__git_local_client.is_head_detached(path=config.runtime.working_directory):
734
- is_commit_allowed = False
735
- if self.__git_local_client.is_head_committed_in_headless_state(path=config.runtime.working_directory):
736
- commit_message = self.__git_local_client.get_current_commit(path=config.runtime.working_directory).message
737
- print(f"[{ColorScheme.GIT_HEADLESS.value}]Your current commit '{commit_message.strip()}' was likely created in detached head state. Checking out will discard all changes.[/{ColorScheme.GIT_HEADLESS.value}]")
738
- response: YesOrNoResponse = typer.prompt(
739
- text=__('Continue?'),
740
- show_choices=True,
741
- default=YesOrNoResponse.YES.value,
742
- type=click.Choice([r.value for r in YesOrNoResponse]),
743
- show_default=True,
744
- )
745
- if response == YesOrNoResponse.NO:
746
- raise typer.Exit(0)
747
- else:
748
- if self.__git_local_client.get_active_branch_name(path=config.runtime.working_directory) == branch_name:
749
- typer.echo(f"You are already at branch '{branch_name}'")
750
- raise typer.Exit(0)
751
-
752
- if is_commit_allowed:
753
- self.__git_local_client.git_add_all(repo_path=config.runtime.working_directory)
754
-
755
- has_changes = self.__git_local_client.has_changes_with_untracked(
756
- path=config.runtime.working_directory,
757
- )
758
-
759
- if has_changes:
760
- active_branch_name = self.__git_local_client.get_active_branch_name(config.runtime.working_directory)
761
- diff_stat = self.__git_local_client.git_diff_stat(repo_path=config.runtime.working_directory)
762
- typer.echo(__('Active branch [%branch_name%] has uncommitted changes: %diff_stat_bottomline%', {
763
- 'diff_stat_bottomline': diff_stat,
764
- 'branch_name': active_branch_name,
765
- }))
766
-
767
- response: str = typer.prompt(
768
- text=__('Commit changes?'),
769
- show_choices=True,
770
- default=YesOrNoResponse.YES.value,
771
- type=click.Choice([r.value for r in YesOrNoResponse]),
772
- show_default=True,
773
- )
774
- if response == YesOrNoResponse.NO.value:
775
- typer.echo(__('Cannot checkout with uncommitted changes'))
776
- raise typer.Exit(0)
777
-
778
- commit_name = typer.prompt(
779
- text=__('Please provide commit message'),
780
- show_choices=False,
781
- type=str,
782
- show_default=False,
783
- )
784
-
785
- if commit_name:
786
- commit_result = self.__git_local_client.commit_local_changes(
787
- path=config.runtime.working_directory,
788
- name=commit_name
789
- )
790
-
791
- if commit_result:
792
- # in docs not Commit object, on real - str
793
- if isinstance(commit_result, str):
794
- typer.echo(commit_result)
795
-
796
- self.__git_local_client.push_changes(
797
- path=config.runtime.working_directory,
798
- deploy_key_path=project_config.deploy_key_path
799
- )
800
- typer.echo(__("Pushed changes to remote repository"))
801
- else:
802
- typer.echo(__('Cannot commit with empty commit name'))
803
- raise typer.Exit(0)
804
-
805
- if target_commit_hash:
806
- if self.__git_local_client.get_current_commit(path=config.runtime.working_directory).hexsha != target_commit_hash:
807
- is_checkout_successful = self.__git_local_client.git_checkout_to_commit(
808
- path=config.runtime.working_directory,
809
- commit_hash=target_commit_hash
810
- )
811
-
812
- if is_checkout_successful:
813
- print(f"Checked out to commit {target_commit_hash}")
814
- print(f"[{ColorScheme.GIT_HEADLESS.value}]HEAD is detached. To be able make changes in repository, checkout to any branch.[/{ColorScheme.GIT_HEADLESS.value}]")
815
- else:
816
- typer.echo("HEAD is already at requested commit")
817
- elif branch_name:
818
- if self.__git_local_client.is_branch_exists(path=config.runtime.working_directory, branch_name=branch_name):
819
- self.__git_local_client.git_checkout_to_branch(
820
- path=config.runtime.working_directory,
821
- branch=branch_name
822
- )
823
- typer.echo(f"Checked out to branch '{branch_name}'")
824
- else:
825
- typer.echo(f"Branch '{branch_name}' was not found in project repository")
826
- else:
827
- main_branch = self.__git_local_client.find_main_branch_name(path=config.runtime.working_directory)
828
- if main_branch:
829
- self.__git_local_client.git_checkout_to_branch(
830
- path=config.runtime.working_directory,
831
- branch=main_branch
832
- )
833
- typer.echo(f"Checked out to detected main branch: '{main_branch}'")
834
- else:
835
- typer.echo("No main branch found")
836
-
837
-
838
-
839
-
840
- @error_handler()
841
- def set_default_container(
842
- self,
843
- container_public_id: Optional[str],
844
- container_slug: Optional[str],
845
- ):
846
- project_config: ProjectConfig = self.__config_provider.read_project_config()
847
-
848
- if project_config is None:
849
- typer.echo(f"No project found in working directory")
850
- raise typer.Exit(1)
851
-
852
- container: Optional[DockerContainerDto] = None
853
- if container_slug or container_public_id:
854
- container: DockerContainerDto = self.__thestage_api_client.get_container(
855
- container_public_id=container_public_id,
856
- container_slug=container_slug,
857
- )
858
- if container is None:
859
- typer.echo(f"Could not find container '{container_slug or container_public_id}'")
860
- raise typer.Exit(1)
861
-
862
- if container.project.public_id != project_config.public_id:
863
- typer.echo(f"Provided container '{container_slug or container_public_id}' is not related to current project '{project_config.public_id}'")
864
- raise typer.Exit(1)
865
-
866
- if container.frontend_status.status_key != DockerContainerStatus.RUNNING:
867
- typer.echo(f"Note: provided container '{container_slug or container_public_id}' is in status '{container.frontend_status.status_translation}'")
868
-
869
- project_config.default_container_public_id = container.public_id if container else None
870
- project_config.prompt_for_default_container = False
871
- self.__config_provider.save_project_config(project_config=project_config)
872
- typer.echo("Default container settings were updated")
873
-
874
-
875
- @error_handler()
876
- def print_project_config(self):
877
- project_config: ProjectConfig = self.__config_provider.read_project_config()
878
-
879
- if project_config is None:
880
- typer.echo(f"No project found in working directory")
881
- raise typer.Exit(1)
882
-
883
- is_deploy_key_exists = project_config.deploy_key_path and self.__file_system_service.check_if_path_exist(project_config.deploy_key_path)
884
-
885
- typer.echo(tabulate(
886
- [
887
- [
888
- "Project ID", project_config.public_id
889
- ],
890
- [
891
- "Project name", project_config.slug
892
- ],
893
- [
894
- "Default docker container ID", project_config.default_container_public_id if project_config.default_container_public_id else "<None>"
895
- ],
896
- [
897
- "Deploy key path", project_config.deploy_key_path if is_deploy_key_exists else "<None>"
898
- ],
899
- ],
900
- showindex=False,
901
- tablefmt="simple",
902
- ))
903
-
904
- if is_deploy_key_exists:
905
- typer.echo("")
906
- typer.echo(f"You can insert the following text:")
907
- print(f"[{ColorScheme.USEFUL_INFO.value}]GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i {project_config.deploy_key_path}\"[{ColorScheme.USEFUL_INFO.value}]")
908
- typer.echo(f"before any regular git command to manage your local Project repository directly")
909
-
910
- @error_handler()
911
- def __get_fixed_project_config(self) -> Optional[ProjectConfig]:
912
- project_config: ProjectConfig = self.__config_provider.read_project_config()
913
- if project_config is None:
914
- return None
915
-
916
- if project_config.public_id is None:
917
- project = self.__thestage_api_client.get_project(public_id=None, slug=project_config.slug)
918
- project_config.public_id = project.public_id
919
- self.__config_provider.save_project_config(project_config=project_config)
920
-
921
- if not Path(project_config.deploy_key_path).is_file():
922
- deploy_ssh_key = self.__thestage_api_client.get_project_deploy_ssh_key(
923
- public_id=project_config.public_id,
924
- )
925
-
926
- deploy_key_path = self.__config_provider.save_project_deploy_ssh_key(
927
- deploy_ssh_key=deploy_ssh_key,
928
- project_public_id=project_config.public_id,
929
- )
930
-
931
- project_config.deploy_key_path = deploy_key_path
932
- self.__config_provider.save_project_config(project_config=project_config)
933
- typer.echo(f'Recreated missing deploy key for the project')
934
-
935
- return project_config
936
-
937
- @error_handler()
938
- def project_deploy_inference_simulator_model_to_instance(
939
- self,
940
- model_public_id: Optional[str] = None,
941
- model_slug: Optional[str] = None,
942
- rented_instance_public_id: Optional[str] = None,
943
- rented_instance_slug: Optional[str] = None,
944
- self_hosted_instance_public_id: Optional[str] = None,
945
- self_hosted_instance_slug: Optional[str] = None,
946
- ) -> str:
947
- config = self.__config_provider.get_config()
948
- project_config: ProjectConfig = self.__get_fixed_project_config()
949
- if not project_config:
950
- typer.echo(
951
- __("No project found at the path: %path%. Please initialize or clone a project first. Or provide path to project using --working-directory option.",
952
- {"path": config.runtime.working_directory}))
953
- raise typer.Exit(1)
954
-
955
- instance_args_count = sum(v is not None for v in [rented_instance_public_id, rented_instance_slug, self_hosted_instance_public_id, self_hosted_instance_slug])
956
- if instance_args_count != 1:
957
- typer.echo("Please provide a single instance (rented or self-hosted) identifier - name or ID.")
958
- raise typer.Exit(1)
959
-
960
- model_args_count = sum(v is not None for v in [model_public_id, model_slug])
961
- if model_args_count != 1:
962
- typer.echo("Please provide a single model identifier - name or ID.")
963
- raise typer.Exit(1)
964
-
965
- typer.echo(f"Creating inference simulator")
966
- deploy_model_to_instance_response: DeployInferenceModelToInstanceResponse = self.__thestage_api_client.deploy_inference_model_to_instance(
967
- model_public_id=model_public_id,
968
- model_slug=model_slug,
969
- rented_instance_public_id=rented_instance_public_id,
970
- rented_instance_slug=rented_instance_slug,
971
- self_hosted_instance_public_id=self_hosted_instance_public_id,
972
- self_hosted_instance_slug=self_hosted_instance_slug,
973
- )
974
- if deploy_model_to_instance_response:
975
- if deploy_model_to_instance_response.message:
976
- typer.echo(deploy_model_to_instance_response.message)
977
- if deploy_model_to_instance_response.is_success:
978
- typer.echo(f"Inference simulator '{deploy_model_to_instance_response.inferenceSimulatorPublicId}' has been scheduled to run successfully.")
979
- else:
980
- typer.echo(__(
981
- 'Inference simulator failed to run with an error: %server_massage%',
982
- {'server_massage': deploy_model_to_instance_response.message or ""}
983
- ))
984
- raise typer.Exit(1)
985
- else:
986
- typer.echo(__("Inference simulator failed to run with an error"))
987
- raise typer.Exit(1)
988
-
989
- return deploy_model_to_instance_response.inferenceSimulatorPublicId
990
-
991
-
992
- @error_handler()
993
- def project_deploy_inference_simulator_model_to_sagemaker(
994
- self,
995
- model_public_id: Optional[str] = None,
996
- model_slug: Optional[str] = None,
997
- arn: Optional[str] = None,
998
- instance_type: Optional[str] = None,
999
- initial_variant_weight: Optional[float] = 1.0,
1000
- initial_instance_count: Optional[int] = None,
1001
- ) -> None:
1002
- config = self.__config_provider.get_config()
1003
- project_config: ProjectConfig = self.__get_fixed_project_config()
1004
- if not project_config:
1005
- typer.echo(
1006
- __("No project found at the path: %path%. Please initialize or clone a project first. Or provide path to project using --working-directory option.",
1007
- {"path": config.runtime.working_directory}))
1008
- raise typer.Exit(1)
1009
-
1010
- if not instance_type:
1011
- typer.echo(__("Error: Instance type is required."))
1012
- raise typer.Exit(1)
1013
-
1014
- if not initial_instance_count:
1015
- typer.echo(__("Error: Initial instance count is required."))
1016
- raise typer.Exit(1)
1017
-
1018
- if not arn:
1019
- typer.echo(__("Error: ARN is required."))
1020
- raise typer.Exit(1)
1021
-
1022
- project_config: ProjectConfig = self.__config_provider.read_project_config()
1023
- if not project_config:
1024
- typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first.",
1025
- {"path": config.runtime.working_directory}))
1026
- raise typer.Exit(1)
1027
-
1028
- deploy_model_to_sagemaker_response: DeployInferenceModelToSagemakerResponse = self.__thestage_api_client.deploy_inference_model_to_sagemaker(
1029
- model_public_id=model_public_id,
1030
- model_slug=model_slug,
1031
- arn=arn,
1032
- )
1033
-
1034
- if not deploy_model_to_sagemaker_response.is_success:
1035
- typer.echo(__(
1036
- 'Failed to prepare model for deployment with an error: %server_massage%',
1037
- {'server_massage': deploy_model_to_sagemaker_response.message or ""}
1038
- ))
1039
- raise typer.Exit(1)
1040
-
1041
- model_id = deploy_model_to_sagemaker_response.modelId
1042
- image_uri = deploy_model_to_sagemaker_response.ecrImageUrl
1043
- model_uri = deploy_model_to_sagemaker_response.s3ArtifactsUrl
1044
- region = "us-east-1"
1045
- sm_client = boto3.client('sagemaker', region_name=region)
1046
-
1047
- try:
1048
- container = {
1049
- "Image": image_uri,
1050
- "ModelDataUrl": model_uri,
1051
- "Environment": {
1052
- "SAGEMAKER_TRITON_DEFAULT_MODEL_NAME": model_id,
1053
- "THESTAGE_API_URL": config.main.thestage_api_url,
1054
- "THESTAGE_AUTH_TOKEN": config.main.thestage_auth_token
1055
- },
1056
- }
1057
-
1058
- sm_model_name = f"{model_slug}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
1059
- create_model_response = sm_client.create_model(
1060
- ModelName=sm_model_name,
1061
- ExecutionRoleArn=arn,
1062
- PrimaryContainer=container,
1063
- )
1064
- typer.echo(f"Model created successfully. Model ARN: {create_model_response['ModelArn']}")
1065
-
1066
- endpoint_config_name = f"{model_slug}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
1067
- create_endpoint_config_response = sm_client.create_endpoint_config(
1068
- EndpointConfigName=endpoint_config_name,
1069
- ProductionVariants=[
1070
- {
1071
- "InstanceType": instance_type,
1072
- "InitialVariantWeight": initial_variant_weight,
1073
- "InitialInstanceCount": initial_instance_count,
1074
- "ModelName": sm_model_name,
1075
- "VariantName": "AllTraffic",
1076
- }
1077
- ],
1078
- )
1079
- typer.echo(
1080
- f"Endpoint configuration created successfully. Endpoint Config ARN: {create_endpoint_config_response['EndpointConfigArn']}")
1081
-
1082
- endpoint_name = f"{model_slug}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
1083
- create_endpoint_response = sm_client.create_endpoint(
1084
- EndpointName=endpoint_name,
1085
- EndpointConfigName=endpoint_config_name,
1086
- )
1087
- typer.echo(f"Endpoint created successfully. Endpoint ARN: {create_endpoint_response['EndpointArn']}")
1088
-
1089
- typer.echo("Waiting for the endpoint to become active...")
1090
- while True:
1091
- resp = sm_client.describe_endpoint(EndpointName=endpoint_name)
1092
- status = resp["EndpointStatus"]
1093
- typer.echo(f"Status: {status}")
1094
- if status == "InService":
1095
- break
1096
- elif status == "Failed":
1097
- typer.echo(f"Endpoint creation failed. Reason: {resp.get('FailureReason', 'Unknown')}")
1098
- raise typer.Exit(1)
1099
- time.sleep(60)
1100
-
1101
- typer.echo(f"Endpoint is ready. ARN: {resp['EndpointArn']} Status: {status}")
1102
-
1103
- except Exception as e:
1104
- typer.echo(__("Failed to deploy the inference simulator model to SageMaker: %error%", {"error": str(e)}))
1105
- raise typer.Exit(1)
1106
-
1107
-
1108
- @error_handler()
1109
- def pull_project(self):
1110
- config = self.__config_provider.get_config()
1111
- project_config: ProjectConfig = self.__get_fixed_project_config()
1112
- if not project_config:
1113
- typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first.", {"path": config.runtime.working_directory}))
1114
- raise typer.Exit(1)
1115
-
1116
- typer.echo("Pulling code from remote repository...")
1117
- self.__git_local_client.git_pull(
1118
- path=config.runtime.working_directory,
1119
- deploy_key_path=project_config.deploy_key_path,
1120
- )
1121
-
1122
-
1123
- @error_handler()
1124
- def reset_project(self):
1125
- config = self.__config_provider.get_config()
1126
- project_config: ProjectConfig = self.__get_fixed_project_config()
1127
- if not project_config:
1128
- typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first.", {"path": config.runtime.working_directory}))
1129
- raise typer.Exit(1)
1130
-
1131
- typer.echo("Fetching code from remote repository...")
1132
- self.__git_local_client.git_fetch(
1133
- path=config.runtime.working_directory,
1134
- deploy_key_path=project_config.deploy_key_path,
1135
- )
1136
- typer.echo("Resetting local branch...")
1137
- self.__git_local_client.reset_hard(
1138
- path=config.runtime.working_directory,
1139
- deploy_key_path=project_config.deploy_key_path,
1140
- reset_to_origin=True
1141
- )
1142
-
1143
-
1144
- @error_handler()
1145
- def print_inference_simulator_list(self, project_public_id, project_slug, statuses, row, page):
1146
- if not project_public_id and not project_slug:
1147
- project_config: ProjectConfig = self.__config_provider.read_project_config()
1148
- if not project_config:
1149
- typer.echo(__("Provide the project identifier or run this command from within an initialized project directory"))
1150
- raise typer.Exit(1)
1151
- project_public_id = project_config.public_id
1152
-
1153
- inference_simulator_status_map = self.__thestage_api_client.get_inference_simulator_business_status_map()
1154
-
1155
- if not statuses:
1156
- statuses = ({key: inference_simulator_status_map[key] for key in [
1157
- InferenceSimulatorStatus.SCHEDULED,
1158
- InferenceSimulatorStatus.CREATING,
1159
- InferenceSimulatorStatus.RUNNING,
1160
- ]}).values()
1161
-
1162
- if "all" in statuses:
1163
- statuses = inference_simulator_status_map.values()
1164
-
1165
- for input_status_item in statuses:
1166
- if input_status_item not in inference_simulator_status_map.values():
1167
- typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
1168
- 'invalid_status': input_status_item,
1169
- 'valid_statuses': str(list(inference_simulator_status_map.values()))
1170
- }))
1171
- raise typer.Exit(1)
1172
-
1173
- typer.echo(__(
1174
- "Listing inference simulators with the following statuses: %statuses%, to view all inference simulators, use --status all",
1175
- placeholders={
1176
- 'statuses': ', '.join([status_item for status_item in statuses])
1177
- }))
1178
-
1179
- backend_statuses: List[str] = [key for key, value in inference_simulator_status_map.items() if value in statuses]
1180
-
1181
- self.print(
1182
- func_get_data=self.get_project_inference_simulator_list,
1183
- func_special_params={
1184
- 'project_public_id': project_public_id,
1185
- 'project_slug': project_slug,
1186
- 'statuses': backend_statuses,
1187
- },
1188
- mapper=ProjectInferenceSimulatorMapper(),
1189
- headers=list(map(lambda x: x.alias, ProjectInferenceSimulatorEntity.model_fields.values())),
1190
- row=row,
1191
- page=page,
1192
- max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
1193
- show_index="never",
1194
- )
1195
-
1196
-
1197
- @error_handler()
1198
- def print_inference_simulator_model_list(self, project_public_id, project_slug, statuses, row, page):
1199
- if not project_public_id and not project_slug:
1200
- project_config: ProjectConfig = self.__config_provider.read_project_config()
1201
- if not project_config:
1202
- typer.echo(__("Provide the project identifier or run this command from within an initialized project directory"))
1203
- raise typer.Exit(1)
1204
- project_public_id = project_config.public_id
1205
-
1206
- inference_simulator_model_status_map = self.__thestage_api_client.get_inference_simulator_model_business_status_map()
1207
-
1208
- if not statuses:
1209
- statuses = ({key: inference_simulator_model_status_map[key] for key in [
1210
- InferenceModelStatus.SCHEDULED,
1211
- InferenceModelStatus.PROCESSING,
1212
- InferenceModelStatus.PUSH_SUCCEED,
1213
- ]}).values()
1214
-
1215
- if "all" in statuses:
1216
- statuses = inference_simulator_model_status_map.values()
1217
-
1218
- for input_status_item in statuses:
1219
- if input_status_item not in inference_simulator_model_status_map.values():
1220
- typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
1221
- 'invalid_status': input_status_item,
1222
- 'valid_statuses': str(list(inference_simulator_model_status_map.values()))
1223
- }))
1224
- raise typer.Exit(1)
1225
-
1226
- typer.echo(__(
1227
- "Listing inference simulator models with the following statuses: %statuses%, to view all inference simulator models, use --status all",
1228
- placeholders={
1229
- 'statuses': ', '.join([status_item for status_item in statuses])
1230
- }))
1231
-
1232
- backend_statuses: List[str] = [key for key, value in inference_simulator_model_status_map.items() if value in statuses]
1233
-
1234
- self.print(
1235
- func_get_data=self.get_project_inference_simulator_model_list,
1236
- func_special_params={
1237
- 'project_public_id': project_public_id,
1238
- 'project_slug': project_slug,
1239
- 'statuses': backend_statuses,
1240
- },
1241
- mapper=ProjectInferenceSimulatorModelMapper(),
1242
- headers=list(map(lambda x: x.alias, ProjectInferenceSimulatorModelEntity.model_fields.values())),
1243
- row=row,
1244
- page=page,
1245
- max_col_width=[100, 100, 100, 100, 25],
1246
- show_index="never",
1247
- )
1248
-
1249
-
1250
- def print_task_list(self, project_public_id: Optional[str], project_slug: Optional[str], row, page):
1251
- if not project_slug and not project_public_id:
1252
- project_config: ProjectConfig = self.__config_provider.read_project_config()
1253
- if not project_config:
1254
- typer.echo(__("Provide the project identifier or run this command from within an initialized project directory"))
1255
- raise typer.Exit(1)
1256
- project_public_id = project_config.public_id
1257
-
1258
- self.print(
1259
- func_get_data=self.get_project_task_list,
1260
- func_special_params={
1261
- 'project_public_id': project_public_id,
1262
- 'project_slug': project_slug,
1263
- },
1264
- mapper=ProjectTaskMapper(),
1265
- headers=list(map(lambda x: x.alias, ProjectTaskEntity.model_fields.values())),
1266
- row=row,
1267
- page=page,
1268
- max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
1269
- show_index="never",
1270
- )
1271
-
1272
- @error_handler()
1273
- def get_project_task_list(
1274
- self,
1275
- project_public_id: Optional[str],
1276
- project_slug: Optional[str],
1277
- row: int = 5,
1278
- page: int = 1,
1279
- ) -> PaginatedEntityList[TaskDto]:
1280
- data: Optional[PaginatedEntityList[TaskDto]] = self.__thestage_api_client.get_task_list_for_project(
1281
- project_public_id=project_public_id,
1282
- project_slug=project_slug,
1283
- page=page,
1284
- limit=row,
1285
- )
1286
-
1287
- return data