divbase-cli 0.1.0.dev2__py3-none-any.whl → 0.1.0.dev4__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.
- divbase_cli/__init__.py +1 -1
- divbase_cli/cli_commands/auth_cli.py +16 -10
- divbase_cli/cli_commands/dimensions_cli.py +322 -26
- divbase_cli/cli_commands/file_cli.py +488 -101
- divbase_cli/cli_commands/query_cli.py +14 -9
- divbase_cli/cli_commands/shared_args_options.py +23 -0
- divbase_cli/cli_commands/task_history_cli.py +3 -8
- divbase_cli/cli_commands/user_config_cli.py +14 -44
- divbase_cli/cli_commands/version_cli.py +98 -37
- divbase_cli/cli_config.py +27 -9
- divbase_cli/cli_exceptions.py +37 -22
- divbase_cli/config_resolver.py +10 -10
- divbase_cli/divbase_cli.py +1 -1
- divbase_cli/retries.py +34 -0
- divbase_cli/services/__init__.py +0 -0
- divbase_cli/services/announcements.py +46 -0
- divbase_cli/services/pre_signed_urls.py +456 -0
- divbase_cli/services/project_versions.py +103 -0
- divbase_cli/services/s3_files.py +442 -0
- divbase_cli/user_auth.py +104 -46
- divbase_cli/user_config.py +20 -9
- divbase_cli/utils.py +28 -0
- {divbase_cli-0.1.0.dev2.dist-info → divbase_cli-0.1.0.dev4.dist-info}/METADATA +3 -4
- divbase_cli-0.1.0.dev4.dist-info/RECORD +28 -0
- {divbase_cli-0.1.0.dev2.dist-info → divbase_cli-0.1.0.dev4.dist-info}/WHEEL +1 -1
- divbase_cli/pre_signed_urls.py +0 -169
- divbase_cli/services.py +0 -219
- divbase_cli-0.1.0.dev2.dist-info/RECORD +0 -22
- {divbase_cli-0.1.0.dev2.dist-info → divbase_cli-0.1.0.dev4.dist-info}/entry_points.txt +0 -0
divbase_cli/cli_exceptions.py
CHANGED
|
@@ -4,7 +4,7 @@ Custom exceptions for the divbase CLI.
|
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from divbase_lib.
|
|
7
|
+
from divbase_lib.divbase_constants import SUPPORTED_DIVBASE_FILE_TYPES, UNSUPPORTED_CHARACTERS_IN_FILENAMES
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DivBaseCLIError(Exception):
|
|
@@ -41,7 +41,7 @@ class DivBaseAPIError(DivBaseCLIError):
|
|
|
41
41
|
self,
|
|
42
42
|
error_details: str = "Not Provided",
|
|
43
43
|
error_type: str = "unknown",
|
|
44
|
-
status_code: int =
|
|
44
|
+
status_code: int = 500,
|
|
45
45
|
http_method: str = "unknown",
|
|
46
46
|
url: str = "unknown",
|
|
47
47
|
):
|
|
@@ -87,8 +87,10 @@ class FilesAlreadyInProjectError(DivBaseCLIError):
|
|
|
87
87
|
and the user does not want to accidently create a new version of any file.
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
|
-
def __init__(self, existing_files:
|
|
91
|
-
files_list = "\n".join(
|
|
90
|
+
def __init__(self, existing_files: dict[Path, str], project_name: str):
|
|
91
|
+
files_list = "\n".join(
|
|
92
|
+
f"'{file_path}' (Checksum: {checksum})" for file_path, checksum in existing_files.items()
|
|
93
|
+
)
|
|
92
94
|
self.existing_files = existing_files
|
|
93
95
|
self.project_name = project_name
|
|
94
96
|
|
|
@@ -106,12 +108,12 @@ class ProjectNameNotSpecifiedError(DivBaseCLIError):
|
|
|
106
108
|
no default project is set in the user config file.
|
|
107
109
|
"""
|
|
108
110
|
|
|
109
|
-
def __init__(self
|
|
110
|
-
self.config_path = config_path
|
|
111
|
+
def __init__(self):
|
|
111
112
|
error_message = (
|
|
112
|
-
"No project name provided
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
"No project name provided.\n"
|
|
114
|
+
"Please either set a default project in your user configuration file.\n"
|
|
115
|
+
"or pass the flag '--project <project_name>' to this command.\n"
|
|
116
|
+
"To set a default project, you can run 'divbase-cli config set-default <project_name>'.\n"
|
|
115
117
|
)
|
|
116
118
|
super().__init__(error_message)
|
|
117
119
|
|
|
@@ -135,17 +137,30 @@ class ProjectNotInConfigError(DivBaseCLIError):
|
|
|
135
137
|
super().__init__(error_message)
|
|
136
138
|
|
|
137
139
|
|
|
138
|
-
class
|
|
139
|
-
"""Raised when
|
|
140
|
+
class UnsupportedFileTypeError(DivBaseCLIError):
|
|
141
|
+
"""Raised when one or more files to be uploaded are not supported by DivBase (based on file extension)."""
|
|
140
142
|
|
|
141
|
-
def __init__(
|
|
142
|
-
self
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
def __init__(self, unsupported_files: list[Path], supported_types: tuple[str, ...] = SUPPORTED_DIVBASE_FILE_TYPES):
|
|
144
|
+
self.unsupported_files = unsupported_files
|
|
145
|
+
self.supported_types = supported_types
|
|
146
|
+
message = (
|
|
147
|
+
f"The following file(s) have types that are not supported by DivBase and therefore cannot be uploaded: \n"
|
|
148
|
+
f"{'\n'.join(str(file) for file in unsupported_files)}\n"
|
|
149
|
+
f"DivBase currently supports the following file types: {', '.join(SUPPORTED_DIVBASE_FILE_TYPES)}\n"
|
|
150
|
+
"If you want us to support another file type, please let us know."
|
|
151
|
+
)
|
|
152
|
+
super().__init__(message)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class UnsupportedFileNameError(DivBaseCLIError):
|
|
156
|
+
"""Raised when one or more files to be uploaded have unsupported characters in their filenames."""
|
|
157
|
+
|
|
158
|
+
def __init__(self, unsupported_files: list[Path]):
|
|
159
|
+
self.unsupported_files = unsupported_files
|
|
160
|
+
message = (
|
|
161
|
+
f"The following file(s) have unsupported characters in their filenames and therefore cannot be uploaded: \n"
|
|
162
|
+
f"{'\n'.join(str(file) for file in unsupported_files)}\n"
|
|
163
|
+
f"Filenames cannot contain any of the following characters: {', '.join(UNSUPPORTED_CHARACTERS_IN_FILENAMES)}\n"
|
|
164
|
+
"Please rename the files and try again."
|
|
165
|
+
)
|
|
166
|
+
super().__init__(message)
|
divbase_cli/config_resolver.py
CHANGED
|
@@ -3,7 +3,7 @@ Functions that resolve for the CLI commands things like:
|
|
|
3
3
|
- which project to use
|
|
4
4
|
- which download directory to use
|
|
5
5
|
- which DivBase API URL to use
|
|
6
|
-
Based on
|
|
6
|
+
Based on provided user input and their config file.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from pathlib import Path
|
|
@@ -12,7 +12,7 @@ from divbase_cli.cli_exceptions import AuthenticationError, ProjectNameNotSpecif
|
|
|
12
12
|
from divbase_cli.user_config import ProjectConfig, load_user_config
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def ensure_logged_in(
|
|
15
|
+
def ensure_logged_in(desired_url: str | None = None) -> str:
|
|
16
16
|
"""
|
|
17
17
|
Ensure the user is logged in by checking the logged_in_url value in the user config.
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ def ensure_logged_in(config_path: Path, desired_url: str | None = None) -> str:
|
|
|
20
20
|
|
|
21
21
|
Returns the logged_in_url if valid, otherwise raises an AuthenticationError.
|
|
22
22
|
"""
|
|
23
|
-
config = load_user_config(
|
|
23
|
+
config = load_user_config()
|
|
24
24
|
if not config.logged_in_url:
|
|
25
25
|
raise AuthenticationError("You are not logged in. Please log in with 'divbase-cli auth login [EMAIL]'.")
|
|
26
26
|
if desired_url and config.logged_in_url != desired_url:
|
|
@@ -30,7 +30,7 @@ def ensure_logged_in(config_path: Path, desired_url: str | None = None) -> str:
|
|
|
30
30
|
return config.logged_in_url
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def resolve_project(project_name: str | None
|
|
33
|
+
def resolve_project(project_name: str | None) -> ProjectConfig:
|
|
34
34
|
"""
|
|
35
35
|
Helper function to resolve the project to use for a CLI command.
|
|
36
36
|
Falls back to the default project set in the user config if not explicitly provided.
|
|
@@ -38,15 +38,15 @@ def resolve_project(project_name: str | None, config_path: Path) -> ProjectConfi
|
|
|
38
38
|
Once the project is resolved a ProjectConfig object is returned,
|
|
39
39
|
which contains the name and API URL of the project.
|
|
40
40
|
"""
|
|
41
|
-
config = load_user_config(
|
|
41
|
+
config = load_user_config()
|
|
42
42
|
if not project_name:
|
|
43
43
|
project_name = config.default_project
|
|
44
44
|
if not project_name:
|
|
45
|
-
raise ProjectNameNotSpecifiedError(
|
|
45
|
+
raise ProjectNameNotSpecifiedError()
|
|
46
46
|
return config.project_info(project_name)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def resolve_divbase_api_url(url: str | None
|
|
49
|
+
def resolve_divbase_api_url(url: str | None) -> str:
|
|
50
50
|
"""
|
|
51
51
|
Helper function to resolve the DivBase API URL to use for a CLI command.
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ def resolve_divbase_api_url(url: str | None, config_path: Path) -> str:
|
|
|
56
56
|
if url:
|
|
57
57
|
return url
|
|
58
58
|
|
|
59
|
-
config = load_user_config(
|
|
59
|
+
config = load_user_config()
|
|
60
60
|
|
|
61
61
|
if not config.default_project:
|
|
62
62
|
raise ValueError(
|
|
@@ -68,7 +68,7 @@ def resolve_divbase_api_url(url: str | None, config_path: Path) -> str:
|
|
|
68
68
|
return project_config.divbase_url
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def resolve_download_dir(download_dir: str | None
|
|
71
|
+
def resolve_download_dir(download_dir: str | None) -> Path:
|
|
72
72
|
"""
|
|
73
73
|
Helper function to resolve the download directory to use for a CLI command involving downloading files.
|
|
74
74
|
|
|
@@ -76,7 +76,7 @@ def resolve_download_dir(download_dir: str | None, config_path: Path) -> Path:
|
|
|
76
76
|
Note: "." or None should default to the current working directory.
|
|
77
77
|
"""
|
|
78
78
|
if not download_dir:
|
|
79
|
-
config = load_user_config(
|
|
79
|
+
config = load_user_config()
|
|
80
80
|
download_dir = config.default_download_dir
|
|
81
81
|
|
|
82
82
|
if download_dir and download_dir != ".":
|
divbase_cli/divbase_cli.py
CHANGED
|
@@ -67,7 +67,7 @@ app.add_typer(task_history_app, name="task-history")
|
|
|
67
67
|
|
|
68
68
|
def main():
|
|
69
69
|
if cli_settings.LOGGING_ON:
|
|
70
|
-
logging.basicConfig(level=cli_settings.LOG_LEVEL, handlers=[logging.StreamHandler(sys.
|
|
70
|
+
logging.basicConfig(level=cli_settings.LOG_LEVEL, handlers=[logging.StreamHandler(sys.stderr)])
|
|
71
71
|
logger.info(f"Starting divbase_cli CLI application with logging level: {cli_settings.LOG_LEVEL}")
|
|
72
72
|
app()
|
|
73
73
|
|
divbase_cli/retries.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Retry logic for the divbase-cli package.
|
|
3
|
+
|
|
4
|
+
Defines functions to determine whether to retry based on the type of exceptions raised.
|
|
5
|
+
Functions are used in stamina (package) decorators.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from divbase_cli.cli_exceptions import DivBaseAPIConnectionError, DivBaseAPIError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def retry_only_on_retryable_http_errors(exc: Exception) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Used by stamina's (library for retries) decorators to determine whether to retry the function or not.
|
|
16
|
+
We avoid retrying on HTTPStatusError for 4xx errors as no point (e.g. 404 Not Found or 403 Forbidden etc...).
|
|
17
|
+
"""
|
|
18
|
+
if isinstance(exc, httpx.HTTPStatusError):
|
|
19
|
+
return exc.response.status_code >= 500
|
|
20
|
+
|
|
21
|
+
# Want to retry on other HTTPError (parent of HTTPStatusError),
|
|
22
|
+
# as this includes timeouts, connection errors, etc.
|
|
23
|
+
return isinstance(exc, httpx.HTTPError)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def retry_only_on_retryable_divbase_api_errors(exception: Exception) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
Retry condition function for stamina to only retry on retryable DivBase API errors.
|
|
29
|
+
"""
|
|
30
|
+
if isinstance(exception, DivBaseAPIConnectionError):
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
# Retry only for server errors (5xx) and rate limiting (429)
|
|
34
|
+
return isinstance(exception, DivBaseAPIError) and (exception.status_code == 429 or exception.status_code >= 500)
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Get announcements from divbase API and display them to the user.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from rich import print
|
|
6
|
+
|
|
7
|
+
from divbase_cli.user_auth import make_unauthenticated_request
|
|
8
|
+
from divbase_lib.api_schemas.announcements import AnnouncementResponse
|
|
9
|
+
|
|
10
|
+
COLOR_MAPPING = {
|
|
11
|
+
"info": "blue",
|
|
12
|
+
"success": "green",
|
|
13
|
+
"warning": "yellow",
|
|
14
|
+
"danger": "red",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_and_display_announcements(divbase_base_url: str) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Get announcements from the API and display them to the user.
|
|
21
|
+
Used when logging in to display any important messages to the user.
|
|
22
|
+
"""
|
|
23
|
+
response = make_unauthenticated_request(
|
|
24
|
+
method="GET",
|
|
25
|
+
divbase_base_url=divbase_base_url,
|
|
26
|
+
api_route="/v1/core/announcements",
|
|
27
|
+
)
|
|
28
|
+
announcements_data = response.json()
|
|
29
|
+
if not announcements_data:
|
|
30
|
+
return
|
|
31
|
+
announcements = [AnnouncementResponse.model_validate(ann) for ann in announcements_data]
|
|
32
|
+
|
|
33
|
+
if len(announcements) == 1:
|
|
34
|
+
print("[bold]An Announcement from DivBase Server:[/bold]")
|
|
35
|
+
color = COLOR_MAPPING.get(announcements[0].level)
|
|
36
|
+
print(f"[bold {color}]{announcements[0].heading}[/bold {color}]")
|
|
37
|
+
if announcements[0].message:
|
|
38
|
+
print(f"{announcements[0].message}\n")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
print("[bold]Announcements from DivBase Server:[/bold]")
|
|
42
|
+
for idx, ann in enumerate(announcements, start=1):
|
|
43
|
+
color = COLOR_MAPPING.get(ann.level)
|
|
44
|
+
print(f"[bold {color}]{idx}) {ann.heading}[/bold {color}]")
|
|
45
|
+
if ann.message:
|
|
46
|
+
print(f"{ann.message}\n")
|