c3po-sdk 0.4.0a1__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.
- c3po_sdk-0.4.0a1/PKG-INFO +129 -0
- c3po_sdk-0.4.0a1/README.md +108 -0
- c3po_sdk-0.4.0a1/pyproject.toml +113 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/__init__.py +5 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/__init__.py +5 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/__init__.py +1 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/cluster.py +90 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/configure.py +123 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/dataset.py +173 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/kb.py +101 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/node.py +592 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/program.py +90 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/tenant.py +143 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/commands/user.py +210 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/context.py +172 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/main.py +88 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/output/__init__.py +179 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/pagination.py +69 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/utils/__init__.py +1 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/utils/download.py +56 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/cli/utils/tree.py +53 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/__init__.py +6 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/client.py +201 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/config/__init__.py +1 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/config/credentials.py +88 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/config/settings.py +119 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/exceptions.py +17 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/__init__.py +133 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/_enums.py +55 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/_utils.py +94 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/auth.py +19 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/cluster.py +28 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/conversation.py +52 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/dataset.py +42 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/invitation.py +33 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/kb.py +58 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/node.py +112 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/pipeline.py +58 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/program.py +52 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/tenant.py +41 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/models/user.py +52 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/__init__.py +42 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/_helpers.py +15 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/auth.py +134 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/dataset.py +64 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/kb.py +134 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/node.py +186 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/tenant.py +73 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/namespaces/user.py +99 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/client/session.py +53 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/__init__.py +1 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/context.py +41 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/descriptions.py +152 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/middleware.py +18 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/server.py +46 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/__init__.py +1 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/cluster.py +30 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/dataset.py +62 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/kb.py +15 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/node.py +111 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/program.py +30 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/tenant.py +52 -0
- c3po_sdk-0.4.0a1/src/c3po_sdk/mcp/tools/user.py +44 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: c3po-sdk
|
|
3
|
+
Version: 0.4.0a1
|
|
4
|
+
Summary: Python SDK, CLI, and MCP server for the C3PO research platform
|
|
5
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
|
+
Requires-Dist: asyncer>=0.0.8
|
|
11
|
+
Requires-Dist: fastmcp>=2.0
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
14
|
+
Requires-Dist: rich>=13.0
|
|
15
|
+
Requires-Dist: sentry-sdk>=2.59
|
|
16
|
+
Requires-Dist: typer>=0.26
|
|
17
|
+
Requires-Dist: prompt-toolkit>=3.0 ; extra == 'interactive'
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Provides-Extra: interactive
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# c3po-sdk
|
|
23
|
+
|
|
24
|
+
Python SDK, CLI, and MCP server for the C3PO research platform.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv tool install c3po-sdk
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Modules
|
|
33
|
+
|
|
34
|
+
### Client (`c3po_sdk.client`)
|
|
35
|
+
|
|
36
|
+
Python SDK for the C3PO API.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from c3po_sdk.client.client import C3poClient
|
|
40
|
+
|
|
41
|
+
async with C3poClient() as client:
|
|
42
|
+
cursor: str | None = None
|
|
43
|
+
while True:
|
|
44
|
+
page = await client.datasets.list(last_evaluated_key=cursor, limit=50)
|
|
45
|
+
for dataset in page.items:
|
|
46
|
+
...
|
|
47
|
+
if not page.has_more:
|
|
48
|
+
break
|
|
49
|
+
cursor = page.last_evaluated_key
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### CLI (`c3po_sdk.cli`)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
c3po --help
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### CLI commands
|
|
59
|
+
|
|
60
|
+
Global options (where supported): `--output` / `-o` (`rich` | `plain` | `json`), `--profile`, `--version` / `-V`. Use `c3po <group> <command> --help` for flags and arguments.
|
|
61
|
+
|
|
62
|
+
**auth**
|
|
63
|
+
|
|
64
|
+
- `c3po auth status`
|
|
65
|
+
|
|
66
|
+
**configure**
|
|
67
|
+
|
|
68
|
+
- `c3po configure auth`
|
|
69
|
+
|
|
70
|
+
**datasets**
|
|
71
|
+
|
|
72
|
+
- `c3po datasets list`
|
|
73
|
+
- `c3po datasets get`
|
|
74
|
+
- `c3po datasets update`
|
|
75
|
+
- `c3po datasets tree`
|
|
76
|
+
- `c3po datasets assets`
|
|
77
|
+
|
|
78
|
+
**kb** (analytics knowledge base — `POST /v1/kb/query`: NL question → generated SQL and executed result rows in one response)
|
|
79
|
+
|
|
80
|
+
- `c3po kb query [QUESTION]` — pass the question as an argument or pipe text on stdin; use `--output json` for the raw `sql` / `columns` / `rows` object.
|
|
81
|
+
- **Timeouts:** In CDK this route uses the streaming API Lambda with a **15-minute** Lambda and API Gateway integration timeout (`c3po_backend/services/api/_constructs/api.py`). The SDK applies the same ceiling for the HTTP **read** timeout so long NL2SQL + Redshift runs are not cut off early by the default httpx limits used elsewhere.
|
|
82
|
+
|
|
83
|
+
**nodes** (all subcommands take a `NODE_ID` unless noted)
|
|
84
|
+
|
|
85
|
+
- `c3po nodes get NODE_ID` — node details; optional `--tree` for dataset subtree
|
|
86
|
+
- `c3po nodes update NODE_ID` — `--name`, `--description`
|
|
87
|
+
- `c3po nodes reset NODE_ID` — clear pipeline outputs (`--force` / `-f`)
|
|
88
|
+
- `c3po nodes cluster NODE_ID` — cell clustering (`--obs-variable`, repeatable `--obs-values`, `--run-pipeline`)
|
|
89
|
+
- `c3po nodes assets NODE_ID` — presigned asset URLs; `--download` to fetch files
|
|
90
|
+
|
|
91
|
+
**nodes pipeline**
|
|
92
|
+
|
|
93
|
+
- `c3po nodes pipeline run NODE_ID` — start execution (see `--help` for Leiden / IDS params); `--watch` / `--no-watch`
|
|
94
|
+
- `c3po nodes pipeline list NODE_ID` — paginated history (`--all` / `-a`, `--limit`)
|
|
95
|
+
- `c3po nodes pipeline status NODE_ID EXECUTION_ID` — Step Functions payload; `--watch` to poll until terminal
|
|
96
|
+
|
|
97
|
+
**nodes programs** (gene programs for a node)
|
|
98
|
+
|
|
99
|
+
- `c3po nodes programs list NODE_ID` — paginated (`--all` / `-a`, `--limit`)
|
|
100
|
+
- `c3po nodes programs get NODE_ID PROGRAM_ID` — full program record
|
|
101
|
+
|
|
102
|
+
**nodes clusters** (cell clusters for a node)
|
|
103
|
+
|
|
104
|
+
- `c3po nodes clusters list NODE_ID` — paginated (`--all` / `-a`, `--limit`)
|
|
105
|
+
- `c3po nodes clusters get NODE_ID CLUSTER_ID` — full cluster record
|
|
106
|
+
|
|
107
|
+
**tenants**
|
|
108
|
+
|
|
109
|
+
- `c3po tenants me`
|
|
110
|
+
- `c3po tenants users list`
|
|
111
|
+
- `c3po tenants credentials generate`
|
|
112
|
+
- `c3po tenants credentials regenerate`
|
|
113
|
+
|
|
114
|
+
**users**
|
|
115
|
+
|
|
116
|
+
- `c3po users me`
|
|
117
|
+
- `c3po users update`
|
|
118
|
+
- `c3po users tenants list`
|
|
119
|
+
- `c3po users tokens list`
|
|
120
|
+
- `c3po users tokens create`
|
|
121
|
+
- `c3po users tokens revoke`
|
|
122
|
+
|
|
123
|
+
### MCP Server (`c3po_sdk.mcp`)
|
|
124
|
+
|
|
125
|
+
See [docs/MCP.md](docs/MCP.md) for detailed configuration instructions.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
c3po.mcp
|
|
129
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# c3po-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK, CLI, and MCP server for the C3PO research platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv tool install c3po-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Modules
|
|
12
|
+
|
|
13
|
+
### Client (`c3po_sdk.client`)
|
|
14
|
+
|
|
15
|
+
Python SDK for the C3PO API.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from c3po_sdk.client.client import C3poClient
|
|
19
|
+
|
|
20
|
+
async with C3poClient() as client:
|
|
21
|
+
cursor: str | None = None
|
|
22
|
+
while True:
|
|
23
|
+
page = await client.datasets.list(last_evaluated_key=cursor, limit=50)
|
|
24
|
+
for dataset in page.items:
|
|
25
|
+
...
|
|
26
|
+
if not page.has_more:
|
|
27
|
+
break
|
|
28
|
+
cursor = page.last_evaluated_key
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### CLI (`c3po_sdk.cli`)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
c3po --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### CLI commands
|
|
38
|
+
|
|
39
|
+
Global options (where supported): `--output` / `-o` (`rich` | `plain` | `json`), `--profile`, `--version` / `-V`. Use `c3po <group> <command> --help` for flags and arguments.
|
|
40
|
+
|
|
41
|
+
**auth**
|
|
42
|
+
|
|
43
|
+
- `c3po auth status`
|
|
44
|
+
|
|
45
|
+
**configure**
|
|
46
|
+
|
|
47
|
+
- `c3po configure auth`
|
|
48
|
+
|
|
49
|
+
**datasets**
|
|
50
|
+
|
|
51
|
+
- `c3po datasets list`
|
|
52
|
+
- `c3po datasets get`
|
|
53
|
+
- `c3po datasets update`
|
|
54
|
+
- `c3po datasets tree`
|
|
55
|
+
- `c3po datasets assets`
|
|
56
|
+
|
|
57
|
+
**kb** (analytics knowledge base — `POST /v1/kb/query`: NL question → generated SQL and executed result rows in one response)
|
|
58
|
+
|
|
59
|
+
- `c3po kb query [QUESTION]` — pass the question as an argument or pipe text on stdin; use `--output json` for the raw `sql` / `columns` / `rows` object.
|
|
60
|
+
- **Timeouts:** In CDK this route uses the streaming API Lambda with a **15-minute** Lambda and API Gateway integration timeout (`c3po_backend/services/api/_constructs/api.py`). The SDK applies the same ceiling for the HTTP **read** timeout so long NL2SQL + Redshift runs are not cut off early by the default httpx limits used elsewhere.
|
|
61
|
+
|
|
62
|
+
**nodes** (all subcommands take a `NODE_ID` unless noted)
|
|
63
|
+
|
|
64
|
+
- `c3po nodes get NODE_ID` — node details; optional `--tree` for dataset subtree
|
|
65
|
+
- `c3po nodes update NODE_ID` — `--name`, `--description`
|
|
66
|
+
- `c3po nodes reset NODE_ID` — clear pipeline outputs (`--force` / `-f`)
|
|
67
|
+
- `c3po nodes cluster NODE_ID` — cell clustering (`--obs-variable`, repeatable `--obs-values`, `--run-pipeline`)
|
|
68
|
+
- `c3po nodes assets NODE_ID` — presigned asset URLs; `--download` to fetch files
|
|
69
|
+
|
|
70
|
+
**nodes pipeline**
|
|
71
|
+
|
|
72
|
+
- `c3po nodes pipeline run NODE_ID` — start execution (see `--help` for Leiden / IDS params); `--watch` / `--no-watch`
|
|
73
|
+
- `c3po nodes pipeline list NODE_ID` — paginated history (`--all` / `-a`, `--limit`)
|
|
74
|
+
- `c3po nodes pipeline status NODE_ID EXECUTION_ID` — Step Functions payload; `--watch` to poll until terminal
|
|
75
|
+
|
|
76
|
+
**nodes programs** (gene programs for a node)
|
|
77
|
+
|
|
78
|
+
- `c3po nodes programs list NODE_ID` — paginated (`--all` / `-a`, `--limit`)
|
|
79
|
+
- `c3po nodes programs get NODE_ID PROGRAM_ID` — full program record
|
|
80
|
+
|
|
81
|
+
**nodes clusters** (cell clusters for a node)
|
|
82
|
+
|
|
83
|
+
- `c3po nodes clusters list NODE_ID` — paginated (`--all` / `-a`, `--limit`)
|
|
84
|
+
- `c3po nodes clusters get NODE_ID CLUSTER_ID` — full cluster record
|
|
85
|
+
|
|
86
|
+
**tenants**
|
|
87
|
+
|
|
88
|
+
- `c3po tenants me`
|
|
89
|
+
- `c3po tenants users list`
|
|
90
|
+
- `c3po tenants credentials generate`
|
|
91
|
+
- `c3po tenants credentials regenerate`
|
|
92
|
+
|
|
93
|
+
**users**
|
|
94
|
+
|
|
95
|
+
- `c3po users me`
|
|
96
|
+
- `c3po users update`
|
|
97
|
+
- `c3po users tenants list`
|
|
98
|
+
- `c3po users tokens list`
|
|
99
|
+
- `c3po users tokens create`
|
|
100
|
+
- `c3po users tokens revoke`
|
|
101
|
+
|
|
102
|
+
### MCP Server (`c3po_sdk.mcp`)
|
|
103
|
+
|
|
104
|
+
See [docs/MCP.md](docs/MCP.md) for detailed configuration instructions.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
c3po.mcp
|
|
108
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "uv_build"
|
|
3
|
+
requires = [ "uv-build>=0.11.7,<0.12.0" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "c3po-sdk"
|
|
7
|
+
version = "0.4.0a1"
|
|
8
|
+
description = "Python SDK, CLI, and MCP server for the C3PO research platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"asyncer>=0.0.8",
|
|
20
|
+
"fastmcp>=2.0",
|
|
21
|
+
"httpx>=0.27",
|
|
22
|
+
"pydantic-settings>=2.0",
|
|
23
|
+
"rich>=13.0",
|
|
24
|
+
"sentry-sdk>=2.59",
|
|
25
|
+
"typer>=0.26",
|
|
26
|
+
]
|
|
27
|
+
optional-dependencies.interactive = [ "prompt-toolkit>=3.0" ]
|
|
28
|
+
scripts."c3po.mcp" = "c3po_sdk.mcp.server:main"
|
|
29
|
+
scripts.c3po = "c3po_sdk.cli.main:app"
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = [
|
|
33
|
+
"polyfactory>=2.0",
|
|
34
|
+
"pre-commit>=4.5.1",
|
|
35
|
+
"pytest>=8.0",
|
|
36
|
+
"pytest-asyncio>=0.23",
|
|
37
|
+
"pytest-cov>=5.0",
|
|
38
|
+
"respx>=0.21",
|
|
39
|
+
"ruff>=0.15.12",
|
|
40
|
+
"ty>=0.0.30",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.uv]
|
|
44
|
+
exclude-newer = "P7D"
|
|
45
|
+
exclude-newer-package = { typer = "P0D" }
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
extend-exclude = [ "uv.lock" ]
|
|
49
|
+
lint.extend-select = [
|
|
50
|
+
"B",
|
|
51
|
+
"C4",
|
|
52
|
+
"D",
|
|
53
|
+
"FLY",
|
|
54
|
+
"I",
|
|
55
|
+
"ICN",
|
|
56
|
+
"N",
|
|
57
|
+
"NPY",
|
|
58
|
+
"PERF",
|
|
59
|
+
"PTH",
|
|
60
|
+
"RET",
|
|
61
|
+
"RUF",
|
|
62
|
+
"SIM",
|
|
63
|
+
"UP",
|
|
64
|
+
]
|
|
65
|
+
lint.ignore = [
|
|
66
|
+
"D100",
|
|
67
|
+
"D104",
|
|
68
|
+
"D107",
|
|
69
|
+
"D212",
|
|
70
|
+
"N802",
|
|
71
|
+
"N803",
|
|
72
|
+
"N806",
|
|
73
|
+
]
|
|
74
|
+
lint.per-file-ignores."__init__.py" = [ "F401", "F403" ]
|
|
75
|
+
lint.per-file-ignores."tests/**" = [ "D100", "D101", "D102", "D103" ]
|
|
76
|
+
lint.pydocstyle.convention = "google"
|
|
77
|
+
|
|
78
|
+
[tool.pyproject-fmt]
|
|
79
|
+
keep_full_version = true
|
|
80
|
+
|
|
81
|
+
[tool.ty]
|
|
82
|
+
rules.possibly-missing-attribute = "ignore"
|
|
83
|
+
src.exclude = [
|
|
84
|
+
"**/site-packages",
|
|
85
|
+
"**/.venv",
|
|
86
|
+
"**/.ruff_cache",
|
|
87
|
+
"**/.pytest_cache",
|
|
88
|
+
"**/htmlcov",
|
|
89
|
+
"**/node_modules",
|
|
90
|
+
"**/__pycache__",
|
|
91
|
+
"**/.*",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.pytest]
|
|
95
|
+
ini_options.testpaths = [ "tests" ]
|
|
96
|
+
ini_options.asyncio_mode = "auto"
|
|
97
|
+
ini_options.addopts = "--import-mode=importlib --cov=. --cov-report=html"
|
|
98
|
+
ini_options.filterwarnings = [
|
|
99
|
+
"ignore:datetime.datetime.utcnow\\(\\) is deprecated",
|
|
100
|
+
"ignore::FutureWarning",
|
|
101
|
+
"ignore::DeprecationWarning",
|
|
102
|
+
]
|
|
103
|
+
ini_options.python_files = [ "*_tests.py", "test_*.py", "tests.py" ]
|
|
104
|
+
|
|
105
|
+
[tool.coverage]
|
|
106
|
+
run.branch = true
|
|
107
|
+
run.omit = [ "*/tests*", "*/tests/*", "*__init__*" ]
|
|
108
|
+
run.relative_files = true
|
|
109
|
+
report.exclude_lines = [ "pragma: no cover" ]
|
|
110
|
+
report.include = [ "src/*" ]
|
|
111
|
+
report.include_namespace_packages = true
|
|
112
|
+
report.omit = [ "*/tests*", "*/tests/*", "*__init__*" ]
|
|
113
|
+
report.show_missing = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""c3po_sdk.cli.commands package."""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Cell cluster sub-commands under ``c3po nodes clusters``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from asyncer import syncify
|
|
10
|
+
|
|
11
|
+
from c3po_sdk.cli.context import GlobalCommand, get_ctx
|
|
12
|
+
from c3po_sdk.cli.output import get_console
|
|
13
|
+
from c3po_sdk.cli.pagination import paginate
|
|
14
|
+
from c3po_sdk.client.exceptions import ApiError
|
|
15
|
+
from c3po_sdk.client.models import ClusterList, Page
|
|
16
|
+
from c3po_sdk.client.session import Session
|
|
17
|
+
|
|
18
|
+
clusters_app = typer.Typer(
|
|
19
|
+
name="clusters",
|
|
20
|
+
help="Cluster commands.",
|
|
21
|
+
no_args_is_help=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@clusters_app.command("list", cls=GlobalCommand)
|
|
26
|
+
@partial(syncify, raise_sync_error=False)
|
|
27
|
+
async def clusters_list(
|
|
28
|
+
node_id: Annotated[str, typer.Argument(help="Node ID")],
|
|
29
|
+
fetch_all: Annotated[
|
|
30
|
+
bool,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--all",
|
|
33
|
+
"-a",
|
|
34
|
+
help="Fetch every page (otherwise only the first page is shown).",
|
|
35
|
+
),
|
|
36
|
+
] = False,
|
|
37
|
+
limit: Annotated[
|
|
38
|
+
int,
|
|
39
|
+
typer.Option("--limit", min=1, max=100, help="Page size."),
|
|
40
|
+
] = 10,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""List cell clusters for a node."""
|
|
43
|
+
console = get_console()
|
|
44
|
+
ctx = get_ctx()
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
async with Session(profile=ctx.profile).client() as client:
|
|
48
|
+
|
|
49
|
+
async def fetch(cursor: str | None) -> Page[ClusterList]:
|
|
50
|
+
return await client.nodes.clusters.list(
|
|
51
|
+
node_id,
|
|
52
|
+
last_evaluated_key=cursor,
|
|
53
|
+
limit=limit,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
items = await paginate(fetch, fetch_all=fetch_all)
|
|
57
|
+
except ApiError as exc:
|
|
58
|
+
console.error(f"Failed to list clusters: {exc}")
|
|
59
|
+
raise typer.Exit(code=1) from None
|
|
60
|
+
|
|
61
|
+
console.rows_table(
|
|
62
|
+
items,
|
|
63
|
+
columns={
|
|
64
|
+
"id",
|
|
65
|
+
"label",
|
|
66
|
+
"created_at",
|
|
67
|
+
"updated_at",
|
|
68
|
+
},
|
|
69
|
+
title="Clusters",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@clusters_app.command("get", cls=GlobalCommand)
|
|
74
|
+
@partial(syncify, raise_sync_error=False)
|
|
75
|
+
async def clusters_get(
|
|
76
|
+
node_id: Annotated[str, typer.Argument(help="Node ID")],
|
|
77
|
+
cluster_id: Annotated[str, typer.Argument(help="Cluster ID")],
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Show full details of a cell cluster."""
|
|
80
|
+
console = get_console()
|
|
81
|
+
ctx = get_ctx()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
async with Session(profile=ctx.profile).client() as client:
|
|
85
|
+
cluster = await client.nodes.clusters.get(node_id, cluster_id)
|
|
86
|
+
except ApiError as exc:
|
|
87
|
+
console.error(f"Failed to fetch cluster: {exc}")
|
|
88
|
+
raise typer.Exit(code=1) from None
|
|
89
|
+
|
|
90
|
+
console.kv_table(cluster, title="Cluster")
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Configure commands: one-time credential setup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import partial
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from asyncer import syncify
|
|
9
|
+
|
|
10
|
+
from c3po_sdk.cli.context import GlobalCommand, get_ctx
|
|
11
|
+
from c3po_sdk.cli.output import get_console
|
|
12
|
+
from c3po_sdk.client.client import C3poClient
|
|
13
|
+
from c3po_sdk.client.config.credentials import (
|
|
14
|
+
_CREDENTIALS_PATH,
|
|
15
|
+
CredentialsFile,
|
|
16
|
+
Profile,
|
|
17
|
+
)
|
|
18
|
+
from c3po_sdk.client.config.settings import Settings
|
|
19
|
+
from c3po_sdk.client.exceptions import ApiError
|
|
20
|
+
from c3po_sdk.client.models._utils import RichModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _AuthResult(RichModel):
|
|
24
|
+
email: str
|
|
25
|
+
tenant_id: str
|
|
26
|
+
profile: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
configure_app = typer.Typer(
|
|
30
|
+
name="configure",
|
|
31
|
+
help="Configure the CLI.",
|
|
32
|
+
no_args_is_help=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@configure_app.command(name="auth", cls=GlobalCommand)
|
|
37
|
+
@partial(syncify, raise_sync_error=False)
|
|
38
|
+
async def configure_auth() -> None:
|
|
39
|
+
"""Authenticate and store a user access token."""
|
|
40
|
+
console = get_console()
|
|
41
|
+
ctx = get_ctx()
|
|
42
|
+
profile = ctx.profile or "default"
|
|
43
|
+
|
|
44
|
+
creds = CredentialsFile.load(_CREDENTIALS_PATH)
|
|
45
|
+
existing = creds.get_profile(profile)
|
|
46
|
+
if (
|
|
47
|
+
existing
|
|
48
|
+
and existing.api_token
|
|
49
|
+
and not typer.confirm(
|
|
50
|
+
f"Profile '{profile}' already has credentials. Overwrite?",
|
|
51
|
+
default=False,
|
|
52
|
+
)
|
|
53
|
+
):
|
|
54
|
+
raise typer.Exit()
|
|
55
|
+
|
|
56
|
+
email = typer.prompt("Email")
|
|
57
|
+
|
|
58
|
+
async with C3poClient(settings=Settings(api_token="dummy")) as client:
|
|
59
|
+
with console.status("Sending sign-in code…"):
|
|
60
|
+
try:
|
|
61
|
+
signin_resp = await client.auth.signin(email)
|
|
62
|
+
except ApiError as exc:
|
|
63
|
+
console.error(f"Sign-in failed: {exc}")
|
|
64
|
+
raise typer.Exit(code=1) from None
|
|
65
|
+
console.print("[dim]A sign-in code has been sent to your email.[/dim]")
|
|
66
|
+
|
|
67
|
+
code = typer.prompt("Code")
|
|
68
|
+
|
|
69
|
+
with console.status("Verifying code…"):
|
|
70
|
+
try:
|
|
71
|
+
token_resp = await client.auth.token(
|
|
72
|
+
"authorization_code",
|
|
73
|
+
code=code,
|
|
74
|
+
tenant_id=signin_resp.tenant_id,
|
|
75
|
+
)
|
|
76
|
+
except ApiError as exc:
|
|
77
|
+
console.error(f"Authentication failed: {exc}")
|
|
78
|
+
raise typer.Exit(code=1) from None
|
|
79
|
+
|
|
80
|
+
jwt_settings = Settings(api_token=token_resp.access_token)
|
|
81
|
+
async with C3poClient(jwt_settings) as jwt_client:
|
|
82
|
+
with console.status("Fetching tenants…"):
|
|
83
|
+
try:
|
|
84
|
+
tenants_page = await jwt_client.user.tenants.list()
|
|
85
|
+
except ApiError as exc:
|
|
86
|
+
console.error(f"Failed to list tenants: {exc}")
|
|
87
|
+
raise typer.Exit(code=1) from None
|
|
88
|
+
tenants = [{"id": t.id, "name": t.name} for t in tenants_page.items]
|
|
89
|
+
|
|
90
|
+
if not tenants:
|
|
91
|
+
console.error("No tenants available for this account.")
|
|
92
|
+
raise typer.Exit(code=1)
|
|
93
|
+
|
|
94
|
+
if len(tenants) == 1:
|
|
95
|
+
chosen = tenants[0]
|
|
96
|
+
console.print(f"Authenticated to tenant: [bold]{chosen['name']}[/bold]")
|
|
97
|
+
else:
|
|
98
|
+
chosen = console.select(
|
|
99
|
+
tenants,
|
|
100
|
+
heading="Available tenants:",
|
|
101
|
+
label="name",
|
|
102
|
+
detail="id",
|
|
103
|
+
prompt="Select a tenant",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
with console.status("Creating access token…"):
|
|
107
|
+
try:
|
|
108
|
+
uat = await jwt_client.user.tokens.create(name=profile)
|
|
109
|
+
except ApiError as exc:
|
|
110
|
+
console.error(f"Failed to create access token: {exc}")
|
|
111
|
+
raise typer.Exit(code=1) from None
|
|
112
|
+
|
|
113
|
+
creds = CredentialsFile.load(_CREDENTIALS_PATH)
|
|
114
|
+
creds.set_profile(
|
|
115
|
+
Profile(api_token=uat.token, tenant_id=chosen["id"]),
|
|
116
|
+
name=profile,
|
|
117
|
+
)
|
|
118
|
+
creds.save(_CREDENTIALS_PATH)
|
|
119
|
+
|
|
120
|
+
console.kv_table(
|
|
121
|
+
_AuthResult(email=email, tenant_id=chosen["id"], profile=profile),
|
|
122
|
+
title="Authenticated",
|
|
123
|
+
)
|