apdf-cloud-cli 0.2.0__tar.gz → 0.3.0__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.
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/PKG-INFO +13 -2
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/README.md +12 -1
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/pyproject.toml +1 -1
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/__init__.py +1 -1
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/cli.py +144 -1
- apdf_cloud_cli-0.3.0/src/apdf_cloud_cli/config.py +178 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/operations.py +23 -0
- apdf_cloud_cli-0.2.0/src/apdf_cloud_cli/config.py +0 -75
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/client.py +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/mcp_server.py +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/py.typed +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/SKILL.md +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/agents/openai.yaml +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/references/live-tests.md +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/references/mcp-config.md +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/references/prompts.md +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/references/security.md +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/resources/skills/apdf-cloud-mcp/references/setup.md +0 -0
- {apdf_cloud_cli-0.2.0 → apdf_cloud_cli-0.3.0}/src/apdf_cloud_cli/skill_installer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apdf-cloud-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: CLI and MCP server tools for Aspose PDF Cloud
|
|
5
5
|
Keywords: aspose,pdf,cli,mcp
|
|
6
6
|
Author: Andriy Andruhovski
|
|
@@ -58,7 +58,16 @@ python -m pip install -e ".[dev]"
|
|
|
58
58
|
|
|
59
59
|
## Configuration
|
|
60
60
|
|
|
61
|
-
Set credentials before running CLI commands or the MCP server
|
|
61
|
+
Set credentials before running CLI commands or the MCP server. The easiest
|
|
62
|
+
local setup path is:
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
apdf auth login
|
|
66
|
+
apdf auth status
|
|
67
|
+
apdf auth test
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You can also set credentials directly in your shell:
|
|
62
71
|
|
|
63
72
|
```powershell
|
|
64
73
|
$env:ASPOSE_CLIENT_ID = "your-client-id"
|
|
@@ -82,6 +91,8 @@ also installed for convenience.
|
|
|
82
91
|
|
|
83
92
|
```powershell
|
|
84
93
|
apdf-cloud-cli storage list /
|
|
94
|
+
apdf-cloud-cli auth status
|
|
95
|
+
apdf-cloud-cli auth test
|
|
85
96
|
apdf-cloud-cli storage upload .\sample.pdf /sample.pdf
|
|
86
97
|
apdf-cloud-cli storage download /sample.pdf .\sample.pdf
|
|
87
98
|
apdf-cloud-cli pdf merge /a.pdf /b.pdf merged.pdf
|
|
@@ -26,7 +26,16 @@ python -m pip install -e ".[dev]"
|
|
|
26
26
|
|
|
27
27
|
## Configuration
|
|
28
28
|
|
|
29
|
-
Set credentials before running CLI commands or the MCP server
|
|
29
|
+
Set credentials before running CLI commands or the MCP server. The easiest
|
|
30
|
+
local setup path is:
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
apdf auth login
|
|
34
|
+
apdf auth status
|
|
35
|
+
apdf auth test
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You can also set credentials directly in your shell:
|
|
30
39
|
|
|
31
40
|
```powershell
|
|
32
41
|
$env:ASPOSE_CLIENT_ID = "your-client-id"
|
|
@@ -50,6 +59,8 @@ also installed for convenience.
|
|
|
50
59
|
|
|
51
60
|
```powershell
|
|
52
61
|
apdf-cloud-cli storage list /
|
|
62
|
+
apdf-cloud-cli auth status
|
|
63
|
+
apdf-cloud-cli auth test
|
|
53
64
|
apdf-cloud-cli storage upload .\sample.pdf /sample.pdf
|
|
54
65
|
apdf-cloud-cli storage download /sample.pdf .\sample.pdf
|
|
55
66
|
apdf-cloud-cli pdf merge /a.pdf /b.pdf merged.pdf
|
|
@@ -11,14 +11,16 @@ from rich.console import Console
|
|
|
11
11
|
from rich.table import Table
|
|
12
12
|
|
|
13
13
|
from . import operations
|
|
14
|
-
from .config import ConfigError
|
|
14
|
+
from .config import ConfigError, get_auth_status, write_auth_env_file
|
|
15
15
|
from .skill_installer import SkillInstallError, install_skill
|
|
16
16
|
|
|
17
17
|
app = typer.Typer(help="APDF CLI tools.")
|
|
18
|
+
auth_app = typer.Typer(help="Credential setup and validation.")
|
|
18
19
|
storage_app = typer.Typer(help="Cloud storage operations.")
|
|
19
20
|
pdf_app = typer.Typer(help="PDF operations.")
|
|
20
21
|
mcp_app = typer.Typer(help="MCP server commands.")
|
|
21
22
|
skill_app = typer.Typer(help="Install bundled agent skills.")
|
|
23
|
+
app.add_typer(auth_app, name="auth")
|
|
22
24
|
app.add_typer(storage_app, name="storage")
|
|
23
25
|
app.add_typer(pdf_app, name="pdf")
|
|
24
26
|
app.add_typer(mcp_app, name="mcp")
|
|
@@ -43,6 +45,147 @@ def _run(action):
|
|
|
43
45
|
_handle_error(exc)
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
@auth_app.command("status")
|
|
49
|
+
def auth_status(
|
|
50
|
+
env_file: Annotated[
|
|
51
|
+
Path | None,
|
|
52
|
+
typer.Option("--env-file", help="Local .env file to inspect."),
|
|
53
|
+
] = Path(".env"),
|
|
54
|
+
as_json: Annotated[bool, typer.Option("--json", help="Emit JSON output.")] = False,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Show redacted APDF credential configuration status."""
|
|
57
|
+
|
|
58
|
+
status = get_auth_status(env_file)
|
|
59
|
+
if as_json:
|
|
60
|
+
_json(status)
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
console.print(
|
|
64
|
+
"[green]Configured[/green]"
|
|
65
|
+
if status["configured"]
|
|
66
|
+
else "[yellow]Missing required credentials[/yellow]"
|
|
67
|
+
)
|
|
68
|
+
table = Table(title=f"Auth status: {status['env_file'] or 'environment only'}")
|
|
69
|
+
table.add_column("Setting")
|
|
70
|
+
table.add_column("Source")
|
|
71
|
+
table.add_column("Value")
|
|
72
|
+
|
|
73
|
+
settings = status["settings"]
|
|
74
|
+
if isinstance(settings, dict):
|
|
75
|
+
for key, details in settings.items():
|
|
76
|
+
if not isinstance(details, dict):
|
|
77
|
+
continue
|
|
78
|
+
table.add_row(
|
|
79
|
+
key,
|
|
80
|
+
str(details.get("source") or "missing"),
|
|
81
|
+
str(details.get("value") or ""),
|
|
82
|
+
)
|
|
83
|
+
console.print(table)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@auth_app.command("login")
|
|
87
|
+
def auth_login(
|
|
88
|
+
env_file: Annotated[
|
|
89
|
+
Path,
|
|
90
|
+
typer.Option("--env-file", help="Local .env file to write."),
|
|
91
|
+
] = Path(".env"),
|
|
92
|
+
client_id: Annotated[
|
|
93
|
+
str | None,
|
|
94
|
+
typer.Option("--client-id", help="Aspose Cloud client ID."),
|
|
95
|
+
] = None,
|
|
96
|
+
client_secret: Annotated[
|
|
97
|
+
str | None,
|
|
98
|
+
typer.Option("--client-secret", help="Aspose Cloud client secret."),
|
|
99
|
+
] = None,
|
|
100
|
+
storage_name: Annotated[
|
|
101
|
+
str | None,
|
|
102
|
+
typer.Option("--storage", help="Default Aspose storage name."),
|
|
103
|
+
] = None,
|
|
104
|
+
base_url: Annotated[
|
|
105
|
+
str | None,
|
|
106
|
+
typer.Option("--base-url", help="Alternate Aspose PDF Cloud base URL."),
|
|
107
|
+
] = None,
|
|
108
|
+
self_host: Annotated[
|
|
109
|
+
bool,
|
|
110
|
+
typer.Option("--self-host/--no-self-host", help="Use self-hosted Aspose PDF Cloud."),
|
|
111
|
+
] = False,
|
|
112
|
+
force: Annotated[
|
|
113
|
+
bool,
|
|
114
|
+
typer.Option("--force", help="Overwrite existing APDF values in the env file."),
|
|
115
|
+
] = False,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Save APDF credentials to a local .env file."""
|
|
118
|
+
|
|
119
|
+
status = get_auth_status(env_file)
|
|
120
|
+
settings = status.get("settings", {})
|
|
121
|
+
has_existing_file_values = (
|
|
122
|
+
isinstance(settings, dict)
|
|
123
|
+
and any(
|
|
124
|
+
isinstance(details, dict) and details.get("source") == "env_file"
|
|
125
|
+
for details in settings.values()
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
if has_existing_file_values and not force:
|
|
129
|
+
typer.confirm(
|
|
130
|
+
f"{env_file} already contains APDF settings. Overwrite them?",
|
|
131
|
+
abort=True,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
prompt_optional = client_id is None or client_secret is None
|
|
135
|
+
if client_id is None:
|
|
136
|
+
client_id = typer.prompt("Aspose client ID")
|
|
137
|
+
if client_secret is None:
|
|
138
|
+
client_secret = typer.prompt("Aspose client secret", hide_input=True)
|
|
139
|
+
if prompt_optional and storage_name is None:
|
|
140
|
+
storage_name = typer.prompt(
|
|
141
|
+
"Default storage name (optional)",
|
|
142
|
+
default="",
|
|
143
|
+
show_default=False,
|
|
144
|
+
)
|
|
145
|
+
if prompt_optional and base_url is None:
|
|
146
|
+
base_url = typer.prompt(
|
|
147
|
+
"Base URL (optional)",
|
|
148
|
+
default="",
|
|
149
|
+
show_default=False,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
result = _run(
|
|
153
|
+
lambda: write_auth_env_file(
|
|
154
|
+
env_file,
|
|
155
|
+
{
|
|
156
|
+
"ASPOSE_CLIENT_ID": client_id,
|
|
157
|
+
"ASPOSE_CLIENT_SECRET": client_secret,
|
|
158
|
+
"ASPOSE_STORAGE_NAME": storage_name,
|
|
159
|
+
"ASPOSE_BASE_URL": base_url,
|
|
160
|
+
"ASPOSE_SELF_HOST": self_host,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
console.print(f"Saved APDF credentials to [bold]{result}[/bold].")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@auth_app.command("test")
|
|
168
|
+
def auth_test(
|
|
169
|
+
path: Annotated[
|
|
170
|
+
str,
|
|
171
|
+
typer.Option("--path", help="Storage folder path to list during validation."),
|
|
172
|
+
] = "/",
|
|
173
|
+
storage: Annotated[str | None, typer.Option("--storage", help="Storage name.")] = None,
|
|
174
|
+
as_json: Annotated[bool, typer.Option("--json", help="Emit JSON output.")] = False,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Validate APDF credentials with a harmless storage API call."""
|
|
177
|
+
|
|
178
|
+
result = _run(lambda: operations.test_auth(path, storage))
|
|
179
|
+
if as_json:
|
|
180
|
+
_json(result)
|
|
181
|
+
return
|
|
182
|
+
console.print(
|
|
183
|
+
"[green]APDF credentials are valid.[/green] "
|
|
184
|
+
f"Listed [bold]{result['path']}[/bold]"
|
|
185
|
+
+ (f" in storage [bold]{result['storage_name']}[/bold]." if result["storage_name"] else ".")
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
46
189
|
@storage_app.command("list")
|
|
47
190
|
def list_storage_files(
|
|
48
191
|
path: Annotated[str, typer.Argument(help="Folder path in Aspose storage.")],
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Configuration helpers for APDF access."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigError(RuntimeError):
|
|
11
|
+
"""Raised when required configuration is missing or invalid."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
APDF_ENV_KEYS = (
|
|
15
|
+
"ASPOSE_CLIENT_ID",
|
|
16
|
+
"ASPOSE_CLIENT_SECRET",
|
|
17
|
+
"ASPOSE_STORAGE_NAME",
|
|
18
|
+
"ASPOSE_BASE_URL",
|
|
19
|
+
"ASPOSE_SELF_HOST",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class AsposeConfig:
|
|
25
|
+
client_id: str
|
|
26
|
+
client_secret: str
|
|
27
|
+
base_url: str | None = None
|
|
28
|
+
self_host: bool = False
|
|
29
|
+
storage_name: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _truthy(value: str | None) -> bool:
|
|
33
|
+
return value is not None and value.strip().lower() in {"1", "true", "yes", "on"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _read_env_file(path: str | Path | None) -> dict[str, str]:
|
|
37
|
+
if path is None:
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
env_path = Path(path)
|
|
41
|
+
if not env_path.is_file():
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
values: dict[str, str] = {}
|
|
45
|
+
for line in env_path.read_text(encoding="utf-8").splitlines():
|
|
46
|
+
stripped = line.strip()
|
|
47
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
48
|
+
continue
|
|
49
|
+
key, value = stripped.split("=", 1)
|
|
50
|
+
key = key.strip()
|
|
51
|
+
value = value.strip().strip('"').strip("'")
|
|
52
|
+
if key:
|
|
53
|
+
values[key] = value
|
|
54
|
+
return values
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_setting(name: str, env_file_values: dict[str, str]) -> str:
|
|
58
|
+
return os.getenv(name, env_file_values.get(name, "")).strip()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _format_env_value(value: str) -> str:
|
|
62
|
+
escaped = value.replace("\\", "\\\\").replace('"', '\\"')
|
|
63
|
+
return f'"{escaped}"'
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _env_key_from_line(line: str) -> str | None:
|
|
67
|
+
stripped = line.strip()
|
|
68
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
69
|
+
return None
|
|
70
|
+
key, _ = stripped.split("=", 1)
|
|
71
|
+
key = key.strip()
|
|
72
|
+
return key or None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_config(env_file: str | Path | None = ".env") -> AsposeConfig:
|
|
76
|
+
"""Load Aspose Cloud configuration from environment variables and optional .env."""
|
|
77
|
+
|
|
78
|
+
env_file_values = _read_env_file(env_file)
|
|
79
|
+
|
|
80
|
+
client_id = _get_setting("ASPOSE_CLIENT_ID", env_file_values)
|
|
81
|
+
client_secret = _get_setting("ASPOSE_CLIENT_SECRET", env_file_values)
|
|
82
|
+
|
|
83
|
+
if not client_id or not client_secret:
|
|
84
|
+
raise ConfigError(
|
|
85
|
+
"Missing Aspose credentials. Set ASPOSE_CLIENT_ID and "
|
|
86
|
+
"ASPOSE_CLIENT_SECRET."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
base_url = _get_setting("ASPOSE_BASE_URL", env_file_values) or None
|
|
90
|
+
storage_name = _get_setting("ASPOSE_STORAGE_NAME", env_file_values) or None
|
|
91
|
+
|
|
92
|
+
return AsposeConfig(
|
|
93
|
+
client_id=client_id,
|
|
94
|
+
client_secret=client_secret,
|
|
95
|
+
base_url=base_url,
|
|
96
|
+
self_host=_truthy(_get_setting("ASPOSE_SELF_HOST", env_file_values)),
|
|
97
|
+
storage_name=storage_name,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_auth_status(env_file: str | Path | None = ".env") -> dict[str, object]:
|
|
102
|
+
"""Return redacted APDF credential status from environment and optional .env."""
|
|
103
|
+
|
|
104
|
+
env_file_values = _read_env_file(env_file)
|
|
105
|
+
settings: dict[str, dict[str, str | bool | None]] = {}
|
|
106
|
+
|
|
107
|
+
for key in APDF_ENV_KEYS:
|
|
108
|
+
env_value = os.getenv(key)
|
|
109
|
+
file_value = env_file_values.get(key)
|
|
110
|
+
value = (env_value if env_value is not None else file_value) or ""
|
|
111
|
+
source = "environment" if env_value is not None else "env_file" if file_value else "missing"
|
|
112
|
+
settings[key] = {
|
|
113
|
+
"configured": bool(value.strip()),
|
|
114
|
+
"source": source,
|
|
115
|
+
"value": redact_secret(value),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
configured = bool(
|
|
119
|
+
settings["ASPOSE_CLIENT_ID"]["configured"]
|
|
120
|
+
and settings["ASPOSE_CLIENT_SECRET"]["configured"]
|
|
121
|
+
)
|
|
122
|
+
return {
|
|
123
|
+
"configured": configured,
|
|
124
|
+
"env_file": str(env_file) if env_file is not None else None,
|
|
125
|
+
"settings": settings,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def redact_secret(value: str | None) -> str:
|
|
130
|
+
"""Return a display-safe representation of a secret-like value."""
|
|
131
|
+
|
|
132
|
+
if not value:
|
|
133
|
+
return ""
|
|
134
|
+
stripped = value.strip()
|
|
135
|
+
if len(stripped) <= 8:
|
|
136
|
+
return "<set>"
|
|
137
|
+
return f"{stripped[:4]}...{stripped[-4:]}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def write_auth_env_file(
|
|
141
|
+
env_file: str | Path,
|
|
142
|
+
values: dict[str, str | bool | None],
|
|
143
|
+
) -> Path:
|
|
144
|
+
"""Write APDF auth settings to a .env file while preserving unrelated lines."""
|
|
145
|
+
|
|
146
|
+
env_path = Path(env_file)
|
|
147
|
+
updates: dict[str, str] = {}
|
|
148
|
+
for key in APDF_ENV_KEYS:
|
|
149
|
+
value = values.get(key)
|
|
150
|
+
if isinstance(value, bool):
|
|
151
|
+
updates[key] = "true" if value else "false"
|
|
152
|
+
elif value is not None and str(value).strip():
|
|
153
|
+
updates[key] = str(value).strip()
|
|
154
|
+
|
|
155
|
+
if not updates.get("ASPOSE_CLIENT_ID") or not updates.get("ASPOSE_CLIENT_SECRET"):
|
|
156
|
+
raise ConfigError("ASPOSE_CLIENT_ID and ASPOSE_CLIENT_SECRET are required.")
|
|
157
|
+
|
|
158
|
+
lines = env_path.read_text(encoding="utf-8").splitlines() if env_path.exists() else []
|
|
159
|
+
written_keys: set[str] = set()
|
|
160
|
+
output: list[str] = []
|
|
161
|
+
|
|
162
|
+
for line in lines:
|
|
163
|
+
key = _env_key_from_line(line)
|
|
164
|
+
if key in updates:
|
|
165
|
+
output.append(f"{key}={_format_env_value(updates[key])}")
|
|
166
|
+
written_keys.add(key)
|
|
167
|
+
else:
|
|
168
|
+
output.append(line)
|
|
169
|
+
|
|
170
|
+
if output and any(key not in written_keys for key in updates):
|
|
171
|
+
output.append("")
|
|
172
|
+
for key in APDF_ENV_KEYS:
|
|
173
|
+
if key in updates and key not in written_keys:
|
|
174
|
+
output.append(f"{key}={_format_env_value(updates[key])}")
|
|
175
|
+
|
|
176
|
+
env_path.parent.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
env_path.write_text("\n".join(output) + "\n", encoding="utf-8")
|
|
178
|
+
return env_path
|
|
@@ -83,6 +83,29 @@ def list_files(
|
|
|
83
83
|
return {"path": path, "storage_name": effective_storage, "items": to_plain_data(response)}
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def test_auth(
|
|
87
|
+
path: str = "/",
|
|
88
|
+
storage_name: str | None = None,
|
|
89
|
+
*,
|
|
90
|
+
api: Any | None = None,
|
|
91
|
+
config: AsposeConfig | None = None,
|
|
92
|
+
) -> dict[str, Any]:
|
|
93
|
+
"""Validate APDF credentials with a harmless storage listing."""
|
|
94
|
+
|
|
95
|
+
result = list_files(path, storage_name, api=api, config=config)
|
|
96
|
+
items = result.get("items", {})
|
|
97
|
+
values = items.get("value") or items.get("Value") or items.get("files") or items
|
|
98
|
+
if isinstance(values, dict):
|
|
99
|
+
values = values.get("value") or values.get("Value") or []
|
|
100
|
+
item_count = len(values) if isinstance(values, list) else None
|
|
101
|
+
return {
|
|
102
|
+
"ok": True,
|
|
103
|
+
"path": result["path"],
|
|
104
|
+
"storage_name": result["storage_name"],
|
|
105
|
+
"item_count": item_count,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
86
109
|
def upload_file(
|
|
87
110
|
local_path: str | Path,
|
|
88
111
|
remote_path: str,
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"""Configuration helpers for APDF access."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ConfigError(RuntimeError):
|
|
11
|
-
"""Raised when required configuration is missing or invalid."""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass(frozen=True)
|
|
15
|
-
class AsposeConfig:
|
|
16
|
-
client_id: str
|
|
17
|
-
client_secret: str
|
|
18
|
-
base_url: str | None = None
|
|
19
|
-
self_host: bool = False
|
|
20
|
-
storage_name: str | None = None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _truthy(value: str | None) -> bool:
|
|
24
|
-
return value is not None and value.strip().lower() in {"1", "true", "yes", "on"}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _read_env_file(path: str | Path | None) -> dict[str, str]:
|
|
28
|
-
if path is None:
|
|
29
|
-
return {}
|
|
30
|
-
|
|
31
|
-
env_path = Path(path)
|
|
32
|
-
if not env_path.is_file():
|
|
33
|
-
return {}
|
|
34
|
-
|
|
35
|
-
values: dict[str, str] = {}
|
|
36
|
-
for line in env_path.read_text(encoding="utf-8").splitlines():
|
|
37
|
-
stripped = line.strip()
|
|
38
|
-
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
39
|
-
continue
|
|
40
|
-
key, value = stripped.split("=", 1)
|
|
41
|
-
key = key.strip()
|
|
42
|
-
value = value.strip().strip('"').strip("'")
|
|
43
|
-
if key:
|
|
44
|
-
values[key] = value
|
|
45
|
-
return values
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _get_setting(name: str, env_file_values: dict[str, str]) -> str:
|
|
49
|
-
return os.getenv(name, env_file_values.get(name, "")).strip()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def load_config(env_file: str | Path | None = ".env") -> AsposeConfig:
|
|
53
|
-
"""Load Aspose Cloud configuration from environment variables and optional .env."""
|
|
54
|
-
|
|
55
|
-
env_file_values = _read_env_file(env_file)
|
|
56
|
-
|
|
57
|
-
client_id = _get_setting("ASPOSE_CLIENT_ID", env_file_values)
|
|
58
|
-
client_secret = _get_setting("ASPOSE_CLIENT_SECRET", env_file_values)
|
|
59
|
-
|
|
60
|
-
if not client_id or not client_secret:
|
|
61
|
-
raise ConfigError(
|
|
62
|
-
"Missing Aspose credentials. Set ASPOSE_CLIENT_ID and "
|
|
63
|
-
"ASPOSE_CLIENT_SECRET."
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
base_url = _get_setting("ASPOSE_BASE_URL", env_file_values) or None
|
|
67
|
-
storage_name = _get_setting("ASPOSE_STORAGE_NAME", env_file_values) or None
|
|
68
|
-
|
|
69
|
-
return AsposeConfig(
|
|
70
|
-
client_id=client_id,
|
|
71
|
-
client_secret=client_secret,
|
|
72
|
-
base_url=base_url,
|
|
73
|
-
self_host=_truthy(_get_setting("ASPOSE_SELF_HOST", env_file_values)),
|
|
74
|
-
storage_name=storage_name,
|
|
75
|
-
)
|
|
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
|
|
File without changes
|