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.
- thestage/__init__.py +1 -1
- thestage/cli_command_helper.py +2 -2
- thestage/config/__init__.py +1 -1
- thestage/{services → config/business}/app_config_service.py +3 -4
- thestage/{services/config_provider → config/business}/config_provider.py +6 -5
- thestage/config/{config_storage.py → business/config_storage.py} +1 -1
- thestage/{services → config/business}/validation_service.py +9 -10
- thestage/{controllers/config_controller.py → config/communication/config_command.py} +7 -7
- thestage/{services/connect → connect/business}/connect_service.py +22 -19
- thestage/{services → connect/business}/remote_server_service.py +4 -5
- thestage/connect/communication/connect_api_client.py +84 -0
- thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_response.py +0 -1
- thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_response.py +1 -4
- thestage/{services/clients/thestage_api/dtos/base_controller → connect/dto}/connect_resolve_response.py +0 -1
- thestage/controllers/base_controller.py +2 -2
- thestage/debug_main.dist.py +16 -14
- thestage/{services/container → docker_container/business}/container_service.py +27 -22
- thestage/{services/container → docker_container/business}/mapper/container_mapper.py +3 -3
- thestage/docker_container/communication/__init__.py +0 -0
- thestage/{controllers/container_controller.py → docker_container/communication/docker_command.py} +17 -23
- thestage/docker_container/communication/docker_container_api_client.py +99 -0
- thestage/docker_container/dto/__init__.py +0 -0
- thestage/docker_container/dto/container_action_request.py +11 -0
- thestage/{services/clients/thestage_api/dtos → docker_container/dto}/container_response.py +4 -4
- thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_response.py +3 -5
- thestage/docker_container/dto/enum/__init__.py +0 -0
- thestage/git/__init__.py +0 -0
- thestage/git/business/__init__.py +0 -0
- thestage/git/communication/__init__.py +0 -0
- thestage/{services/clients/git → git/communication}/git_client.py +4 -4
- thestage/global_dto/__init__.py +0 -0
- thestage/global_dto/enums/__init__.py +0 -0
- thestage/helpers/error_handler.py +3 -3
- thestage/helpers/logger/app_logger.py +1 -3
- thestage/i18n/en_GB/messages.po +14 -14
- thestage/inference_model/__init__.py +0 -0
- thestage/inference_model/business/__init__.py +0 -0
- thestage/inference_model/business/inference_model_service.py +281 -0
- thestage/inference_model/business/mapper/__init__.py +0 -0
- thestage/{services/project/mapper/project_inference_simulator_model_mapper.py → inference_model/business/mapper/inference_model_mapper.py} +5 -5
- thestage/inference_model/communication/__init__.py +0 -0
- thestage/inference_model/communication/inference_model_api_client.py +139 -0
- thestage/inference_model/communication/inference_model_command.py +246 -0
- thestage/inference_model/dto/__init__.py +0 -0
- thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_response.py +0 -1
- thestage/inference_model/dto/enum/__init__.py +0 -0
- thestage/{services/project/dto/inference_simulator_model_dto.py → inference_model/dto/inference_model.py} +1 -1
- thestage/{entities/project_inference_simulator_model.py → inference_model/dto/inference_model_entity.py} +1 -1
- thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_response.py +2 -3
- 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
- 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
- thestage/inference_simulator/__init__.py +0 -0
- thestage/inference_simulator/business/__init__.py +0 -0
- thestage/inference_simulator/business/inference_simulator_service.py +338 -0
- thestage/inference_simulator/business/mapper/__init__.py +0 -0
- thestage/{services/project/mapper/project_inference_simulator_mapper.py → inference_simulator/business/mapper/inference_simulator_mapper.py} +5 -5
- thestage/inference_simulator/communication/__init__.py +0 -0
- thestage/inference_simulator/communication/inference_simulator_api_client.py +114 -0
- thestage/inference_simulator/communication/inference_simulator_command.py +347 -0
- thestage/inference_simulator/dto/__init__.py +0 -0
- thestage/inference_simulator/dto/enum/__init__.py +0 -0
- thestage/inference_simulator/dto/get_inference_simulator_response.py +12 -0
- thestage/{services/project/dto/inference_simulator_dto.py → inference_simulator/dto/inference_simulator.py} +1 -1
- thestage/{entities/project_inference_simulator.py → inference_simulator/dto/inference_simulator_entity.py} +1 -1
- thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_response.py +2 -2
- thestage/{services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_request.py → inference_simulator/dto/start_inference_simulator_request.py} +1 -1
- thestage/inference_simulator/dto/start_inference_simulator_response.py +10 -0
- thestage/instance/__init__.py +0 -0
- thestage/instance/business/__init__.py +0 -0
- thestage/{services/instance → instance/business}/instance_service.py +26 -27
- thestage/instance/business/mapper/__init__.py +0 -0
- thestage/{services/instance/mapper/instance_mapper.py → instance/business/mapper/rented_instance_mapper.py} +3 -3
- thestage/{services/instance/mapper/selfhosted_mapper.py → instance/business/mapper/selfhosted_instance_mapper.py} +5 -7
- thestage/instance/communication/__init__.py +0 -0
- thestage/instance/communication/instance_api_client.py +150 -0
- thestage/{controllers/instance_controller.py → instance/communication/instance_command.py} +5 -5
- thestage/instance/dto/__init__.py +0 -0
- thestage/instance/dto/enum/__init__.py +0 -0
- thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_detected_gpus.py +1 -2
- thestage/{services/clients/thestage_api/dtos → instance/dto}/instance_rented_response.py +2 -2
- thestage/{services/clients/thestage_api/dtos → instance/dto}/selfhosted_instance_response.py +2 -3
- thestage/logging/__init__.py +0 -0
- thestage/logging/business/__init__.py +0 -0
- thestage/{services/logging → logging/business}/logging_service.py +40 -28
- thestage/logging/communication/__init__.py +0 -0
- thestage/logging/communication/logging_api_client.py +63 -0
- thestage/logging/dto/__init__.py +0 -0
- thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_response.py +2 -2
- thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_response.py +2 -2
- thestage/main.py +47 -8
- thestage/project/__init__.py +0 -0
- thestage/project/business/__init__.py +0 -0
- thestage/project/business/project_service.py +480 -0
- thestage/project/communication/__init__.py +0 -0
- thestage/project/communication/project_api_client.py +46 -0
- thestage/project/communication/project_command.py +284 -0
- thestage/project/dto/__init__.py +0 -0
- thestage/services/clients/thestage_api/core/api_client_core.py +1 -1
- thestage/services/clients/thestage_api/dtos/entity_filter_request.py +1 -1
- thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +1 -1
- thestage/services/filesystem_service.py +2 -2
- thestage/services/service_factory.py +130 -43
- thestage/task/__init__.py +0 -0
- thestage/task/business/__init__.py +0 -0
- thestage/task/business/mapper/__init__.py +0 -0
- thestage/{services/project/mapper/project_task_mapper.py → task/business/mapper/task_mapper.py} +5 -5
- thestage/task/business/task_service.py +304 -0
- thestage/task/communication/__init__.py +0 -0
- thestage/task/communication/task_api_client.py +122 -0
- thestage/task/communication/task_command.py +212 -0
- thestage/task/dto/__init__.py +0 -0
- thestage/task/dto/enum/__init__.py +0 -0
- thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_response.py → task/dto/list_for_project_response.py} +2 -2
- thestage/{services/clients/thestage_api/dtos/project_controller/project_run_task_request.py → task/dto/run_task_request.py} +1 -1
- thestage/task/dto/run_task_response.py +13 -0
- thestage/{services/task/dto/task_dto.py → task/dto/task.py} +1 -4
- thestage/{entities/project_task.py → task/dto/task_entity.py} +1 -1
- thestage/{services/clients/thestage_api/dtos/task_controller/task_view_response.py → task/dto/view_response.py} +2 -2
- {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/METADATA +1 -1
- thestage-0.6.8.dist-info/RECORD +219 -0
- {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/WHEEL +1 -1
- thestage/controllers/project_controller.py +0 -1056
- thestage/services/clients/thestage_api/api_client.py +0 -751
- thestage/services/clients/thestage_api/dtos/container_param_request.py +0 -11
- thestage/services/clients/thestage_api/dtos/inference_controller/get_inference_simulator_response.py +0 -13
- thestage/services/clients/thestage_api/dtos/project_controller/project_run_task_response.py +0 -13
- thestage/services/clients/thestage_api/dtos/project_controller/project_start_inference_simulator_response.py +0 -10
- thestage/services/clients/thestage_api/dtos/user_controller/user_profile.py +0 -12
- thestage/services/project/project_service.py +0 -1287
- thestage-0.6.7.dist-info/RECORD +0 -167
- /thestage/{entities → color_scheme}/__init__.py +0 -0
- /thestage/{entities/enums → config/business}/__init__.py +0 -0
- /thestage/{services/clients/git → config/communication}/__init__.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → config/dto}/__init__.py +0 -0
- /thestage/{services/core_files → config/dto}/config_entity.py +0 -0
- /thestage/{services/connect → config}/dto/remote_server_config.py +0 -0
- /thestage/{services/config_provider → connect}/__init__.py +0 -0
- /thestage/{services/container → connect/business}/__init__.py +0 -0
- /thestage/{services/container/mapper → connect/communication}/__init__.py +0 -0
- /thestage/{services/instance → connect/dto}/__init__.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_key_to_user_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/add_ssh_public_key_to_instance_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/ssh_key_controller → connect/dto}/is_user_has_public_ssh_key_response.py +0 -0
- /thestage/{services/instance/mapper → docker_container}/__init__.py +0 -0
- /thestage/{services/project → docker_container/business}/__init__.py +0 -0
- /thestage/{services/project → docker_container/business}/mapper/__init__.py +0 -0
- /thestage/{entities/container.py → docker_container/dto/container_entity.py} +0 -0
- /thestage/{services/clients/thestage_api/dtos/docker_container_controller → docker_container/dto}/docker_container_list_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos → docker_container/dto}/docker_container_mapping.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_pending_action.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → docker_container/dto/enum}/container_status.py +0 -0
- /thestage/{services/logging/exception → exceptions}/log_polling_exception.py +0 -0
- /thestage/git/{ProgressPrinter.py → business/ProgressPrinter.py} +0 -0
- /thestage/{entities → global_dto}/enums/order_direction_type.py +0 -0
- /thestage/{entities → global_dto}/enums/shell_type.py +0 -0
- /thestage/{entities → global_dto}/enums/tail_output_type.py +0 -0
- /thestage/{entities → global_dto}/enums/yes_no_response.py +0 -0
- /thestage/{entities → global_dto}/file_item.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_instance_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/deploy_inference_model_to_sagemaker_response.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → inference_model/dto/enum}/inference_model_status.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_model/dto}/inference_simulator_model_list_for_project_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos → inference_model/dto}/inference_simulator_model_response.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → inference_simulator/dto/enum}/inference_simulator_status.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/get_inference_simulator_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/inference_controller → inference_simulator/dto}/inference_simulator_list_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos → inference_simulator/dto}/inference_simulator_response.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/cpu_type.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/gpu_name.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/instance_rented_status.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/provider_name.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → instance/dto/enum}/selfhosted_status.py +0 -0
- /thestage/{entities → instance/dto}/rented_instance.py +0 -0
- /thestage/{entities → instance/dto}/self_hosted_instance.py +0 -0
- /thestage/{services/logging → logging}/byte_print_style.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/docker_container_log_stream_request.py +0 -0
- /thestage/{services/logging → logging}/dto/log_message.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/log_polling_request.py +0 -0
- /thestage/{services/logging → logging}/dto/log_type.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/task_log_stream_request.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/logging_controller → logging/dto}/user_logs_query_request.py +0 -0
- /thestage/{services/logging → logging}/logging_constants.py +0 -0
- /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
- /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
- /thestage/{services/project → project}/dto/project_config.py +0 -0
- /thestage/{services/clients/thestage_api/dtos → project/dto}/project_response.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_execution_status.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/enums → task/dto/enum}/task_status.py +0 -0
- /thestage/{services/clients/thestage_api/dtos/task_controller/task_list_for_project_request.py → task/dto/list_for_project_request.py} +0 -0
- /thestage/{services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py → task/dto/status_localized_map_response.py} +0 -0
- {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/entry_points.txt +0 -0
- {thestage-0.6.7.dist-info → thestage-0.6.8.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import typer
|
|
5
|
+
from git import Commit
|
|
6
|
+
from rich import print
|
|
7
|
+
|
|
8
|
+
from thestage.color_scheme.color_scheme import ColorScheme
|
|
9
|
+
from thestage.config.business.config_provider import ConfigProvider
|
|
10
|
+
from thestage.docker_container.communication.docker_container_api_client import DockerContainerApiClient
|
|
11
|
+
from thestage.docker_container.dto.container_response import DockerContainerDto
|
|
12
|
+
from thestage.git.communication.git_client import GitLocalClient
|
|
13
|
+
from thestage.global_dto.enums.yes_no_response import YesOrNoResponse
|
|
14
|
+
from thestage.helpers.error_handler import error_handler
|
|
15
|
+
from thestage.i18n.translation import __
|
|
16
|
+
from thestage.project.business.project_service import ProjectService
|
|
17
|
+
from thestage.project.dto.project_config import ProjectConfig
|
|
18
|
+
from thestage.services.abstract_service import AbstractService
|
|
19
|
+
from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
|
|
20
|
+
from thestage.services.filesystem_service import FileSystemService
|
|
21
|
+
from thestage.task.business.mapper.task_mapper import TaskMapper
|
|
22
|
+
from thestage.task.communication.task_api_client import TaskApiClient
|
|
23
|
+
from thestage.task.dto.run_task_response import RunTaskResponse
|
|
24
|
+
from thestage.task.dto.task import Task
|
|
25
|
+
from thestage.task.dto.task_entity import TaskEntity
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TaskService(AbstractService):
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
docker_container_api_client: DockerContainerApiClient,
|
|
32
|
+
task_api_client: TaskApiClient,
|
|
33
|
+
config_provider: ConfigProvider,
|
|
34
|
+
git_local_client: GitLocalClient,
|
|
35
|
+
file_system_service: FileSystemService,
|
|
36
|
+
project_service: ProjectService,
|
|
37
|
+
):
|
|
38
|
+
self.__docker_container_api_client = docker_container_api_client
|
|
39
|
+
self.__task_api_client = task_api_client
|
|
40
|
+
self.__config_provider = config_provider
|
|
41
|
+
self.__git_local_client = git_local_client
|
|
42
|
+
self.__file_system_service = file_system_service
|
|
43
|
+
self.__project_service = project_service
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@error_handler()
|
|
47
|
+
def project_run_task(
|
|
48
|
+
self,
|
|
49
|
+
run_command: str,
|
|
50
|
+
docker_container_slug: str,
|
|
51
|
+
docker_container_public_id: str,
|
|
52
|
+
task_title: Optional[str] = None,
|
|
53
|
+
commit_hash: Optional[str] = None,
|
|
54
|
+
files_to_add: Optional[str] = None,
|
|
55
|
+
is_skip_auto_commit: Optional[bool] = False,
|
|
56
|
+
) -> Optional[Task]:
|
|
57
|
+
config = self.__config_provider.get_config()
|
|
58
|
+
project_config: ProjectConfig = self.__project_service.get_fixed_project_config()
|
|
59
|
+
if not project_config:
|
|
60
|
+
typer.echo(__("No project found at the path: %path%. Initialize or clone a project first.",
|
|
61
|
+
{"path": config.runtime.working_directory}))
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
|
|
64
|
+
if not docker_container_public_id and not docker_container_slug and not project_config.default_container_public_id:
|
|
65
|
+
typer.echo(__('Docker container ID or name is required'))
|
|
66
|
+
raise typer.Exit(1)
|
|
67
|
+
|
|
68
|
+
final_container_public_id = docker_container_public_id
|
|
69
|
+
final_container_slug = docker_container_slug
|
|
70
|
+
if not final_container_public_id and not final_container_slug:
|
|
71
|
+
final_container_public_id = project_config.default_container_public_id
|
|
72
|
+
typer.echo(
|
|
73
|
+
f"Using default docker container for this project: '{project_config.default_container_public_id}'")
|
|
74
|
+
|
|
75
|
+
container: DockerContainerDto = self.__docker_container_api_client.get_container(
|
|
76
|
+
container_slug=final_container_slug,
|
|
77
|
+
container_public_id=final_container_public_id
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if container is None:
|
|
81
|
+
if final_container_slug:
|
|
82
|
+
typer.echo(f"Could not find container with name '{final_container_slug}'")
|
|
83
|
+
if final_container_public_id:
|
|
84
|
+
typer.echo(f"Could not find container with ID '{final_container_public_id}'")
|
|
85
|
+
if project_config.default_container_public_id == final_container_public_id:
|
|
86
|
+
project_config.default_container_public_id = None
|
|
87
|
+
project_config.prompt_for_default_container = True
|
|
88
|
+
self.__config_provider.save_project_config(project_config=project_config)
|
|
89
|
+
typer.echo(f"Default container settings were reset")
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
if container.project.public_id != project_config.public_id:
|
|
93
|
+
typer.echo(
|
|
94
|
+
f"Provided container '{container.public_id}' is not related to project '{project_config.public_id}'")
|
|
95
|
+
raise typer.Exit(1)
|
|
96
|
+
|
|
97
|
+
if (project_config.prompt_for_default_container is None or project_config.prompt_for_default_container) and (
|
|
98
|
+
docker_container_slug or docker_container_public_id) and (
|
|
99
|
+
project_config.default_container_public_id != container.public_id):
|
|
100
|
+
set_default_container_answer: str = typer.prompt(
|
|
101
|
+
text=f"Would you like to set '{docker_container_slug}' as a default container for this project installation?",
|
|
102
|
+
show_choices=True,
|
|
103
|
+
default=YesOrNoResponse.YES.value,
|
|
104
|
+
type=click.Choice([r.value for r in YesOrNoResponse]),
|
|
105
|
+
show_default=True,
|
|
106
|
+
)
|
|
107
|
+
project_config.prompt_for_default_container = False
|
|
108
|
+
if set_default_container_answer == YesOrNoResponse.YES.value:
|
|
109
|
+
project_config.default_container_public_id = container.public_id
|
|
110
|
+
|
|
111
|
+
self.__config_provider.save_project_config(project_config=project_config)
|
|
112
|
+
|
|
113
|
+
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
|
|
114
|
+
|
|
115
|
+
if has_wrong_args:
|
|
116
|
+
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}]"
|
|
117
|
+
print(warning_msg)
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
|
|
120
|
+
if not is_skip_auto_commit and not commit_hash:
|
|
121
|
+
is_git_folder = self.__git_local_client.is_present_local_git(path=config.runtime.working_directory)
|
|
122
|
+
if not is_git_folder:
|
|
123
|
+
typer.echo("Error: Working directory is not a git repository")
|
|
124
|
+
raise typer.Exit(1)
|
|
125
|
+
|
|
126
|
+
is_commit_allowed: bool = True
|
|
127
|
+
has_changes = self.__git_local_client.has_changes_with_untracked(
|
|
128
|
+
path=config.runtime.working_directory,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if self.__git_local_client.is_head_detached(path=config.runtime.working_directory):
|
|
132
|
+
is_commit_allowed = False
|
|
133
|
+
print(f"[{ColorScheme.GIT_HEADLESS.value}]HEAD is detached[{ColorScheme.GIT_HEADLESS.value}]")
|
|
134
|
+
|
|
135
|
+
is_headless_commits_present = self.__git_local_client.is_head_committed_in_headless_state(
|
|
136
|
+
path=config.runtime.working_directory)
|
|
137
|
+
if is_headless_commits_present:
|
|
138
|
+
print(
|
|
139
|
+
f"[{ColorScheme.GIT_HEADLESS.value}]Current commit created 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}]")
|
|
140
|
+
raise typer.Exit(1)
|
|
141
|
+
|
|
142
|
+
if has_changes:
|
|
143
|
+
print(
|
|
144
|
+
f"[{ColorScheme.GIT_HEADLESS.value}]Local changes detected in detached head state. They will not impact the task execution.[{ColorScheme.GIT_HEADLESS.value}]")
|
|
145
|
+
response: YesOrNoResponse = typer.prompt(
|
|
146
|
+
text=__('Continue?'),
|
|
147
|
+
show_choices=True,
|
|
148
|
+
default=YesOrNoResponse.YES.value,
|
|
149
|
+
type=click.Choice([r.value for r in YesOrNoResponse]),
|
|
150
|
+
show_default=True,
|
|
151
|
+
)
|
|
152
|
+
if response == YesOrNoResponse.NO:
|
|
153
|
+
raise typer.Exit(0)
|
|
154
|
+
|
|
155
|
+
if is_commit_allowed:
|
|
156
|
+
if not self.__git_local_client.add_files_with_size_limit_or_warn(config.runtime.working_directory,
|
|
157
|
+
files_to_add):
|
|
158
|
+
warning_msg = f"[{ColorScheme.WARNING.value}][WARNING] Task was not started [{ColorScheme.WARNING.value}]"
|
|
159
|
+
print(warning_msg)
|
|
160
|
+
raise typer.Exit(1)
|
|
161
|
+
|
|
162
|
+
diff_stat = self.__git_local_client.git_diff_stat(repo_path=config.runtime.working_directory)
|
|
163
|
+
|
|
164
|
+
if has_changes and diff_stat:
|
|
165
|
+
branch_name = self.__git_local_client.get_active_branch_name(config.runtime.working_directory)
|
|
166
|
+
|
|
167
|
+
typer.echo(__('Active branch [%branch_name%] has uncommitted changes: %diff_stat_bottomline%', {
|
|
168
|
+
'diff_stat_bottomline': diff_stat,
|
|
169
|
+
'branch_name': branch_name,
|
|
170
|
+
}))
|
|
171
|
+
|
|
172
|
+
response: str = typer.prompt(
|
|
173
|
+
text=__('Commit changes?'),
|
|
174
|
+
show_choices=True,
|
|
175
|
+
default=YesOrNoResponse.YES.value,
|
|
176
|
+
type=click.Choice([r.value for r in YesOrNoResponse]),
|
|
177
|
+
show_default=True,
|
|
178
|
+
)
|
|
179
|
+
if response == YesOrNoResponse.NO.value:
|
|
180
|
+
typer.echo("Cannot run task with uncommitted changes - aborting")
|
|
181
|
+
raise typer.Exit(0)
|
|
182
|
+
|
|
183
|
+
commit_name = typer.prompt(
|
|
184
|
+
text=__('Please provide commit message'),
|
|
185
|
+
show_choices=False,
|
|
186
|
+
type=str,
|
|
187
|
+
show_default=False,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if commit_name:
|
|
191
|
+
commit_result = self.__git_local_client.commit_local_changes(
|
|
192
|
+
path=config.runtime.working_directory,
|
|
193
|
+
name=commit_name
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if commit_result:
|
|
197
|
+
# in docs not Commit object, on real - str
|
|
198
|
+
if isinstance(commit_result, str):
|
|
199
|
+
typer.echo(commit_result)
|
|
200
|
+
else:
|
|
201
|
+
typer.echo(__('Commit message cannot be empty'))
|
|
202
|
+
raise typer.Exit(0)
|
|
203
|
+
else:
|
|
204
|
+
pass
|
|
205
|
+
# possible to push new empty branch - only that there's a wrong place to do so
|
|
206
|
+
|
|
207
|
+
self.__git_local_client.push_changes(
|
|
208
|
+
path=config.runtime.working_directory,
|
|
209
|
+
deploy_key_path=project_config.deploy_key_path
|
|
210
|
+
)
|
|
211
|
+
typer.echo(__("Pushed changes to remote repository"))
|
|
212
|
+
|
|
213
|
+
if not commit_hash:
|
|
214
|
+
commit = self.__git_local_client.get_current_commit(path=config.runtime.working_directory)
|
|
215
|
+
if not commit or not isinstance(commit, Commit):
|
|
216
|
+
print('[red]Error: No current commit found in the local repository[/red]')
|
|
217
|
+
raise typer.Exit(0)
|
|
218
|
+
commit_hash = commit.hexsha
|
|
219
|
+
else:
|
|
220
|
+
commit = self.__git_local_client.get_commit_by_hash(path=config.runtime.working_directory,
|
|
221
|
+
commit_hash=commit_hash)
|
|
222
|
+
if not commit or not isinstance(commit, Commit):
|
|
223
|
+
print(f'[red]Error: commit \'{commit_hash}\' was not found in the local repository[/red]')
|
|
224
|
+
raise typer.Exit(0)
|
|
225
|
+
|
|
226
|
+
if not task_title:
|
|
227
|
+
task_title = commit.message.strip() if commit.message else f'Task_{commit_hash}'
|
|
228
|
+
if not commit.message:
|
|
229
|
+
typer.echo(f'Commit message is empty. Task title is set to "{task_title}"')
|
|
230
|
+
|
|
231
|
+
run_task_response: RunTaskResponse = self.__task_api_client.execute_project_task(
|
|
232
|
+
project_public_id=project_config.public_id,
|
|
233
|
+
docker_container_public_id=container.public_id,
|
|
234
|
+
run_command=run_command,
|
|
235
|
+
commit_hash=commit_hash,
|
|
236
|
+
task_title=task_title,
|
|
237
|
+
)
|
|
238
|
+
if run_task_response:
|
|
239
|
+
if run_task_response.message:
|
|
240
|
+
print(f"[{ColorScheme.WARNING.value}]{run_task_response.message}[{ColorScheme.WARNING.value}]")
|
|
241
|
+
if run_task_response.is_success and run_task_response.task:
|
|
242
|
+
typer.echo(f"Task '{run_task_response.task.title}' has been scheduled successfully. Task ID: {run_task_response.task.public_id}")
|
|
243
|
+
if run_task_response.tasksInQueue:
|
|
244
|
+
typer.echo(f"There are tasks in queue ahead of this new task:")
|
|
245
|
+
for queued_task_item in run_task_response.tasksInQueue:
|
|
246
|
+
typer.echo(f"{queued_task_item.public_id} - {queued_task_item.frontend_status.status_translation}")
|
|
247
|
+
return run_task_response.task
|
|
248
|
+
else:
|
|
249
|
+
typer.echo(f'The task failed with an error: {run_task_response.message}')
|
|
250
|
+
raise typer.Exit(1)
|
|
251
|
+
else:
|
|
252
|
+
typer.echo("The task failed with an error")
|
|
253
|
+
raise typer.Exit(1)
|
|
254
|
+
|
|
255
|
+
@error_handler()
|
|
256
|
+
def cancel_task(self, task_public_id: str):
|
|
257
|
+
cancel_result = self.__task_api_client.cancel_task(
|
|
258
|
+
task_public_id=task_public_id,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if cancel_result.is_success:
|
|
262
|
+
typer.echo(f'Task {task_public_id} has been canceled')
|
|
263
|
+
else:
|
|
264
|
+
typer.echo(f'Task {task_public_id} could not be canceled: {cancel_result.message}')
|
|
265
|
+
|
|
266
|
+
def print_task_list(self, project_public_id: Optional[str], project_slug: Optional[str], row, page):
|
|
267
|
+
if not project_slug and not project_public_id:
|
|
268
|
+
project_config: ProjectConfig = self.__config_provider.read_project_config()
|
|
269
|
+
if not project_config:
|
|
270
|
+
typer.echo(
|
|
271
|
+
__("Provide the project unique ID or run this command from within an initialized project directory"))
|
|
272
|
+
raise typer.Exit(1)
|
|
273
|
+
project_public_id = project_config.public_id
|
|
274
|
+
|
|
275
|
+
self.print(
|
|
276
|
+
func_get_data=self.get_project_task_list,
|
|
277
|
+
func_special_params={
|
|
278
|
+
'project_public_id': project_public_id,
|
|
279
|
+
'project_slug': project_slug,
|
|
280
|
+
},
|
|
281
|
+
mapper=TaskMapper(),
|
|
282
|
+
headers=list(map(lambda x: x.alias, TaskEntity.model_fields.values())),
|
|
283
|
+
row=row,
|
|
284
|
+
page=page,
|
|
285
|
+
max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
|
|
286
|
+
show_index="never",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
@error_handler()
|
|
290
|
+
def get_project_task_list(
|
|
291
|
+
self,
|
|
292
|
+
project_public_id: Optional[str],
|
|
293
|
+
project_slug: Optional[str],
|
|
294
|
+
row: int = 5,
|
|
295
|
+
page: int = 1,
|
|
296
|
+
) -> PaginatedEntityList[Task]:
|
|
297
|
+
data: Optional[PaginatedEntityList[Task]] = self.__task_api_client.get_task_list_for_project(
|
|
298
|
+
project_public_id=project_public_id,
|
|
299
|
+
project_slug=project_slug,
|
|
300
|
+
page=page,
|
|
301
|
+
limit=row,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
return data
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from typing import Optional, List, Dict
|
|
2
|
+
|
|
3
|
+
from thestage.config.business.config_provider import ConfigProvider
|
|
4
|
+
from thestage.global_dto.enums.order_direction_type import OrderDirectionType
|
|
5
|
+
from thestage.services.clients.thestage_api.core.api_client_core import TheStageApiClientCore
|
|
6
|
+
from thestage.services.clients.thestage_api.dtos.entity_filter_request import EntityFilterRequest
|
|
7
|
+
from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
|
|
8
|
+
from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
|
|
9
|
+
from thestage.task.dto.list_for_project_request import TaskListForProjectRequest
|
|
10
|
+
from thestage.task.dto.list_for_project_response import TaskListForProjectResponse
|
|
11
|
+
from thestage.task.dto.run_task_request import RunTaskRequest
|
|
12
|
+
from thestage.task.dto.run_task_response import RunTaskResponse
|
|
13
|
+
from thestage.task.dto.status_localized_map_response import TaskStatusLocalizedMapResponse
|
|
14
|
+
from thestage.task.dto.view_response import TaskViewResponse
|
|
15
|
+
from thestage.task.dto.task import Task
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TaskApiClient(TheStageApiClientCore):
|
|
19
|
+
def __init__(self, config_provider: ConfigProvider):
|
|
20
|
+
super().__init__(url=config_provider.get_config().main.thestage_api_url)
|
|
21
|
+
self.__config_provider = config_provider
|
|
22
|
+
|
|
23
|
+
def get_task(
|
|
24
|
+
self,
|
|
25
|
+
task_public_id: str,
|
|
26
|
+
) -> Optional[TaskViewResponse]:
|
|
27
|
+
data = {
|
|
28
|
+
"taskPublicId": task_public_id,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
response = self._request(
|
|
32
|
+
method='POST',
|
|
33
|
+
url='/user-api/v2/task/view',
|
|
34
|
+
data=data,
|
|
35
|
+
token=self.__config_provider.get_config().main.thestage_auth_token,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
result = TaskViewResponse.model_validate(response) if response else None
|
|
39
|
+
return result if result and result.is_success else None
|
|
40
|
+
|
|
41
|
+
def get_task_list_for_project(
|
|
42
|
+
self,
|
|
43
|
+
project_public_id: Optional[str],
|
|
44
|
+
project_slug: Optional[str],
|
|
45
|
+
page: int = 1,
|
|
46
|
+
limit: int = 10,
|
|
47
|
+
) -> Optional[PaginatedEntityList[Task]]:
|
|
48
|
+
request = TaskListForProjectRequest(
|
|
49
|
+
projectPublicId=project_public_id,
|
|
50
|
+
projectSlug=project_slug,
|
|
51
|
+
entityFilterRequest=EntityFilterRequest(
|
|
52
|
+
orderByField="createdAt",
|
|
53
|
+
orderByDirection=OrderDirectionType.DESC,
|
|
54
|
+
page=page,
|
|
55
|
+
limit=limit,
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
response = self._request(
|
|
60
|
+
method='POST',
|
|
61
|
+
url='/user-api/v2/task/list',
|
|
62
|
+
data=request.model_dump(),
|
|
63
|
+
token=self.__config_provider.get_config().main.thestage_auth_token,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
result = TaskListForProjectResponse.model_validate(response) if response else None
|
|
67
|
+
return result.tasks if result and result.is_success else None
|
|
68
|
+
|
|
69
|
+
def get_task_localized_status_map(self) -> Optional[Dict[str, str]]:
|
|
70
|
+
response = self._request(
|
|
71
|
+
method='POST',
|
|
72
|
+
url='/user-api/v1/task/status-localized-mapping',
|
|
73
|
+
data=None,
|
|
74
|
+
token=self.__config_provider.get_config().main.thestage_auth_token,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
data = TaskStatusLocalizedMapResponse.model_validate(response) if response else None
|
|
78
|
+
|
|
79
|
+
return data.taskStatusMap if data else None
|
|
80
|
+
|
|
81
|
+
def cancel_task(
|
|
82
|
+
self,
|
|
83
|
+
task_public_id: str,
|
|
84
|
+
) -> Optional[TheStageBaseResponse]:
|
|
85
|
+
data = {
|
|
86
|
+
"taskPublicId": task_public_id,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
response = self._request(
|
|
90
|
+
method='POST',
|
|
91
|
+
url='/user-api/v2/task/cancel',
|
|
92
|
+
data=data,
|
|
93
|
+
token=self.__config_provider.get_config().main.thestage_auth_token,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
result = TheStageBaseResponse.model_validate(response) if response else None
|
|
97
|
+
return result if result else None
|
|
98
|
+
|
|
99
|
+
def execute_project_task(
|
|
100
|
+
self,
|
|
101
|
+
project_public_id: str,
|
|
102
|
+
run_command: str,
|
|
103
|
+
task_title: str,
|
|
104
|
+
docker_container_public_id: str,
|
|
105
|
+
commit_hash: Optional[str] = None,
|
|
106
|
+
) -> Optional[RunTaskResponse]:
|
|
107
|
+
request = RunTaskRequest(
|
|
108
|
+
projectPublicId=project_public_id,
|
|
109
|
+
dockerContainerPublicId=docker_container_public_id,
|
|
110
|
+
commitHash=commit_hash,
|
|
111
|
+
runCommand=run_command,
|
|
112
|
+
taskTitle=task_title,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
response = self._request(
|
|
116
|
+
method='POST',
|
|
117
|
+
url='/user-api/v2/task/execute',
|
|
118
|
+
data=request.model_dump(),
|
|
119
|
+
token=self.__config_provider.get_config().main.thestage_auth_token,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return RunTaskResponse.model_validate(response) if response else None
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from typing_extensions import Annotated
|
|
5
|
+
|
|
6
|
+
from thestage.cli_command import CliCommand
|
|
7
|
+
from thestage.cli_command_helper import get_command_metadata, check_command_permission
|
|
8
|
+
from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
|
|
9
|
+
from thestage.helpers.logger.app_logger import app_logger
|
|
10
|
+
from thestage.i18n.translation import __
|
|
11
|
+
from thestage.logging.business.logging_service import LoggingService
|
|
12
|
+
from thestage.task.dto.task import Task
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(no_args_is_help=True, help=__("Manage project tasks"))
|
|
15
|
+
runner_app = typer.Typer(help="Project Runner")
|
|
16
|
+
|
|
17
|
+
@runner_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))
|
|
18
|
+
def run(
|
|
19
|
+
command: Annotated[List[str], typer.Argument(
|
|
20
|
+
help=__("Command to run (required)"),
|
|
21
|
+
)],
|
|
22
|
+
commit_hash: Optional[str] = typer.Option(
|
|
23
|
+
None,
|
|
24
|
+
'--commit-hash',
|
|
25
|
+
'-hash',
|
|
26
|
+
help=__("Commit hash to use. By default, the current HEAD commit is used."),
|
|
27
|
+
is_eager=False,
|
|
28
|
+
),
|
|
29
|
+
docker_container_public_id: Optional[str] = typer.Option(
|
|
30
|
+
None,
|
|
31
|
+
'--container-id',
|
|
32
|
+
'-cid',
|
|
33
|
+
help=__("Docker container ID"),
|
|
34
|
+
is_eager=False,
|
|
35
|
+
),
|
|
36
|
+
docker_container_slug: Optional[str] = typer.Option(
|
|
37
|
+
None,
|
|
38
|
+
'--container-name',
|
|
39
|
+
'-cn',
|
|
40
|
+
help=__("Docker container name"),
|
|
41
|
+
is_eager=False,
|
|
42
|
+
),
|
|
43
|
+
working_directory: Optional[str] = typer.Option(
|
|
44
|
+
None,
|
|
45
|
+
"--working-directory",
|
|
46
|
+
"-wd",
|
|
47
|
+
help=__("Full path to working directory"),
|
|
48
|
+
show_default=False,
|
|
49
|
+
is_eager=False,
|
|
50
|
+
),
|
|
51
|
+
enable_log_stream: Optional[bool] = typer.Option(
|
|
52
|
+
True,
|
|
53
|
+
" /--no-logs",
|
|
54
|
+
" /-nl",
|
|
55
|
+
help=__("Disable real-time log streaming"),
|
|
56
|
+
is_eager=False,
|
|
57
|
+
),
|
|
58
|
+
task_title: Optional[str] = typer.Option(
|
|
59
|
+
None,
|
|
60
|
+
"--title",
|
|
61
|
+
"-t",
|
|
62
|
+
help=__("Provide a custom task title. Git commit message is used by default."),
|
|
63
|
+
is_eager=False,
|
|
64
|
+
),
|
|
65
|
+
files_to_add: Optional[str] = typer.Option(
|
|
66
|
+
None,
|
|
67
|
+
"--files-add",
|
|
68
|
+
"-fa",
|
|
69
|
+
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."),
|
|
70
|
+
is_eager=False,
|
|
71
|
+
),
|
|
72
|
+
is_skip_auto_commit: Optional[bool] = typer.Option(
|
|
73
|
+
False,
|
|
74
|
+
"--skip-autocommit",
|
|
75
|
+
"-sa",
|
|
76
|
+
help=__("Skip automatic commit of the changes"),
|
|
77
|
+
is_eager=False,
|
|
78
|
+
),
|
|
79
|
+
):
|
|
80
|
+
command_name = CliCommand.PROJECT_RUN
|
|
81
|
+
app_logger.info(f'Running {command_name} from {get_current_directory()}')
|
|
82
|
+
check_command_permission(command_name)
|
|
83
|
+
|
|
84
|
+
if sum(v is not None for v in [docker_container_public_id, docker_container_slug]) != 1:
|
|
85
|
+
typer.echo("Provide a single identifier for the container - name or ID.")
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
if not command:
|
|
89
|
+
typer.echo(__('Command is required'))
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
|
|
93
|
+
task_service = service_factory.get_task_service()
|
|
94
|
+
|
|
95
|
+
task: Optional[Task] = task_service.project_run_task(
|
|
96
|
+
run_command=" ".join(command),
|
|
97
|
+
commit_hash=commit_hash,
|
|
98
|
+
docker_container_public_id=docker_container_public_id,
|
|
99
|
+
docker_container_slug=docker_container_slug,
|
|
100
|
+
task_title=task_title,
|
|
101
|
+
files_to_add=files_to_add,
|
|
102
|
+
is_skip_auto_commit=is_skip_auto_commit,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if enable_log_stream:
|
|
106
|
+
logging_service: LoggingService = service_factory.get_logging_service()
|
|
107
|
+
logging_service.stream_task_logs_with_controls(task_public_id=task.public_id)
|
|
108
|
+
|
|
109
|
+
raise typer.Exit(0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command(name='cancel', no_args_is_help=True, help=__("Cancel a task by ID"), **get_command_metadata(CliCommand.PROJECT_TASK_CANCEL))
|
|
113
|
+
def cancel_task(
|
|
114
|
+
task_id: Annotated[str, typer.Argument(
|
|
115
|
+
help=__("Task ID (required)"),
|
|
116
|
+
)],
|
|
117
|
+
):
|
|
118
|
+
command_name = CliCommand.PROJECT_TASK_CANCEL
|
|
119
|
+
app_logger.info(f'Running {command_name} from {get_current_directory()}')
|
|
120
|
+
check_command_permission(command_name)
|
|
121
|
+
|
|
122
|
+
if not task_id:
|
|
123
|
+
typer.echo('Task ID is required')
|
|
124
|
+
raise typer.Exit(1)
|
|
125
|
+
|
|
126
|
+
service_factory = validate_config_and_get_service_factory()
|
|
127
|
+
task_service = service_factory.get_task_service()
|
|
128
|
+
|
|
129
|
+
task_service.cancel_task(
|
|
130
|
+
task_public_id=task_id
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
raise typer.Exit(0)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.command("ls", help=__("List tasks"), **get_command_metadata(CliCommand.PROJECT_TASK_LS))
|
|
137
|
+
def list_runs(
|
|
138
|
+
project_public_id: Optional[str] = typer.Option(
|
|
139
|
+
None,
|
|
140
|
+
'--project-id',
|
|
141
|
+
'-pid',
|
|
142
|
+
help=__("Project ID. By default, project info is taken from the current directory"),
|
|
143
|
+
is_eager=False,
|
|
144
|
+
),
|
|
145
|
+
project_slug: Optional[str] = typer.Option(
|
|
146
|
+
None,
|
|
147
|
+
'--project-name',
|
|
148
|
+
'-pn',
|
|
149
|
+
help=__("Project name. By default, project info is taken from the current directory"),
|
|
150
|
+
is_eager=False,
|
|
151
|
+
),
|
|
152
|
+
row: int = typer.Option(
|
|
153
|
+
5,
|
|
154
|
+
'--row',
|
|
155
|
+
'-r',
|
|
156
|
+
help=__("Set number of rows displayed per page"),
|
|
157
|
+
is_eager=False,
|
|
158
|
+
),
|
|
159
|
+
page: int = typer.Option(
|
|
160
|
+
1,
|
|
161
|
+
'--page',
|
|
162
|
+
'-p',
|
|
163
|
+
help=__("Set starting page for displaying output"),
|
|
164
|
+
is_eager=False,
|
|
165
|
+
),
|
|
166
|
+
):
|
|
167
|
+
command_name = CliCommand.PROJECT_TASK_LS
|
|
168
|
+
app_logger.info(f'Running {command_name} from {get_current_directory()}')
|
|
169
|
+
check_command_permission(command_name)
|
|
170
|
+
|
|
171
|
+
if sum(v is not None for v in [project_public_id, project_slug]) > 1:
|
|
172
|
+
typer.echo("Provide a single identifier for project - ID or name.")
|
|
173
|
+
raise typer.Exit(1)
|
|
174
|
+
|
|
175
|
+
service_factory = validate_config_and_get_service_factory()
|
|
176
|
+
task_service = service_factory.get_task_service()
|
|
177
|
+
|
|
178
|
+
task_service.print_task_list(project_public_id=project_public_id, project_slug=project_slug, row=row, page=page)
|
|
179
|
+
|
|
180
|
+
typer.echo(__("Tasks listing complete"))
|
|
181
|
+
raise typer.Exit(0)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@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))
|
|
185
|
+
def task_logs(
|
|
186
|
+
task_id: Optional[str] = typer.Argument(help=__("Task ID"),),
|
|
187
|
+
logs_number: Optional[int] = typer.Option(
|
|
188
|
+
None,
|
|
189
|
+
'--number',
|
|
190
|
+
'-n',
|
|
191
|
+
help=__("Display a number of latest log entries. No real-time stream if provided."),
|
|
192
|
+
is_eager=False,
|
|
193
|
+
),
|
|
194
|
+
):
|
|
195
|
+
command_name = CliCommand.PROJECT_TASK_LOGS
|
|
196
|
+
app_logger.info(f'Running {command_name} from {get_current_directory()}')
|
|
197
|
+
check_command_permission(command_name)
|
|
198
|
+
|
|
199
|
+
if not task_id:
|
|
200
|
+
typer.echo(__('Task ID is required'))
|
|
201
|
+
raise typer.Exit(1)
|
|
202
|
+
|
|
203
|
+
service_factory = validate_config_and_get_service_factory()
|
|
204
|
+
logging_service: LoggingService = service_factory.get_logging_service()
|
|
205
|
+
|
|
206
|
+
if logs_number is None:
|
|
207
|
+
logging_service.stream_task_logs_with_controls(task_public_id=task_id)
|
|
208
|
+
else:
|
|
209
|
+
logging_service.print_last_task_logs(task_public_id=task_id, logs_number=logs_number)
|
|
210
|
+
|
|
211
|
+
app_logger.info(f'Task logs - end')
|
|
212
|
+
raise typer.Exit(0)
|
|
File without changes
|
|
File without changes
|
|
@@ -3,10 +3,10 @@ from pydantic import Field, ConfigDict
|
|
|
3
3
|
|
|
4
4
|
from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
|
|
5
5
|
from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
|
|
6
|
-
from thestage.
|
|
6
|
+
from thestage.task.dto.task import Task
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TaskListForProjectResponse(TheStageBaseResponse):
|
|
10
10
|
model_config = ConfigDict(use_enum_values=True)
|
|
11
11
|
|
|
12
|
-
tasks: PaginatedEntityList[
|
|
12
|
+
tasks: PaginatedEntityList[Task] = Field(None, alias='tasks')
|
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
|
3
3
|
from pydantic import Field, ConfigDict, BaseModel
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class RunTaskRequest(BaseModel):
|
|
7
7
|
model_config = ConfigDict(use_enum_values=True)
|
|
8
8
|
|
|
9
9
|
projectPublicId: str = Field(None, alias='projectPublicId')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, ConfigDict
|
|
4
|
+
|
|
5
|
+
from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
|
|
6
|
+
from thestage.task.dto.task import Task
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RunTaskResponse(TheStageBaseResponse):
|
|
10
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
11
|
+
|
|
12
|
+
task: Task = Field(None, alias='task')
|
|
13
|
+
tasksInQueue: Optional[List[Task]] = Field(None, alias='tasksInQueue')
|