thestage 0.5.38__py3-none-any.whl → 0.5.39__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/.env +3 -4
- thestage/__init__.py +1 -1
- thestage/controllers/config_controller.py +3 -4
- thestage/controllers/container_controller.py +12 -16
- thestage/controllers/project_controller.py +10 -3
- thestage/controllers/utils_controller.py +2 -3
- thestage/entities/file_item.py +27 -0
- thestage/exceptions/file_system_exception.py +6 -0
- thestage/helpers/error_handler.py +2 -2
- thestage/helpers/logger/app_logger.py +3 -4
- thestage/services/abstract_service.py +1 -2
- thestage/services/app_config_service.py +2 -3
- thestage/services/clients/.DS_Store +0 -0
- thestage/services/clients/git/git_client.py +3 -3
- thestage/services/clients/thestage_api/api_client.py +3 -61
- thestage/services/clients/thestage_api/core/api_client_abstract.py +91 -0
- thestage/services/clients/thestage_api/core/api_client_core.py +25 -0
- thestage/services/clients/thestage_api/core/http_client_exception.py +12 -0
- thestage/services/clients/thestage_api/dtos/logging_controller/log_polling_request.py +1 -1
- thestage/services/clients/thestage_api/dtos/sftp_path_helper.py +3 -2
- thestage/services/config_provider/config_provider.py +98 -44
- thestage/services/connect/connect_service.py +1 -1
- thestage/services/container/container_service.py +2 -8
- thestage/services/core_files/config_entity.py +25 -0
- thestage/services/filesystem_service.py +115 -0
- thestage/services/instance/instance_service.py +1 -2
- thestage/services/logging/logging_service.py +76 -95
- thestage/services/project/project_service.py +9 -7
- thestage/services/remote_server_service.py +3 -3
- thestage/services/service_factory.py +1 -2
- thestage/services/validation_service.py +26 -10
- {thestage-0.5.38.dist-info → thestage-0.5.39.dist-info}/METADATA +1 -2
- {thestage-0.5.38.dist-info → thestage-0.5.39.dist-info}/RECORD +36 -28
- {thestage-0.5.38.dist-info → thestage-0.5.39.dist-info}/WHEEL +1 -1
- {thestage-0.5.38.dist-info → thestage-0.5.39.dist-info}/LICENSE.txt +0 -0
- {thestage-0.5.38.dist-info → thestage-0.5.39.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
|
-
|
|
4
|
-
THESTAGE_API_URL=
|
|
5
|
-
|
|
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
|
@@ -2,8 +2,7 @@ import os
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
|
-
from
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
422
|
+
container_uid=container_uid
|
|
427
423
|
)
|
|
428
424
|
else:
|
|
429
|
-
|
|
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
|
|
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.
|
|
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
|
+
)
|
|
@@ -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
|
-
|
|
8
|
-
from
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
264
|
-
|
|
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[
|
|
12
|
+
dockerContainerId: Optional[int] = Field(None, alias='dockerContainerId')
|
|
13
13
|
lastLogTimestamp: Optional[str] = Field(None, alias='lastLogTimestamp')
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from typing import Optional, List
|
|
2
2
|
|
|
3
|
-
from pydantic import
|
|
4
|
-
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from thestage.entities.file_item import FileItemEntity
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class SftpFileItemEntity(FileItemEntity):
|