thestage 0.5.46__py3-none-any.whl → 0.5.471__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 (49) hide show
  1. thestage/.env +5 -0
  2. thestage/__init__.py +2 -1
  3. thestage/cli_command.py +56 -0
  4. thestage/cli_command_helper.py +51 -0
  5. thestage/config/config_storage.py +5 -0
  6. thestage/controllers/base_controller.py +19 -15
  7. thestage/controllers/config_controller.py +30 -38
  8. thestage/controllers/container_controller.py +43 -79
  9. thestage/controllers/instance_controller.py +23 -40
  10. thestage/controllers/project_controller.py +95 -184
  11. thestage/controllers/utils_controller.py +12 -8
  12. thestage/debug_tests.py +12 -0
  13. thestage/entities/container.py +0 -5
  14. thestage/entities/rented_instance.py +0 -5
  15. thestage/entities/self_hosted_instance.py +0 -2
  16. thestage/helpers/error_handler.py +14 -14
  17. thestage/helpers/exception_hook.py +14 -0
  18. thestage/helpers/logger/app_logger.py +3 -4
  19. thestage/main.py +25 -13
  20. thestage/services/abstract_service.py +0 -40
  21. thestage/services/app_config_service.py +7 -6
  22. thestage/services/clients/.DS_Store +0 -0
  23. thestage/services/clients/thestage_api/api_client.py +54 -68
  24. thestage/services/clients/thestage_api/core/api_client_core.py +92 -9
  25. thestage/services/clients/thestage_api/dtos/container_response.py +1 -1
  26. thestage/services/clients/thestage_api/dtos/inference_simulator_model_response.py +1 -1
  27. thestage/services/clients/thestage_api/dtos/inference_simulator_response.py +1 -1
  28. thestage/services/clients/thestage_api/dtos/instance_rented_response.py +1 -1
  29. thestage/services/clients/thestage_api/dtos/selfhosted_instance_response.py +1 -1
  30. thestage/services/clients/thestage_api/dtos/task_controller/task_status_localized_map_response.py +1 -1
  31. thestage/services/clients/thestage_api/dtos/validate_token_response.py +11 -0
  32. thestage/services/config_provider/config_provider.py +75 -47
  33. thestage/services/connect/connect_service.py +11 -22
  34. thestage/services/container/container_service.py +6 -23
  35. thestage/services/core_files/config_entity.py +7 -1
  36. thestage/services/filesystem_service.py +2 -2
  37. thestage/services/instance/instance_service.py +12 -26
  38. thestage/services/logging/logging_service.py +17 -45
  39. thestage/services/project/project_service.py +33 -72
  40. thestage/services/remote_server_service.py +3 -4
  41. thestage/services/service_factory.py +21 -27
  42. thestage/services/validation_service.py +14 -9
  43. {thestage-0.5.46.dist-info → thestage-0.5.471.dist-info}/METADATA +3 -4
  44. {thestage-0.5.46.dist-info → thestage-0.5.471.dist-info}/RECORD +47 -41
  45. {thestage-0.5.46.dist-info → thestage-0.5.471.dist-info}/WHEEL +1 -1
  46. thestage/exceptions/http_error_exception.py +0 -12
  47. thestage/services/clients/thestage_api/core/api_client_abstract.py +0 -91
  48. {thestage-0.5.46.dist-info → thestage-0.5.471.dist-info}/LICENSE.txt +0 -0
  49. {thestage-0.5.46.dist-info → thestage-0.5.471.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,9 @@
1
1
  import os
2
2
  import pathlib
3
- from typing import Optional, Dict, Tuple
3
+ from typing import Optional
4
4
 
5
- from thestage.services.core_files.config_entity import ConfigEntity
6
5
  from thestage.helpers.error_handler import error_handler
7
6
  from thestage.services.service_factory import ServiceFactory
8
- from thestage.services.config_provider.config_provider import ConfigProvider
9
7
 
10
8
 
11
9
  def get_current_directory() -> pathlib.Path:
@@ -17,12 +15,18 @@ def validate_config_and_get_service_factory(
17
15
  working_directory: Optional[str] = None,
18
16
  ) -> ServiceFactory:
19
17
  local_path = get_current_directory() if not working_directory else os.path.abspath(working_directory)
20
- config_provider = ConfigProvider(local_path=local_path)
21
- service_factory = ServiceFactory(config_provider=config_provider)
22
- config: ConfigEntity = config_provider.get_full_config()
18
+
19
+ service_factory = ServiceFactory()
20
+ config_provider = service_factory.get_config_provider()
21
+
22
+ config = config_provider.get_config()
23
+ config.runtime.working_directory = str(local_path)
24
+
25
+ config_provider.update_config(updated_config=config)
23
26
 
24
27
  validation_service = service_factory.get_validation_service()
25
- validation_service.check_token(config=config)
26
- config_provider.save_config(config=config)
28
+
29
+ validation_service.check_token()
30
+ config_provider.save_config()
27
31
 
28
32
  return service_factory
@@ -0,0 +1,12 @@
1
+ import requests
2
+ import os
3
+
4
+ API_TOKEN = 'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjNjMzYzRlZC1mOGI2LTRlMWQtODRmOS0zZDM1Zjg1YzZlYzIiLCJzdWIiOiJhcGlVc2VyIiwidXNlcklkIjoyLCJjbGllbnRJZCI6MiwidG9rZW5UeXBlIjoiYWNjZXNzIiwiZXhwIjoxNzc4MjU4Nzg1fQ.q3bGF_ekdXrPGvFpsHzozvIh-XNVky1Jp_mDRW6Woro'
5
+
6
+ api_url = 'https://backend.thestage.ai/user-api/v1/elastic-model/local-run-bundle/get-available-options'
7
+ headers = {
8
+ 'accept': '*/*',
9
+ 'Authorization': f'Bearer {API_TOKEN}'
10
+ }
11
+ response = requests.post(api_url, headers=headers, json='')
12
+ print(response.json())
@@ -10,13 +10,8 @@ class DockerContainerEntity(BaseModel):
10
10
  use_enum_values=True,
11
11
  )
12
12
 
13
- # id: Optional[int] = Field(None, alias='id')
14
13
  slug: Optional[str] = Field(None, alias='UNIQUE ID')
15
14
  status: Optional[str] = Field(None, alias='STATUS')
16
15
  title: Optional[str] = Field(None, alias='TITLE')
17
16
  instance_type: Optional[str] = Field(None, alias='INSTANCE TYPE')
18
17
  instance_slug: Optional[str] = Field(None, alias='INSTANCE UNIQUE ID')
19
- # instance_rented_slug: Optional[str] = Field(None, alias='INSTANCE UNIQUE ID')
20
- # selfhosted_rented_slug: Optional[str] = Field(None, alias='SELF-HOSTED UNIQUE ID')
21
- # system_name: Optional[str] = Field(None, alias='SYSTEM NAME')
22
- # docker_image: Optional[str] = Field(None, alias='DOCKER IMAGE')
@@ -10,15 +10,10 @@ class RentedInstanceEntity(BaseModel):
10
10
  use_enum_values=True
11
11
  )
12
12
 
13
- # id: Optional[int] = Field(None, alias='ID')
14
13
  status: Optional[str] = Field(None, alias='STATUS')
15
14
  title: Optional[str] = Field(None, alias='TITLE')
16
15
  slug: Optional[str] = Field(None, alias='UNIQUE ID')
17
16
  cpu_type: Optional[str] = Field(None, alias='CPU TYPE')
18
17
  cpu_cores: Optional[str] = Field(None, alias='CPU CORES')
19
18
  gpu_type: Optional[str] = Field(None, alias='GPU TYPE')
20
- # ram_size_gb: Optional[int] = Field(None, alias='RAM SIZE GB')
21
19
  ip_address: Optional[str] = Field(None, alias='IP ADDRESS')
22
- # username: Optional[str] = Field(None, alias='USERNAME')
23
- # created_at: Optional[str] = Field(None, alias='CREATED AT')
24
- # updated_at: Optional[str] = Field(None, alias='UPDATED AT')
@@ -16,5 +16,3 @@ class SelfHostedInstanceEntity(BaseModel):
16
16
  cpu_cores: Optional[int] = Field(None, alias='CPU_CORES')
17
17
  gpu_type: Optional[str] = Field(str, alias='GPU_TYPE')
18
18
  ip_address: Optional[str] = Field(None, alias='IP_ADDRESS')
19
- # created_at: Optional[str] = Field(None, alias='CREATED_AT')
20
- # updated_at: Optional[str] = Field(None, alias='UPDATED_AT')
@@ -27,11 +27,11 @@ def error_handler() -> Callable:
27
27
  return result
28
28
  except AuthException as e1:
29
29
  typer.echo(__('Authentication failed: update API token'))
30
- app_logger.info(f'{traceback.format_exc()}')
30
+ app_logger.error(f'{traceback.format_exc()}')
31
31
  raise typer.Exit(1)
32
32
  except BusinessLogicException as e2:
33
33
  typer.echo(__('Business logic error encountered: contact TheStage AI team'))
34
- app_logger.info(f'{traceback.format_exc()}')
34
+ app_logger.error(f'{traceback.format_exc()}')
35
35
  raise typer.Exit(1)
36
36
  except ConfigException as e3:
37
37
  typer.echo(__(
@@ -40,7 +40,7 @@ def error_handler() -> Callable:
40
40
  'error_message': e3.get_message()
41
41
  }
42
42
  ))
43
- app_logger.info(f'{traceback.format_exc()}')
43
+ app_logger.error(f'{traceback.format_exc()}')
44
44
  raise typer.Exit(1)
45
45
  except FileSystemException as e4:
46
46
  typer.echo(__(
@@ -49,7 +49,7 @@ def error_handler() -> Callable:
49
49
  'error_message': e4.get_message()
50
50
  }
51
51
  ))
52
- app_logger.info(f'{traceback.format_exc()}')
52
+ app_logger.error(f'{traceback.format_exc()}')
53
53
  raise typer.Exit(1)
54
54
  except HttpClientException as e5:
55
55
  typer.echo(__(
@@ -60,8 +60,8 @@ def error_handler() -> Callable:
60
60
  'error_message': e5.get_message()
61
61
  }
62
62
  ))
63
- app_logger.info(f"Connection error to {THESTAGE_API_URL} - {e5}")
64
- app_logger.info(f'{traceback.format_exc()}')
63
+ app_logger.error(f"Connection error to {THESTAGE_API_URL} - {e5}")
64
+ app_logger.error(f'{traceback.format_exc()}')
65
65
  raise typer.Exit(1)
66
66
  except GitAccessException as e6:
67
67
  typer.echo(e6.get_message())
@@ -72,11 +72,11 @@ def error_handler() -> Callable:
72
72
  'git_url': e6.get_url(),
73
73
  }
74
74
  ))
75
- app_logger.info(f'{traceback.format_exc()}')
75
+ app_logger.error(f'{traceback.format_exc()}')
76
76
  raise typer.Exit(1)
77
77
  except GitCommandError as e7:
78
78
  typer.echo(f'Git command error encountered: {e7.stderr} (status: {e7.status})')
79
- app_logger.info(f'{traceback.format_exc()}')
79
+ app_logger.error(f'{traceback.format_exc()}')
80
80
  raise typer.Exit(1)
81
81
  except RemoteServerException as e8:
82
82
  typer.echo(__(
@@ -85,21 +85,21 @@ def error_handler() -> Callable:
85
85
  'ip_address': e8.ip_address,
86
86
  'username': e8.username,
87
87
  }))
88
- app_logger.info(f'Error connecting to server or Docker container at {e8.ip_address} as {e8.username} - {e8}')
89
- app_logger.info(f'{traceback.format_exc()}')
88
+ app_logger.error(f'Error connecting to server or Docker container at {e8.ip_address} as {e8.username} - {e8}')
89
+ app_logger.error(f'{traceback.format_exc()}')
90
90
  raise typer.Exit(1)
91
91
  except Abort as e9:
92
- app_logger.info(f'{traceback.format_exc()}')
92
+ app_logger.error(f'{traceback.format_exc()}')
93
93
  raise typer.Exit(1)
94
94
  except requests.exceptions.ConnectionError as e10:
95
95
  # TODO we don't know for sure if it is Thestage connection error - throw appropriate exception (on token validation?)
96
96
  typer.echo("Connection error")
97
- app_logger.info(f'{traceback.format_exc()}')
97
+ app_logger.error(f'{traceback.format_exc()}')
98
98
  raise typer.Exit(1)
99
99
  except PasswordRequiredException as e11:
100
100
  # technically we can use encrypted keys but dealing with passwords is big bs
101
101
  typer.echo("Provided key requires password. Please use non-encrypted key.")
102
- app_logger.info(f'{traceback.format_exc()}')
102
+ app_logger.error(f'{traceback.format_exc()}')
103
103
  raise typer.Exit(1)
104
104
  except Exception as e100:
105
105
  if isinstance(e100, Exit):
@@ -109,7 +109,7 @@ def error_handler() -> Callable:
109
109
  # typer.echo(e100.__class__.__name__)
110
110
  # print(traceback.format_exc())
111
111
  # TODO send all exceptions to backend?
112
- app_logger.info(f'{traceback.format_exc()}')
112
+ app_logger.error(f'{traceback.format_exc()}')
113
113
  raise typer.Exit(1)
114
114
  return wrapper
115
115
  return wrap
@@ -0,0 +1,14 @@
1
+ import sys
2
+ import traceback
3
+
4
+ from thestage.helpers.logger.app_logger import app_logger
5
+
6
+
7
+ def exception_handler(exception_type, exception, tb):
8
+ print(exception)
9
+ tb_list = traceback.format_exception(exception_type, exception, tb)
10
+ tb_text = ''.join(tb_list)
11
+ app_logger.error(tb_text)
12
+
13
+
14
+ sys.excepthook = exception_handler
@@ -2,7 +2,7 @@ import logging
2
2
  import platform
3
3
  from logging.handlers import RotatingFileHandler
4
4
  from pathlib import Path
5
-
5
+ from thestage import __app_name__, __version__
6
6
 
7
7
  from thestage.config import THESTAGE_CONFIG_DIR
8
8
 
@@ -31,16 +31,15 @@ def get_log_path_from_os() -> Path:
31
31
 
32
32
 
33
33
  def build_logger(level) -> logging.Logger:
34
- logger = logging.getLogger("thestage_log")
34
+ logger = logging.getLogger(f"{__app_name__} v{__version__}")
35
35
  formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
36
36
  logger.setLevel(level)
37
- # logger.remove()
38
37
 
39
38
  log_path = get_log_path_from_os()
40
39
  tsr_log_file = log_path.joinpath(THESTAGE_LOGGING_FILE)
41
40
 
42
41
  if tsr_log_file:
43
- file_h = RotatingFileHandler(filename=tsr_log_file, maxBytes=1024, backupCount=5)
42
+ file_h = RotatingFileHandler(filename=tsr_log_file, maxBytes=1024 * 10, backupCount=5)
44
43
  file_h.setLevel(level)
45
44
  file_h.setFormatter(formatter)
46
45
  logger.addHandler(file_h)
thestage/main.py CHANGED
@@ -1,24 +1,36 @@
1
- from thestage.services.core_files.config_entity import ConfigEntity
1
+ from thestage.helpers import exception_hook
2
+ import traceback
3
+
4
+ from thestage.cli_command_helper import get_command_group_help_panel
5
+ from thestage.helpers.logger.app_logger import app_logger, get_log_path_from_os
6
+ from thestage.services.service_factory import ServiceFactory
7
+ from rich import print
2
8
 
3
- # TODO use this in config_provider
4
- initial_config: ConfigEntity = ConfigEntity()
5
9
 
6
10
  def main():
7
- try:
8
- import warnings
9
- warnings.filterwarnings(action='ignore', module='.*paramiko.*')
11
+ service_factory = ServiceFactory()
12
+ config_provider = service_factory.get_config_provider()
13
+ config = config_provider.build_config()
10
14
 
11
- from . import __app_name__
15
+ try:
16
+ try:
17
+ api_client = service_factory.get_thestage_api_client()
18
+ token_info = api_client.validate_token(config.main.thestage_auth_token)
19
+ config_provider.update_allowed_commands_and_is_token_valid(validate_token_response=token_info)
20
+ except Exception as e:
21
+ app_logger.error(f'{traceback.format_exc()}')
22
+ print('Unexpected error occurred') # TODO inquire what we want here if backend is offline
23
+ print(f'Application logs path: {str(get_log_path_from_os())}')
24
+ return
12
25
 
13
26
  from thestage.controllers import base_controller, container_controller, instance_controller, project_controller, \
14
27
  config_controller
15
28
 
16
- base_controller.app.add_typer(project_controller.app, name="project")
17
- base_controller.app.add_typer(container_controller.app, name="container")
18
- base_controller.app.add_typer(instance_controller.app, name="instance")
19
- base_controller.app.add_typer(config_controller.app, name="config")
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())
20
33
 
21
- import thestage.config
22
- base_controller.app(prog_name=__app_name__)
34
+ base_controller.app()
23
35
  except KeyboardInterrupt:
24
36
  print('THESTAGE: Keyboard Interrupt')
@@ -3,56 +3,18 @@ from typing import List, Dict, Any, Optional
3
3
 
4
4
  import typer
5
5
  from tabulate import tabulate
6
- from thestage.services.core_files.config_entity import ConfigEntity
7
- from thestage.helpers.logger.app_logger import app_logger
8
6
  from thestage.i18n.translation import __
9
7
  from thestage.services.abstract_mapper import AbstractMapper
10
8
  from thestage.services.clients.thestage_api.dtos.paginated_entity_list import PaginatedEntityList
11
- from thestage.services.config_provider.config_provider import ConfigProvider
12
9
 
13
10
 
14
11
  class AbstractService(ABC):
15
-
16
- def __init__(
17
- self,
18
- config_provider: ConfigProvider,
19
- ):
20
- self._config_provider = config_provider
21
-
22
- def map_frontend_statuses(self, statuses_mapper: Dict[str, str], frontend: List[str]) -> Optional[List[str]]:
23
-
24
- real_statuses = []
25
-
26
- mapper: Dict[str, List] = {}
27
- for key, value in statuses_mapper.items():
28
-
29
- map_key = value if ' ' not in value else value.replace(' ', '_')
30
- if value not in mapper:
31
- mapper[map_key] = [key]
32
- else:
33
- mapper[map_key].append(key)
34
-
35
- for item in frontend:
36
- if item == 'all':
37
- return []
38
- elif item == 'unknown':
39
- continue
40
- else:
41
- if item in mapper:
42
- real_statuses.extend(mapper[item])
43
- else:
44
- app_logger.error(f'Not found status: {item}')
45
-
46
- return real_statuses
47
-
48
-
49
12
  # TODO remove recursion
50
13
  def print(
51
14
  self,
52
15
  func_get_data,
53
16
  func_special_params: Dict[str, Any],
54
17
  mapper: AbstractMapper,
55
- config: ConfigEntity,
56
18
  headers: List[str],
57
19
  row: int = 5,
58
20
  page: int = 1,
@@ -61,7 +23,6 @@ class AbstractService(ABC):
61
23
  depth: int = 0,
62
24
  ):
63
25
  paginated_entity_list: PaginatedEntityList = func_get_data(
64
- config=config,
65
26
  row=row,
66
27
  page=page,
67
28
  **func_special_params,
@@ -117,7 +78,6 @@ class AbstractService(ABC):
117
78
  func_get_data=func_get_data,
118
79
  func_special_params=func_special_params,
119
80
  mapper=mapper,
120
- config=config,
121
81
  headers=headers,
122
82
  row=row,
123
83
  page=next_page,
@@ -2,17 +2,17 @@ import os
2
2
 
3
3
  import click
4
4
  import typer
5
+
6
+ from thestage.services.config_provider.config_provider import ConfigProvider
5
7
  from thestage.services.core_files.config_entity import ConfigEntity
6
8
  from thestage.i18n.translation import __
7
9
  from thestage.entities.enums.yes_no_response import YesOrNoResponse
8
- from thestage.services.config_provider.config_provider import ConfigProvider
9
10
  from thestage.services.validation_service import ValidationService
10
11
 
11
12
 
12
13
  class AppConfigService:
13
14
 
14
15
  __validation_service: ValidationService
15
- __config_provider: ConfigProvider
16
16
 
17
17
  def __init__(
18
18
  self,
@@ -24,9 +24,10 @@ class AppConfigService:
24
24
 
25
25
  def app_change_token(
26
26
  self,
27
- config: ConfigEntity,
28
27
  token: str,
29
28
  ):
29
+ config = self.__config_provider.get_config()
30
+
30
31
  if config.main.thestage_auth_token:
31
32
  response: YesOrNoResponse = typer.prompt(
32
33
  text=__('Do you want to change current token?'),
@@ -39,9 +40,9 @@ class AppConfigService:
39
40
  raise typer.Exit(0)
40
41
 
41
42
  config.main.thestage_auth_token = token
42
-
43
- self.__validation_service.check_token(config=config)
44
- self.__config_provider.save_config(config=config)
43
+ self.__config_provider.update_config(updated_config=config)
44
+ self.__validation_service.check_token()
45
+ self.__config_provider.save_config()
45
46
 
46
47
  @staticmethod
47
48
  def app_remove_env():
Binary file