kleinkram 0.38.1.dev20241119134715__tar.gz → 0.38.1.dev20241120100707__tar.gz
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.
Potentially problematic release.
This version of kleinkram might be problematic. Click here for more details.
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/PKG-INFO +1 -1
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/client.py +35 -5
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/file_transfer.py +33 -24
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/routes.py +3 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/app.py +44 -13
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/verify.py +4 -2
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/errors.py +6 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/utils.py +16 -6
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram.egg-info/PKG-INFO +1 -1
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/setup.cfg +1 -1
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/LICENSE +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/README.md +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/__init__.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/__main__.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/_version.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/__init__.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/auth.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/__init__.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/download.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/endpoint.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/list.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/mission.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/project.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/upload.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/config.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/consts.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/core.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/enums.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/main.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/models.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram.egg-info/SOURCES.txt +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram.egg-info/dependency_links.txt +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram.egg-info/entry_points.txt +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram.egg-info/requires.txt +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram.egg-info/top_level.txt +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/pyproject.toml +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/requirements.txt +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/setup.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/tests/__init__.py +0 -0
- {kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/tests/test_utils.py +0 -0
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/client.py
RENAMED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
3
6
|
import httpx
|
|
4
7
|
from kleinkram.auth import Config
|
|
5
8
|
from kleinkram.config import Credentials
|
|
6
9
|
from kleinkram.errors import LOGIN_MESSAGE
|
|
7
10
|
from kleinkram.errors import NotAuthenticatedException
|
|
8
11
|
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
COOKIE_AUTH_TOKEN = "authtoken"
|
|
11
16
|
COOKIE_REFRESH_TOKEN = "refreshtoken"
|
|
12
17
|
COOKIE_CLI_KEY = "clikey"
|
|
13
18
|
|
|
14
19
|
|
|
20
|
+
class NotLoggedInException(Exception): ...
|
|
21
|
+
|
|
22
|
+
|
|
15
23
|
class AuthenticatedClient(httpx.Client):
|
|
16
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
24
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
17
25
|
super().__init__(*args, **kwargs)
|
|
18
26
|
self.config = Config()
|
|
19
27
|
|
|
20
28
|
if self.config.has_cli_key:
|
|
21
29
|
assert self.config.cli_key, "unreachable"
|
|
30
|
+
logger.info("using cli key...")
|
|
22
31
|
self.cookies.set(COOKIE_CLI_KEY, self.config.cli_key)
|
|
23
32
|
|
|
24
33
|
elif self.config.has_refresh_token:
|
|
34
|
+
logger.info("using refresh token...")
|
|
25
35
|
assert self.config.auth_token is not None, "unreachable"
|
|
26
36
|
self.cookies.set(COOKIE_AUTH_TOKEN, self.config.auth_token)
|
|
27
37
|
else:
|
|
38
|
+
logger.info("not authenticated...")
|
|
28
39
|
raise NotAuthenticatedException(self.config.endpoint)
|
|
29
40
|
|
|
30
41
|
def _refresh_token(self) -> None:
|
|
@@ -37,6 +48,7 @@ class AuthenticatedClient(httpx.Client):
|
|
|
37
48
|
|
|
38
49
|
self.cookies.set(COOKIE_REFRESH_TOKEN, refresh_token)
|
|
39
50
|
|
|
51
|
+
logger.info("refreshing token...")
|
|
40
52
|
response = self.post(
|
|
41
53
|
"/auth/refresh-token",
|
|
42
54
|
)
|
|
@@ -45,21 +57,39 @@ class AuthenticatedClient(httpx.Client):
|
|
|
45
57
|
new_access_token = response.cookies[COOKIE_AUTH_TOKEN]
|
|
46
58
|
creds = Credentials(auth_token=new_access_token, refresh_token=refresh_token)
|
|
47
59
|
|
|
60
|
+
logger.info("saving new tokens...")
|
|
48
61
|
self.config.save_credentials(creds)
|
|
49
62
|
self.cookies.set(COOKIE_AUTH_TOKEN, new_access_token)
|
|
50
63
|
|
|
51
|
-
def request(
|
|
64
|
+
def request(
|
|
65
|
+
self, method: str, url: str | httpx.URL, *args: Any, **kwargs: Any
|
|
66
|
+
) -> httpx.Response:
|
|
67
|
+
if isinstance(url, httpx.URL):
|
|
68
|
+
raise ValueError("url must be a slug")
|
|
69
|
+
|
|
70
|
+
# try to do a request
|
|
52
71
|
full_url = f"{self.config.endpoint}{url}"
|
|
72
|
+
logger.info(f"requesting {method} {full_url}")
|
|
53
73
|
response = super().request(method, full_url, *args, **kwargs)
|
|
74
|
+
logger.info(f"got response {response}")
|
|
54
75
|
|
|
76
|
+
# if the requesting a refresh token fails, we are not logged in
|
|
55
77
|
if (url == "/auth/refresh-token") and response.status_code == 401:
|
|
56
|
-
|
|
78
|
+
logger.info("got 401, not logged in...")
|
|
79
|
+
raise NotLoggedInException(LOGIN_MESSAGE)
|
|
57
80
|
|
|
81
|
+
# otherwise we try to refresh the token
|
|
58
82
|
if response.status_code == 401:
|
|
83
|
+
logger.info("got 401, trying to refresh token...")
|
|
59
84
|
try:
|
|
60
85
|
self._refresh_token()
|
|
61
86
|
except Exception:
|
|
62
|
-
raise
|
|
63
|
-
|
|
87
|
+
raise NotLoggedInException(LOGIN_MESSAGE)
|
|
88
|
+
|
|
89
|
+
logger.info(f"retrying request {method} {full_url}")
|
|
90
|
+
resp = super().request(method, full_url, *args, **kwargs)
|
|
91
|
+
logger.info(f"got response {resp}")
|
|
92
|
+
return resp
|
|
93
|
+
|
|
64
94
|
else:
|
|
65
95
|
return response
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
import sys
|
|
5
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -20,12 +21,19 @@ from kleinkram.config import Config
|
|
|
20
21
|
from kleinkram.config import LOCAL_S3
|
|
21
22
|
from kleinkram.errors import AccessDeniedException
|
|
22
23
|
from kleinkram.errors import CorruptedFile
|
|
24
|
+
from kleinkram.errors import NotValidUUID
|
|
25
|
+
from kleinkram.errors import UploadCredentialsFailed
|
|
23
26
|
from kleinkram.errors import UploadFailed
|
|
24
27
|
from kleinkram.utils import b64_md5
|
|
25
|
-
from kleinkram.utils import
|
|
26
|
-
from
|
|
28
|
+
from kleinkram.utils import format_error
|
|
29
|
+
from kleinkram.utils import format_traceback
|
|
30
|
+
from kleinkram.utils import styled_string
|
|
31
|
+
from rich.console import Console
|
|
27
32
|
from tqdm import tqdm
|
|
28
33
|
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
29
37
|
UPLOAD_CREDS = "/file/temporaryAccess"
|
|
30
38
|
UPLOAD_CONFIRM = "/queue/confirmUpload"
|
|
31
39
|
UPLOAD_CANCEL = "/file/cancelUpload"
|
|
@@ -71,7 +79,7 @@ def _confirm_file_upload(
|
|
|
71
79
|
resp = client.post(UPLOAD_CONFIRM, json=data)
|
|
72
80
|
|
|
73
81
|
if 400 <= resp.status_code < 500:
|
|
74
|
-
raise CorruptedFile()
|
|
82
|
+
raise CorruptedFile("failed to confirm upload")
|
|
75
83
|
resp.raise_for_status()
|
|
76
84
|
|
|
77
85
|
|
|
@@ -96,7 +104,7 @@ def _get_file_download(client: AuthenticatedClient, id: UUID) -> str:
|
|
|
96
104
|
if 400 <= resp.status_code < 500:
|
|
97
105
|
raise AccessDeniedException(
|
|
98
106
|
f"Failed to download file: {resp.json()['message']}",
|
|
99
|
-
"Status Code:
|
|
107
|
+
f"Status Code: {resp.status_code}",
|
|
100
108
|
)
|
|
101
109
|
|
|
102
110
|
resp.raise_for_status()
|
|
@@ -108,7 +116,7 @@ def _get_upload_creditials(
|
|
|
108
116
|
client: AuthenticatedClient, internal_filenames: List[str], mission_id: UUID
|
|
109
117
|
) -> Dict[str, UploadCredentials]:
|
|
110
118
|
if mission_id.version != 4:
|
|
111
|
-
raise
|
|
119
|
+
raise NotValidUUID("Mission ID must be a UUIDv4")
|
|
112
120
|
dct = {
|
|
113
121
|
"filenames": internal_filenames,
|
|
114
122
|
"missionUUID": str(mission_id),
|
|
@@ -116,9 +124,8 @@ def _get_upload_creditials(
|
|
|
116
124
|
resp = client.post(UPLOAD_CREDS, json=dct)
|
|
117
125
|
|
|
118
126
|
if resp.status_code >= 400:
|
|
119
|
-
raise
|
|
120
|
-
"Failed to get temporary credentials
|
|
121
|
-
f"{resp.status_code}\n{resp.json()['message'][0]}"
|
|
127
|
+
raise UploadCredentialsFailed(
|
|
128
|
+
f"Failed to get temporary credentials {internal_filenames}"
|
|
122
129
|
)
|
|
123
130
|
|
|
124
131
|
data = resp.json()
|
|
@@ -178,8 +185,8 @@ def _s3_upload(
|
|
|
178
185
|
Callback=pbar.update,
|
|
179
186
|
)
|
|
180
187
|
except Exception as e:
|
|
181
|
-
|
|
182
|
-
pbar.write(
|
|
188
|
+
logger.error(format_traceback(e))
|
|
189
|
+
pbar.write(format_error(f"error uploading file {local_path}", e))
|
|
183
190
|
return False
|
|
184
191
|
return True
|
|
185
192
|
|
|
@@ -212,6 +219,7 @@ def _upload_file(
|
|
|
212
219
|
# upload file
|
|
213
220
|
creds = access[job.name]
|
|
214
221
|
except Exception as e:
|
|
222
|
+
logger.error(format_traceback(e))
|
|
215
223
|
pbar.write(f"unable to get upload credentials for file {job.path.name}: {e}")
|
|
216
224
|
pbar.close()
|
|
217
225
|
if global_pbar is not None:
|
|
@@ -226,8 +234,8 @@ def _upload_file(
|
|
|
226
234
|
try:
|
|
227
235
|
_cancel_file_upload(client, creds.file_id, job.mission_id)
|
|
228
236
|
except Exception as e:
|
|
229
|
-
|
|
230
|
-
pbar.write(
|
|
237
|
+
logger.error(format_traceback(e))
|
|
238
|
+
pbar.write(format_error(f"error cancelling upload {job.path}", e))
|
|
231
239
|
else:
|
|
232
240
|
# tell backend that upload is complete
|
|
233
241
|
try:
|
|
@@ -235,15 +243,14 @@ def _upload_file(
|
|
|
235
243
|
_confirm_file_upload(client, creds.file_id, local_hash)
|
|
236
244
|
|
|
237
245
|
if global_pbar is not None:
|
|
238
|
-
msg =
|
|
239
|
-
|
|
246
|
+
msg = f"uploaded {job.path}"
|
|
247
|
+
logger.info(msg)
|
|
248
|
+
global_pbar.write(styled_string(msg, style="green"))
|
|
240
249
|
global_pbar.update()
|
|
241
250
|
|
|
242
251
|
except Exception as e:
|
|
243
|
-
msg =
|
|
244
|
-
|
|
245
|
-
)
|
|
246
|
-
pbar.write(raw_rich(msg))
|
|
252
|
+
msg = format_error(f"error confirming upload {job.path}", e)
|
|
253
|
+
pbar.write(msg)
|
|
247
254
|
|
|
248
255
|
pbar.close()
|
|
249
256
|
return (job.path.stat().st_size, job.path)
|
|
@@ -292,14 +299,16 @@ def upload_files(
|
|
|
292
299
|
|
|
293
300
|
total_size += size
|
|
294
301
|
except Exception as e:
|
|
302
|
+
logger.error(format_traceback(e))
|
|
295
303
|
errors.append(e)
|
|
296
304
|
|
|
297
305
|
pbar.close()
|
|
298
306
|
|
|
299
307
|
time = monotonic() - start
|
|
300
|
-
|
|
301
|
-
print(f"
|
|
302
|
-
print(f"
|
|
308
|
+
c = Console(file=sys.stderr)
|
|
309
|
+
c.print(f"upload took {time:.2f} seconds")
|
|
310
|
+
c.print(f"total size: {int(total_size)} MB")
|
|
311
|
+
c.print(f"average speed: {total_size / time:.2f} MB/s")
|
|
303
312
|
|
|
304
313
|
if errors:
|
|
305
314
|
raise UploadFailed(f"got unhandled errors: {errors} when uploading files")
|
|
@@ -307,12 +316,12 @@ def upload_files(
|
|
|
307
316
|
|
|
308
317
|
def _url_download(url: str, path: Path, size: int, overwrite: bool = False) -> None:
|
|
309
318
|
if path.exists() and not overwrite:
|
|
310
|
-
raise FileExistsError(f"
|
|
319
|
+
raise FileExistsError(f"file already exists: {path}")
|
|
311
320
|
|
|
312
321
|
with httpx.stream("GET", url) as response:
|
|
313
322
|
with open(path, "wb") as f:
|
|
314
323
|
with tqdm(
|
|
315
|
-
total=size, desc=f"
|
|
324
|
+
total=size, desc=f"downloading {path.name}", unit="B", unit_scale=True
|
|
316
325
|
) as pbar:
|
|
317
326
|
for chunk in response.iter_bytes(chunk_size=DOWNLOAD_CHUNK_SIZE):
|
|
318
327
|
f.write(chunk)
|
|
@@ -334,4 +343,4 @@ def download_file(
|
|
|
334
343
|
observed_hash = b64_md5(file_path)
|
|
335
344
|
|
|
336
345
|
if observed_hash != hash:
|
|
337
|
-
raise CorruptedFile("file hash does not match")
|
|
346
|
+
raise CorruptedFile(f"file hash does not match: {dest}")
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/routes.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from typing import Any
|
|
4
5
|
from typing import cast
|
|
5
6
|
from typing import Dict
|
|
@@ -28,6 +29,8 @@ from kleinkram.models import TagType
|
|
|
28
29
|
from kleinkram.utils import filtered_by_patterns
|
|
29
30
|
from kleinkram.utils import is_valid_uuid4
|
|
30
31
|
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
MAX_PAGINATION = 10_000
|
|
33
36
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
3
7
|
from collections import OrderedDict
|
|
4
8
|
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
5
10
|
from typing import Any
|
|
6
11
|
from typing import Callable
|
|
7
12
|
from typing import List
|
|
@@ -25,10 +30,18 @@ from kleinkram.commands.verify import verify_typer
|
|
|
25
30
|
from kleinkram.config import Config
|
|
26
31
|
from kleinkram.config import get_shared_state
|
|
27
32
|
from kleinkram.errors import InvalidCLIVersion
|
|
33
|
+
from kleinkram.utils import format_traceback
|
|
28
34
|
from kleinkram.utils import get_supported_api_version
|
|
29
35
|
from rich.console import Console
|
|
30
36
|
from typer.core import TyperGroup
|
|
31
37
|
|
|
38
|
+
LOG_DIR = Path() / "logs"
|
|
39
|
+
LOG_FILE = LOG_DIR / f"{time.time_ns()}.log"
|
|
40
|
+
LOG_FORMAT = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
|
|
41
|
+
|
|
42
|
+
# setup default logging
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
32
45
|
|
|
33
46
|
CLI_HELP = """\
|
|
34
47
|
Kleinkram CLI
|
|
@@ -40,6 +53,14 @@ for more information.
|
|
|
40
53
|
"""
|
|
41
54
|
|
|
42
55
|
|
|
56
|
+
class LogLevel(str, Enum):
|
|
57
|
+
DEBUG = "DEBUG"
|
|
58
|
+
INFO = "INFO"
|
|
59
|
+
WARNING = "WARNING"
|
|
60
|
+
ERROR = "ERROR"
|
|
61
|
+
CRITICAL = "CRITICAL"
|
|
62
|
+
|
|
63
|
+
|
|
43
64
|
class CommandTypes(str, Enum):
|
|
44
65
|
AUTH = "Authentication Commands"
|
|
45
66
|
CORE = "Core Commands"
|
|
@@ -97,10 +118,9 @@ app = ErrorHandledTyper(
|
|
|
97
118
|
@app.error_handler(Exception)
|
|
98
119
|
def base_handler(exc: Exception) -> int:
|
|
99
120
|
if not get_shared_state().debug:
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
Console(file=sys.stderr).print(f"{type(exc).__name__}: {exc}", style="red")
|
|
122
|
+
logger.error(format_traceback(exc))
|
|
102
123
|
return 1
|
|
103
|
-
|
|
104
124
|
raise exc
|
|
105
125
|
|
|
106
126
|
|
|
@@ -134,7 +154,7 @@ def claim():
|
|
|
134
154
|
print("admin rights claimed successfully.")
|
|
135
155
|
|
|
136
156
|
|
|
137
|
-
def
|
|
157
|
+
def _version_callback(value: bool) -> None:
|
|
138
158
|
if value:
|
|
139
159
|
typer.echo(__version__)
|
|
140
160
|
raise typer.Exit()
|
|
@@ -151,11 +171,9 @@ def check_version_compatiblity() -> None:
|
|
|
151
171
|
)
|
|
152
172
|
|
|
153
173
|
if cli_version[1] != api_version[1]:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
style="red",
|
|
158
|
-
)
|
|
174
|
+
msg = f"CLI version {__version__} might not be compatible with API version {api_vers_str}"
|
|
175
|
+
Console(file=sys.stderr).print(msg, style="red")
|
|
176
|
+
logger.warning(msg)
|
|
159
177
|
|
|
160
178
|
|
|
161
179
|
@app.callback()
|
|
@@ -163,18 +181,31 @@ def cli(
|
|
|
163
181
|
verbose: bool = typer.Option(True, help="Enable verbose mode."),
|
|
164
182
|
debug: bool = typer.Option(False, help="Enable debug mode."),
|
|
165
183
|
version: Optional[bool] = typer.Option(
|
|
166
|
-
None, "--version", "-v", callback=
|
|
184
|
+
None, "--version", "-v", callback=_version_callback
|
|
167
185
|
),
|
|
186
|
+
log_level: Optional[LogLevel] = typer.Option(None, help="Set log level."),
|
|
168
187
|
):
|
|
169
188
|
_ = version # suppress unused variable warning
|
|
170
189
|
shared_state = get_shared_state()
|
|
171
190
|
shared_state.verbose = verbose
|
|
172
191
|
shared_state.debug = debug
|
|
173
192
|
|
|
193
|
+
if shared_state.debug:
|
|
194
|
+
log_level = LogLevel.DEBUG
|
|
195
|
+
|
|
196
|
+
if log_level is not None:
|
|
197
|
+
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
198
|
+
level = logging.getLevelName(log_level)
|
|
199
|
+
logging.basicConfig(level=level, filename=LOG_FILE, format=LOG_FORMAT)
|
|
200
|
+
|
|
201
|
+
logger.info(f"CLI version: {__version__}")
|
|
202
|
+
|
|
174
203
|
try:
|
|
175
204
|
check_version_compatiblity()
|
|
176
|
-
except InvalidCLIVersion:
|
|
205
|
+
except InvalidCLIVersion as e:
|
|
206
|
+
logger.error(format_traceback(e))
|
|
177
207
|
raise
|
|
178
208
|
except Exception:
|
|
179
|
-
|
|
180
|
-
|
|
209
|
+
err = "failed to check version compatibility"
|
|
210
|
+
Console(file=sys.stderr).print(err, style="yellow")
|
|
211
|
+
logger.error(err)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import sys
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from pathlib import Path
|
|
@@ -22,6 +23,8 @@ from rich.table import Table
|
|
|
22
23
|
from rich.text import Text
|
|
23
24
|
from tqdm import tqdm
|
|
24
25
|
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
class FileVerificationStatus(str, Enum):
|
|
27
30
|
UPLAODED = "uploaded"
|
|
@@ -107,8 +110,7 @@ def verify(
|
|
|
107
110
|
for path, status in status_dct.items():
|
|
108
111
|
table.add_row(str(path), Text(status, style=FILE_STATUS_STYLES[status]))
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
console.print(table)
|
|
113
|
+
Console().print(table)
|
|
112
114
|
else:
|
|
113
115
|
for path, status in status_dct.items():
|
|
114
116
|
stream = (
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/errors.py
RENAMED
|
@@ -18,6 +18,9 @@ class MissionDoesNotExist(Exception): ...
|
|
|
18
18
|
class NoPermission(Exception): ...
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
class UploadCredentialsFailed(Exception): ...
|
|
22
|
+
|
|
23
|
+
|
|
21
24
|
class AccessDeniedException(Exception):
|
|
22
25
|
def __init__(self, message: str, api_error: str):
|
|
23
26
|
self.message = message
|
|
@@ -38,6 +41,9 @@ class CorruptedFile(Exception): ...
|
|
|
38
41
|
class NameIsValidUUID(Exception): ...
|
|
39
42
|
|
|
40
43
|
|
|
44
|
+
class NotValidUUID(Exception): ...
|
|
45
|
+
|
|
46
|
+
|
|
41
47
|
class UploadFailed(Exception): ...
|
|
42
48
|
|
|
43
49
|
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/utils.py
RENAMED
|
@@ -3,14 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
import base64
|
|
4
4
|
import fnmatch
|
|
5
5
|
import hashlib
|
|
6
|
-
import os
|
|
7
6
|
import string
|
|
7
|
+
import traceback
|
|
8
8
|
from hashlib import md5
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
from typing import Dict
|
|
12
12
|
from typing import List
|
|
13
|
-
from typing import NamedTuple
|
|
14
13
|
from typing import Optional
|
|
15
14
|
from typing import Sequence
|
|
16
15
|
from typing import Tuple
|
|
@@ -44,6 +43,20 @@ def check_file_paths(files: Sequence[Path]) -> None:
|
|
|
44
43
|
)
|
|
45
44
|
|
|
46
45
|
|
|
46
|
+
def format_error(msg: str, exc: Exception, *, verbose: bool = False) -> str:
|
|
47
|
+
if not verbose:
|
|
48
|
+
ret = f"{msg}: {type(exc).__name__}"
|
|
49
|
+
else:
|
|
50
|
+
ret = f"{msg}: {exc}"
|
|
51
|
+
return styled_string(ret, style="red")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def format_traceback(exc: Exception) -> str:
|
|
55
|
+
return "".join(
|
|
56
|
+
traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
47
60
|
def filtered_by_patterns(names: Sequence[str], patterns: List[str]) -> List[str]:
|
|
48
61
|
filtered = []
|
|
49
62
|
for name in names:
|
|
@@ -52,17 +65,14 @@ def filtered_by_patterns(names: Sequence[str], patterns: List[str]) -> List[str]
|
|
|
52
65
|
return filtered
|
|
53
66
|
|
|
54
67
|
|
|
55
|
-
def
|
|
68
|
+
def styled_string(*objects: Any, **kwargs: Any) -> str:
|
|
56
69
|
"""\
|
|
57
70
|
accepts any object that Console.print can print
|
|
58
71
|
returns the raw string output
|
|
59
72
|
"""
|
|
60
|
-
|
|
61
73
|
console = Console()
|
|
62
|
-
|
|
63
74
|
with console.capture() as capture:
|
|
64
75
|
console.print(*objects, **kwargs, end="")
|
|
65
|
-
|
|
66
76
|
return capture.get()
|
|
67
77
|
|
|
68
78
|
|
|
File without changes
|
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/__init__.py
RENAMED
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/__main__.py
RENAMED
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/_version.py
RENAMED
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/api/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/commands/list.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/config.py
RENAMED
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/consts.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/enums.py
RENAMED
|
File without changes
|
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/kleinkram/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kleinkram-0.38.1.dev20241119134715 → kleinkram-0.38.1.dev20241120100707}/tests/test_utils.py
RENAMED
|
File without changes
|