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
File without changes
@@ -1,9 +1,9 @@
1
1
  from typing import Optional, List
2
2
 
3
- from pydantic import Field, ConfigDict, BaseModel
3
+ from pydantic import Field, ConfigDict
4
4
 
5
5
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
6
- from thestage.services.logging.dto.log_message import LogMessage
6
+ from thestage.logging.dto.log_message import LogMessage
7
7
 
8
8
 
9
9
  class LogPollingResponse(TheStageBaseResponse):
@@ -1,9 +1,9 @@
1
1
  from typing import Optional, List
2
2
 
3
- from pydantic import Field, ConfigDict, BaseModel
3
+ from pydantic import Field, ConfigDict
4
4
 
5
5
  from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
6
- from thestage.services.logging.dto.log_message import LogMessage
6
+ from thestage.logging.dto.log_message import LogMessage
7
7
 
8
8
 
9
9
  class UserLogsQueryResponse(TheStageBaseResponse):
thestage/main.py CHANGED
@@ -1,8 +1,8 @@
1
- from thestage.helpers import exception_hook
2
1
  import traceback
3
2
 
4
3
  from thestage.cli_command_helper import get_command_group_help_panel
5
4
  from thestage.helpers.logger.app_logger import app_logger, get_log_path_from_os
5
+ from thestage.services.clients.thestage_api.core.api_client_core import TheStageApiClientCore
6
6
  from thestage.services.service_factory import ServiceFactory
7
7
  from rich import print
8
8
 
@@ -14,7 +14,7 @@ def main():
14
14
 
15
15
  try:
16
16
  try:
17
- api_client = service_factory.get_thestage_api_client()
17
+ api_client = TheStageApiClientCore(url=config.main.thestage_api_url)
18
18
  token_info = api_client.validate_token(config.main.thestage_auth_token)
19
19
  config_provider.update_allowed_commands_and_is_token_valid(validate_token_response=token_info)
20
20
  except Exception as e:
@@ -23,13 +23,52 @@ def main():
23
23
  print(f'Application logs path: {str(get_log_path_from_os())}')
24
24
  return
25
25
 
26
- from thestage.controllers import base_controller, container_controller, instance_controller, project_controller, \
27
- config_controller
26
+ from thestage.controllers import base_controller
27
+ from thestage.config.communication import config_command
28
+ from thestage.project.communication import project_command
29
+ from thestage.inference_simulator.communication import inference_simulator_command
30
+ from thestage.inference_model.communication import inference_model_command
31
+ from thestage.task.communication import task_command
32
+ from thestage.instance.communication import instance_command
33
+ from thestage.docker_container.communication import docker_command
28
34
 
29
- base_controller.app.add_typer(project_controller.app, name="project", rich_help_panel=get_command_group_help_panel())
30
- base_controller.app.add_typer(container_controller.app, name="container", rich_help_panel=get_command_group_help_panel())
31
- base_controller.app.add_typer(instance_controller.app, name="instance", rich_help_panel=get_command_group_help_panel())
32
- base_controller.app.add_typer(config_controller.app, name="config", rich_help_panel=get_command_group_help_panel())
35
+ project_command.app.add_typer(
36
+ inference_simulator_command.app,
37
+ name="inference-simulator",
38
+ rich_help_panel=get_command_group_help_panel()
39
+ )
40
+ project_command.app.add_typer(
41
+ inference_model_command.app,
42
+ name="model",
43
+ rich_help_panel=get_command_group_help_panel()
44
+ )
45
+ project_command.app.add_typer(
46
+ task_command.app,
47
+ name="task",
48
+ rich_help_panel=get_command_group_help_panel()
49
+ )
50
+
51
+ base_controller.app.add_typer(
52
+ project_command.app,
53
+ name="project",
54
+ rich_help_panel=get_command_group_help_panel()
55
+ )
56
+
57
+ base_controller.app.add_typer(
58
+ docker_command.app,
59
+ name="container",
60
+ rich_help_panel=get_command_group_help_panel()
61
+ )
62
+ base_controller.app.add_typer(
63
+ instance_command.app,
64
+ name="instance",
65
+ rich_help_panel=get_command_group_help_panel()
66
+ )
67
+ base_controller.app.add_typer(
68
+ config_command.app,
69
+ name="config",
70
+ rich_help_panel=get_command_group_help_panel()
71
+ )
33
72
 
34
73
  base_controller.app()
35
74
  except KeyboardInterrupt:
File without changes
File without changes
@@ -0,0 +1,480 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ import click
5
+ import typer
6
+ from rich import print
7
+ from tabulate import tabulate
8
+
9
+ from thestage.color_scheme.color_scheme import ColorScheme
10
+ from thestage.config.business.config_provider import ConfigProvider
11
+ from thestage.connect.business.remote_server_service import RemoteServerService
12
+ from thestage.docker_container.communication.docker_container_api_client import DockerContainerApiClient
13
+ from thestage.docker_container.dto.container_response import DockerContainerDto
14
+ from thestage.docker_container.dto.enum.container_status import DockerContainerStatus
15
+ from thestage.exceptions.git_access_exception import GitAccessException
16
+ from thestage.git.communication.git_client import GitLocalClient
17
+ from thestage.global_dto.enums.yes_no_response import YesOrNoResponse
18
+ from thestage.helpers.error_handler import error_handler
19
+ from thestage.i18n.translation import __
20
+ from thestage.project.communication.project_api_client import ProjectApiClient
21
+ from thestage.project.dto.project_config import ProjectConfig
22
+ from thestage.project.dto.project_response import ProjectDto
23
+ from thestage.services.abstract_service import AbstractService
24
+ from thestage.services.clients.thestage_api.core.http_client_exception import HttpClientException
25
+ from thestage.services.filesystem_service import FileSystemService
26
+ from thestage.task.communication.task_api_client import TaskApiClient
27
+ from thestage.task.dto.view_response import TaskViewResponse
28
+
29
+
30
+ class ProjectService(AbstractService):
31
+ __docker_container_api_client: DockerContainerApiClient = None
32
+ __project_api_client: ProjectApiClient = None
33
+ __task_api_client: TaskApiClient = None
34
+ __config_provider: ConfigProvider = None
35
+
36
+ def __init__(
37
+ self,
38
+ task_api_client: TaskApiClient,
39
+ project_api_client: ProjectApiClient,
40
+ docker_container_api_client: DockerContainerApiClient,
41
+ config_provider: ConfigProvider,
42
+ remote_server_service: RemoteServerService,
43
+ file_system_service: FileSystemService,
44
+ git_local_client: GitLocalClient,
45
+ ):
46
+ self.__docker_container_api_client = docker_container_api_client
47
+ self.__task_api_client = task_api_client
48
+ self.__project_api_client = project_api_client
49
+ self.__remote_server_service = remote_server_service
50
+ self.__file_system_service = file_system_service
51
+ self.__git_local_client = git_local_client
52
+ self.__config_provider = config_provider
53
+
54
+
55
+ @error_handler()
56
+ def init_project(
57
+ self,
58
+ project_slug: Optional[str] = None,
59
+ project_public_id: Optional[str] = None,
60
+ ):
61
+ config = self.__config_provider.get_config()
62
+ project: Optional[ProjectDto] = self.__project_api_client.get_project(
63
+ slug=project_slug,
64
+ public_id=project_public_id,
65
+ )
66
+
67
+ if not project:
68
+ typer.echo('Project not found')
69
+ raise typer.Exit(1)
70
+
71
+ is_git_folder = self.__git_local_client.is_present_local_git(
72
+ path=config.runtime.working_directory,
73
+ )
74
+ if is_git_folder:
75
+ has_remote = self.__git_local_client.has_remote(
76
+ path=config.runtime.working_directory,
77
+ )
78
+ if has_remote:
79
+ typer.echo(__('Local repository already has a remote configured; aborting initialization'))
80
+ raise typer.Exit(1)
81
+
82
+ if not project.git_repository_url:
83
+ typer.echo(__('Project does not have git repository url'))
84
+ raise typer.Exit(1)
85
+
86
+ if project.last_commit_hash or project.last_commit_description:
87
+ continue_with_non_empty_repo: YesOrNoResponse = typer.prompt(
88
+ 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),
89
+ show_choices=True,
90
+ default=YesOrNoResponse.YES.value,
91
+ type=click.Choice([r.value for r in YesOrNoResponse]),
92
+ show_default=True,
93
+ )
94
+ if continue_with_non_empty_repo == YesOrNoResponse.NO:
95
+ typer.echo(__('Project init aborted'))
96
+ raise typer.Exit(0)
97
+
98
+ deploy_ssh_key = self.__project_api_client.get_project_deploy_ssh_key(
99
+ public_id=project.public_id,
100
+ )
101
+
102
+ deploy_key_path = self.__config_provider.save_project_deploy_ssh_key(
103
+ deploy_ssh_key=deploy_ssh_key,
104
+ project_public_id=project.public_id,
105
+ )
106
+
107
+ if is_git_folder:
108
+ has_changes = self.__git_local_client.has_changes_with_untracked(
109
+ path=config.runtime.working_directory,
110
+ )
111
+ if has_changes:
112
+ typer.echo(__('Local repository has uncommitted changes or untracked files. Use an empty directory'))
113
+ raise typer.Exit(1)
114
+ else:
115
+ repo = self.__git_local_client.init_repository(
116
+ path=config.runtime.working_directory,
117
+ )
118
+
119
+ is_remote_added = self.__git_local_client.add_remote_to_repo(
120
+ path=config.runtime.working_directory,
121
+ remote_url=project.git_repository_url,
122
+ remote_name=project.git_repository_name,
123
+ )
124
+ if not is_remote_added:
125
+ typer.echo(__('We cannot add remote, something wrong'))
126
+ raise typer.Exit(2)
127
+
128
+ self.__git_local_client.git_fetch(path=config.runtime.working_directory, deploy_key_path=deploy_key_path)
129
+
130
+ self.__git_local_client.init_gitignore(path=config.runtime.working_directory)
131
+
132
+ self.__git_local_client.git_add_all(repo_path=config.runtime.working_directory)
133
+
134
+ project_config = ProjectConfig()
135
+ project_config.public_id = project.public_id
136
+ project_config.slug = project.slug
137
+ project_config.git_repository_url = project.git_repository_url
138
+ project_config.deploy_key_path = str(deploy_key_path)
139
+ self.__config_provider.save_project_config(project_config=project_config)
140
+
141
+ typer.echo(__("Project successfully initialized at %path%", {"path": config.runtime.working_directory}))
142
+
143
+
144
+ @error_handler()
145
+ def clone_project(
146
+ self,
147
+ project_slug: str,
148
+ project_public_id: str
149
+ ):
150
+ config = self.__config_provider.get_config()
151
+ project: Optional[ProjectDto] = self.__project_api_client.get_project(
152
+ slug=project_slug,
153
+ public_id=project_public_id
154
+ )
155
+
156
+ if not project:
157
+ typer.echo('Project not found')
158
+ raise typer.Exit(1)
159
+
160
+ if not self.__file_system_service.is_folder_empty(folder=config.runtime.working_directory, auto_create=True):
161
+ typer.echo(__("Cannot clone: the folder is not empty"))
162
+ raise typer.Exit(1)
163
+
164
+ is_git_folder = self.__git_local_client.is_present_local_git(
165
+ path=config.runtime.working_directory,
166
+ )
167
+
168
+ if is_git_folder:
169
+ typer.echo(__('Directory already contains a git repository. Cannot clone here'))
170
+ raise typer.Exit(1)
171
+
172
+ if not project.git_repository_url:
173
+ typer.echo(__("Unexpected Project error, missing Repository"))
174
+ raise typer.Exit(1)
175
+
176
+ deploy_ssh_key = self.__project_api_client.get_project_deploy_ssh_key(public_id=project.public_id)
177
+ deploy_key_path = self.__config_provider.save_project_deploy_ssh_key(deploy_ssh_key=deploy_ssh_key, project_public_id=project.public_id,)
178
+
179
+ try:
180
+ self.__git_local_client.clone(
181
+ url=project.git_repository_url,
182
+ path=config.runtime.working_directory,
183
+ deploy_key_path=deploy_key_path
184
+ )
185
+ self.__git_local_client.init_gitignore(path=config.runtime.working_directory)
186
+ except GitAccessException as ex:
187
+ typer.echo(ex.get_message())
188
+ typer.echo(ex.get_dop_message())
189
+ typer.echo(__(
190
+ "Check you email or open this repo url %git_url% and 'Accept invitation'",
191
+ {
192
+ 'git_url': ex.get_url()
193
+ }
194
+ ))
195
+ raise typer.Exit(1)
196
+
197
+ project_config = ProjectConfig()
198
+ project_config.public_id = project.public_id
199
+ project_config.slug = project.slug
200
+ project_config.git_repository_url = project.git_repository_url
201
+ project_config.deploy_key_path = str(deploy_key_path)
202
+ self.__config_provider.save_project_config(project_config=project_config)
203
+ typer.echo(__("Project successfully cloned to %path%", {"path": config.runtime.working_directory}))
204
+
205
+ @error_handler()
206
+ def checkout_project(
207
+ self,
208
+ task_public_id: Optional[str],
209
+ branch_name: Optional[str],
210
+ ):
211
+ config = self.__config_provider.get_config()
212
+ project_config: ProjectConfig = self.get_fixed_project_config()
213
+ if not project_config:
214
+ typer.echo(__("Command must be run from an initialized project directory"))
215
+ raise typer.Exit(1)
216
+
217
+ target_commit_hash: Optional[str] = None
218
+ if task_public_id:
219
+ task_view_response: Optional[TaskViewResponse] = None
220
+ try:
221
+ task_view_response = self.__task_api_client.get_task(task_public_id=task_public_id)
222
+ except HttpClientException as e:
223
+ if e.get_status_code() == 400:
224
+ typer.echo(f"Task {task_public_id} was not found")
225
+ # overriding arguments here
226
+ branch_name = str(task_public_id)
227
+ task_public_id = None
228
+
229
+ if task_view_response and task_view_response.task:
230
+ target_commit_hash = task_view_response.task.commit_hash
231
+ if not target_commit_hash:
232
+ typer.echo(f"Task ({task_public_id}) has no commit hash") # possible legacy problems
233
+ raise typer.Exit(1)
234
+
235
+ is_commit_allowed: bool = True
236
+
237
+ if self.__git_local_client.is_head_detached(path=config.runtime.working_directory):
238
+ is_commit_allowed = False
239
+ if self.__git_local_client.is_head_committed_in_headless_state(path=config.runtime.working_directory):
240
+ commit_message = self.__git_local_client.get_current_commit(
241
+ path=config.runtime.working_directory).message
242
+ print(
243
+ 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}]")
244
+ response: YesOrNoResponse = typer.prompt(
245
+ text=__('Continue?'),
246
+ show_choices=True,
247
+ default=YesOrNoResponse.YES.value,
248
+ type=click.Choice([r.value for r in YesOrNoResponse]),
249
+ show_default=True,
250
+ )
251
+ if response == YesOrNoResponse.NO:
252
+ raise typer.Exit(0)
253
+ else:
254
+ if self.__git_local_client.get_active_branch_name(path=config.runtime.working_directory) == branch_name:
255
+ typer.echo(f"You are already at branch '{branch_name}'")
256
+ raise typer.Exit(0)
257
+
258
+ if is_commit_allowed:
259
+ self.__git_local_client.git_add_all(repo_path=config.runtime.working_directory)
260
+
261
+ has_changes = self.__git_local_client.has_changes_with_untracked(
262
+ path=config.runtime.working_directory,
263
+ )
264
+
265
+ if has_changes:
266
+ active_branch_name = self.__git_local_client.get_active_branch_name(config.runtime.working_directory)
267
+ diff_stat = self.__git_local_client.git_diff_stat(repo_path=config.runtime.working_directory)
268
+ typer.echo(__('Active branch [%branch_name%] has uncommitted changes: %diff_stat_bottomline%', {
269
+ 'diff_stat_bottomline': diff_stat,
270
+ 'branch_name': active_branch_name,
271
+ }))
272
+
273
+ response: str = typer.prompt(
274
+ text=__('Commit changes?'),
275
+ show_choices=True,
276
+ default=YesOrNoResponse.YES.value,
277
+ type=click.Choice([r.value for r in YesOrNoResponse]),
278
+ show_default=True,
279
+ )
280
+ if response == YesOrNoResponse.NO.value:
281
+ typer.echo(__('Cannot checkout with uncommitted changes'))
282
+ raise typer.Exit(0)
283
+
284
+ commit_name = typer.prompt(
285
+ text=__('Please provide commit message'),
286
+ show_choices=False,
287
+ type=str,
288
+ show_default=False,
289
+ )
290
+
291
+ if commit_name:
292
+ commit_result = self.__git_local_client.commit_local_changes(
293
+ path=config.runtime.working_directory,
294
+ name=commit_name
295
+ )
296
+
297
+ if commit_result:
298
+ # in docs not Commit object, on real - str
299
+ if isinstance(commit_result, str):
300
+ typer.echo(commit_result)
301
+
302
+ self.__git_local_client.push_changes(
303
+ path=config.runtime.working_directory,
304
+ deploy_key_path=project_config.deploy_key_path
305
+ )
306
+ typer.echo(__("Pushed changes to remote repository"))
307
+ else:
308
+ typer.echo(__('Cannot commit with empty commit name'))
309
+ raise typer.Exit(0)
310
+
311
+ if target_commit_hash:
312
+ if self.__git_local_client.get_current_commit(
313
+ path=config.runtime.working_directory).hexsha != target_commit_hash:
314
+ is_checkout_successful = self.__git_local_client.git_checkout_to_commit(
315
+ path=config.runtime.working_directory,
316
+ commit_hash=target_commit_hash
317
+ )
318
+
319
+ if is_checkout_successful:
320
+ print(f"Checked out to commit {target_commit_hash}")
321
+ print(
322
+ f"[{ColorScheme.GIT_HEADLESS.value}]HEAD is detached. To be able make changes in repository, checkout to any branch.[/{ColorScheme.GIT_HEADLESS.value}]")
323
+ else:
324
+ typer.echo("HEAD is already at requested commit")
325
+ elif branch_name:
326
+ if self.__git_local_client.is_branch_exists(path=config.runtime.working_directory, branch_name=branch_name):
327
+ self.__git_local_client.git_checkout_to_branch(
328
+ path=config.runtime.working_directory,
329
+ branch=branch_name
330
+ )
331
+ typer.echo(f"Checked out to branch '{branch_name}'")
332
+ else:
333
+ typer.echo(f"Branch '{branch_name}' was not found in project repository")
334
+ else:
335
+ main_branch = self.__git_local_client.find_main_branch_name(path=config.runtime.working_directory)
336
+ if main_branch:
337
+ self.__git_local_client.git_checkout_to_branch(
338
+ path=config.runtime.working_directory,
339
+ branch=main_branch
340
+ )
341
+ typer.echo(f"Checked out to detected main branch: '{main_branch}'")
342
+ else:
343
+ typer.echo("No main branch found")
344
+
345
+ @error_handler()
346
+ def set_default_container(
347
+ self,
348
+ container_public_id: Optional[str],
349
+ container_slug: Optional[str],
350
+ ):
351
+ project_config: ProjectConfig = self.__config_provider.read_project_config()
352
+
353
+ if project_config is None:
354
+ typer.echo(f"No project found in working directory")
355
+ raise typer.Exit(1)
356
+
357
+ container: Optional[DockerContainerDto] = None
358
+ if container_slug or container_public_id:
359
+ container: DockerContainerDto = self.__docker_container_api_client.get_container(
360
+ container_public_id=container_public_id,
361
+ container_slug=container_slug,
362
+ )
363
+ if container is None:
364
+ typer.echo(f"Could not find container '{container_slug or container_public_id}'")
365
+ raise typer.Exit(1)
366
+
367
+ if container.project.public_id != project_config.public_id:
368
+ typer.echo(
369
+ f"Provided container '{container_slug or container_public_id}' is not related to current project '{project_config.public_id}'")
370
+ raise typer.Exit(1)
371
+
372
+ if container.frontend_status.status_key != DockerContainerStatus.RUNNING:
373
+ typer.echo(
374
+ f"Note: provided container '{container_slug or container_public_id}' is in status '{container.frontend_status.status_translation}'")
375
+
376
+ project_config.default_container_public_id = container.public_id if container else None
377
+ project_config.prompt_for_default_container = False
378
+ self.__config_provider.save_project_config(project_config=project_config)
379
+ typer.echo("Default container settings were updated")
380
+
381
+ @error_handler()
382
+ def print_project_config(self):
383
+ project_config: ProjectConfig = self.__config_provider.read_project_config()
384
+
385
+ if project_config is None:
386
+ typer.echo(f"No project found in working directory")
387
+ raise typer.Exit(1)
388
+
389
+ is_deploy_key_exists = project_config.deploy_key_path and self.__file_system_service.check_if_path_exist(
390
+ project_config.deploy_key_path)
391
+
392
+ typer.echo(tabulate(
393
+ [
394
+ [
395
+ "Project ID", project_config.public_id
396
+ ],
397
+ [
398
+ "Project name", project_config.slug
399
+ ],
400
+ [
401
+ "Default docker container ID",
402
+ project_config.default_container_public_id if project_config.default_container_public_id else "<None>"
403
+ ],
404
+ [
405
+ "Deploy key path", project_config.deploy_key_path if is_deploy_key_exists else "<None>"
406
+ ],
407
+ ],
408
+ showindex=False,
409
+ tablefmt="simple",
410
+ ))
411
+
412
+ if is_deploy_key_exists:
413
+ typer.echo("")
414
+ typer.echo(f"You can insert the following text:")
415
+ print(
416
+ 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}]")
417
+ typer.echo(f"before any regular git command to manage your local Project repository directly")
418
+
419
+ @error_handler()
420
+ def get_fixed_project_config(self) -> Optional[ProjectConfig]:
421
+ project_config: ProjectConfig = self.__config_provider.read_project_config()
422
+ if project_config is None:
423
+ return None
424
+
425
+ if project_config.public_id is None:
426
+ project = self.__project_api_client.get_project(public_id=None, slug=project_config.slug)
427
+ project_config.public_id = project.public_id
428
+ self.__config_provider.save_project_config(project_config=project_config)
429
+
430
+ if not Path(project_config.deploy_key_path).is_file():
431
+ deploy_ssh_key = self.__project_api_client.get_project_deploy_ssh_key(
432
+ public_id=project_config.public_id,
433
+ )
434
+
435
+ deploy_key_path = self.__config_provider.save_project_deploy_ssh_key(
436
+ deploy_ssh_key=deploy_ssh_key,
437
+ project_public_id=project_config.public_id,
438
+ )
439
+
440
+ project_config.deploy_key_path = deploy_key_path
441
+ self.__config_provider.save_project_config(project_config=project_config)
442
+ typer.echo(f'Recreated missing deploy key for the project')
443
+
444
+ return project_config
445
+
446
+ @error_handler()
447
+ def pull_project(self):
448
+ config = self.__config_provider.get_config()
449
+ project_config: ProjectConfig = self.get_fixed_project_config()
450
+ if not project_config:
451
+ typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first.",
452
+ {"path": config.runtime.working_directory}))
453
+ raise typer.Exit(1)
454
+
455
+ typer.echo("Pulling code from remote repository...")
456
+ self.__git_local_client.git_pull(
457
+ path=config.runtime.working_directory,
458
+ deploy_key_path=project_config.deploy_key_path,
459
+ )
460
+
461
+ @error_handler()
462
+ def reset_project(self):
463
+ config = self.__config_provider.get_config()
464
+ project_config: ProjectConfig = self.get_fixed_project_config()
465
+ if not project_config:
466
+ typer.echo(__("No project found at the path: %path%. Please initialize or clone a project first.",
467
+ {"path": config.runtime.working_directory}))
468
+ raise typer.Exit(1)
469
+
470
+ typer.echo("Fetching code from remote repository...")
471
+ self.__git_local_client.git_fetch(
472
+ path=config.runtime.working_directory,
473
+ deploy_key_path=project_config.deploy_key_path,
474
+ )
475
+ typer.echo("Resetting local branch...")
476
+ self.__git_local_client.reset_hard(
477
+ path=config.runtime.working_directory,
478
+ deploy_key_path=project_config.deploy_key_path,
479
+ reset_to_origin=True
480
+ )
File without changes
@@ -0,0 +1,46 @@
1
+ from typing import Optional
2
+
3
+ from thestage.config.business.config_provider import ConfigProvider
4
+ from thestage.services.clients.thestage_api.core.api_client_core import TheStageApiClientCore
5
+
6
+ from thestage.project.dto.project_response import ProjectDto, ProjectViewResponse
7
+ from thestage.project.dto.get_deploy_ssh_key_request import ProjectGetDeploySshKeyRequest
8
+ from thestage.project.dto.get_deploy_ssh_key_response import ProjectGetDeploySshKeyResponse
9
+
10
+
11
+ class ProjectApiClient(TheStageApiClientCore):
12
+ def __init__(self, config_provider: ConfigProvider):
13
+ super().__init__(url=config_provider.get_config().main.thestage_api_url)
14
+ self.__config_provider = config_provider
15
+
16
+ def get_project(self, slug: Optional[str], public_id: Optional[str]) -> Optional[ProjectDto]:
17
+ data = {
18
+ "projectSlug": slug,
19
+ "projectPublicId": public_id,
20
+ }
21
+
22
+ response = self._request(
23
+ method='POST',
24
+ url='/user-api/v2/project/view',
25
+ data=data,
26
+ token=self.__config_provider.get_config().main.thestage_auth_token,
27
+ )
28
+
29
+ result = ProjectViewResponse.model_validate(response) if response else None
30
+ project = ProjectDto.model_validate(result.project) if result else None
31
+ return project if result and result.is_success else None
32
+
33
+ def get_project_deploy_ssh_key(self, public_id: str) -> str:
34
+ request = ProjectGetDeploySshKeyRequest(
35
+ projectPublicId=public_id,
36
+ )
37
+
38
+ response = self._request(
39
+ method='POST',
40
+ url='/user-api/v1/project/get-deploy-ssh-key',
41
+ data=request.model_dump(),
42
+ token=self.__config_provider.get_config().main.thestage_auth_token,
43
+ )
44
+
45
+ result = ProjectGetDeploySshKeyResponse.model_validate(response) if response else None
46
+ return result.privateKey if result and result.is_success else None