socaity-cli 0.1.1__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.
- socaity_cli-0.1.1/PKG-INFO +73 -0
- socaity_cli-0.1.1/README.md +56 -0
- socaity_cli-0.1.1/pyproject.toml +52 -0
- socaity_cli-0.1.1/setup.cfg +4 -0
- socaity_cli-0.1.1/socaity_cli/__init__.py +30 -0
- socaity_cli-0.1.1/socaity_cli/__main__.py +4 -0
- socaity_cli-0.1.1/socaity_cli/apipod_commands.py +82 -0
- socaity_cli-0.1.1/socaity_cli/auth.py +197 -0
- socaity_cli-0.1.1/socaity_cli/backend_client.py +23 -0
- socaity_cli-0.1.1/socaity_cli/cli.py +352 -0
- socaity_cli-0.1.1/socaity_cli/clients/__init__.py +18 -0
- socaity_cli-0.1.1/socaity_cli/clients/analytics.py +60 -0
- socaity_cli-0.1.1/socaity_cli/clients/catalog.py +147 -0
- socaity_cli-0.1.1/socaity_cli/clients/deployment.py +129 -0
- socaity_cli-0.1.1/socaity_cli/clients/files.py +82 -0
- socaity_cli-0.1.1/socaity_cli/clients/jobs.py +80 -0
- socaity_cli-0.1.1/socaity_cli/clients/projects.py +67 -0
- socaity_cli-0.1.1/socaity_cli/clients/sdk_install.py +16 -0
- socaity_cli-0.1.1/socaity_cli/credentials.py +128 -0
- socaity_cli-0.1.1/socaity_cli/deployment.py +349 -0
- socaity_cli-0.1.1/socaity_cli/errors.py +35 -0
- socaity_cli-0.1.1/socaity_cli/http.py +105 -0
- socaity_cli-0.1.1/socaity_cli/prompts.py +17 -0
- socaity_cli-0.1.1/socaity_cli/registry_push.py +122 -0
- socaity_cli-0.1.1/socaity_cli/requires.py +47 -0
- socaity_cli-0.1.1/socaity_cli/sdk_commands.py +28 -0
- socaity_cli-0.1.1/socaity_cli.egg-info/PKG-INFO +73 -0
- socaity_cli-0.1.1/socaity_cli.egg-info/SOURCES.txt +33 -0
- socaity_cli-0.1.1/socaity_cli.egg-info/dependency_links.txt +1 -0
- socaity_cli-0.1.1/socaity_cli.egg-info/entry_points.txt +2 -0
- socaity_cli-0.1.1/socaity_cli.egg-info/requires.txt +7 -0
- socaity_cli-0.1.1/socaity_cli.egg-info/top_level.txt +1 -0
- socaity_cli-0.1.1/test/test_cli.py +116 -0
- socaity_cli-0.1.1/test/test_credentials.py +39 -0
- socaity_cli-0.1.1/test/test_private_slot_error.py +47 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: socaity-cli
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands.
|
|
5
|
+
Author: SocAIty
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/SocAIty/socaity-cli
|
|
8
|
+
Project-URL: Homepage, https://www.socaity.ai
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: httpx
|
|
12
|
+
Requires-Dist: socaity-schemas>=0.0.1
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
|
15
|
+
Requires-Dist: ruff; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# socaity-cli
|
|
19
|
+
|
|
20
|
+
The command line for the [socaity.ai](https://www.socaity.ai) platform: login, catalog browsing,
|
|
21
|
+
SDK installs, and APIPod deployment tooling. One `socaity` command, shared by the
|
|
22
|
+
[socaity SDK](https://github.com/SocAIty/socaity) and [APIPod](https://github.com/SocAIty/apipod).
|
|
23
|
+
|
|
24
|
+
The package is deliberately small: `httpx` and `socaity-schemas` are the only dependencies.
|
|
25
|
+
Commands that need more (stub generation, container builds) delegate to the `socaity` or
|
|
26
|
+
`apipod` package and tell you what to install when it is missing.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install socaity-cli
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You get `socaity-cli` as part of `pip install socaity` or `pip install apipod` as well.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
| Command | Needs | What it does |
|
|
39
|
+
|---------|-------|--------------|
|
|
40
|
+
| `socaity login` / `logout` / `whoami` | - | Browser login, credentials in `~/.config/socaity/` |
|
|
41
|
+
| `socaity list services\|models` | - | Browse the AI catalog |
|
|
42
|
+
| `socaity search "flux schnel"` | - | Typo-tolerant search (`--collections services,models,jobs`) |
|
|
43
|
+
| `socaity jobs [job_id]` | login | List/search jobs; `--q`, `--expand data,files,billing,social`, `--visibility` |
|
|
44
|
+
| `socaity projects [project_id]` | login | Your projects; `--expand jobs` embeds member jobs |
|
|
45
|
+
| `socaity install <service>` | `socaity` | Generate an SDK stub for a service |
|
|
46
|
+
| `socaity update` | `socaity` | Sync installed services with the platform |
|
|
47
|
+
| `socaity scan` | `apipod` | Scan a project and generate `apipod.json` |
|
|
48
|
+
| `socaity build [file]` | `apipod` | Build the deployment container |
|
|
49
|
+
| `socaity start [entrypoint]` | `apipod` | Run an APIPod service locally |
|
|
50
|
+
| `socaity deploy [target]` | `apipod` | Deploy to Socaity (wraps `apipod deploy`) |
|
|
51
|
+
|
|
52
|
+
Use `-C DIR` with scan/build/start/deploy to point at a service project directory
|
|
53
|
+
(e.g. `socaity -C simple_test_service deploy serverless-runpod --yes`).
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
| Env var | Default | Purpose |
|
|
58
|
+
|---------|---------|---------|
|
|
59
|
+
| `SOCAITY_API_KEY` | from login | API key (wins over stored credentials) |
|
|
60
|
+
| `SOCAITY_BACKEND_URL` | `https://webapi.socaity.ai` | Platform API |
|
|
61
|
+
| `SOCAITY_FRONTEND_URL` | `https://socaity.ai` | Web UI for browser login |
|
|
62
|
+
|
|
63
|
+
## Using it as a library
|
|
64
|
+
|
|
65
|
+
`socaity` and `apipod` build on the same primitives this package exposes:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from socaity_cli import SocaityBackendClient, get_api_key, requires
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`SocaityBackendClient` covers catalog reads, search, and SDK install/update calls.
|
|
72
|
+
`requires("apipod")` guards a function behind an optional package with a friendly
|
|
73
|
+
install hint instead of an ImportError traceback.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# socaity-cli
|
|
2
|
+
|
|
3
|
+
The command line for the [socaity.ai](https://www.socaity.ai) platform: login, catalog browsing,
|
|
4
|
+
SDK installs, and APIPod deployment tooling. One `socaity` command, shared by the
|
|
5
|
+
[socaity SDK](https://github.com/SocAIty/socaity) and [APIPod](https://github.com/SocAIty/apipod).
|
|
6
|
+
|
|
7
|
+
The package is deliberately small: `httpx` and `socaity-schemas` are the only dependencies.
|
|
8
|
+
Commands that need more (stub generation, container builds) delegate to the `socaity` or
|
|
9
|
+
`apipod` package and tell you what to install when it is missing.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install socaity-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You get `socaity-cli` as part of `pip install socaity` or `pip install apipod` as well.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
| Command | Needs | What it does |
|
|
22
|
+
|---------|-------|--------------|
|
|
23
|
+
| `socaity login` / `logout` / `whoami` | - | Browser login, credentials in `~/.config/socaity/` |
|
|
24
|
+
| `socaity list services\|models` | - | Browse the AI catalog |
|
|
25
|
+
| `socaity search "flux schnel"` | - | Typo-tolerant search (`--collections services,models,jobs`) |
|
|
26
|
+
| `socaity jobs [job_id]` | login | List/search jobs; `--q`, `--expand data,files,billing,social`, `--visibility` |
|
|
27
|
+
| `socaity projects [project_id]` | login | Your projects; `--expand jobs` embeds member jobs |
|
|
28
|
+
| `socaity install <service>` | `socaity` | Generate an SDK stub for a service |
|
|
29
|
+
| `socaity update` | `socaity` | Sync installed services with the platform |
|
|
30
|
+
| `socaity scan` | `apipod` | Scan a project and generate `apipod.json` |
|
|
31
|
+
| `socaity build [file]` | `apipod` | Build the deployment container |
|
|
32
|
+
| `socaity start [entrypoint]` | `apipod` | Run an APIPod service locally |
|
|
33
|
+
| `socaity deploy [target]` | `apipod` | Deploy to Socaity (wraps `apipod deploy`) |
|
|
34
|
+
|
|
35
|
+
Use `-C DIR` with scan/build/start/deploy to point at a service project directory
|
|
36
|
+
(e.g. `socaity -C simple_test_service deploy serverless-runpod --yes`).
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
| Env var | Default | Purpose |
|
|
41
|
+
|---------|---------|---------|
|
|
42
|
+
| `SOCAITY_API_KEY` | from login | API key (wins over stored credentials) |
|
|
43
|
+
| `SOCAITY_BACKEND_URL` | `https://webapi.socaity.ai` | Platform API |
|
|
44
|
+
| `SOCAITY_FRONTEND_URL` | `https://socaity.ai` | Web UI for browser login |
|
|
45
|
+
|
|
46
|
+
## Using it as a library
|
|
47
|
+
|
|
48
|
+
`socaity` and `apipod` build on the same primitives this package exposes:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from socaity_cli import SocaityBackendClient, get_api_key, requires
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`SocaityBackendClient` covers catalog reads, search, and SDK install/update calls.
|
|
55
|
+
`requires("apipod")` guards a function behind an optional package with a friendly
|
|
56
|
+
install hint instead of an ImportError traceback.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "socaity-cli"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Socaity platform CLI: login, catalog browsing, SDK installs and APIPod deployment commands."
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "SocAIty" }
|
|
12
|
+
]
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx",
|
|
17
|
+
"socaity-schemas>=0.0.1",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
socaity = "socaity_cli.cli:main"
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"pytest",
|
|
26
|
+
"ruff",
|
|
27
|
+
"mypy",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Repository = "https://github.com/SocAIty/socaity-cli"
|
|
32
|
+
Homepage = "https://www.socaity.ai"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
include = ["socaity_cli*"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["test"]
|
|
39
|
+
# Resolve in-repo package and sibling schemas in local dev (CI installs from pip).
|
|
40
|
+
pythonpath = [".", "../socaity-schemas"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 120
|
|
44
|
+
exclude = ["venv", "dist", "build", "*.egg-info"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
ignore = ["E203", "E261", "E501", "W293", "W291"]
|
|
48
|
+
|
|
49
|
+
[tool.mypy]
|
|
50
|
+
ignore_missing_imports = true
|
|
51
|
+
follow_imports = "silent"
|
|
52
|
+
warn_unused_ignores = false
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Socaity platform CLI.
|
|
2
|
+
|
|
3
|
+
Lightweight bundle of all Socaity CLI commands and the backend HTTP client
|
|
4
|
+
(httpx + socaity-schemas only). Heavy features delegate to the optional
|
|
5
|
+
``socaity`` (SDK installs) and ``apipod`` (deployment tooling) packages via
|
|
6
|
+
the ``requires`` decorator.
|
|
7
|
+
"""
|
|
8
|
+
from socaity_cli.backend_client import SocaityBackendClient
|
|
9
|
+
from socaity_cli.credentials import (
|
|
10
|
+
Credentials,
|
|
11
|
+
clear_credentials,
|
|
12
|
+
get_api_key,
|
|
13
|
+
load_credentials,
|
|
14
|
+
require_credentials,
|
|
15
|
+
save_credentials,
|
|
16
|
+
)
|
|
17
|
+
from socaity_cli.requires import requires
|
|
18
|
+
from socaity_cli.prompts import input_yes_no
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"SocaityBackendClient",
|
|
22
|
+
"Credentials",
|
|
23
|
+
"clear_credentials",
|
|
24
|
+
"get_api_key",
|
|
25
|
+
"load_credentials",
|
|
26
|
+
"require_credentials",
|
|
27
|
+
"save_credentials",
|
|
28
|
+
"requires",
|
|
29
|
+
"input_yes_no",
|
|
30
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Bridge to the APIPod CLI (requires ``pip install apipod``).
|
|
2
|
+
|
|
3
|
+
Imports stay inside the functions: ``apipod`` is optional for the CLI and the
|
|
4
|
+
``requires`` decorator guards each entry with a friendly install hint.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from socaity_cli.requires import requires
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _with_project_dir(argv: List[str], project_dir: Optional[str]) -> List[str]:
|
|
16
|
+
if project_dir:
|
|
17
|
+
return ["-C", project_dir, *argv]
|
|
18
|
+
return argv
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@requires("apipod")
|
|
22
|
+
def _run_apipod_argv(argv: List[str]) -> None:
|
|
23
|
+
import apipod.cli
|
|
24
|
+
|
|
25
|
+
previous = sys.argv
|
|
26
|
+
sys.argv = ["apipod", *argv]
|
|
27
|
+
try:
|
|
28
|
+
apipod.cli.main()
|
|
29
|
+
finally:
|
|
30
|
+
sys.argv = previous
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_scan(*, project_dir: Optional[str] = None) -> None:
|
|
34
|
+
_run_apipod_argv(_with_project_dir(["scan"], project_dir))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_build(target_file: Optional[str], *, project_dir: Optional[str] = None) -> None:
|
|
38
|
+
argv = ["build"]
|
|
39
|
+
if target_file:
|
|
40
|
+
argv.append(target_file)
|
|
41
|
+
_run_apipod_argv(_with_project_dir(argv, project_dir))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_start(
|
|
45
|
+
*,
|
|
46
|
+
entrypoint: Optional[str],
|
|
47
|
+
host: Optional[str],
|
|
48
|
+
port: Optional[int],
|
|
49
|
+
project_dir: Optional[str] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
argv = ["start"]
|
|
52
|
+
if entrypoint:
|
|
53
|
+
argv.append(entrypoint)
|
|
54
|
+
if host:
|
|
55
|
+
argv.extend(["--host", host])
|
|
56
|
+
if port is not None:
|
|
57
|
+
argv.extend(["--port", str(port)])
|
|
58
|
+
_run_apipod_argv(_with_project_dir(argv, project_dir))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def run_deploy(
|
|
62
|
+
*,
|
|
63
|
+
target: Optional[str] = None,
|
|
64
|
+
project_dir: Optional[str] = None,
|
|
65
|
+
resume: Optional[str] = None,
|
|
66
|
+
skip_build: bool = False,
|
|
67
|
+
push_only: bool = False,
|
|
68
|
+
yes: bool = False,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Wrap ``apipod deploy`` (analyze, build, push, provision)."""
|
|
71
|
+
argv = ["deploy"]
|
|
72
|
+
if target:
|
|
73
|
+
argv.append(target)
|
|
74
|
+
if resume:
|
|
75
|
+
argv.extend(["--resume", resume])
|
|
76
|
+
if skip_build:
|
|
77
|
+
argv.append("--skip-build")
|
|
78
|
+
if push_only:
|
|
79
|
+
argv.append("--push-only")
|
|
80
|
+
if yes:
|
|
81
|
+
argv.append("--yes")
|
|
82
|
+
_run_apipod_argv(_with_project_dir(argv, project_dir))
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Browser-based CLI login (device authorization flow via web UI)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
import webbrowser
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
from urllib.parse import urljoin
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from socaity_cli.credentials import (
|
|
16
|
+
Credentials,
|
|
17
|
+
DEFAULT_BACKEND_URL,
|
|
18
|
+
DEFAULT_FRONTEND_URL,
|
|
19
|
+
require_credentials,
|
|
20
|
+
save_credentials,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
USER_AGENT = "socaity-cli/0.1.0"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _backend_base_url() -> str:
|
|
27
|
+
return os.environ.get("SOCAITY_BACKEND_URL", DEFAULT_BACKEND_URL).rstrip("/")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _frontend_base_url(frontend_url: Optional[str] = None) -> str:
|
|
31
|
+
if frontend_url is not None:
|
|
32
|
+
value = frontend_url.strip()
|
|
33
|
+
return value.rstrip("/") if value else DEFAULT_FRONTEND_URL
|
|
34
|
+
return os.environ.get("SOCAITY_FRONTEND_URL", DEFAULT_FRONTEND_URL).rstrip("/")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _web_api_url(base: str) -> str:
|
|
38
|
+
return urljoin(base + "/", "v1/")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def open_login_url(url: str) -> bool:
|
|
42
|
+
try:
|
|
43
|
+
if webbrowser.open(url):
|
|
44
|
+
return True
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
for cmd in ("wslview", "xdg-open"):
|
|
48
|
+
exe = shutil.which(cmd)
|
|
49
|
+
if not exe:
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
subprocess.run(
|
|
53
|
+
[exe, url],
|
|
54
|
+
check=False,
|
|
55
|
+
timeout=8,
|
|
56
|
+
stdout=subprocess.DEVNULL,
|
|
57
|
+
stderr=subprocess.DEVNULL,
|
|
58
|
+
)
|
|
59
|
+
return True
|
|
60
|
+
except Exception:
|
|
61
|
+
continue
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def start_cli_login(
|
|
66
|
+
*,
|
|
67
|
+
backend_url: Optional[str] = None,
|
|
68
|
+
frontend_url: Optional[str] = None,
|
|
69
|
+
expires_in_seconds: int = 600,
|
|
70
|
+
) -> Dict[str, Any]:
|
|
71
|
+
base = (backend_url or _backend_base_url()).rstrip("/")
|
|
72
|
+
payload: Dict[str, Any] = {"expires_in_seconds": expires_in_seconds}
|
|
73
|
+
payload["frontend_base_url"] = _frontend_base_url(frontend_url)
|
|
74
|
+
with httpx.Client(timeout=20.0) as client:
|
|
75
|
+
response = client.post(
|
|
76
|
+
_web_api_url(base) + "cli-auth/start",
|
|
77
|
+
json=payload,
|
|
78
|
+
headers={"User-Agent": USER_AGENT},
|
|
79
|
+
)
|
|
80
|
+
response.raise_for_status()
|
|
81
|
+
return response.json()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def poll_cli_login(
|
|
85
|
+
*,
|
|
86
|
+
request_id: str,
|
|
87
|
+
poll_code: str,
|
|
88
|
+
backend_url: Optional[str] = None,
|
|
89
|
+
) -> Dict[str, Any]:
|
|
90
|
+
base = (backend_url or _backend_base_url()).rstrip("/")
|
|
91
|
+
with httpx.Client(timeout=20.0) as client:
|
|
92
|
+
response = client.post(
|
|
93
|
+
_web_api_url(base) + "cli-auth/poll",
|
|
94
|
+
json={"request_id": request_id, "poll_code": poll_code},
|
|
95
|
+
headers={"User-Agent": USER_AGENT},
|
|
96
|
+
)
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
return response.json()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def fetch_profile(*, api_key: str, backend_url: str) -> Dict[str, Any]:
|
|
102
|
+
base = backend_url.rstrip("/")
|
|
103
|
+
with httpx.Client(timeout=20.0) as client:
|
|
104
|
+
response = client.get(
|
|
105
|
+
_web_api_url(base) + "profile/profile",
|
|
106
|
+
headers={
|
|
107
|
+
"Authorization": f"Bearer {api_key}",
|
|
108
|
+
"User-Agent": USER_AGENT,
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
response.raise_for_status()
|
|
112
|
+
return response.json()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def run_login(
|
|
116
|
+
*,
|
|
117
|
+
no_browser: bool = False,
|
|
118
|
+
timeout: int = 300,
|
|
119
|
+
backend_url: Optional[str] = None,
|
|
120
|
+
frontend_url: Optional[str] = None,
|
|
121
|
+
) -> Credentials:
|
|
122
|
+
base = (backend_url or _backend_base_url()).rstrip("/")
|
|
123
|
+
|
|
124
|
+
print("Login to Socaity")
|
|
125
|
+
session = start_cli_login(
|
|
126
|
+
backend_url=base,
|
|
127
|
+
frontend_url=frontend_url,
|
|
128
|
+
)
|
|
129
|
+
verification_uri = session["verification_uri"]
|
|
130
|
+
request_id = session["request_id"]
|
|
131
|
+
poll_code = session["poll_code"]
|
|
132
|
+
interval = max(int(session.get("interval_seconds", 2)), 1)
|
|
133
|
+
|
|
134
|
+
print(f"Open this URL to authenticate:\n{verification_uri}")
|
|
135
|
+
|
|
136
|
+
if not no_browser:
|
|
137
|
+
if open_login_url(verification_uri):
|
|
138
|
+
print("Browser opened for login.")
|
|
139
|
+
else:
|
|
140
|
+
print("Could not open a browser automatically. Open the URL manually.")
|
|
141
|
+
|
|
142
|
+
print("Waiting for login confirmation...")
|
|
143
|
+
deadline = time.time() + max(timeout, 10)
|
|
144
|
+
|
|
145
|
+
while time.time() < deadline:
|
|
146
|
+
poll_response = poll_cli_login(
|
|
147
|
+
request_id=request_id,
|
|
148
|
+
poll_code=poll_code,
|
|
149
|
+
backend_url=base,
|
|
150
|
+
)
|
|
151
|
+
status = poll_response.get("status")
|
|
152
|
+
|
|
153
|
+
if status == "pending":
|
|
154
|
+
time.sleep(interval)
|
|
155
|
+
continue
|
|
156
|
+
if status == "expired":
|
|
157
|
+
print("Login request expired. Run: socaity login")
|
|
158
|
+
raise SystemExit(1)
|
|
159
|
+
if status == "approved":
|
|
160
|
+
api_key = (poll_response.get("api_key") or "").strip()
|
|
161
|
+
if not api_key:
|
|
162
|
+
print("Login failed: missing API key in response.")
|
|
163
|
+
raise SystemExit(1)
|
|
164
|
+
creds = Credentials(
|
|
165
|
+
api_key=api_key,
|
|
166
|
+
user_id=poll_response.get("user_id"),
|
|
167
|
+
user_email=poll_response.get("user_email"),
|
|
168
|
+
backend_url=base,
|
|
169
|
+
)
|
|
170
|
+
path = save_credentials(creds)
|
|
171
|
+
email = creds.user_email or "user"
|
|
172
|
+
print(f"Logged in as {email}")
|
|
173
|
+
print(f"Credentials saved to {path}")
|
|
174
|
+
return creds
|
|
175
|
+
|
|
176
|
+
print(f"Login failed: unexpected status '{status}'")
|
|
177
|
+
raise SystemExit(1)
|
|
178
|
+
|
|
179
|
+
print("Login timed out. Run: socaity login")
|
|
180
|
+
raise SystemExit(1)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def run_whoami() -> None:
|
|
184
|
+
creds = require_credentials()
|
|
185
|
+
email = creds.user_email
|
|
186
|
+
user_id = creds.user_id
|
|
187
|
+
if not email or not user_id:
|
|
188
|
+
try:
|
|
189
|
+
profile = fetch_profile(api_key=creds.api_key, backend_url=creds.backend_url)
|
|
190
|
+
email = email or profile.get("email") or profile.get("user_email")
|
|
191
|
+
user_id = user_id or profile.get("id") or profile.get("user_id")
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
print(f"Could not fetch profile: {exc}")
|
|
194
|
+
raise SystemExit(1) from exc
|
|
195
|
+
print(f"user_id: {user_id}")
|
|
196
|
+
print(f"email: {email}")
|
|
197
|
+
print(f"backend: {creds.backend_url}")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Composed platform backend client (catalog, jobs, projects, analytics, files, sdk, deployment)."""
|
|
2
|
+
from socaity_cli.clients.analytics import AnalyticsClient
|
|
3
|
+
from socaity_cli.clients.catalog import CatalogClient
|
|
4
|
+
from socaity_cli.clients.deployment import DeploymentClient
|
|
5
|
+
from socaity_cli.clients.files import FilesClient
|
|
6
|
+
from socaity_cli.clients.jobs import JobsClient
|
|
7
|
+
from socaity_cli.clients.projects import ProjectsClient
|
|
8
|
+
from socaity_cli.clients.sdk_install import SdkInstallClient
|
|
9
|
+
from socaity_cli.http import BackendHttp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SocaityBackendClient(
|
|
13
|
+
BackendHttp,
|
|
14
|
+
CatalogClient,
|
|
15
|
+
JobsClient,
|
|
16
|
+
ProjectsClient,
|
|
17
|
+
AnalyticsClient,
|
|
18
|
+
FilesClient,
|
|
19
|
+
SdkInstallClient,
|
|
20
|
+
DeploymentClient,
|
|
21
|
+
):
|
|
22
|
+
"""Facade over domain mixins. Prefer importing a domain client when you only need one surface."""
|
|
23
|
+
pass
|