thestage 0.5.43__py3-none-any.whl → 0.5.45__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 CHANGED
@@ -1,3 +1,3 @@
1
1
  from . import *
2
2
  __app_name__ = "thestage"
3
- __version__ = "0.5.43"
3
+ __version__ = "0.5.45"
@@ -1,6 +1,7 @@
1
1
  from enum import Enum
2
2
 
3
-
3
+ # https://rich.readthedocs.io/en/stable/appendix/colors.html
4
4
  class ColorScheme(str, Enum):
5
5
  GIT_HEADLESS = "orange_red1"
6
6
  WARNING = "orange_red1"
7
+ USEFUL_INFO = "deep_sky_blue1"
@@ -3,13 +3,10 @@ import re
3
3
  from pathlib import Path
4
4
  from typing import Optional, List
5
5
 
6
- from thestage.entities.container import DockerContainerEntity
7
6
  from thestage.services.clients.thestage_api.dtos.enums.container_pending_action import DockerContainerAction
8
7
  from thestage.services.clients.thestage_api.dtos.container_response import DockerContainerDto
9
8
  from thestage.i18n.translation import __
10
- from thestage.services.clients.thestage_api.dtos.enums.container_status import DockerContainerStatus
11
9
  from thestage.services.container.container_service import ContainerService
12
- from thestage.services.container.mapper.container_mapper import ContainerMapper
13
10
  from thestage.helpers.logger.app_logger import app_logger
14
11
  from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
15
12
 
@@ -57,46 +57,12 @@ def rented_list(
57
57
  config = service_factory.get_config_provider().get_full_config()
58
58
 
59
59
  instance_service: InstanceService = service_factory.get_instance_service()
60
- instance_rented_status_map = service_factory.get_thestage_api_client().get_rented_business_status_map(config.main.thestage_auth_token)
61
-
62
- if not statuses:
63
- statuses = ({key: instance_rented_status_map[key] for key in [
64
- InstanceRentedBusinessStatus.ONLINE,
65
- InstanceRentedBusinessStatus.CREATING,
66
- InstanceRentedBusinessStatus.TERMINATING,
67
- InstanceRentedBusinessStatus.REBOOTING,
68
- ]}).values()
69
-
70
- if "all" in statuses:
71
- statuses = instance_rented_status_map.values()
72
-
73
- for input_status_item in statuses:
74
- if input_status_item not in instance_rented_status_map.values():
75
- typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
76
- 'invalid_status': input_status_item,
77
- 'valid_statuses': str(list(instance_rented_status_map.values()))
78
- }))
79
- raise typer.Exit(1)
80
-
81
- typer.echo(__(
82
- "Listing rented server instances with the following statuses: %statuses%, to view all rented server instances, use --status all",
83
- placeholders={
84
- 'statuses': ', '.join([status_item for status_item in statuses])
85
- }))
86
-
87
- backend_statuses: List[str] = [key for key, value in instance_rented_status_map.items() if value in statuses]
88
-
89
- instance_service.print(
90
- func_get_data=instance_service.get_rented_list,
91
- func_special_params={
92
- 'statuses': backend_statuses,
93
- },
94
- mapper=InstanceMapper(),
60
+
61
+ instance_service.print_rented_instance_list(
95
62
  config=config,
96
- headers=list(map(lambda x: x.alias, RentedInstanceEntity.model_fields.values())),
63
+ statuses=statuses,
97
64
  row=row,
98
- page=page,
99
- show_index="never",
65
+ page=page
100
66
  )
101
67
 
102
68
  typer.echo(__("Rented server instances listing complete"))
@@ -175,45 +141,12 @@ def self_hosted_list(
175
141
  config = service_factory.get_config_provider().get_full_config()
176
142
 
177
143
  instance_service: InstanceService = service_factory.get_instance_service()
178
- selfhosted_instance_status_map = service_factory.get_thestage_api_client().get_selfhosted_business_status_map(config.main.thestage_auth_token)
179
-
180
- if not statuses:
181
- statuses = ({key: selfhosted_instance_status_map[key] for key in [
182
- SelfhostedBusinessStatus.AWAITING_CONFIGURATION,
183
- SelfhostedBusinessStatus.RUNNING,
184
- SelfhostedBusinessStatus.TERMINATED,
185
- ]}).values()
186
-
187
- if "all" in statuses:
188
- statuses = selfhosted_instance_status_map.values()
189
-
190
- for input_status_item in statuses:
191
- if input_status_item not in selfhosted_instance_status_map.values():
192
- typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
193
- 'invalid_status': input_status_item,
194
- 'valid_statuses': str(list(selfhosted_instance_status_map.values()))
195
- }))
196
- raise typer.Exit(1)
197
-
198
- typer.echo(__(
199
- "Listing self-hosted instances with the following statuses: %statuses%, to view all self-hosted instances, use --status all",
200
- placeholders={
201
- 'statuses': ', '.join([status_item for status_item in statuses])
202
- }))
203
-
204
- backend_statuses: List[str] = [key for key, value in selfhosted_instance_status_map.items() if value in statuses]
205
-
206
- instance_service.print(
207
- func_get_data=instance_service.get_self_hosted_list,
208
- func_special_params={
209
- 'statuses': backend_statuses,
210
- },
211
- mapper=SelfHostedMapper(),
144
+
145
+ instance_service.print_self_hosted_instance_list(
212
146
  config=config,
213
- headers=list(map(lambda x: x.alias, SelfHostedInstanceEntity.model_fields.values())),
147
+ statuses=statuses,
214
148
  row=row,
215
- page=page,
216
- show_index="never",
149
+ page=page
217
150
  )
218
151
 
219
152
  typer.echo(__("Self-hosted instances listing complete"))
@@ -183,6 +183,34 @@ def run(
183
183
  raise typer.Exit(0)
184
184
 
185
185
 
186
+ @task_app.command(name='cancel', no_args_is_help=True, help=__("Cancel a task by ID"))
187
+ def cancel_task(
188
+ task_id: Annotated[int, typer.Argument(
189
+ help=__("Task ID (required)"),
190
+ )],
191
+ ):
192
+ """
193
+ Cancels a task
194
+ """
195
+ app_logger.info(f'Start cancel task from {get_current_directory()}')
196
+
197
+ if not task_id:
198
+ typer.echo(__('Task ID is required'))
199
+ raise typer.Exit(1)
200
+
201
+ service_factory = validate_config_and_get_service_factory()
202
+ config = service_factory.get_config_provider().get_full_config()
203
+
204
+ project_service = service_factory.get_project_service()
205
+
206
+ project_service.cancel_task(
207
+ config=config,
208
+ task_id=task_id
209
+ )
210
+
211
+ raise typer.Exit(0)
212
+
213
+
186
214
  @task_app.command("ls", help=__("List tasks"))
187
215
  def list_runs(
188
216
  project_uid: Annotated[str, typer.Argument(help=__("Project unique ID. By default, project info is taken from the current directory"), metavar="OPTIONAL")] = None,
@@ -208,29 +236,9 @@ def list_runs(
208
236
 
209
237
  service_factory = validate_config_and_get_service_factory()
210
238
  config = service_factory.get_config_provider().get_full_config()
211
-
212
239
  project_service: ProjectService = service_factory.get_project_service()
213
240
 
214
- if not project_uid:
215
- project_config: ProjectConfig = service_factory.get_config_provider().read_project_config()
216
- if not project_config:
217
- typer.echo(__("Provide the project unique ID or run this command from within an initialized project directory"))
218
- raise typer.Exit(1)
219
- project_uid = project_config.slug
220
-
221
- project_service.print(
222
- func_get_data=project_service.get_project_task_list,
223
- func_special_params={
224
- 'project_slug': project_uid,
225
- },
226
- mapper=ProjectTaskMapper(),
227
- config=config,
228
- headers=list(map(lambda x: x.alias, ProjectTaskEntity.model_fields.values())),
229
- row=row,
230
- page=page,
231
- max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
232
- show_index="never",
233
- )
241
+ project_service.print_task_list(config, project_uid, row, page)
234
242
 
235
243
  typer.echo(__("Tasks listing complete"))
236
244
  raise typer.Exit(0)
@@ -287,9 +295,9 @@ def checkout_project(
287
295
  ),
288
296
  ):
289
297
  """
290
- Initializes project in current working directory
298
+ Checkout project in current working directory
291
299
  """
292
- app_logger.info(f'Start project init from {get_current_directory()}')
300
+ app_logger.info(f'Start project checkout from {get_current_directory()}')
293
301
 
294
302
  service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
295
303
  config = service_factory.get_config_provider().get_full_config()
@@ -315,11 +323,8 @@ def checkout_project(
315
323
  raise typer.Exit(0)
316
324
 
317
325
 
318
- @config_app.command(name='set-default-container', no_args_is_help=True, help=__("Set default docker container for a project installation"))
319
- def set_default_container(
320
- container_uid: Annotated[Optional[str], typer.Argument(
321
- help=__("Unique ID of the container to use by default for running tasks"),
322
- )] = None,
326
+ @app.command(name='pull', help=__("Pulls the changes from the remote project repository. Equivalent to 'git pull'."))
327
+ def pull_project(
323
328
  working_directory: Optional[str] = typer.Option(
324
329
  None,
325
330
  "--working-directory",
@@ -327,6 +332,56 @@ def set_default_container(
327
332
  help=__("Full path to working directory"),
328
333
  is_eager=False,
329
334
  ),
335
+ ):
336
+ """
337
+ Pull project in current working directory
338
+ """
339
+ app_logger.info(f'Start project pull from {get_current_directory()}')
340
+
341
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
342
+ config = service_factory.get_config_provider().get_full_config()
343
+
344
+ project_service = service_factory.get_project_service()
345
+
346
+ project_service.pull_project(
347
+ config=config,
348
+ )
349
+
350
+ raise typer.Exit(0)
351
+
352
+
353
+ @app.command(name='reset', help=__("Resets the current project branch to remote counterpart. All working tree changes will be lost. Equivalent to 'git fetch && git reset --hard origin/{ref}'."))
354
+ def reset_project(
355
+ working_directory: Optional[str] = typer.Option(
356
+ None,
357
+ "--working-directory",
358
+ "-wd",
359
+ help=__("Full path to working directory"),
360
+ is_eager=False,
361
+ ),
362
+ ):
363
+ """
364
+ Pull project in current working directory
365
+ """
366
+ app_logger.info(f'Start project pull from {get_current_directory()}')
367
+
368
+ service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
369
+ config = service_factory.get_config_provider().get_full_config()
370
+
371
+ project_service = service_factory.get_project_service()
372
+
373
+ project_service.reset_project(
374
+ config=config
375
+ )
376
+
377
+ raise typer.Exit(0)
378
+
379
+
380
+ @config_app.command(name='set-default-container', no_args_is_help=True, help=__("Set default docker container for a project installation"))
381
+ def set_default_container(
382
+ container_uid: Annotated[Optional[str], typer.Argument(
383
+ help=__("Unique ID of the container to use by default for running tasks"),
384
+ )] = None,
330
385
  unset_default_container: Optional[bool] = typer.Option(
331
386
  False,
332
387
  "--unset",
@@ -334,6 +389,13 @@ def set_default_container(
334
389
  help=__("Unsets the default docker container"),
335
390
  is_eager=False,
336
391
  ),
392
+ working_directory: Optional[str] = typer.Option(
393
+ None,
394
+ "--working-directory",
395
+ "-wd",
396
+ help=__("Full path to working directory"),
397
+ is_eager=False,
398
+ ),
337
399
  ):
338
400
  """
339
401
  Initializes project in current working directory
@@ -582,58 +644,9 @@ def list_inference_simulators(
582
644
 
583
645
  service_factory = validate_config_and_get_service_factory()
584
646
  config = service_factory.get_config_provider().get_full_config()
585
-
586
647
  project_service: ProjectService = service_factory.get_project_service()
587
- if not project_uid:
588
- project_config: ProjectConfig = service_factory.get_config_provider().read_project_config()
589
- if not project_config:
590
- typer.echo(__("Provide the project unique ID or run this command from within an initialized project directory"))
591
- raise typer.Exit(1)
592
- project_uid = project_config.slug
593
-
594
- inference_simulator_status_map = service_factory.get_thestage_api_client().get_inference_simulator_business_status_map(
595
- config.main.thestage_auth_token)
596
-
597
- if not statuses:
598
- statuses = ({key: inference_simulator_status_map[key] for key in [
599
- InferenceSimulatorStatus.SCHEDULED,
600
- InferenceSimulatorStatus.CREATING,
601
- InferenceSimulatorStatus.RUNNING,
602
- ]}).values()
603
-
604
- if "all" in statuses:
605
- statuses = inference_simulator_status_map.values()
606
-
607
- for input_status_item in statuses:
608
- if input_status_item not in inference_simulator_status_map.values():
609
- typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
610
- 'invalid_status': input_status_item,
611
- 'valid_statuses': str(list(inference_simulator_status_map.values()))
612
- }))
613
- raise typer.Exit(1)
614
648
 
615
- typer.echo(__(
616
- "Listing inference simulators with the following statuses: %statuses%, to view all inference simulators, use --status all",
617
- placeholders={
618
- 'statuses': ', '.join([status_item for status_item in statuses])
619
- }))
620
-
621
- backend_statuses: List[str] = [key for key, value in inference_simulator_status_map.items() if value in statuses]
622
-
623
- project_service.print(
624
- func_get_data=project_service.get_project_inference_simulator_list,
625
- func_special_params={
626
- 'project_slug': project_uid,
627
- 'statuses': backend_statuses,
628
- },
629
- mapper=ProjectInferenceSimulatorMapper(),
630
- config=config,
631
- headers=list(map(lambda x: x.alias, ProjectInferenceSimulatorEntity.model_fields.values())),
632
- row=row,
633
- page=page,
634
- max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
635
- show_index="never",
636
- )
649
+ project_service.print_inference_simulator_list(config, project_uid, statuses, row, page)
637
650
 
638
651
  typer.echo(__("Inference simulators listing complete"))
639
652
  raise typer.Exit(0)
@@ -671,58 +684,9 @@ def list_inference_simulator_models(
671
684
 
672
685
  service_factory = validate_config_and_get_service_factory()
673
686
  config = service_factory.get_config_provider().get_full_config()
674
-
675
687
  project_service: ProjectService = service_factory.get_project_service()
676
- if not project_uid:
677
- project_config: ProjectConfig = service_factory.get_config_provider().read_project_config()
678
- if not project_config:
679
- typer.echo(__("Provide the project unique ID or run this command from within an initialized project directory"))
680
- raise typer.Exit(1)
681
- project_uid = project_config.slug
682
-
683
- inference_simulator_model_status_map = service_factory.get_thestage_api_client().get_inference_simulator_model_business_status_map(
684
- config.main.thestage_auth_token)
685
-
686
- if not statuses:
687
- statuses = ({key: inference_simulator_model_status_map[key] for key in [
688
- InferenceModelStatus.SCHEDULED,
689
- InferenceModelStatus.PROCESSING,
690
- InferenceModelStatus.PUSH_SUCCEED,
691
- ]}).values()
692
-
693
- if "all" in statuses:
694
- statuses = inference_simulator_model_status_map.values()
695
-
696
- for input_status_item in statuses:
697
- if input_status_item not in inference_simulator_model_status_map.values():
698
- typer.echo(__("'%invalid_status%' is not one of %valid_statuses%", {
699
- 'invalid_status': input_status_item,
700
- 'valid_statuses': str(list(inference_simulator_model_status_map.values()))
701
- }))
702
- raise typer.Exit(1)
703
688
 
704
- typer.echo(__(
705
- "Listing inference simulator models with the following statuses: %statuses%, to view all inference simulator models, use --status all",
706
- placeholders={
707
- 'statuses': ', '.join([status_item for status_item in statuses])
708
- }))
709
-
710
- backend_statuses: List[str] = [key for key, value in inference_simulator_model_status_map.items() if value in statuses]
711
-
712
- project_service.print(
713
- func_get_data=project_service.get_project_inference_simulator_model_list,
714
- func_special_params={
715
- 'project_slug': project_uid,
716
- 'statuses': backend_statuses,
717
- },
718
- mapper=ProjectInferenceSimulatorModelMapper(),
719
- config=config,
720
- headers=list(map(lambda x: x.alias, ProjectInferenceSimulatorModelEntity.model_fields.values())),
721
- row=row,
722
- page=page,
723
- max_col_width=[100, 100, 100, 100, 100, 100, 100, 100],
724
- show_index="never",
725
- )
689
+ project_service.print_inference_simulator_model_list(config, project_uid, statuses, row, page)
726
690
 
727
691
  typer.echo(__("Inference simulator models listing complete"))
728
692
  raise typer.Exit(0)
thestage/main.py CHANGED
@@ -1,3 +1,8 @@
1
+ from thestage.services.core_files.config_entity import ConfigEntity
2
+
3
+ # TODO use this in config_provider
4
+ initial_config: ConfigEntity = ConfigEntity()
5
+
1
6
  def main():
2
7
  try:
3
8
  import warnings
@@ -8,9 +13,9 @@ def main():
8
13
  from thestage.controllers import base_controller, container_controller, instance_controller, project_controller, \
9
14
  config_controller
10
15
 
16
+ base_controller.app.add_typer(project_controller.app, name="project")
11
17
  base_controller.app.add_typer(container_controller.app, name="container")
12
18
  base_controller.app.add_typer(instance_controller.app, name="instance")
13
- base_controller.app.add_typer(project_controller.app, name="project")
14
19
  base_controller.app.add_typer(config_controller.app, name="config")
15
20
 
16
21
  import thestage.config
@@ -10,15 +10,16 @@ from gitdb.exc import BadName
10
10
  from rich import print
11
11
 
12
12
  from thestage.color_scheme.color_scheme import ColorScheme
13
+ from thestage.config import THESTAGE_CONFIG_DIR
13
14
  from thestage.exceptions.git_access_exception import GitAccessException
14
15
  from thestage.git.ProgressPrinter import ProgressPrinter
15
- from thestage.services.filesystem_service import FileSystemServiceCore
16
+ from thestage.services.filesystem_service import FileSystemService
16
17
 
17
18
 
18
19
  class GitLocalClient:
19
20
  __base_name_remote: str = 'origin'
20
21
  __base_name_local: str = 'main'
21
- __git_ignore_thestage_line: str = '/.thestage/'
22
+ __git_ignore_thestage_line: str = f'/{THESTAGE_CONFIG_DIR}/'
22
23
 
23
24
  __special_main_branches = ['main', 'master']
24
25
 
@@ -26,10 +27,11 @@ class GitLocalClient:
26
27
 
27
28
  def __init__(
28
29
  self,
29
- file_system_service: FileSystemServiceCore,
30
+ file_system_service: FileSystemService,
30
31
  ):
31
32
  self.__file_system_service = file_system_service
32
33
 
34
+ # todo delete this fuckery
33
35
  def __get_repo(self, path: str) -> Repo:
34
36
  return git.Repo(path)
35
37
 
@@ -105,32 +107,37 @@ class GitLocalClient:
105
107
  with repo.git.custom_environment(GIT_SSH_COMMAND=git_ssh_cmd):
106
108
  remote: Remote = repo.remote(self.__base_name_remote)
107
109
  if remote:
110
+ progress = ProgressPrinter()
108
111
  try:
109
- remote.fetch()
110
- except GitCommandError as base_ex:
111
- msg = base_ex.stderr
112
- if msg and 'fatal: Could not read from remote repository' in msg:
113
- raise GitAccessException(
114
- message='You dont have access to repository, or repository not found.',
115
- url=self.build_http_repo_url(git_path=remote.url),
116
- dop_message=msg,
117
- )
118
- else:
119
- raise base_ex
120
-
121
- def git_pull(self, path: str, deploy_key_path: str, branch: Optional[str] = None):
112
+ remote.fetch(progress=progress)
113
+ except GitCommandError as ex:
114
+ for line in progress.allDroppedLines():
115
+ # returning the whole output if failed - so that user have any idea what's going on
116
+ print(f'>> {line}')
117
+ raise ex
118
+
119
+
120
+ def git_pull(self, path: str, deploy_key_path: str):
122
121
  repo = self.__get_repo(path=path)
123
122
  git_ssh_cmd = 'ssh -F /dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i %s' % deploy_key_path
124
123
 
125
124
  with repo.git.custom_environment(GIT_SSH_COMMAND=git_ssh_cmd):
126
125
  local_branch = self.__base_name_local
127
- if branch:
128
- if self.__base_name_remote in branch:
129
- local_branch = branch.replace(f"{self.__base_name_remote}/", '').strip()
130
- else:
131
- local_branch = branch
126
+ if repo.active_branch.name:
127
+ local_branch = repo.active_branch.name
132
128
 
133
- repo.git.pull(self.__base_name_remote, local_branch)
129
+ origin = repo.remote(self.__base_name_remote)
130
+
131
+ if origin:
132
+ progress = ProgressPrinter()
133
+ try:
134
+ origin.pull(refspec=local_branch, progress=progress)
135
+ typer.echo(f"Pulled remote changes to branch '{local_branch}'")
136
+ except GitCommandError as ex:
137
+ for line in progress.allDroppedLines():
138
+ # returning the whole output if failed - so that user have any idea what's going on
139
+ print(f'>> {line}')
140
+ raise ex
134
141
 
135
142
  def find_main_branch_name(self, path: str) -> Optional[str]:
136
143
  repo = self.__get_repo(path=path)
@@ -275,10 +282,16 @@ class GitLocalClient:
275
282
  if repo:
276
283
  return repo.head.is_detached
277
284
 
278
- def reset_hard(self, path: str):
285
+ def reset_hard(self, path: str, deploy_key_path: str, reset_to_origin: bool):
279
286
  repo = self.__get_repo(path=path)
280
287
  if repo:
281
- repo.git.reset('--hard')
288
+ git_ssh_cmd = 'ssh -F /dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i %s' % deploy_key_path
289
+ with repo.git.custom_environment(GIT_SSH_COMMAND=git_ssh_cmd):
290
+ if reset_to_origin:
291
+ repo.git.reset('--hard', f'origin/{repo.active_branch.name}')
292
+ typer.echo(f'Branch "{repo.active_branch.name}" is now synced to its remote counterpart')
293
+ else:
294
+ typer.echo('simple branch reset is not implemented')
282
295
 
283
296
  # refers to a "headless commit" where something was committed while in detached head state and head is pointing at that commit
284
297
  def is_head_committed_in_headless_state(self, path: str) -> bool:
@@ -297,6 +310,11 @@ class GitLocalClient:
297
310
  for branch in repo.heads:
298
311
  if branch.name == branch_name:
299
312
  return True
313
+
314
+ for ref in repo.remotes.origin.refs:
315
+ if ref.remote_head == branch_name:
316
+ typer.echo(f'Found remote branch "{branch_name}"')
317
+ return True
300
318
  return False
301
319
 
302
320
  def checkout_to_new_branch(self, path: str, branch_name: str):