charisma-cli 0.3.1__tar.gz → 0.5.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.
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/.gitignore +13 -1
- charisma_cli-0.5.0/PKG-INFO +229 -0
- charisma_cli-0.5.0/README.md +203 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/pyproject.toml +5 -1
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/__init__.py +1 -1
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/config.py +14 -0
- charisma_cli-0.5.0/src/charisma_cli/read_client.py +629 -0
- charisma_cli-0.5.0/src/charisma_cli/read_models.py +311 -0
- charisma_cli-0.5.0/tests/test_bamboo_labels.py +77 -0
- charisma_cli-0.5.0/tests/test_read_client.py +371 -0
- charisma_cli-0.5.0/tests/test_read_client_attachments.py +459 -0
- charisma_cli-0.5.0/tests/test_read_client_integration.py +340 -0
- charisma_cli-0.5.0/tests/test_read_client_launches.py +440 -0
- charisma_cli-0.5.0/tests/test_read_client_pagination.py +318 -0
- charisma_cli-0.5.0/tests/test_read_client_results.py +462 -0
- charisma_cli-0.5.0/tests/test_read_client_single_result.py +550 -0
- charisma_cli-0.3.1/PKG-INFO +0 -121
- charisma_cli-0.3.1/README.md +0 -96
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/launch_url.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/main.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/models.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/parser.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/retry.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/subprocess_mgr.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/uploader.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/src/charisma_cli/watcher.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/__init__.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/conftest.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_cli.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_config.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_integration.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_launch_url.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_models.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_open_launch_payload.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_parser.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_retry.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_silent_mode.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_subprocess_mgr.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_uploader.py +0 -0
- {charisma_cli-0.3.1 → charisma_cli-0.5.0}/tests/test_watcher.py +0 -0
|
@@ -65,6 +65,10 @@ venv/
|
|
|
65
65
|
# Hypothesys cache
|
|
66
66
|
.hypothesis/
|
|
67
67
|
|
|
68
|
+
# mypy incremental cache
|
|
69
|
+
.mypy_cache/
|
|
70
|
+
backend/.mypy_cache/
|
|
71
|
+
|
|
68
72
|
# Kiro hooks (auto-generated)
|
|
69
73
|
.kiro/hooks/
|
|
70
74
|
.kiro/settings/*
|
|
@@ -109,6 +113,9 @@ backend/api_integration_test_setup_scripts/
|
|
|
109
113
|
# Local test/debug scripts
|
|
110
114
|
scripts/test-ingestion-token.py
|
|
111
115
|
|
|
116
|
+
# Local-only migration verification test (run manually when migration 044 is applied; not part of the suite)
|
|
117
|
+
backend/tests/integration/test_backfill_variant_history_id_migration_local.py
|
|
118
|
+
|
|
112
119
|
# vs code settings
|
|
113
120
|
.vscode/settings.json
|
|
114
121
|
backend/integration_test_report.xml
|
|
@@ -139,4 +146,9 @@ allure-results-live/
|
|
|
139
146
|
|
|
140
147
|
# Session-specific work-state tracking (see .kiro update-work-state skill)
|
|
141
148
|
.current-task.md
|
|
142
|
-
|
|
149
|
+
|
|
150
|
+
# Local agent verdicts, RCAs, and pipeline state — never tracked in the repo
|
|
151
|
+
.agents/
|
|
152
|
+
|
|
153
|
+
# sql files for locally run DB queries
|
|
154
|
+
/*.sql
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: charisma-cli
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: CLI tool that watches allure-results and streams test results + attachments to Charisma.
|
|
5
|
+
Author: Charisma Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: click<9.0,>=8.0
|
|
18
|
+
Requires-Dist: httpx<1.0,>=0.24
|
|
19
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
20
|
+
Requires-Dist: watchdog<5.0,>=3.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: hypothesis; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest<10.0,>=7.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: respx; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# charisma-cli
|
|
28
|
+
|
|
29
|
+
CLI tool that watches an `allure-results` directory during test execution and streams results + attachments to the Charisma ingestion API in real time.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install charisma-cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
No token, no index URL — works on any laptop and in any CI. The package is published to public PyPI; the Charisma repo itself stays private (only this client wheel is public).
|
|
38
|
+
|
|
39
|
+
For local development from a checkout of the repo:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e packages/charisma-cli
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Publishing (maintainers)
|
|
46
|
+
|
|
47
|
+
Releases publish to public PyPI automatically from the merge-queue workflow using a **PyPI API token**.
|
|
48
|
+
|
|
49
|
+
> Trusted publishing (OIDC) is not used because this repo runs on **GitHub Enterprise Server**, whose OIDC issuer PyPI does not trust. A stored API token is required instead.
|
|
50
|
+
|
|
51
|
+
One-time setup (done once by a PyPI project owner):
|
|
52
|
+
|
|
53
|
+
1. Sign in to [PyPI](https://pypi.org) with the team-owned account (2FA enabled).
|
|
54
|
+
2. Go to **Account settings → API tokens → Add API token**. Scope it to the `charisma-cli` project once the project exists; for the very first publish use an account-scoped token, then re-scope it to the project afterward.
|
|
55
|
+
3. Copy the token (starts with `pypi-`).
|
|
56
|
+
4. In the GitHub Enterprise repo, add it as an Actions secret named **`PYPI_API_TOKEN`** (org-level secret recommended so both packages share it).
|
|
57
|
+
5. Bump `version` in `pyproject.toml` and merge — the workflow builds and publishes on the next release.
|
|
58
|
+
|
|
59
|
+
Rotate the token periodically and store it in the team secret manager.
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export CHARISMA_ENDPOINT=https://charisma.company.com
|
|
65
|
+
export CHARISMA_TOKEN=<your-service-token>
|
|
66
|
+
export CHARISMA_PROJECT_ID=my-project
|
|
67
|
+
|
|
68
|
+
charismactl watch --results allure-results -- uv run pytest -n 4 tests/
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Environment Variables
|
|
72
|
+
|
|
73
|
+
| Variable | Required | Description |
|
|
74
|
+
| --------------------- | -------- | -------------------------------------------------- |
|
|
75
|
+
| `CHARISMA_ENDPOINT` | Yes | Base URL of the Charisma API |
|
|
76
|
+
| `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
|
|
77
|
+
| `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
|
|
78
|
+
| `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
|
|
79
|
+
| `CHARISMA_RUN_KEY` | No | Run identity / merge key (`--run-key` fallback) |
|
|
80
|
+
| `BUILD_ID` | No | CI build identifier (`--build-id` fallback; `CHARISMA_BUILD_ID` also honored) |
|
|
81
|
+
| `BUILD_PLAN_KEY` | No | Build plan key (`--build-plan-key` fallback; `CHARISMA_BUILD_PLAN_KEY` also honored) |
|
|
82
|
+
| `ENVIRONMENT` | No | Environment code (`--environment` fallback; `CHARISMA_ENVIRONMENT` also honored) |
|
|
83
|
+
| `COMPONENT` | No | Component alias (`--component` fallback; `CHARISMA_COMPONENT` also honored) |
|
|
84
|
+
| `SOURCE_URL` | No | CI/CD build link (`--source-url` fallback; `CHARISMA_SOURCE_URL` also honored) |
|
|
85
|
+
|
|
86
|
+
## Flags
|
|
87
|
+
|
|
88
|
+
Both `upload` and `watch` accept the identical option set.
|
|
89
|
+
|
|
90
|
+
| Flag | Description |
|
|
91
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
92
|
+
| `--endpoint URL` | Charisma API endpoint (overrides `CHARISMA_ENDPOINT`) |
|
|
93
|
+
| `--token TOKEN` | Bearer token (overrides `CHARISMA_TOKEN`) |
|
|
94
|
+
| `--results PATH` | Allure results directory (overrides env var) |
|
|
95
|
+
| `--project ID` | Project alias (overrides env var) |
|
|
96
|
+
| `--silent` | Don't fail pipeline if upload errors occur |
|
|
97
|
+
| `--skip-too-big` | Skip result files larger than 2MB |
|
|
98
|
+
| `--component ALIAS` | Component alias within the project |
|
|
99
|
+
| `--source-url URL` | CI/CD build link for this run |
|
|
100
|
+
| `--build-plan-key K` | Explicit build plan key for resolution (forwarded as `buildPlanKey`) |
|
|
101
|
+
| `--build-id ID` | CI build identifier for this run (forwarded as `buildId`) |
|
|
102
|
+
| `--environment ENV` | Environment code (e.g. `qat`, `prod`), stored on the launch |
|
|
103
|
+
| `--run-key KEY` | Run identity / merge key (reruns + parallel workers share it) |
|
|
104
|
+
|
|
105
|
+
## Launch URL in CI
|
|
106
|
+
|
|
107
|
+
`charismactl` prints the Charisma launch URL (`Charisma launch: <url>`) and, when running
|
|
108
|
+
in GitHub Actions, writes it as a step output named `launch_url`. Attach it to the job
|
|
109
|
+
summary or a Teams message:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
- name: Run tests and stream to Charisma
|
|
113
|
+
id: charisma
|
|
114
|
+
run: charismactl watch --project my-project -- pytest tests/
|
|
115
|
+
|
|
116
|
+
- name: Add Charisma link to summary
|
|
117
|
+
if: always()
|
|
118
|
+
run: echo "[View Charisma launch](${{ steps.charisma.outputs.launch_url }})" >> "$GITHUB_STEP_SUMMARY"
|
|
119
|
+
|
|
120
|
+
# The same value can feed a Teams-notification step:
|
|
121
|
+
# text: "Results: ${{ steps.charisma.outputs.launch_url }}"
|
|
122
|
+
```
|
|
123
|
+
## Read API
|
|
124
|
+
|
|
125
|
+
Alongside the write path (`watch`/`upload`), the package ships an importable **read client** for
|
|
126
|
+
querying launches, results, history, and attachments from Charisma's Phase-1 read API. The client
|
|
127
|
+
returns the server JSON **verbatim** (Charisma-native shapes — UUID ids, `{items, total}` envelopes,
|
|
128
|
+
per-endpoint casing). It performs no Allure reshaping and is fully decoupled from the upload
|
|
129
|
+
machinery.
|
|
130
|
+
|
|
131
|
+
### Project id is a UUID in read mode
|
|
132
|
+
|
|
133
|
+
Read is **project-scoped by the project UUID**, not by the write-path `projectAlias`.
|
|
134
|
+
|
|
135
|
+
- **Write path** (`watch`/`upload`): `CHARISMA_PROJECT_ID` / `--project` is the project **alias**.
|
|
136
|
+
- **Read path** (`CharismaReadClient`): the `project_id` argument must be the project **UUID**.
|
|
137
|
+
|
|
138
|
+
A non-UUID value (for example, passing the write alias to the read client) fails **early** with a
|
|
139
|
+
clear `CharismaProjectIdError` naming the UUID-vs-alias distinction — not a late, confusing 404.
|
|
140
|
+
|
|
141
|
+
### Python read client
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from charisma_cli.read_client import (
|
|
145
|
+
CharismaReadClient,
|
|
146
|
+
CharismaNotFoundError,
|
|
147
|
+
CharismaAuthError,
|
|
148
|
+
CharismaReadError,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# project_id MUST be the project UUID (NOT the write-path projectAlias).
|
|
152
|
+
client = CharismaReadClient(
|
|
153
|
+
endpoint="https://charisma.company.com",
|
|
154
|
+
token="<service-token>",
|
|
155
|
+
project_id="3f2504e0-4f89-41d3-9a0c-0305e82c3301",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# --- Launches -------------------------------------------------------------
|
|
159
|
+
# list_launches is newest-first (no sort param) and takes up to 9 filters.
|
|
160
|
+
page = client.list_launches(offset=0, limit=25, status="failed", branch="main")
|
|
161
|
+
for launch in page.items: # PaginatedLaunchList -> BaseLaunch items (snake_case attrs)
|
|
162
|
+
print(launch.id, launch.name)
|
|
163
|
+
print("total:", page.total)
|
|
164
|
+
|
|
165
|
+
launch = client.get_launch("8a1b...launch-uuid") # LaunchRecord (snake_case attrs)
|
|
166
|
+
stats = client.get_launch_statistic("8a1b...launch-uuid") # LaunchStatisticRecord -> .counts[]
|
|
167
|
+
|
|
168
|
+
# --- Results of a launch (tags/metadataTags/zephyrIds populated) -----------
|
|
169
|
+
results = client.list_launch_results(
|
|
170
|
+
"8a1b...launch-uuid",
|
|
171
|
+
offset=0,
|
|
172
|
+
limit=25,
|
|
173
|
+
sort_by="startedAt", # name|status|startedAt|createdAt|durationMs
|
|
174
|
+
sort_order="desc", # asc|desc
|
|
175
|
+
status="failed",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# --- A single result (tags/metadataTags/zephyrIds are always []) -----------
|
|
179
|
+
result = client.get_result("c4d5...result-uuid") # TestResultRecord
|
|
180
|
+
execution = client.get_result_execution("c4d5...result-uuid") # ExecutionRecord
|
|
181
|
+
# execution.id / .status / .steps / .beforeHooks / .afterHooks / .backgroundSteps / .attachments
|
|
182
|
+
|
|
183
|
+
# --- History (TestCaseHistoryRecord) ---------------------------------------
|
|
184
|
+
history = client.get_result_history("c4d5...result-uuid")
|
|
185
|
+
print(history.history_id, history.stability.passed_count)
|
|
186
|
+
|
|
187
|
+
# --- Attachments ----------------------------------------------------------
|
|
188
|
+
atts = client.list_result_attachments("c4d5...result-uuid") # PaginatedAttachments (.items/.total)
|
|
189
|
+
url = client.get_attachment_url("e6f7...attachment-uuid") # AttachmentUrl (.download_url/.file_name/.content_type)
|
|
190
|
+
content = client.get_attachment_content("e6f7...attachment-uuid") # AttachmentContent
|
|
191
|
+
# content.content / content.file_name / content.truncated — text-only, truncated=True past the 1 MB cap
|
|
192
|
+
|
|
193
|
+
# --- Paginate any {items, total} endpoint ----------------------------------
|
|
194
|
+
# iter_pages drives offset/limit for you, yielding every item once (page_size
|
|
195
|
+
# defaults to 100, the server cap). Pass the list method plus its kwargs.
|
|
196
|
+
for launch in client.iter_pages(client.list_launches, status="failed"):
|
|
197
|
+
print(launch.id)
|
|
198
|
+
|
|
199
|
+
for row in client.iter_pages(
|
|
200
|
+
client.list_launch_results, launch_id="8a1b...launch-uuid", status="failed"
|
|
201
|
+
):
|
|
202
|
+
print(row.name, row.status)
|
|
203
|
+
|
|
204
|
+
client.close() # or use the client as a context manager: `with CharismaReadClient(...) as client:`
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
All methods return **typed Pydantic models** (from `charisma_cli.read_models`) parsed from the server JSON — e.g. `list_launches` -> `PaginatedLaunchList`, `get_launch` -> `LaunchRecord`, `get_result_history` -> `TestCaseHistoryRecord`. **Model attributes are uniform `snake_case`** (`launch.started_at`, `result.duration_ms`, `history.history_id`), consistent with the rest of the package. The server's wire casing is handled by Pydantic aliases, not the attribute names: `model_validate` accepts the server body as-is, and `model_dump(by_alias=True)` / `model_dump_json(by_alias=True)` reproduce the exact server JSON (camelCase for most endpoints, snake_case for history). `model_dump()` without `by_alias` gives you snake_case. Datetime fields parse to `datetime`; models set `extra="allow"` so a new server field is preserved. Failures raise a typed error:
|
|
208
|
+
|
|
209
|
+
| Error | Raised on |
|
|
210
|
+
| ----- | --------- |
|
|
211
|
+
| `CharismaNotFoundError` | HTTP 404 — the launch/result/attachment does not exist |
|
|
212
|
+
| `CharismaAuthError` | HTTP 401/403 — token missing, invalid, or lacking access |
|
|
213
|
+
| `CharismaReadError` | base error — a 400 (e.g. an invalid sort value), a persistent 5xx after retries, or a transport failure. `CharismaProjectIdError` (non-UUID `project_id`) also subclasses it |
|
|
214
|
+
|
|
215
|
+
Transient failures (429/5xx/timeouts) are retried with backoff before surfacing as
|
|
216
|
+
`CharismaReadError`; terminal statuses (400/401/403/404) are raised immediately, not retried.
|
|
217
|
+
|
|
218
|
+
### Attributes are snake_case; the wire is reproduced via aliases
|
|
219
|
+
|
|
220
|
+
Every model exposes **uniform `snake_case` attributes** — there is no per-endpoint attribute casing to remember. The server's on-the-wire casing still differs per endpoint (camelCase for launches/results/attachments, snake_case for `get_result_history`), but that lives in the Pydantic **aliases**, not the attribute names. So you always read `launch.source_url`, `result.duration_ms`, `history.history_id`; and if you need the exact server JSON back, dump with `by_alias=True`. This keeps the Python API consistent while the client stays a faithful mirror of the Phase-1 wire contract.
|
|
221
|
+
|
|
222
|
+
### Attachment endpoints and the token type
|
|
223
|
+
|
|
224
|
+
`get_attachment_url` and `get_attachment_content` require a token the Attachments Lambda
|
|
225
|
+
accepts. These two endpoints were historically **user-token-only** (`allowed_token_types=["user"]`),
|
|
226
|
+
which rejected the CLI's service token. They have been widened to accept **both user and service
|
|
227
|
+
tokens** (`["user", "service"]`, ADR-035 / FR-0), so a **service token now works** — client-side
|
|
228
|
+
these methods behave exactly like the others, with no token-type special-casing. The `ci` token type
|
|
229
|
+
remains excluded from these two endpoints.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# charisma-cli
|
|
2
|
+
|
|
3
|
+
CLI tool that watches an `allure-results` directory during test execution and streams results + attachments to the Charisma ingestion API in real time.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install charisma-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No token, no index URL — works on any laptop and in any CI. The package is published to public PyPI; the Charisma repo itself stays private (only this client wheel is public).
|
|
12
|
+
|
|
13
|
+
For local development from a checkout of the repo:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e packages/charisma-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Publishing (maintainers)
|
|
20
|
+
|
|
21
|
+
Releases publish to public PyPI automatically from the merge-queue workflow using a **PyPI API token**.
|
|
22
|
+
|
|
23
|
+
> Trusted publishing (OIDC) is not used because this repo runs on **GitHub Enterprise Server**, whose OIDC issuer PyPI does not trust. A stored API token is required instead.
|
|
24
|
+
|
|
25
|
+
One-time setup (done once by a PyPI project owner):
|
|
26
|
+
|
|
27
|
+
1. Sign in to [PyPI](https://pypi.org) with the team-owned account (2FA enabled).
|
|
28
|
+
2. Go to **Account settings → API tokens → Add API token**. Scope it to the `charisma-cli` project once the project exists; for the very first publish use an account-scoped token, then re-scope it to the project afterward.
|
|
29
|
+
3. Copy the token (starts with `pypi-`).
|
|
30
|
+
4. In the GitHub Enterprise repo, add it as an Actions secret named **`PYPI_API_TOKEN`** (org-level secret recommended so both packages share it).
|
|
31
|
+
5. Bump `version` in `pyproject.toml` and merge — the workflow builds and publishes on the next release.
|
|
32
|
+
|
|
33
|
+
Rotate the token periodically and store it in the team secret manager.
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export CHARISMA_ENDPOINT=https://charisma.company.com
|
|
39
|
+
export CHARISMA_TOKEN=<your-service-token>
|
|
40
|
+
export CHARISMA_PROJECT_ID=my-project
|
|
41
|
+
|
|
42
|
+
charismactl watch --results allure-results -- uv run pytest -n 4 tests/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Environment Variables
|
|
46
|
+
|
|
47
|
+
| Variable | Required | Description |
|
|
48
|
+
| --------------------- | -------- | -------------------------------------------------- |
|
|
49
|
+
| `CHARISMA_ENDPOINT` | Yes | Base URL of the Charisma API |
|
|
50
|
+
| `CHARISMA_TOKEN` | Yes | Service or CI bearer token |
|
|
51
|
+
| `CHARISMA_PROJECT_ID` | Yes | Project alias for result routing |
|
|
52
|
+
| `CHARISMA_RESULTS` | No | Path to allure-results (default: `allure-results`) |
|
|
53
|
+
| `CHARISMA_RUN_KEY` | No | Run identity / merge key (`--run-key` fallback) |
|
|
54
|
+
| `BUILD_ID` | No | CI build identifier (`--build-id` fallback; `CHARISMA_BUILD_ID` also honored) |
|
|
55
|
+
| `BUILD_PLAN_KEY` | No | Build plan key (`--build-plan-key` fallback; `CHARISMA_BUILD_PLAN_KEY` also honored) |
|
|
56
|
+
| `ENVIRONMENT` | No | Environment code (`--environment` fallback; `CHARISMA_ENVIRONMENT` also honored) |
|
|
57
|
+
| `COMPONENT` | No | Component alias (`--component` fallback; `CHARISMA_COMPONENT` also honored) |
|
|
58
|
+
| `SOURCE_URL` | No | CI/CD build link (`--source-url` fallback; `CHARISMA_SOURCE_URL` also honored) |
|
|
59
|
+
|
|
60
|
+
## Flags
|
|
61
|
+
|
|
62
|
+
Both `upload` and `watch` accept the identical option set.
|
|
63
|
+
|
|
64
|
+
| Flag | Description |
|
|
65
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
66
|
+
| `--endpoint URL` | Charisma API endpoint (overrides `CHARISMA_ENDPOINT`) |
|
|
67
|
+
| `--token TOKEN` | Bearer token (overrides `CHARISMA_TOKEN`) |
|
|
68
|
+
| `--results PATH` | Allure results directory (overrides env var) |
|
|
69
|
+
| `--project ID` | Project alias (overrides env var) |
|
|
70
|
+
| `--silent` | Don't fail pipeline if upload errors occur |
|
|
71
|
+
| `--skip-too-big` | Skip result files larger than 2MB |
|
|
72
|
+
| `--component ALIAS` | Component alias within the project |
|
|
73
|
+
| `--source-url URL` | CI/CD build link for this run |
|
|
74
|
+
| `--build-plan-key K` | Explicit build plan key for resolution (forwarded as `buildPlanKey`) |
|
|
75
|
+
| `--build-id ID` | CI build identifier for this run (forwarded as `buildId`) |
|
|
76
|
+
| `--environment ENV` | Environment code (e.g. `qat`, `prod`), stored on the launch |
|
|
77
|
+
| `--run-key KEY` | Run identity / merge key (reruns + parallel workers share it) |
|
|
78
|
+
|
|
79
|
+
## Launch URL in CI
|
|
80
|
+
|
|
81
|
+
`charismactl` prints the Charisma launch URL (`Charisma launch: <url>`) and, when running
|
|
82
|
+
in GitHub Actions, writes it as a step output named `launch_url`. Attach it to the job
|
|
83
|
+
summary or a Teams message:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
- name: Run tests and stream to Charisma
|
|
87
|
+
id: charisma
|
|
88
|
+
run: charismactl watch --project my-project -- pytest tests/
|
|
89
|
+
|
|
90
|
+
- name: Add Charisma link to summary
|
|
91
|
+
if: always()
|
|
92
|
+
run: echo "[View Charisma launch](${{ steps.charisma.outputs.launch_url }})" >> "$GITHUB_STEP_SUMMARY"
|
|
93
|
+
|
|
94
|
+
# The same value can feed a Teams-notification step:
|
|
95
|
+
# text: "Results: ${{ steps.charisma.outputs.launch_url }}"
|
|
96
|
+
```
|
|
97
|
+
## Read API
|
|
98
|
+
|
|
99
|
+
Alongside the write path (`watch`/`upload`), the package ships an importable **read client** for
|
|
100
|
+
querying launches, results, history, and attachments from Charisma's Phase-1 read API. The client
|
|
101
|
+
returns the server JSON **verbatim** (Charisma-native shapes — UUID ids, `{items, total}` envelopes,
|
|
102
|
+
per-endpoint casing). It performs no Allure reshaping and is fully decoupled from the upload
|
|
103
|
+
machinery.
|
|
104
|
+
|
|
105
|
+
### Project id is a UUID in read mode
|
|
106
|
+
|
|
107
|
+
Read is **project-scoped by the project UUID**, not by the write-path `projectAlias`.
|
|
108
|
+
|
|
109
|
+
- **Write path** (`watch`/`upload`): `CHARISMA_PROJECT_ID` / `--project` is the project **alias**.
|
|
110
|
+
- **Read path** (`CharismaReadClient`): the `project_id` argument must be the project **UUID**.
|
|
111
|
+
|
|
112
|
+
A non-UUID value (for example, passing the write alias to the read client) fails **early** with a
|
|
113
|
+
clear `CharismaProjectIdError` naming the UUID-vs-alias distinction — not a late, confusing 404.
|
|
114
|
+
|
|
115
|
+
### Python read client
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from charisma_cli.read_client import (
|
|
119
|
+
CharismaReadClient,
|
|
120
|
+
CharismaNotFoundError,
|
|
121
|
+
CharismaAuthError,
|
|
122
|
+
CharismaReadError,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# project_id MUST be the project UUID (NOT the write-path projectAlias).
|
|
126
|
+
client = CharismaReadClient(
|
|
127
|
+
endpoint="https://charisma.company.com",
|
|
128
|
+
token="<service-token>",
|
|
129
|
+
project_id="3f2504e0-4f89-41d3-9a0c-0305e82c3301",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# --- Launches -------------------------------------------------------------
|
|
133
|
+
# list_launches is newest-first (no sort param) and takes up to 9 filters.
|
|
134
|
+
page = client.list_launches(offset=0, limit=25, status="failed", branch="main")
|
|
135
|
+
for launch in page.items: # PaginatedLaunchList -> BaseLaunch items (snake_case attrs)
|
|
136
|
+
print(launch.id, launch.name)
|
|
137
|
+
print("total:", page.total)
|
|
138
|
+
|
|
139
|
+
launch = client.get_launch("8a1b...launch-uuid") # LaunchRecord (snake_case attrs)
|
|
140
|
+
stats = client.get_launch_statistic("8a1b...launch-uuid") # LaunchStatisticRecord -> .counts[]
|
|
141
|
+
|
|
142
|
+
# --- Results of a launch (tags/metadataTags/zephyrIds populated) -----------
|
|
143
|
+
results = client.list_launch_results(
|
|
144
|
+
"8a1b...launch-uuid",
|
|
145
|
+
offset=0,
|
|
146
|
+
limit=25,
|
|
147
|
+
sort_by="startedAt", # name|status|startedAt|createdAt|durationMs
|
|
148
|
+
sort_order="desc", # asc|desc
|
|
149
|
+
status="failed",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# --- A single result (tags/metadataTags/zephyrIds are always []) -----------
|
|
153
|
+
result = client.get_result("c4d5...result-uuid") # TestResultRecord
|
|
154
|
+
execution = client.get_result_execution("c4d5...result-uuid") # ExecutionRecord
|
|
155
|
+
# execution.id / .status / .steps / .beforeHooks / .afterHooks / .backgroundSteps / .attachments
|
|
156
|
+
|
|
157
|
+
# --- History (TestCaseHistoryRecord) ---------------------------------------
|
|
158
|
+
history = client.get_result_history("c4d5...result-uuid")
|
|
159
|
+
print(history.history_id, history.stability.passed_count)
|
|
160
|
+
|
|
161
|
+
# --- Attachments ----------------------------------------------------------
|
|
162
|
+
atts = client.list_result_attachments("c4d5...result-uuid") # PaginatedAttachments (.items/.total)
|
|
163
|
+
url = client.get_attachment_url("e6f7...attachment-uuid") # AttachmentUrl (.download_url/.file_name/.content_type)
|
|
164
|
+
content = client.get_attachment_content("e6f7...attachment-uuid") # AttachmentContent
|
|
165
|
+
# content.content / content.file_name / content.truncated — text-only, truncated=True past the 1 MB cap
|
|
166
|
+
|
|
167
|
+
# --- Paginate any {items, total} endpoint ----------------------------------
|
|
168
|
+
# iter_pages drives offset/limit for you, yielding every item once (page_size
|
|
169
|
+
# defaults to 100, the server cap). Pass the list method plus its kwargs.
|
|
170
|
+
for launch in client.iter_pages(client.list_launches, status="failed"):
|
|
171
|
+
print(launch.id)
|
|
172
|
+
|
|
173
|
+
for row in client.iter_pages(
|
|
174
|
+
client.list_launch_results, launch_id="8a1b...launch-uuid", status="failed"
|
|
175
|
+
):
|
|
176
|
+
print(row.name, row.status)
|
|
177
|
+
|
|
178
|
+
client.close() # or use the client as a context manager: `with CharismaReadClient(...) as client:`
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
All methods return **typed Pydantic models** (from `charisma_cli.read_models`) parsed from the server JSON — e.g. `list_launches` -> `PaginatedLaunchList`, `get_launch` -> `LaunchRecord`, `get_result_history` -> `TestCaseHistoryRecord`. **Model attributes are uniform `snake_case`** (`launch.started_at`, `result.duration_ms`, `history.history_id`), consistent with the rest of the package. The server's wire casing is handled by Pydantic aliases, not the attribute names: `model_validate` accepts the server body as-is, and `model_dump(by_alias=True)` / `model_dump_json(by_alias=True)` reproduce the exact server JSON (camelCase for most endpoints, snake_case for history). `model_dump()` without `by_alias` gives you snake_case. Datetime fields parse to `datetime`; models set `extra="allow"` so a new server field is preserved. Failures raise a typed error:
|
|
182
|
+
|
|
183
|
+
| Error | Raised on |
|
|
184
|
+
| ----- | --------- |
|
|
185
|
+
| `CharismaNotFoundError` | HTTP 404 — the launch/result/attachment does not exist |
|
|
186
|
+
| `CharismaAuthError` | HTTP 401/403 — token missing, invalid, or lacking access |
|
|
187
|
+
| `CharismaReadError` | base error — a 400 (e.g. an invalid sort value), a persistent 5xx after retries, or a transport failure. `CharismaProjectIdError` (non-UUID `project_id`) also subclasses it |
|
|
188
|
+
|
|
189
|
+
Transient failures (429/5xx/timeouts) are retried with backoff before surfacing as
|
|
190
|
+
`CharismaReadError`; terminal statuses (400/401/403/404) are raised immediately, not retried.
|
|
191
|
+
|
|
192
|
+
### Attributes are snake_case; the wire is reproduced via aliases
|
|
193
|
+
|
|
194
|
+
Every model exposes **uniform `snake_case` attributes** — there is no per-endpoint attribute casing to remember. The server's on-the-wire casing still differs per endpoint (camelCase for launches/results/attachments, snake_case for `get_result_history`), but that lives in the Pydantic **aliases**, not the attribute names. So you always read `launch.source_url`, `result.duration_ms`, `history.history_id`; and if you need the exact server JSON back, dump with `by_alias=True`. This keeps the Python API consistent while the client stays a faithful mirror of the Phase-1 wire contract.
|
|
195
|
+
|
|
196
|
+
### Attachment endpoints and the token type
|
|
197
|
+
|
|
198
|
+
`get_attachment_url` and `get_attachment_content` require a token the Attachments Lambda
|
|
199
|
+
accepts. These two endpoints were historically **user-token-only** (`allowed_token_types=["user"]`),
|
|
200
|
+
which rejected the CLI's service token. They have been widened to accept **both user and service
|
|
201
|
+
tokens** (`["user", "service"]`, ADR-035 / FR-0), so a **service token now works** — client-side
|
|
202
|
+
these methods behave exactly like the others, with no token-type special-casing. The `ci` token type
|
|
203
|
+
remains excluded from these two endpoints.
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "charisma-cli"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.5.0"
|
|
9
9
|
description = "CLI tool that watches allure-results and streams test results + attachments to Charisma."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
license = "MIT"
|
|
@@ -27,6 +27,7 @@ classifiers = [
|
|
|
27
27
|
dependencies = [
|
|
28
28
|
"click>=8.0,<9.0",
|
|
29
29
|
"httpx>=0.24,<1.0",
|
|
30
|
+
"pydantic>=2.0,<3.0",
|
|
30
31
|
"watchdog>=3.0,<5.0",
|
|
31
32
|
]
|
|
32
33
|
|
|
@@ -45,6 +46,9 @@ packages = ["src/charisma_cli"]
|
|
|
45
46
|
|
|
46
47
|
[tool.pytest.ini_options]
|
|
47
48
|
testpaths = ["tests"]
|
|
49
|
+
markers = [
|
|
50
|
+
"integration: live, token-gated smoke tests against a deployed Charisma env (skipped unless CHARISMA_LIVE_TEST=1 and the live env vars are set).",
|
|
51
|
+
]
|
|
48
52
|
|
|
49
53
|
[tool.ruff]
|
|
50
54
|
line-length = 120
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
# is generated from pyproject.toml [project].version, so keeping this literal
|
|
5
5
|
# equal to pyproject guarantees `--version` reports the published version. The
|
|
6
6
|
# prepare-commit step owns the PATCH bump — do not hand-bump here.
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.5.0"
|
|
@@ -121,6 +121,20 @@ class Config:
|
|
|
121
121
|
resolved_name = (_resolve_env("LAUNCH_NAME") or "").strip() or None
|
|
122
122
|
resolved_tags = [t.strip() for t in (_resolve_env("TAGS") or "").split(",") if t.strip()] or None
|
|
123
123
|
|
|
124
|
+
# Merge Bamboo agent labels into resolved_tags (zero-config, opportunistic).
|
|
125
|
+
# bamboo_agentLabelString is a space-separated list of CI agent labels set
|
|
126
|
+
# automatically by Bamboo. Tokens are deduplicated against any explicit TAGS.
|
|
127
|
+
bamboo_raw = os.environ.get("bamboo_agentLabelString", "").strip()
|
|
128
|
+
if bamboo_raw:
|
|
129
|
+
base = list(resolved_tags or [])
|
|
130
|
+
seen: set[str] = set(base)
|
|
131
|
+
for label in bamboo_raw.split():
|
|
132
|
+
label = label.strip()
|
|
133
|
+
if label and label not in seen:
|
|
134
|
+
base.append(label)
|
|
135
|
+
seen.add(label)
|
|
136
|
+
resolved_tags = base or None
|
|
137
|
+
|
|
124
138
|
if not resolved_endpoint:
|
|
125
139
|
raise click.UsageError(
|
|
126
140
|
"Missing required configuration: endpoint. Set CHARISMA_ENDPOINT or pass --endpoint."
|