pytest-allure-host 0.1.1__py3-none-any.whl

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.
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import mimetypes
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ def default_run_id() -> str:
9
+ from datetime import datetime
10
+
11
+ return datetime.utcnow().strftime("%Y%m%d-%H%M%S")
12
+
13
+
14
+ def build_s3_keys(
15
+ prefix: str,
16
+ project: str,
17
+ branch: str,
18
+ run_id: str,
19
+ ) -> dict[str, str]:
20
+ base = f"{prefix.rstrip('/')}/{project}/{branch}"
21
+ return {"run": f"{base}/{run_id}/", "latest": f"{base}/latest/"}
22
+
23
+
24
+ def branch_root(prefix: str, project: str, branch: str) -> str:
25
+ return f"{prefix.rstrip('/')}/{project}/{branch}"
26
+
27
+
28
+ def cache_control_for_key(key: str) -> str:
29
+ if key.endswith("index.html"):
30
+ return "no-cache"
31
+ # widgets optional no-cache could be configurable later
32
+ if "/widgets/" in key:
33
+ return "no-cache"
34
+ return "public, max-age=31536000, immutable"
35
+
36
+
37
+ def guess_content_type(path: Path) -> str | None:
38
+ ctype, _ = mimetypes.guess_type(str(path))
39
+ # Common overrides if needed later
40
+ return ctype or None
41
+
42
+
43
+ @dataclass
44
+ class PublishConfig:
45
+ bucket: str
46
+ prefix: str
47
+ project: str
48
+ branch: str
49
+ run_id: str
50
+ cloudfront_domain: str | None = None
51
+ ttl_days: int | None = None
52
+ max_keep_runs: int | None = None
53
+ s3_endpoint: str | None = None # custom S3 endpoint (e.g. LocalStack)
54
+ # optional link to change context (e.g. Jira ticket / work item)
55
+ context_url: str | None = None
56
+
57
+ @property
58
+ def s3_run_prefix(self) -> str:
59
+ keys = build_s3_keys(
60
+ self.prefix,
61
+ self.project,
62
+ self.branch,
63
+ self.run_id,
64
+ )
65
+ return keys["run"]
66
+
67
+ @property
68
+ def s3_latest_prefix(self) -> str:
69
+ keys = build_s3_keys(
70
+ self.prefix,
71
+ self.project,
72
+ self.branch,
73
+ self.run_id,
74
+ )
75
+ return keys["latest"]
76
+
77
+ def url_run(self) -> str | None:
78
+ if not self.cloudfront_domain:
79
+ return None
80
+ base = (
81
+ f"{self.cloudfront_domain.rstrip('/')}/{self.prefix}/"
82
+ f"{self.project}/{self.branch}/{self.run_id}/"
83
+ )
84
+ return base
85
+
86
+ def url_latest(self) -> str | None:
87
+ if not self.cloudfront_domain:
88
+ return None
89
+ base = (
90
+ f"{self.cloudfront_domain.rstrip('/')}/{self.prefix}/"
91
+ f"{self.project}/{self.branch}/latest/"
92
+ )
93
+ return base
94
+
95
+
96
+ def compute_dir_size(path: Path) -> int:
97
+ total = 0
98
+ for p in path.rglob("*"):
99
+ if p.is_file():
100
+ total += p.stat().st_size
101
+ return total
102
+
103
+
104
+ def merge_manifest(existing: dict | None, new_entry: dict) -> dict:
105
+ base = existing or {
106
+ "schema": 1,
107
+ "project": new_entry.get("project"),
108
+ "branch": new_entry.get("branch"),
109
+ "updated": 0,
110
+ "runs": [],
111
+ }
112
+ runs = [r for r in base.get("runs", []) if r.get("run_id") != new_entry["run_id"]]
113
+ runs.append(new_entry)
114
+ runs.sort(key=lambda r: r.get("time", 0), reverse=True)
115
+ base["runs"] = runs
116
+ from time import time
117
+
118
+ base["updated"] = int(time())
119
+ return base
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-allure-host
3
+ Version: 0.1.1
4
+ Summary: Publish Allure static reports to private S3 behind CloudFront with history preservation
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: allure,pytest,aws,s3,cloudfront,reporting
8
+ Author: Allure Hosting Maintainers
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Framework :: Pytest
19
+ Classifier: Development Status :: 3 - Alpha
20
+ Classifier: Operating System :: OS Independent
21
+ Requires-Dist: PyYAML (>=6,<7)
22
+ Requires-Dist: boto3 (>=1.28,<2.0)
23
+ Project-URL: Bug Tracker, https://github.com/darrenrabbs/allurehosting/issues
24
+ Project-URL: Changelog, https://github.com/darrenrabbs/allurehosting/releases
25
+ Project-URL: Documentation, https://github.com/darrenrabbs/allurehosting#readme
26
+ Project-URL: Homepage, https://github.com/darrenrabbs/allurehosting
27
+ Project-URL: Repository, https://github.com/darrenrabbs/allurehosting
28
+ Description-Content-Type: text/markdown
29
+
30
+ # pytest-allure-host
31
+
32
+ ![CI](https://github.com/darrenrabbs/allurehosting/actions/workflows/ci.yml/badge.svg)
33
+ ![CodeQL](https://github.com/darrenrabbs/allurehosting/actions/workflows/codeql.yml/badge.svg)
34
+ ![PyPI - Version](https://img.shields.io/pypi/v/pytest-allure-host.svg)
35
+ ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
36
+
37
+ Publish Allure static reports to private S3 behind CloudFront with history preservation and SPA-friendly routing.
38
+
39
+ See `docs/architecture.md` and `.github/copilot-instructions.md` for architecture and design constraints.
40
+
41
+ ## Features
42
+
43
+ - Generate Allure static report from `allure-results`
44
+ - Preserve history by pulling `latest/history` before generation
45
+ - Upload to S3 under `<project>/<branch>/<run_id>/` and update `<project>/<branch>/latest/`
46
+ - Two-phase latest update: upload to `latest_tmp/`, swap into `latest/`, write `LATEST_READY` marker
47
+ - Optional retention: keep only N newest runs (`--max-keep-runs`)
48
+ - Correct caching headers: `index.html` and `widgets/` → `no-cache`; assets → immutable
49
+ - Optional TTL tagging on objects (`--ttl-days`) for S3 lifecycle policies
50
+ - Optional summary JSON output for CI
51
+ - Best-effort manifest at `runs/index.json` with new run metadata
52
+ - Lightweight pointer file `latest.json` (branch root)
53
+ - Human-friendly HTML index at `runs/index.html` for navigating past runs
54
+ - Columns: Run ID, raw epoch, UTC Time (human readable), Size (pretty units), P/F/B (passed/failed/broken counts), links to the immutable run and the moving latest
55
+ - Newest run highlighted with a star (★) and soft background
56
+
57
+ ## Requirements
58
+
59
+ - Python 3.9+
60
+ - AWS credentials with S3 access to the target bucket
61
+ - Allure commandline available on PATH (e.g., via Allure CLI or allure-pytest)
62
+
63
+ ## S3 layout and caching
64
+
65
+ - Keys:
66
+ - `s3://<bucket>/<prefix>/<project>/<branch>/<run_id>/...`
67
+ - `s3://<bucket>/<prefix>/<project>/<branch>/latest/...`
68
+ - `s3://<bucket>/<prefix>/<project>/<branch>/runs/index.json` (manifest)
69
+ - `s3://<bucket>/<prefix>/<project>/<branch>/runs/index.html` (HTML index)
70
+ - `s3://<bucket>/<prefix>/<project>/<branch>/latest.json` (pointer)
71
+ - Two-phase swap writes a `LATEST_READY` marker file under `latest/` when ready.
72
+ - Cache-Control:
73
+ - `index.html`: `no-cache`
74
+ - files under `widgets/`: `no-cache`
75
+ - everything else: `public, max-age=31536000, immutable`
76
+
77
+ ## CLI usage
78
+
79
+ Install locally for development:
80
+
81
+ ```bash
82
+ pip install -e .[dev]
83
+ ```
84
+
85
+ Run the publisher after tests generate `allure-results`:
86
+
87
+ ```bash
88
+ publish-allure \
89
+ --bucket your-bucket \
90
+ --prefix reports \
91
+ --project demo \
92
+ --branch main \
93
+ --cloudfront https://reports.example.com \
94
+ --ttl-days 30 \
95
+ --max-keep-runs 10
96
+ ```
97
+
98
+ Flags (CLI):
99
+
100
+ - `--bucket` (required): S3 bucket name
101
+ - `--prefix` (default: `reports`): Root prefix
102
+ - `--project` (required): Project name
103
+ - `--branch` (default: `$GIT_BRANCH` or `main`)
104
+ - `--run-id` (default: `$ALLURE_RUN_ID` or `YYYYMMDD-HHMMSS`)
105
+ - `--cloudfront` (optional; default: `$ALLURE_CLOUDFRONT`)
106
+ - `--results` (default: `allure-results`): Input directory
107
+ - `--report` (default: `allure-report`): Output directory
108
+ - `--ttl-days` (optional): Add `ttl-days=<N>` object tag
109
+ - `--max-keep-runs` (optional): Keep N newest run prefixes, delete older
110
+ - `--summary-json <path>`: Write machine-readable summary
111
+ - `--check`: Preflight validation (AWS access, allure, inputs)
112
+ - `--dry-run`: Print planned prefixes and sample headers, no upload
113
+ - `--s3-endpoint`: Custom S3 endpoint (e.g. `http://localhost:4566` for LocalStack)
114
+
115
+ Environment fallbacks:
116
+
117
+ - `GIT_BRANCH` → `--branch` default
118
+ - `ALLURE_RUN_ID` → `--run-id` default
119
+ - `ALLURE_CLOUDFRONT` → `--cloudfront` default
120
+ - `ALLURE_S3_ENDPOINT` → `--s3-endpoint` default (LocalStack / custom S3)
121
+
122
+ ## Pytest plugin usage
123
+
124
+ Run tests and publish during terminal summary:
125
+
126
+ ```bash
127
+ pytest \
128
+ --allure-bucket your-bucket \
129
+ --allure-prefix reports \
130
+ --allure-project demo \
131
+ --allure-branch main \
132
+ --allure-cloudfront https://reports.example.com \
133
+ --allure-ttl-days 30 \
134
+ --allure-max-keep-runs 10
135
+ ```
136
+
137
+ Flags (pytest):
138
+
139
+ - `--allure-bucket` (required)
140
+ - `--allure-prefix` (default: `reports`)
141
+ - `--allure-project` (required)
142
+ - `--allure-branch` (default: `$GIT_BRANCH` or `main`)
143
+ - `--allure-run-id` (default: `$ALLURE_RUN_ID` or `YYYYMMDD-HHMMSS`)
144
+ - `--allure-cloudfront` (optional; default: `$ALLURE_CLOUDFRONT`)
145
+ - `--allure-ttl-days` (optional)
146
+ - `--allure-max-keep-runs` (optional)
147
+ - `--allure-summary-json <path>` (optional)
148
+ - `--allure-check` / `--allure-dry-run` (optional)
149
+
150
+ ## Preflight and dry-run
151
+
152
+ - `--check`/`--allure-check` verifies:
153
+ - S3 bucket reachability (HeadBucket/List)
154
+ - `allure-results` exists and is non-empty
155
+ - Allure CLI exists on PATH
156
+ - `--dry-run`/`--allure-dry-run` prints planned prefixes and sample headers; no uploads occur.
157
+
158
+ ## Outputs
159
+
160
+ - S3 prefixes: run and latest
161
+ - Optional CDN URLs (if `--cloudfront` provided)
162
+ - `runs/index.json` manifest updated with new run entry
163
+ - `runs/index.html` HTML table of recent runs (newest first) with columns: Run ID, Epoch, UTC Time, Size, P/F/B, Run, Latest (newest row highlighted with ★)
164
+ - `latest.json` pointer to current run (simple machine-readable metadata)
165
+ - Optional `--summary-json` with sizes, file counts, and destination URLs
166
+ - `latest/LATEST_READY` marker indicates the swap is complete
167
+
168
+ ## Security
169
+
170
+ See `SECURITY.md` for how to report vulnerabilities. Never open a public issue containing sensitive details.
171
+
172
+ ## Badges / Status
173
+
174
+ - CI: multi-version test matrix + lint/security
175
+ - CodeQL: static code analysis
176
+
177
+ ## Contributing
178
+
179
+ See `CONTRIBUTING.md` and follow the pre-commit hooks (`pre-commit install`).
180
+
181
+ ## Release
182
+
183
+ Tagged versions (`vX.Y.Z`) are published to PyPI automatically via GitHub OIDC.
184
+
185
+ ## CI examples
186
+
187
+ GitHub Actions (CLI):
188
+
189
+ ```yaml
190
+ jobs:
191
+ tests:
192
+ runs-on: ubuntu-latest
193
+ steps:
194
+ - uses: actions/checkout@v4
195
+ - uses: actions/setup-python@v5
196
+ with: { python-version: "3.11" }
197
+ - run: pip install .[dev]
198
+ - run: pytest -q
199
+ - name: Publish Allure
200
+ env:
201
+ AWS_REGION: us-east-1
202
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
203
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
204
+ GIT_BRANCH: ${{ github.ref_name }}
205
+ run: >-
206
+ publish-allure --bucket $BUCKET --prefix reports --project demo
207
+ --branch "$GIT_BRANCH" --cloudfront https://reports.example.com
208
+ --ttl-days 30 --max-keep-runs 10
209
+ ```
210
+
211
+ Pytest-driven (plugin):
212
+
213
+ ```yaml
214
+ - run: pytest -q \
215
+ --allure-bucket $BUCKET \
216
+ --allure-prefix reports \
217
+ --allure-project demo \
218
+ --allure-branch "$GIT_BRANCH" \
219
+ --allure-cloudfront https://reports.example.com \
220
+ --allure-ttl-days 30 \
221
+ --allure-max-keep-runs 10
222
+ ```
223
+
224
+ ## Troubleshooting
225
+
226
+ - Missing Allure binary: ensure the Allure CLI is installed and on PATH.
227
+ - Access denied: verify AWS credentials and bucket IAM for Put/Get/List/Delete.
228
+ - SPA routing 403/404: configure CloudFront error mapping to `/index.html`.
229
+
230
+ ## Development
231
+
232
+ - Install with Poetry: `poetry install`
233
+ - Run tests: `poetry run pytest -q`
234
+ - Lint (security quick): `poetry run bandit -r pytest_allure_host`
235
+ - Unified lint helper (mirrors CI):
236
+ ```bash
237
+ scripts/lint.sh # check mode (ruff lint+format check, bandit, pip-audit)
238
+ scripts/lint.sh --fix # apply ruff fixes + format
239
+ scripts/lint.sh pre-commit # also run pre-commit hooks on all files
240
+ ```
241
+
242
+ ## Quick local trial (macOS)
243
+
244
+ This section walks you through a minimal end-to-end run locally.
245
+
246
+ 1. Prereqs
247
+
248
+ - AWS credentials configured (via `AWS_PROFILE` or access keys); set `AWS_REGION`.
249
+ - Allure CLI installed on PATH:
250
+ ```bash
251
+ brew install allure
252
+ ```
253
+ - Python deps installed:
254
+ ```bash
255
+ poetry install
256
+ # or
257
+ pip install -e .[dev]
258
+ ```
259
+
260
+ 2. Generate Allure results
261
+
262
+ - Create a tiny test (optional example):
263
+ ```bash
264
+ mkdir -p tests
265
+ cat > tests/test_sample.py <<'PY'
266
+ def test_ok():
267
+ assert True
268
+ PY
269
+ ```
270
+ - Run pytest to emit results:
271
+ ```bash
272
+ poetry run pytest --alluredir=allure-results
273
+ ```
274
+
275
+ 3. Preflight and dry-run
276
+
277
+ ```bash
278
+ poetry run publish-allure \
279
+ --bucket <bucket> \
280
+ --prefix reports \
281
+ --project demo \
282
+ --branch $(git rev-parse --abbrev-ref HEAD) \
283
+ --cloudfront https://<cloudfront_domain> \
284
+ --check \
285
+ --dry-run
286
+ ```
287
+
288
+ 4. Publish
289
+
290
+ ```bash
291
+ poetry run publish-allure \
292
+ --bucket <bucket> \
293
+ --prefix reports \
294
+ --project demo \
295
+ --branch $(git rev-parse --abbrev-ref HEAD) \
296
+ --cloudfront https://<cloudfront_domain> \
297
+ --ttl-days 30 \
298
+ --max-keep-runs 5
299
+ ```
300
+
301
+ 5. Verify
302
+
303
+ - S3: `reports/demo/<branch>/<run_id>/...` and `reports/demo/<branch>/latest/` with `LATEST_READY`.
304
+ - CDN: open printed `run_url` / `latest_url`.
305
+
@@ -0,0 +1,12 @@
1
+ pytest_allure_host/__init__.py,sha256=Us3L46Kd1Rh1FM9e74PJB7TN4G37WkM12edi8GnGhgc,130
2
+ pytest_allure_host/__main__.py,sha256=1dzo3_74YYjXLo4xf_OjbmayOAzDdTlIgCVX00tHdoU,99
3
+ pytest_allure_host/cli.py,sha256=spAuHeyykEa7Dnm6eNsommVZI-0ovKZvGnajgySdpYc,3541
4
+ pytest_allure_host/config.py,sha256=2I3qGLU_L6urayLIwGVin3Mp3nLRTyzJ1NzHiNnpAwc,3850
5
+ pytest_allure_host/plugin.py,sha256=zLiu7qbUU-jN1QLglO1bAlX2UQRPiwLtN8JS4ui8kA8,4933
6
+ pytest_allure_host/publisher.py,sha256=bSgE5dULYXSpDoXNEkaDDjcEaVDErcsrMcT6fasbjpo,26437
7
+ pytest_allure_host/utils.py,sha256=0nPzsViqKSV7Wwx9bbMvPVMZBzwD_i439GltyGXKbLQ,3104
8
+ pytest_allure_host-0.1.1.dist-info/METADATA,sha256=g9Yfx_igtu1U4AW8kJOjPTYq_qSpR-f89K6BUQ1kz-k,10044
9
+ pytest_allure_host-0.1.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
10
+ pytest_allure_host-0.1.1.dist-info/entry_points.txt,sha256=PWnSY4aqAumEX4-dc1TkNHfju5VTKPUHfYPv1SdAlIA,119
11
+ pytest_allure_host-0.1.1.dist-info/licenses/LICENSE,sha256=jcHN7r3njxuM4ilSLkK0fuuQAGMMkqzvYL27CYZGnWs,1084
12
+ pytest_allure_host-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ publish-allure=pytest_allure_host.cli:main
3
+
4
+ [pytest11]
5
+ pytest_allure_host=pytest_allure_host.plugin
6
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Allure Hosting Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.