htx-cli 0.1.0__tar.gz → 0.1.2__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.
- {htx_cli-0.1.0 → htx_cli-0.1.2}/PKG-INFO +8 -3
- {htx_cli-0.1.0 → htx_cli-0.1.2}/README.md +6 -1
- {htx_cli-0.1.0 → htx_cli-0.1.2}/pyproject.toml +2 -2
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/disk_cli/__init__.py +1 -1
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/disk_cli/api.py +37 -37
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/disk_cli/cli.py +23 -23
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/disk_cli/config.py +5 -5
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/htx_cli.egg-info/PKG-INFO +8 -3
- {htx_cli-0.1.0 → htx_cli-0.1.2}/setup.cfg +0 -0
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/disk_cli/__main__.py +0 -0
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/htx_cli.egg-info/SOURCES.txt +0 -0
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/htx_cli.egg-info/dependency_links.txt +0 -0
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/htx_cli.egg-info/entry_points.txt +0 -0
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/htx_cli.egg-info/requires.txt +0 -0
- {htx_cli-0.1.0 → htx_cli-0.1.2}/src/htx_cli.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: htx-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: htx-cli command-line client for the Disk backend API
|
|
5
|
-
Requires-Python: >=3.
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: rich<14,>=13.9
|
|
8
8
|
Requires-Dist: requests<3,>=2.32
|
|
@@ -19,7 +19,12 @@ Requires-Dist: requests<3,>=2.32
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
22
|
+
pip install htx-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For local development:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
23
28
|
pip install -e .
|
|
24
29
|
```
|
|
25
30
|
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "htx-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "htx-cli command-line client for the Disk backend API"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"rich>=13.9,<14",
|
|
13
13
|
"requests>=2.32,<3",
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import time
|
|
6
|
-
from typing import Any, Callable
|
|
6
|
+
from typing import Any, Callable, Dict, Optional
|
|
7
7
|
from urllib.parse import urljoin
|
|
8
8
|
|
|
9
9
|
import requests
|
|
@@ -16,9 +16,9 @@ class DiskAPIError(RuntimeError):
|
|
|
16
16
|
self,
|
|
17
17
|
message: str,
|
|
18
18
|
*,
|
|
19
|
-
status_code: int
|
|
20
|
-
identifier: str
|
|
21
|
-
debug_msg: str
|
|
19
|
+
status_code: Optional[int] = None,
|
|
20
|
+
identifier: Optional[str] = None,
|
|
21
|
+
debug_msg: Optional[str] = None,
|
|
22
22
|
) -> None:
|
|
23
23
|
super().__init__(message)
|
|
24
24
|
self.status_code = status_code
|
|
@@ -32,13 +32,13 @@ class UploadToken:
|
|
|
32
32
|
upload_token: str
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
ProgressCallback = Callable[[int, int
|
|
35
|
+
ProgressCallback = Callable[[int, Optional[int]], None]
|
|
36
36
|
MAX_RETRIES = 5
|
|
37
37
|
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class UploadMonitor:
|
|
41
|
-
def __init__(self, local_path: Path, callback: ProgressCallback
|
|
41
|
+
def __init__(self, local_path: Path, callback: Optional[ProgressCallback] = None) -> None:
|
|
42
42
|
self.local_path = local_path
|
|
43
43
|
self.callback = callback
|
|
44
44
|
self.handle = local_path.open("rb")
|
|
@@ -74,8 +74,8 @@ class DiskClient:
|
|
|
74
74
|
self.timeout = timeout
|
|
75
75
|
self.session = requests.Session()
|
|
76
76
|
|
|
77
|
-
def _headers(self, *, include_auth: bool = True) ->
|
|
78
|
-
headers:
|
|
77
|
+
def _headers(self, *, include_auth: bool = True) -> Dict[str, str]:
|
|
78
|
+
headers: Dict[str, str] = {}
|
|
79
79
|
if include_auth and self.settings.token:
|
|
80
80
|
headers["Token"] = self.settings.token
|
|
81
81
|
return headers
|
|
@@ -114,10 +114,10 @@ class DiskClient:
|
|
|
114
114
|
method: str,
|
|
115
115
|
url: str,
|
|
116
116
|
*,
|
|
117
|
-
params:
|
|
118
|
-
json:
|
|
119
|
-
data:
|
|
120
|
-
files:
|
|
117
|
+
params: Optional[Dict[str, Any]] = None,
|
|
118
|
+
json: Optional[Dict[str, Any]] = None,
|
|
119
|
+
data: Optional[Dict[str, Any]] = None,
|
|
120
|
+
files: Optional[Dict[str, Any]] = None,
|
|
121
121
|
include_auth: bool = True,
|
|
122
122
|
) -> Any:
|
|
123
123
|
response = self.session.request(
|
|
@@ -137,15 +137,15 @@ class DiskClient:
|
|
|
137
137
|
method: str,
|
|
138
138
|
path: str,
|
|
139
139
|
*,
|
|
140
|
-
params:
|
|
141
|
-
json:
|
|
142
|
-
absolute_url: str
|
|
140
|
+
params: Optional[Dict[str, Any]] = None,
|
|
141
|
+
json: Optional[Dict[str, Any]] = None,
|
|
142
|
+
absolute_url: Optional[str] = None,
|
|
143
143
|
include_auth: bool = True,
|
|
144
|
-
data:
|
|
145
|
-
files:
|
|
144
|
+
data: Optional[Dict[str, Any]] = None,
|
|
145
|
+
files: Optional[Dict[str, Any]] = None,
|
|
146
146
|
) -> Any:
|
|
147
147
|
url = absolute_url or urljoin(f"{self.settings.backend_url}/", path.lstrip("/"))
|
|
148
|
-
last_error: Exception
|
|
148
|
+
last_error: Optional[Exception] = None
|
|
149
149
|
for attempt in range(1, MAX_RETRIES + 1):
|
|
150
150
|
try:
|
|
151
151
|
return self._request_once(
|
|
@@ -173,7 +173,7 @@ class DiskClient:
|
|
|
173
173
|
raise last_error
|
|
174
174
|
raise DiskAPIError(f"Request failed after {MAX_RETRIES} attempts.")
|
|
175
175
|
|
|
176
|
-
def exchange_code(self, code: str) ->
|
|
176
|
+
def exchange_code(self, code: str) -> Dict[str, Any]:
|
|
177
177
|
body = self.request(
|
|
178
178
|
"GET",
|
|
179
179
|
"/api/oauth/qtb/callback",
|
|
@@ -185,26 +185,26 @@ class DiskClient:
|
|
|
185
185
|
self.settings.token = body["token"]
|
|
186
186
|
return body
|
|
187
187
|
|
|
188
|
-
def get_current_user(self) ->
|
|
188
|
+
def get_current_user(self) -> Dict[str, Any]:
|
|
189
189
|
body = self.request("GET", "/api/user/")
|
|
190
190
|
if not isinstance(body, dict):
|
|
191
191
|
raise DiskAPIError("User endpoint returned an unexpected payload.")
|
|
192
192
|
return body
|
|
193
193
|
|
|
194
|
-
def get_resource(self, res_str_id: str, visit_key: str
|
|
194
|
+
def get_resource(self, res_str_id: str, visit_key: Optional[str] = None) -> Dict[str, Any]:
|
|
195
195
|
params = {"visit_key": visit_key} if visit_key else None
|
|
196
196
|
body = self.request("GET", f"/api/res/{res_str_id}", params=params)
|
|
197
197
|
if not isinstance(body, dict):
|
|
198
198
|
raise DiskAPIError("Resource endpoint returned an unexpected payload.")
|
|
199
199
|
return body
|
|
200
200
|
|
|
201
|
-
def get_selector(self, res_str_id: str) ->
|
|
201
|
+
def get_selector(self, res_str_id: str) -> Dict[str, Any]:
|
|
202
202
|
body = self.request("GET", f"/api/res/{res_str_id}/selector")
|
|
203
203
|
if not isinstance(body, dict):
|
|
204
204
|
raise DiskAPIError("Selector endpoint returned an unexpected payload.")
|
|
205
205
|
return body
|
|
206
206
|
|
|
207
|
-
def create_folder(self, parent_id: str, folder_name: str) ->
|
|
207
|
+
def create_folder(self, parent_id: str, folder_name: str) -> Dict[str, Any]:
|
|
208
208
|
body = self.request(
|
|
209
209
|
"POST",
|
|
210
210
|
f"/api/res/{parent_id}/folder",
|
|
@@ -214,7 +214,7 @@ class DiskClient:
|
|
|
214
214
|
raise DiskAPIError("Create-folder endpoint returned an unexpected payload.")
|
|
215
215
|
return body
|
|
216
216
|
|
|
217
|
-
def create_link(self, parent_id: str, link_name: str, link: str) ->
|
|
217
|
+
def create_link(self, parent_id: str, link_name: str, link: str) -> Dict[str, Any]:
|
|
218
218
|
body = self.request(
|
|
219
219
|
"POST",
|
|
220
220
|
f"/api/res/{parent_id}/link",
|
|
@@ -228,13 +228,13 @@ class DiskClient:
|
|
|
228
228
|
self,
|
|
229
229
|
res_str_id: str,
|
|
230
230
|
*,
|
|
231
|
-
rname: str
|
|
232
|
-
status: int
|
|
233
|
-
description: str
|
|
234
|
-
visit_key: str
|
|
235
|
-
right_bubble: bool
|
|
236
|
-
parent_str_id: str
|
|
237
|
-
) ->
|
|
231
|
+
rname: Optional[str] = None,
|
|
232
|
+
status: Optional[int] = None,
|
|
233
|
+
description: Optional[str] = None,
|
|
234
|
+
visit_key: Optional[str] = None,
|
|
235
|
+
right_bubble: Optional[bool] = None,
|
|
236
|
+
parent_str_id: Optional[str] = None,
|
|
237
|
+
) -> Dict[str, Any]:
|
|
238
238
|
body = self.request(
|
|
239
239
|
"PUT",
|
|
240
240
|
f"/api/res/{res_str_id}",
|
|
@@ -259,12 +259,12 @@ class DiskClient:
|
|
|
259
259
|
res_str_id: str,
|
|
260
260
|
destination: Path,
|
|
261
261
|
*,
|
|
262
|
-
progress_callback: ProgressCallback
|
|
262
|
+
progress_callback: Optional[ProgressCallback] = None,
|
|
263
263
|
) -> str:
|
|
264
264
|
url = urljoin(f"{self.settings.backend_url}/", f"/api/res/{res_str_id}/dl".lstrip("/"))
|
|
265
|
-
last_error: Exception
|
|
265
|
+
last_error: Optional[Exception] = None
|
|
266
266
|
for attempt in range(1, MAX_RETRIES + 1):
|
|
267
|
-
response: requests.Response
|
|
267
|
+
response: Optional[requests.Response] = None
|
|
268
268
|
try:
|
|
269
269
|
response = self.session.get(
|
|
270
270
|
url,
|
|
@@ -335,9 +335,9 @@ class DiskClient:
|
|
|
335
335
|
upload: UploadToken,
|
|
336
336
|
local_path: Path,
|
|
337
337
|
*,
|
|
338
|
-
progress_callback: ProgressCallback
|
|
339
|
-
) ->
|
|
340
|
-
last_error: Exception
|
|
338
|
+
progress_callback: Optional[ProgressCallback] = None,
|
|
339
|
+
) -> Dict[str, Any]:
|
|
340
|
+
last_error: Optional[Exception] = None
|
|
341
341
|
for attempt in range(1, MAX_RETRIES + 1):
|
|
342
342
|
try:
|
|
343
343
|
with UploadMonitor(local_path, progress_callback) as handle:
|
|
@@ -8,7 +8,7 @@ import sys
|
|
|
8
8
|
import webbrowser
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
12
12
|
from urllib.parse import parse_qs, urlparse
|
|
13
13
|
|
|
14
14
|
from rich import box
|
|
@@ -76,7 +76,7 @@ class CommandContext:
|
|
|
76
76
|
if not self.settings.token:
|
|
77
77
|
raise CLIUsageError("Please run `htx login` first.")
|
|
78
78
|
|
|
79
|
-
def sync_user(self) ->
|
|
79
|
+
def sync_user(self) -> Dict[str, Any]:
|
|
80
80
|
self.require_login()
|
|
81
81
|
user = self.client.get_current_user()
|
|
82
82
|
self.settings.user = user
|
|
@@ -256,7 +256,7 @@ def status_text(status: int) -> Text:
|
|
|
256
256
|
)
|
|
257
257
|
|
|
258
258
|
|
|
259
|
-
def resource_name_text(resource:
|
|
259
|
+
def resource_name_text(resource: Dict[str, Any]) -> Text:
|
|
260
260
|
text = Text()
|
|
261
261
|
text.append(format_resource_ref(resource["res_str_id"]), style="bold yellow")
|
|
262
262
|
text.append(" ")
|
|
@@ -264,13 +264,13 @@ def resource_name_text(resource: dict[str, Any]) -> Text:
|
|
|
264
264
|
return text
|
|
265
265
|
|
|
266
266
|
|
|
267
|
-
def display_size(resource:
|
|
267
|
+
def display_size(resource: Dict[str, Any]) -> str:
|
|
268
268
|
if resource.get("rtype") != RTYPE_FILE:
|
|
269
269
|
return "-"
|
|
270
270
|
return format_size(resource.get("rsize"))
|
|
271
271
|
|
|
272
272
|
|
|
273
|
-
def progress_columns() ->
|
|
273
|
+
def progress_columns() -> List[Any]:
|
|
274
274
|
return [
|
|
275
275
|
SpinnerColumn(style="bold cyan"),
|
|
276
276
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -294,7 +294,7 @@ def print_warning(message: str) -> None:
|
|
|
294
294
|
error_console.print(f"[bold yellow]WARN[/bold yellow] {message}")
|
|
295
295
|
|
|
296
296
|
|
|
297
|
-
def sort_children(children:
|
|
297
|
+
def sort_children(children: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
298
298
|
return sorted(
|
|
299
299
|
children,
|
|
300
300
|
key=lambda item: (
|
|
@@ -304,13 +304,13 @@ def sort_children(children: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
304
304
|
)
|
|
305
305
|
|
|
306
306
|
|
|
307
|
-
def ensure_folder_resource(resource:
|
|
307
|
+
def ensure_folder_resource(resource: Dict[str, Any], *, label: str) -> None:
|
|
308
308
|
info = resource.get("info", resource)
|
|
309
309
|
if info.get("rtype") != RTYPE_FOLDER:
|
|
310
310
|
raise CLIUsageError(f"{label} must refer to a remote directory.")
|
|
311
311
|
|
|
312
312
|
|
|
313
|
-
def parse_resource_reference(reference: str) ->
|
|
313
|
+
def parse_resource_reference(reference: str) -> Tuple[Optional[str], str]:
|
|
314
314
|
if reference.startswith("@"):
|
|
315
315
|
return reference[1:], "resource id"
|
|
316
316
|
if reference.startswith("id:"):
|
|
@@ -318,7 +318,7 @@ def parse_resource_reference(reference: str) -> tuple[str | None, str]:
|
|
|
318
318
|
return None, "path"
|
|
319
319
|
|
|
320
320
|
|
|
321
|
-
def resolve_remote_reference(ctx: CommandContext, reference: str) ->
|
|
321
|
+
def resolve_remote_reference(ctx: CommandContext, reference: str) -> Dict[str, Any]:
|
|
322
322
|
ctx.require_login()
|
|
323
323
|
resource_id, _ = parse_resource_reference(reference)
|
|
324
324
|
if resource_id:
|
|
@@ -345,7 +345,7 @@ def resolve_remote_reference(ctx: CommandContext, reference: str) -> dict[str, A
|
|
|
345
345
|
return current_resource
|
|
346
346
|
|
|
347
347
|
|
|
348
|
-
def find_child_folder(ctx: CommandContext, parent_id: str, folder_name: str) ->
|
|
348
|
+
def find_child_folder(ctx: CommandContext, parent_id: str, folder_name: str) -> Optional[Dict[str, Any]]:
|
|
349
349
|
selector = ctx.client.get_selector(parent_id)
|
|
350
350
|
for child in selector.get("child_list", []):
|
|
351
351
|
if child.get("rname") == folder_name:
|
|
@@ -357,7 +357,7 @@ def find_child_folder(ctx: CommandContext, parent_id: str, folder_name: str) ->
|
|
|
357
357
|
return None
|
|
358
358
|
|
|
359
359
|
|
|
360
|
-
def find_child_resource(ctx: CommandContext, parent_id: str, resource_name: str) ->
|
|
360
|
+
def find_child_resource(ctx: CommandContext, parent_id: str, resource_name: str) -> Optional[Dict[str, Any]]:
|
|
361
361
|
selector = ctx.client.get_selector(parent_id)
|
|
362
362
|
for child in selector.get("child_list", []):
|
|
363
363
|
if child.get("rname") == resource_name:
|
|
@@ -374,7 +374,7 @@ def ensure_remote_folder(ctx: CommandContext, parent_id: str, folder_name: str)
|
|
|
374
374
|
return created["res_str_id"]
|
|
375
375
|
|
|
376
376
|
|
|
377
|
-
def upload_file_to_remote(ctx: CommandContext, local_path: Path, parent_id: str) ->
|
|
377
|
+
def upload_file_to_remote(ctx: CommandContext, local_path: Path, parent_id: str) -> Dict[str, Any]:
|
|
378
378
|
with console.status(f"[bold cyan]Preparing upload for[/bold cyan] {local_path.name}", spinner="dots"):
|
|
379
379
|
upload_token = ctx.client.get_upload_token(parent_id, local_path.name)
|
|
380
380
|
|
|
@@ -382,7 +382,7 @@ def upload_file_to_remote(ctx: CommandContext, local_path: Path, parent_id: str)
|
|
|
382
382
|
with Progress(*progress_columns(), console=console, transient=True) as progress:
|
|
383
383
|
task_id = progress.add_task(f"Uploading {local_path.name}", total=total)
|
|
384
384
|
|
|
385
|
-
def on_progress(transferred: int, callback_total: int
|
|
385
|
+
def on_progress(transferred: int, callback_total: Optional[int]) -> None:
|
|
386
386
|
progress.update(task_id, completed=transferred, total=callback_total or total)
|
|
387
387
|
|
|
388
388
|
uploaded = ctx.client.upload_file(upload_token, local_path, progress_callback=on_progress)
|
|
@@ -394,7 +394,7 @@ def upload_file_to_remote(ctx: CommandContext, local_path: Path, parent_id: str)
|
|
|
394
394
|
|
|
395
395
|
def upload_directory_to_remote(ctx: CommandContext, local_dir: Path, parent_id: str) -> None:
|
|
396
396
|
root_remote_id = ensure_remote_folder(ctx, parent_id, local_dir.name)
|
|
397
|
-
remote_folder_map:
|
|
397
|
+
remote_folder_map: Dict[Path, str] = {Path("."): root_remote_id}
|
|
398
398
|
|
|
399
399
|
for current_root, dirnames, filenames in os.walk(local_dir):
|
|
400
400
|
dirnames.sort()
|
|
@@ -411,7 +411,7 @@ def upload_directory_to_remote(ctx: CommandContext, local_dir: Path, parent_id:
|
|
|
411
411
|
upload_file_to_remote(ctx, current_root_path / filename, remote_parent_id)
|
|
412
412
|
|
|
413
413
|
|
|
414
|
-
def resolve_download_destination(resource:
|
|
414
|
+
def resolve_download_destination(resource: Dict[str, Any], local_path: Optional[Path]) -> Path:
|
|
415
415
|
resource_name = resource["info"]["rname"]
|
|
416
416
|
if local_path is None:
|
|
417
417
|
return Path.cwd() / resource_name
|
|
@@ -422,11 +422,11 @@ def resolve_download_destination(resource: dict[str, Any], local_path: Path | No
|
|
|
422
422
|
return resolved
|
|
423
423
|
|
|
424
424
|
|
|
425
|
-
def download_file_to_local(ctx: CommandContext, resource:
|
|
425
|
+
def download_file_to_local(ctx: CommandContext, resource: Dict[str, Any], destination: Path) -> str:
|
|
426
426
|
with Progress(*progress_columns(), console=console, transient=True) as progress:
|
|
427
427
|
task_id = progress.add_task(f"Downloading {resource['info']['rname']}", total=None)
|
|
428
428
|
|
|
429
|
-
def on_progress(transferred: int, total: int
|
|
429
|
+
def on_progress(transferred: int, total: Optional[int]) -> None:
|
|
430
430
|
progress.update(task_id, completed=transferred, total=total)
|
|
431
431
|
|
|
432
432
|
return ctx.client.download_resource(
|
|
@@ -438,9 +438,9 @@ def download_file_to_local(ctx: CommandContext, resource: dict[str, Any], destin
|
|
|
438
438
|
|
|
439
439
|
def download_directory_to_local(
|
|
440
440
|
ctx: CommandContext,
|
|
441
|
-
resource:
|
|
441
|
+
resource: Dict[str, Any],
|
|
442
442
|
destination: Path,
|
|
443
|
-
) ->
|
|
443
|
+
) -> Tuple[int, int]:
|
|
444
444
|
destination.mkdir(parents=True, exist_ok=True)
|
|
445
445
|
file_count = 0
|
|
446
446
|
directory_count = 1
|
|
@@ -466,7 +466,7 @@ def download_directory_to_local(
|
|
|
466
466
|
return file_count, directory_count
|
|
467
467
|
|
|
468
468
|
|
|
469
|
-
def delete_resource_recursive(ctx: CommandContext, resource:
|
|
469
|
+
def delete_resource_recursive(ctx: CommandContext, resource: Dict[str, Any]) -> None:
|
|
470
470
|
info = resource["info"]
|
|
471
471
|
if info["rtype"] == RTYPE_FOLDER:
|
|
472
472
|
for child in sort_children(resource.get("child_list", [])):
|
|
@@ -476,7 +476,7 @@ def delete_resource_recursive(ctx: CommandContext, resource: dict[str, Any]) ->
|
|
|
476
476
|
print_success(f"deleted {info['rname']} ({format_resource_ref(info['res_str_id'])})")
|
|
477
477
|
|
|
478
478
|
|
|
479
|
-
def print_resource_listing(resource:
|
|
479
|
+
def print_resource_listing(resource: Dict[str, Any]) -> None:
|
|
480
480
|
info = resource["info"]
|
|
481
481
|
child_list = sort_children(resource.get("child_list", []))
|
|
482
482
|
|
|
@@ -529,7 +529,7 @@ def print_resource_listing(resource: dict[str, Any]) -> None:
|
|
|
529
529
|
console.print(table)
|
|
530
530
|
|
|
531
531
|
|
|
532
|
-
def print_user_summary(user:
|
|
532
|
+
def print_user_summary(user: Dict[str, Any]) -> None:
|
|
533
533
|
summary = Table.grid(padding=(0, 2))
|
|
534
534
|
summary.add_column(style="bold cyan", justify="right")
|
|
535
535
|
summary.add_column()
|
|
@@ -753,7 +753,7 @@ def dispatch(args: argparse.Namespace) -> int:
|
|
|
753
753
|
return handlers[args.command](ctx, args)
|
|
754
754
|
|
|
755
755
|
|
|
756
|
-
def main(argv:
|
|
756
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
757
757
|
parser = build_parser()
|
|
758
758
|
args = parser.parse_args(argv)
|
|
759
759
|
try:
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from dataclasses import asdict, dataclass, field
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
8
|
from urllib.parse import urlencode
|
|
9
9
|
|
|
10
10
|
DEFAULT_APP_ID = "R41nXZEwpfl6tw6KYyoEZ4FYST4BXPlF"
|
|
@@ -21,12 +21,12 @@ class DiskSettings:
|
|
|
21
21
|
sso_url: str = field(default_factory=lambda: os.getenv("DISK_SSO_URL", DEFAULT_SSO_URL))
|
|
22
22
|
sso_app_id: str = field(default_factory=lambda: os.getenv("DISK_SSO_APP_ID", DEFAULT_APP_ID))
|
|
23
23
|
qiniu_upload_url: str = field(default_factory=lambda: os.getenv("DISK_QINIU_UPLOAD_URL", DEFAULT_QINIU_UPLOAD_URL))
|
|
24
|
-
token: str
|
|
25
|
-
user:
|
|
24
|
+
token: Optional[str] = None
|
|
25
|
+
user: Optional[Dict[str, Any]] = None
|
|
26
26
|
state_path: Path = DEFAULT_STATE_PATH
|
|
27
27
|
|
|
28
28
|
@classmethod
|
|
29
|
-
def load(cls, state_path: Path
|
|
29
|
+
def load(cls, state_path: Optional[Path] = None) -> "DiskSettings":
|
|
30
30
|
resolved_path = (state_path or DEFAULT_STATE_PATH).expanduser()
|
|
31
31
|
if state_path is None and not resolved_path.exists() and LEGACY_STATE_PATH.exists():
|
|
32
32
|
resolved_path = LEGACY_STATE_PATH
|
|
@@ -60,7 +60,7 @@ class DiskSettings:
|
|
|
60
60
|
self.token = None
|
|
61
61
|
self.user = None
|
|
62
62
|
|
|
63
|
-
def oauth_authorize_url(self, *, state: str
|
|
63
|
+
def oauth_authorize_url(self, *, state: Optional[str] = "htx-cli") -> str:
|
|
64
64
|
params = {"app_id": self.sso_app_id}
|
|
65
65
|
if state:
|
|
66
66
|
params["state"] = state
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: htx-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: htx-cli command-line client for the Disk backend API
|
|
5
|
-
Requires-Python: >=3.
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: rich<14,>=13.9
|
|
8
8
|
Requires-Dist: requests<3,>=2.32
|
|
@@ -19,7 +19,12 @@ Requires-Dist: requests<3,>=2.32
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
22
|
+
pip install htx-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For local development:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
23
28
|
pip install -e .
|
|
24
29
|
```
|
|
25
30
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|