thestage 0.5.38__py3-none-any.whl → 0.5.40__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 (37) hide show
  1. thestage/.env +3 -4
  2. thestage/__init__.py +1 -1
  3. thestage/controllers/config_controller.py +3 -4
  4. thestage/controllers/container_controller.py +12 -16
  5. thestage/controllers/project_controller.py +10 -3
  6. thestage/controllers/utils_controller.py +2 -3
  7. thestage/entities/file_item.py +27 -0
  8. thestage/exceptions/file_system_exception.py +6 -0
  9. thestage/helpers/error_handler.py +2 -2
  10. thestage/helpers/logger/app_logger.py +3 -4
  11. thestage/services/abstract_service.py +1 -2
  12. thestage/services/app_config_service.py +2 -3
  13. thestage/services/clients/.DS_Store +0 -0
  14. thestage/services/clients/git/git_client.py +3 -3
  15. thestage/services/clients/thestage_api/api_client.py +3 -61
  16. thestage/services/clients/thestage_api/core/api_client_abstract.py +91 -0
  17. thestage/services/clients/thestage_api/core/api_client_core.py +25 -0
  18. thestage/services/clients/thestage_api/core/http_client_exception.py +12 -0
  19. thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +1 -1
  20. thestage/services/clients/thestage_api/dtos/project_response.py +0 -2
  21. thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +3 -2
  22. thestage/services/config_provider/config_provider.py +98 -44
  23. thestage/services/connect/connect_service.py +1 -1
  24. thestage/services/container/container_service.py +2 -8
  25. thestage/services/core_files/config_entity.py +25 -0
  26. thestage/services/filesystem_service.py +115 -0
  27. thestage/services/instance/instance_service.py +1 -2
  28. thestage/services/logging/logging_service.py +76 -95
  29. thestage/services/project/project_service.py +9 -7
  30. thestage/services/remote_server_service.py +3 -3
  31. thestage/services/service_factory.py +1 -2
  32. thestage/services/validation_service.py +26 -10
  33. {thestage-0.5.38.dist-info → thestage-0.5.40.dist-info}/METADATA +1 -2
  34. {thestage-0.5.38.dist-info → thestage-0.5.40.dist-info}/RECORD +37 -29
  35. {thestage-0.5.38.dist-info → thestage-0.5.40.dist-info}/WHEEL +1 -1
  36. {thestage-0.5.38.dist-info → thestage-0.5.40.dist-info}/LICENSE.txt +0 -0
  37. {thestage-0.5.38.dist-info → thestage-0.5.40.dist-info}/entry_points.txt +0 -0
thestage/.env CHANGED
@@ -1,6 +1,5 @@
1
1
  THESTAGE_CONFIG_DIR=.thestage
2
2
  THESTAGE_CONFIG_FILE=config.json
3
- THESTAGE_CLI_ENV=DEV
4
- THESTAGE_API_URL=https://backend-staging.thestage.ai
5
- THESTAGE_API_URL=https://backend.thestage.ai
6
- LOG_FILE=thestage.log
3
+ THESTAGE_API_URL=https://backend-staging3.thestage.ai
4
+ THESTAGE_API_URL=http://localhost:8100
5
+ LOG_FILE=thestage.log
thestage/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from . import *
2
2
  __app_name__ = "thestage"
3
- __version__ = "0.5.38"
3
+ __version__ = "0.5.40"
@@ -2,8 +2,7 @@ import os
2
2
  from pathlib import Path
3
3
 
4
4
  import click
5
- from thestage_core.entities.config_entity import ConfigEntity
6
- from thestage_core.services.filesystem_service import FileSystemServiceCore
5
+ from thestage.services.core_files.config_entity import ConfigEntity
7
6
 
8
7
  from thestage.entities.enums.yes_no_response import YesOrNoResponse
9
8
  from thestage.i18n.translation import __
@@ -33,7 +32,7 @@ def config_get():
33
32
  typer.echo(__('No configuration found'))
34
33
  raise typer.Exit(1)
35
34
 
36
- config_provider.save_global_config(config=config)
35
+ config_provider.save_config(config=config)
37
36
 
38
37
  typer.echo(__('THESTAGE TOKEN: %token%', {'token': config.main.thestage_auth_token or ''}))
39
38
  typer.echo(__('THESTAGE API LINK: %link%', {'link': config.main.thestage_api_url or ''}))
@@ -80,7 +79,7 @@ def config_clear():
80
79
  local_path = get_current_directory()
81
80
  config_provider = ConfigProvider(local_path=local_path)
82
81
  config_dir = config_provider.get_full_config().runtime.config_global_path
83
- config_provider.remove_all_config()
82
+ config_provider.clear_config()
84
83
  typer.echo(f'Removed {config_dir}')
85
84
 
86
85
  raise typer.Exit(0)
@@ -214,11 +214,6 @@ def put_file(
214
214
  typer.echo(__('Container unique ID is required'))
215
215
  raise typer.Exit(1)
216
216
 
217
- # TODO delete when we change mappings
218
- if destination_path.startswith("/app/") or destination_path == "/app":
219
- typer.echo("/app/ is reserved for project code and must not contain custom files. Сonsider adding your files to the project repository")
220
- raise typer.Exit(1)
221
-
222
217
  service_factory = validate_config_and_get_service_factory()
223
218
  config = service_factory.get_config_provider().get_full_config()
224
219
 
@@ -396,9 +391,16 @@ def stop_container(
396
391
  raise typer.Exit(0)
397
392
 
398
393
 
399
- @app.command(name="logs", no_args_is_help=True, help=__("Stream real-time container logs; log history is available in your TheStage AI account"))
394
+ @app.command(name="logs", no_args_is_help=True, help=__("Stream real-time Docker container logs or view last logs for a container"))
400
395
  def container_logs(
401
396
  container_uid: Optional[str] = typer.Argument(help=__("Container unique id")),
397
+ logs_number: Optional[int] = typer.Option(
398
+ None,
399
+ '--number',
400
+ '-n',
401
+ help=__("Display a number of latest log entries. No real-time stream if provided."),
402
+ is_eager=False,
403
+ ),
402
404
  ):
403
405
  """
404
406
  Streams real-time container logs
@@ -412,21 +414,15 @@ def container_logs(
412
414
  service_factory = validate_config_and_get_service_factory()
413
415
  config = service_factory.get_config_provider().get_full_config()
414
416
 
415
- container_service: ContainerService = service_factory.get_container_service()
416
417
  logging_service: LoggingService = service_factory.get_logging_service()
417
418
 
418
- container: Optional[DockerContainerDto] = container_service.get_container(
419
- config=config,
420
- container_slug=container_uid,
421
- )
422
-
423
- if container:
424
- logging_service.stream_container_logs(
419
+ if logs_number is None:
420
+ logging_service.stream_container_logs_with_controls(
425
421
  config=config,
426
- container=container
422
+ container_uid=container_uid
427
423
  )
428
424
  else:
429
- typer.echo(__("Container not found: %container_slug%", {'container_slug': container_uid}))
425
+ logging_service.print_last_container_logs(config=config, container_uid=container_uid, logs_number=logs_number)
430
426
 
431
427
  app_logger.info(f'Container logs - end')
432
428
  raise typer.Exit(0)
@@ -103,11 +103,10 @@ def init(
103
103
  )
104
104
 
105
105
  typer.echo(__("Project successfully initialized at %path%", {"path": config.runtime.working_directory}))
106
- typer.echo(__("Initialization complete"))
107
106
  raise typer.Exit(0)
108
107
 
109
108
 
110
- @app.command(name='run', no_args_is_help=True, help=__("Run task within project. By default, it uses the latest commit from the main branch and streams real-time task logs."))
109
+ @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."))
111
110
  def run(
112
111
  command: Annotated[List[str], typer.Argument(
113
112
  help=__("Command to run (required)"),
@@ -141,6 +140,13 @@ def run(
141
140
  help=__("Disable real-time log streaming"),
142
141
  is_eager=False,
143
142
  ),
143
+ task_title: Optional[str] = typer.Option(
144
+ None,
145
+ "--title",
146
+ "-t",
147
+ help=__("Provide a custom task title. Git commit message is used by default."),
148
+ is_eager=False,
149
+ ),
144
150
  ):
145
151
  """
146
152
  Runs a task within a project
@@ -161,6 +167,7 @@ def run(
161
167
  run_command=" ".join(command),
162
168
  commit_hash=commit_hash,
163
169
  docker_container_slug=docker_container_slug,
170
+ task_title=task_title,
164
171
  )
165
172
 
166
173
  if enable_log_stream:
@@ -858,4 +865,4 @@ def deploy_inference_simulator_model_to_sagemaker(
858
865
  instance_type=instance_type,
859
866
  initial_variant_weight=initial_variant_weight,
860
867
  initial_instance_count=initial_instance_count,
861
- )
868
+ )
@@ -2,8 +2,7 @@ import os
2
2
  import pathlib
3
3
  from typing import Optional, Dict, Tuple
4
4
 
5
- from thestage_core.entities.config_entity import ConfigEntity
6
-
5
+ from thestage.services.core_files.config_entity import ConfigEntity
7
6
  from thestage.helpers.error_handler import error_handler
8
7
  from thestage.services.service_factory import ServiceFactory
9
8
  from thestage.services.config_provider.config_provider import ConfigProvider
@@ -24,6 +23,6 @@ def validate_config_and_get_service_factory(
24
23
 
25
24
  validation_service = service_factory.get_validation_service()
26
25
  validation_service.check_token(config=config)
27
- config_provider.save_global_config(config=config)
26
+ config_provider.save_config(config=config)
28
27
 
29
28
  return service_factory
@@ -0,0 +1,27 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Optional, List
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class FileItemEntity(BaseModel):
9
+ model_config = ConfigDict(use_enum_values=True)
10
+
11
+ name: Optional[str] = Field(None)
12
+ path: Optional[str] = Field(None)
13
+ is_file: Optional[bool] = Field(False)
14
+ is_folder: Optional[bool] = Field(False)
15
+ file_size: Optional[int] = Field(None)
16
+ children: List['FileItemEntity'] = Field(default=[])
17
+
18
+ @staticmethod
19
+ def build_from_path(path: Path) -> 'FileItemEntity':
20
+ file_stat = os.stat(path)
21
+ return FileItemEntity(
22
+ name=path.name,
23
+ path=str(path.absolute()),
24
+ is_file=path.is_file(),
25
+ is_folder=path.is_dir(),
26
+ file_size=file_stat.st_size,
27
+ )
@@ -0,0 +1,6 @@
1
+ from thestage.exceptions.base_exception import BaseAbstractException
2
+
3
+
4
+ class FileSystemException(BaseAbstractException):
5
+ def __init__(self, message: str):
6
+ super(FileSystemException, self).__init__(message=message)
@@ -6,10 +6,9 @@ import typer
6
6
  from click.exceptions import Exit, Abort
7
7
  from git import GitCommandError
8
8
  from paramiko.ssh_exception import PasswordRequiredException
9
- from thestage_core.exceptions.file_system_exception import FileSystemException
10
- from thestage_core.exceptions.http_error_exception import HttpClientException
11
9
 
12
10
  from thestage.config import THESTAGE_API_URL
11
+ from thestage.exceptions.file_system_exception import FileSystemException
13
12
  from thestage.exceptions.remote_server_exception import RemoteServerException
14
13
  from thestage.i18n.translation import __
15
14
  from thestage.exceptions.git_access_exception import GitAccessException
@@ -17,6 +16,7 @@ from thestage.exceptions.auth_exception import AuthException
17
16
  from thestage.exceptions.business_logic_exception import BusinessLogicException
18
17
  from thestage.exceptions.config_exception import ConfigException
19
18
  from thestage.helpers.logger.app_logger import app_logger
19
+ from thestage.services.clients.thestage_api.core.http_client_exception import HttpClientException
20
20
 
21
21
 
22
22
  def error_handler() -> Callable:
@@ -2,13 +2,12 @@ import logging
2
2
  import platform
3
3
  from logging.handlers import RotatingFileHandler
4
4
  from pathlib import Path
5
- from typing import Any
6
5
 
7
- import typer
8
- from thestage_core.config import THESTAGE_CONFIG_DIR, THESTAGE_CONFIG_FILE
9
- from thestage_core.exceptions.file_system_exception import FileSystemException
6
+
7
+ from thestage.config import THESTAGE_CONFIG_DIR
10
8
 
11
9
  from thestage.config import THESTAGE_LOGGING_FILE
10
+ from thestage.exceptions.file_system_exception import FileSystemException
12
11
 
13
12
 
14
13
  def get_log_path_from_os() -> Path:
@@ -3,8 +3,7 @@ from typing import List, Dict, Any, Optional
3
3
 
4
4
  import typer
5
5
  from tabulate import tabulate
6
- from thestage_core.entities.config_entity import ConfigEntity
7
-
6
+ from thestage.services.core_files.config_entity import ConfigEntity
8
7
  from thestage.helpers.logger.app_logger import app_logger
9
8
  from thestage.i18n.translation import __
10
9
  from thestage.services.abstract_mapper import AbstractMapper
@@ -2,8 +2,7 @@ import os
2
2
 
3
3
  import click
4
4
  import typer
5
- from thestage_core.entities.config_entity import ConfigEntity
6
-
5
+ from thestage.services.core_files.config_entity import ConfigEntity
7
6
  from thestage.i18n.translation import __
8
7
  from thestage.entities.enums.yes_no_response import YesOrNoResponse
9
8
  from thestage.services.config_provider.config_provider import ConfigProvider
@@ -42,7 +41,7 @@ class AppConfigService:
42
41
  config.main.thestage_auth_token = token
43
42
 
44
43
  self.__validation_service.check_token(config=config)
45
- self.__config_provider.save_global_config(config=config)
44
+ self.__config_provider.save_config(config=config)
46
45
 
47
46
  @staticmethod
48
47
  def app_remove_env():
Binary file
@@ -7,11 +7,11 @@ import git
7
7
  import typer
8
8
  from git import Remote, Repo, GitCommandError, Commit
9
9
  from rich import print
10
- from thestage_core.services.filesystem_service import FileSystemServiceCore
11
10
 
12
11
  from thestage.color_scheme.color_scheme import ColorScheme
13
12
  from thestage.exceptions.git_access_exception import GitAccessException
14
13
  from thestage.git.ProgressPrinter import ProgressPrinter
14
+ from thestage.services.filesystem_service import FileSystemServiceCore
15
15
 
16
16
 
17
17
  class GitLocalClient:
@@ -260,8 +260,8 @@ class GitLocalClient:
260
260
  self.__file_system_service.create_if_not_exists_file(gitignore_path)
261
261
  self.git_add_by_path(repo_path=path, file_path=str(gitignore_path))
262
262
 
263
- is_present_tsr = self.__file_system_service.find_in_text_file(file=str(gitignore_path),
264
- find=self.__git_ignore_thestage_line)
263
+ is_present_tsr = self.__file_system_service.find_line_in_text_file(file=str(gitignore_path),
264
+ find=self.__git_ignore_thestage_line)
265
265
  if not is_present_tsr:
266
266
  self.__file_system_service.add_line_to_text_file(file=str(gitignore_path),
267
267
  new_line=self.__git_ignore_thestage_line)
@@ -2,9 +2,9 @@ from typing import Optional, List, Tuple, Dict, Iterator
2
2
 
3
3
  import httpx
4
4
  import requests
5
- from thestage_core.services.clients.thestage_api.api_client import TheStageApiClientCore
6
5
 
7
6
  from thestage.helpers.error_handler import error_handler
7
+ from thestage.services.clients.thestage_api.core.api_client_core import TheStageApiClientCore
8
8
  from thestage.services.clients.thestage_api.dtos.docker_container_controller.docker_container_list_request import \
9
9
  DockerContainerListRequest
10
10
  from thestage.services.clients.thestage_api.dtos.docker_container_controller.docker_container_list_response import \
@@ -522,65 +522,6 @@ class TheStageApiClient(TheStageApiClientCore):
522
522
  return ProjectRunTaskResponse.model_validate(response) if response else None
523
523
 
524
524
 
525
- def get_container_log_stream(self, token: str, container_id: int) -> Iterator[str]:
526
- request_headers = {'Content-Type': 'application/json'}
527
- if token: request_headers['Authorization'] = f"Bearer {token}"
528
-
529
- session = requests.Session()
530
- request = DockerContainerLogStreamRequest(
531
- dockerContainerId=container_id,
532
- )
533
-
534
- with session.post(
535
- url=f"{self._get_host()}/user-api/v1/logging/stream/container",
536
- headers=request_headers,
537
- stream=True,
538
- json=request.model_dump(),
539
- ) as response:
540
- for line in response.iter_lines():
541
- if line:
542
- yield line.decode('utf-8')
543
-
544
-
545
- async def get_container_log_stream_httpx(self, token: str, container_id: int) -> Iterator[str]:
546
- request_headers = {'Content-Type': 'application/json'}
547
- if token: request_headers['Authorization'] = f"Bearer {token}"
548
-
549
- request = DockerContainerLogStreamRequest(
550
- dockerContainerId=container_id,
551
- )
552
-
553
- async with httpx.stream(
554
- method="POST",
555
- url=f"{self._get_host()}/user-api/v1/logging/stream/container",
556
- headers=request_headers,
557
- json=request.model_dump(),
558
- ) as response:
559
- async for line in response.iter_lines():
560
- if line:
561
- yield line.decode('utf-8')
562
-
563
-
564
- async def get_task_log_stream_httpx(self, token: str, task_id: int):
565
- request_headers = {'Content-Type': 'application/json'}
566
- if token: request_headers['Authorization'] = f"Bearer {token}"
567
-
568
- request = TaskLogStreamRequest(
569
- taskId=task_id,
570
- )
571
-
572
- async with httpx.AsyncClient() as client:
573
- async with client.stream(
574
- method="POST",
575
- url=f"{self._get_host()}/user-api/v1/logging/stream/task",
576
- headers=request_headers,
577
- json=request.model_dump(),
578
- ) as response:
579
- async for line in response.aiter_lines():
580
- if line:
581
- yield line
582
-
583
-
584
525
  async def poll_logs_httpx(self, token: str, docker_container_id: Optional[int], last_log_timestamp: str, last_log_id: str, task_id: Optional[int] = None, inference_simulator_id: Optional[int] = None) -> Optional[LogPollingResponse]:
585
526
  request_headers = {'Content-Type': 'application/json'}
586
527
  if token: request_headers['Authorization'] = f"Bearer {token}"
@@ -791,10 +732,11 @@ class TheStageApiClient(TheStageApiClientCore):
791
732
  return DeployInferenceModelToSagemakerResponse.model_validate(response) if response else None
792
733
 
793
734
 
794
- def query_user_logs(self, token: str, limit: int, task_id: Optional[int] = None, inference_simulator_id: Optional[int] = None) -> UserLogsQueryResponse:
735
+ def query_user_logs(self, token: str, limit: int, task_id: Optional[int] = None, inference_simulator_id: Optional[int] = None, container_id: Optional[int] = None) -> UserLogsQueryResponse:
795
736
  request = UserLogsQueryRequest(
796
737
  inferenceSimulatorId=inference_simulator_id,
797
738
  taskId=task_id,
739
+ containerId=container_id,
798
740
  limit=limit,
799
741
  ascendingOrder=False,
800
742
  )
@@ -0,0 +1,91 @@
1
+ import json
2
+ from abc import ABC
3
+ from typing import Optional
4
+
5
+ import requests
6
+
7
+ from thestage.config import THESTAGE_API_URL
8
+ from thestage.exceptions.auth_exception import AuthException
9
+ from thestage.services.clients.thestage_api.core.http_client_exception import HttpClientException
10
+
11
+
12
+ class TheStageApiClientAbstract(ABC):
13
+ def __init__(self, timeout: int, url: Optional[str] = None):
14
+ self.__timeout = timeout
15
+ self.__api_url = url
16
+
17
+ def _get_host(self, ) -> str:
18
+ return self.__api_url or THESTAGE_API_URL
19
+
20
+ def _request(
21
+ self,
22
+ method: str,
23
+ url: str,
24
+ data: dict = None,
25
+ query_params: dict = None,
26
+ headers: Optional[dict] = None,
27
+ token: Optional[str] = None,
28
+ ):
29
+ if not data:
30
+ data = {}
31
+
32
+ host = self._get_host()
33
+ url = f'{host}{url}'
34
+
35
+ request_headers = {
36
+ 'Content-Type': 'application/json',
37
+ }
38
+
39
+ if token:
40
+ request_headers['Authorization'] = f"Bearer {token}"
41
+
42
+ if headers:
43
+ request_headers.update(headers)
44
+ response = requests.request(
45
+ method=method,
46
+ url=url,
47
+ json=data,
48
+ params=query_params,
49
+ headers=request_headers,
50
+ timeout=self.__timeout,
51
+
52
+ )
53
+ return self._parse_api_response(response)
54
+
55
+ @staticmethod
56
+ def _parse_api_response(raw_response):
57
+ content_type = raw_response.headers.get('content-type')
58
+ message_error = None
59
+ if content_type == 'application/json':
60
+ try:
61
+ result = raw_response.json()
62
+ message_error = result.get('message', None)
63
+ except json.JSONDecodeError:
64
+ raise HttpClientException(
65
+ message=f"Failed to parse server response",
66
+ status_code=raw_response.status_code,
67
+ )
68
+ else:
69
+ result = raw_response.content.text()
70
+
71
+ if raw_response.status_code == 401:
72
+ raise AuthException(
73
+ message=f"Unauthorized",
74
+ )
75
+ elif raw_response.status_code == 403:
76
+ raise HttpClientException(
77
+ message=f"{message_error if message_error else 'Forbidden'} ({raw_response.status_code})",
78
+ status_code=raw_response.status_code,
79
+ )
80
+ elif raw_response.status_code >= 400:
81
+ raise HttpClientException(
82
+ message=f"{message_error if message_error else 'Request error'} ({raw_response.status_code})",
83
+ status_code=raw_response.status_code,
84
+ )
85
+ elif raw_response.status_code < 200 or raw_response.status_code > 300:
86
+ raise HttpClientException(
87
+ message=f"{message_error if message_error else 'Request error'} ({raw_response.status_code})",
88
+ status_code=raw_response.status_code,
89
+ )
90
+
91
+ return result
@@ -0,0 +1,25 @@
1
+ from typing import Optional, List, Tuple
2
+
3
+ from thestage.services.clients.thestage_api.core.api_client_abstract import TheStageApiClientAbstract
4
+ from thestage.services.clients.thestage_api.dtos.base_response import TheStageBaseResponse
5
+ from thestage import __version__
6
+
7
+
8
+ class TheStageApiClientCore(TheStageApiClientAbstract):
9
+
10
+ def __init__(self, timeout: int = 90, url: Optional[str] = None):
11
+ super(TheStageApiClientCore, self).__init__(timeout=timeout, url=url)
12
+
13
+ def validate_token(self, token: str) -> bool:
14
+ data = {
15
+ "userApiToken": token,
16
+ "cliVersion": __version__,
17
+ }
18
+
19
+ response = self._request(
20
+ method='POST',
21
+ url='/user-api/v1/validate-token',
22
+ data=data,
23
+ )
24
+ result = TheStageBaseResponse.model_validate(response) if response else None
25
+ return result.is_success if result else False
@@ -0,0 +1,12 @@
1
+ from thestage.exceptions.base_exception import BaseAbstractException
2
+
3
+
4
+ class HttpClientException(BaseAbstractException):
5
+ _status_code: int = 0
6
+
7
+ def __init__(self, message: str, status_code: int, ):
8
+ super(HttpClientException, self).__init__(message=message)
9
+ self._status_code = status_code
10
+
11
+ def get_status_code(self) -> int:
12
+ return self._status_code
@@ -9,5 +9,5 @@ class LogPollingRequest(BaseModel):
9
9
  taskId: Optional[int] = Field(None, alias='taskId')
10
10
  inferenceSimulatorId: Optional[int] = Field(None, alias='inferenceSimulatorId')
11
11
  lastLogId: Optional[str] = Field(None, alias='lastLogId')
12
- dockerContainerId: Optional[str] = Field(None, alias='dockerContainerId')
12
+ dockerContainerId: Optional[int] = Field(None, alias='dockerContainerId')
13
13
  lastLogTimestamp: Optional[str] = Field(None, alias='lastLogTimestamp')
@@ -26,8 +26,6 @@ class ProjectDto(BaseModel):
26
26
  last_task_run_date: Optional[str] = Field(None, alias='lastTaskRunDate')
27
27
  created_at: Optional[str] = Field(None, alias='createdAt')
28
28
  updated_at: Optional[str] = Field(None, alias='updatedAt')
29
- instance_rented_list: List[InstanceRentedDto] = Field(default_factory=list, alias='instanceRentedList')
30
- selfhosted_instance_list: List[SelfHostedInstanceDto] = Field(default_factory=list, alias='selfhostedInstanceList')
31
29
 
32
30
 
33
31
  class ProjectViewResponse(TheStageBaseResponse):
@@ -1,7 +1,8 @@
1
1
  from typing import Optional, List
2
2
 
3
- from pydantic import BaseModel, Field
4
- from thestage_core.entities.file_item import FileItemEntity
3
+ from pydantic import Field
4
+
5
+ from thestage.entities.file_item import FileItemEntity
5
6
 
6
7
 
7
8
  class SftpFileItemEntity(FileItemEntity):