charisma-cli 0.4.0__tar.gz → 0.5.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.
Files changed (40) hide show
  1. charisma_cli-0.5.1/PKG-INFO +229 -0
  2. charisma_cli-0.5.1/README.md +203 -0
  3. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/pyproject.toml +5 -1
  4. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/__init__.py +1 -1
  5. charisma_cli-0.5.1/src/charisma_cli/read_client.py +636 -0
  6. charisma_cli-0.5.1/src/charisma_cli/read_models.py +311 -0
  7. charisma_cli-0.5.1/tests/test_read_client.py +371 -0
  8. charisma_cli-0.5.1/tests/test_read_client_attachments.py +459 -0
  9. charisma_cli-0.5.1/tests/test_read_client_integration.py +340 -0
  10. charisma_cli-0.5.1/tests/test_read_client_launches.py +446 -0
  11. charisma_cli-0.5.1/tests/test_read_client_pagination.py +318 -0
  12. charisma_cli-0.5.1/tests/test_read_client_results.py +462 -0
  13. charisma_cli-0.5.1/tests/test_read_client_single_result.py +550 -0
  14. charisma_cli-0.4.0/PKG-INFO +0 -121
  15. charisma_cli-0.4.0/README.md +0 -96
  16. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/.gitignore +0 -0
  17. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/config.py +0 -0
  18. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/launch_url.py +0 -0
  19. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/main.py +0 -0
  20. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/models.py +0 -0
  21. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/parser.py +0 -0
  22. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/retry.py +0 -0
  23. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/subprocess_mgr.py +0 -0
  24. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/uploader.py +0 -0
  25. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/src/charisma_cli/watcher.py +0 -0
  26. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/__init__.py +0 -0
  27. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/conftest.py +0 -0
  28. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_bamboo_labels.py +0 -0
  29. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_cli.py +0 -0
  30. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_config.py +0 -0
  31. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_integration.py +0 -0
  32. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_launch_url.py +0 -0
  33. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_models.py +0 -0
  34. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_open_launch_payload.py +0 -0
  35. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_parser.py +0 -0
  36. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_retry.py +0 -0
  37. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_silent_mode.py +0 -0
  38. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_subprocess_mgr.py +0 -0
  39. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_uploader.py +0 -0
  40. {charisma_cli-0.4.0 → charisma_cli-0.5.1}/tests/test_watcher.py +0 -0
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.5
2
+ Name: charisma-cli
3
+ Version: 0.5.1
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.4.0"
8
+ version = "0.5.1"
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.4.0"
7
+ __version__ = "0.5.1"