argospy 0.2.2__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.
- argospy-0.2.2/.dockerignore +7 -0
- argospy-0.2.2/.github/workflows/ci.yml +42 -0
- argospy-0.2.2/.github/workflows/release.yml +105 -0
- argospy-0.2.2/.gitignore +19 -0
- argospy-0.2.2/Dockerfile +25 -0
- argospy-0.2.2/PKG-INFO +28 -0
- argospy-0.2.2/README.md +17 -0
- argospy-0.2.2/RELEASING.md +33 -0
- argospy-0.2.2/argos/case.py +166 -0
- argospy-0.2.2/argos/cli.py +351 -0
- argospy-0.2.2/argos/client.py +111 -0
- argospy-0.2.2/argos/duration.py +32 -0
- argospy-0.2.2/argos/pack.py +59 -0
- argospy-0.2.2/argos/paths.py +9 -0
- argospy-0.2.2/argos/report.py +127 -0
- argospy-0.2.2/argos/runner.py +156 -0
- argospy-0.2.2/argos/secrets.py +49 -0
- argospy-0.2.2/argos/select.py +33 -0
- argospy-0.2.2/argos/suite.py +97 -0
- argospy-0.2.2/argos/term.py +164 -0
- argospy-0.2.2/chart/Chart.yaml +6 -0
- argospy-0.2.2/chart/templates/deployment.yaml +95 -0
- argospy-0.2.2/chart/templates/ingress.yaml +39 -0
- argospy-0.2.2/chart/templates/secret.yaml +12 -0
- argospy-0.2.2/chart/values-office.yaml +22 -0
- argospy-0.2.2/chart/values.yaml +22 -0
- argospy-0.2.2/dash/alembic/env.py +63 -0
- argospy-0.2.2/dash/alembic/script.py.mako +25 -0
- argospy-0.2.2/dash/alembic/versions/20260914_0001_baseline.py +78 -0
- argospy-0.2.2/dash/alembic.ini +38 -0
- argospy-0.2.2/dash/analyze.py +337 -0
- argospy-0.2.2/dash/auth.py +73 -0
- argospy-0.2.2/dash/config.py +49 -0
- argospy-0.2.2/dash/database.py +18 -0
- argospy-0.2.2/dash/entrypoint.sh +5 -0
- argospy-0.2.2/dash/main.py +69 -0
- argospy-0.2.2/dash/models.py +74 -0
- argospy-0.2.2/dash/pyproject.toml +20 -0
- argospy-0.2.2/dash/routers/auth.py +34 -0
- argospy-0.2.2/dash/routers/comments.py +61 -0
- argospy-0.2.2/dash/routers/runs.py +361 -0
- argospy-0.2.2/dash/skill.md +74 -0
- argospy-0.2.2/dash/skill.py +36 -0
- argospy-0.2.2/dash/ui/components.json +21 -0
- argospy-0.2.2/dash/ui/index.html +12 -0
- argospy-0.2.2/dash/ui/package-lock.json +7096 -0
- argospy-0.2.2/dash/ui/package.json +37 -0
- argospy-0.2.2/dash/ui/src/App.tsx +29 -0
- argospy-0.2.2/dash/ui/src/components/field.tsx +15 -0
- argospy-0.2.2/dash/ui/src/components/iter-strip.tsx +24 -0
- argospy-0.2.2/dash/ui/src/components/pass-bar.tsx +18 -0
- argospy-0.2.2/dash/ui/src/components/spark.tsx +27 -0
- argospy-0.2.2/dash/ui/src/components/ui/badge.tsx +31 -0
- argospy-0.2.2/dash/ui/src/components/ui/button.tsx +32 -0
- argospy-0.2.2/dash/ui/src/components/ui/card.tsx +12 -0
- argospy-0.2.2/dash/ui/src/components/ui/input.tsx +18 -0
- argospy-0.2.2/dash/ui/src/components/ui/textarea.tsx +16 -0
- argospy-0.2.2/dash/ui/src/index.css +75 -0
- argospy-0.2.2/dash/ui/src/lib/api.ts +163 -0
- argospy-0.2.2/dash/ui/src/lib/fmt.ts +32 -0
- argospy-0.2.2/dash/ui/src/lib/i18n.ts +165 -0
- argospy-0.2.2/dash/ui/src/lib/plan.ts +68 -0
- argospy-0.2.2/dash/ui/src/lib/status.ts +13 -0
- argospy-0.2.2/dash/ui/src/lib/utils.ts +1 -0
- argospy-0.2.2/dash/ui/src/main.tsx +11 -0
- argospy-0.2.2/dash/ui/src/pages/cases.tsx +116 -0
- argospy-0.2.2/dash/ui/src/pages/home.tsx +298 -0
- argospy-0.2.2/dash/ui/src/pages/login.tsx +48 -0
- argospy-0.2.2/dash/ui/src/pages/plan.tsx +133 -0
- argospy-0.2.2/dash/ui/src/pages/run-detail.tsx +356 -0
- argospy-0.2.2/dash/ui/src/pages/runs.tsx +108 -0
- argospy-0.2.2/dash/ui/src/pages/shell.tsx +83 -0
- argospy-0.2.2/dash/ui/src/pages/skill.tsx +44 -0
- argospy-0.2.2/dash/ui/src/vite-env.d.ts +1 -0
- argospy-0.2.2/dash/ui/tsconfig.app.json +23 -0
- argospy-0.2.2/dash/ui/tsconfig.json +8 -0
- argospy-0.2.2/dash/ui/tsconfig.node.json +17 -0
- argospy-0.2.2/dash/ui/vite.config.ts +26 -0
- argospy-0.2.2/pyproject.toml +23 -0
- argospy-0.2.2/scripts/dash-release.sh +61 -0
- argospy-0.2.2/scripts/release.sh +68 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
python:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
with:
|
|
17
|
+
persist-credentials: false
|
|
18
|
+
- uses: astral-sh/setup-uv@v10.1.0
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- run: uv build
|
|
22
|
+
- run: |
|
|
23
|
+
uv venv
|
|
24
|
+
uv pip install dist/*.whl
|
|
25
|
+
.venv/bin/argos --help
|
|
26
|
+
|
|
27
|
+
dash-ui:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
defaults:
|
|
30
|
+
run:
|
|
31
|
+
working-directory: dash/ui
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v7
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- uses: actions/setup-node@v7
|
|
37
|
+
with:
|
|
38
|
+
node-version: 22
|
|
39
|
+
cache: npm
|
|
40
|
+
cache-dependency-path: dash/ui/package-lock.json
|
|
41
|
+
- run: npm ci
|
|
42
|
+
- run: npm run build
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["[0-9]+.[0-9]+.[0-9]+"]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: release-${{ github.ref }}
|
|
9
|
+
cancel-in-progress: false
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
check:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v7
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Check tag matches package versions
|
|
25
|
+
run: |
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
TAG="${GITHUB_REF_NAME}"
|
|
28
|
+
PY_VER=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
29
|
+
DASH_VER=$(python3 -c "import tomllib; print(tomllib.load(open('dash/pyproject.toml','rb'))['project']['version'])")
|
|
30
|
+
CHART_VER=$(python3 -c "import pathlib,re; t=pathlib.Path('chart/Chart.yaml').read_text(); print(re.search(r'(?m)^version:\\s*(\\S+)', t).group(1))")
|
|
31
|
+
APP_VER=$(python3 -c "import pathlib,re; t=pathlib.Path('chart/Chart.yaml').read_text(); print(re.search(r'(?m)^appVersion:\\s*[\"\\']?([^\"\\']+)', t).group(1))")
|
|
32
|
+
echo "tag=$TAG py=$PY_VER dash=$DASH_VER chart=$CHART_VER app=$APP_VER"
|
|
33
|
+
if [ "$TAG" != "$PY_VER" ] || [ "$TAG" != "$DASH_VER" ] || [ "$TAG" != "$CHART_VER" ] || [ "$TAG" != "$APP_VER" ]; then
|
|
34
|
+
echo "::error::Tag $TAG must match pyproject.toml, dash/pyproject.toml, and chart/Chart.yaml"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
- uses: astral-sh/setup-uv@v10.1.0
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.12"
|
|
41
|
+
enable-cache: true
|
|
42
|
+
|
|
43
|
+
- name: Build wheel
|
|
44
|
+
run: uv build
|
|
45
|
+
|
|
46
|
+
- uses: actions/upload-artifact@v7
|
|
47
|
+
with:
|
|
48
|
+
name: python-dist
|
|
49
|
+
path: dist/
|
|
50
|
+
if-no-files-found: error
|
|
51
|
+
|
|
52
|
+
publish-pypi:
|
|
53
|
+
needs: check
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
environment:
|
|
56
|
+
name: pypi
|
|
57
|
+
url: https://pypi.org/project/argospy/
|
|
58
|
+
permissions:
|
|
59
|
+
id-token: write
|
|
60
|
+
contents: read
|
|
61
|
+
actions: read
|
|
62
|
+
steps:
|
|
63
|
+
- uses: astral-sh/setup-uv@v10.1.0
|
|
64
|
+
with:
|
|
65
|
+
enable-cache: false
|
|
66
|
+
ignore-empty-workdir: true
|
|
67
|
+
|
|
68
|
+
- uses: actions/download-artifact@v8
|
|
69
|
+
with:
|
|
70
|
+
name: python-dist
|
|
71
|
+
path: dist/
|
|
72
|
+
|
|
73
|
+
- name: Publish to PyPI
|
|
74
|
+
run: uv publish --trusted-publishing always
|
|
75
|
+
|
|
76
|
+
deploy-dash:
|
|
77
|
+
needs: check
|
|
78
|
+
runs-on: [self-hosted, buildof]
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/checkout@v7
|
|
81
|
+
with:
|
|
82
|
+
persist-credentials: false
|
|
83
|
+
- name: Build image and helm office
|
|
84
|
+
env:
|
|
85
|
+
TAG: ${{ github.ref_name }}
|
|
86
|
+
run: bash scripts/dash-release.sh
|
|
87
|
+
|
|
88
|
+
github-release:
|
|
89
|
+
needs: [publish-pypi, deploy-dash]
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
permissions:
|
|
92
|
+
contents: write
|
|
93
|
+
actions: read
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/download-artifact@v8
|
|
96
|
+
with:
|
|
97
|
+
name: python-dist
|
|
98
|
+
path: dist/
|
|
99
|
+
- name: Create GitHub Release
|
|
100
|
+
uses: softprops/action-gh-release@v2
|
|
101
|
+
with:
|
|
102
|
+
generate_release_notes: true
|
|
103
|
+
files: |
|
|
104
|
+
dist/*.whl
|
|
105
|
+
dist/*.tar.gz
|
argospy-0.2.2/.gitignore
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
out/
|
|
2
|
+
dash/ui/node_modules/
|
|
3
|
+
dash/ui/dist/
|
|
4
|
+
.venv/
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.python-version
|
|
12
|
+
.DS_Store
|
|
13
|
+
.env
|
|
14
|
+
.env.*
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
argospy-0.2.2/Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
ARG NODE_IMAGE=harbor.saidc/base/saidc-node:22-pnpm11.12.0
|
|
2
|
+
ARG BASE_IMAGE=harbor.saidc/base/saidc-uv:0.12.0
|
|
3
|
+
|
|
4
|
+
FROM ${NODE_IMAGE} AS ui
|
|
5
|
+
WORKDIR /ui
|
|
6
|
+
COPY dash/ui/package.json dash/ui/package-lock.json* ./
|
|
7
|
+
RUN --mount=type=cache,target=/root/.npm npm install
|
|
8
|
+
COPY dash/ui/ ./
|
|
9
|
+
RUN npm run build
|
|
10
|
+
|
|
11
|
+
FROM ${BASE_IMAGE}
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
COPY dash/pyproject.toml dash/alembic.ini dash/entrypoint.sh dash/skill.md ./
|
|
14
|
+
COPY dash/alembic ./alembic
|
|
15
|
+
COPY dash/*.py ./
|
|
16
|
+
COPY dash/routers ./routers
|
|
17
|
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
18
|
+
python -c 'import tomllib, pathlib; print("\n".join(tomllib.load(pathlib.Path("pyproject.toml").open("rb"))["project"]["dependencies"]))' \
|
|
19
|
+
| uv pip install --system -r -
|
|
20
|
+
COPY --from=ui /ui/dist ./ui/dist
|
|
21
|
+
RUN chmod +x /app/entrypoint.sh
|
|
22
|
+
ENV PYTHONPATH=/app
|
|
23
|
+
ENV PORT=8080
|
|
24
|
+
EXPOSE 8080
|
|
25
|
+
ENTRYPOINT ["/app/entrypoint.sh"]
|
argospy-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: argospy
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: once / soak test framework
|
|
5
|
+
Project-URL: Homepage, https://github.com/lpythu/argos
|
|
6
|
+
Project-URL: Repository, https://github.com/lpythu/argos
|
|
7
|
+
Project-URL: Issues, https://github.com/lpythu/argos/issues
|
|
8
|
+
Author: Peiyang Li
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# argos
|
|
13
|
+
|
|
14
|
+
once / soak 框架库 + dash。
|
|
15
|
+
|
|
16
|
+
PyPI 项目名是 **`argospy`**(`argos` 已被占用)。安装后命令仍是 `argos`。
|
|
17
|
+
|
|
18
|
+
写 case 的人请去 [argos-pack](https://acahti.saidc.ai/saidc/argos-pack),不要用这个仓。
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install argospy
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- Git:`https://github.com/lpythu/argos`(private)
|
|
25
|
+
- Wheel:tag `X.Y.Z`(不带 `v`)→ PyPI `argospy` + office dash
|
|
26
|
+
- Dash:`https://argos.saidc.ai`
|
|
27
|
+
|
|
28
|
+
CLI 零第三方依赖。`argos-pack` 依赖 `argospy`。
|
argospy-0.2.2/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# argos
|
|
2
|
+
|
|
3
|
+
once / soak 框架库 + dash。
|
|
4
|
+
|
|
5
|
+
PyPI 项目名是 **`argospy`**(`argos` 已被占用)。安装后命令仍是 `argos`。
|
|
6
|
+
|
|
7
|
+
写 case 的人请去 [argos-pack](https://acahti.saidc.ai/saidc/argos-pack),不要用这个仓。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install argospy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- Git:`https://github.com/lpythu/argos`(private)
|
|
14
|
+
- Wheel:tag `X.Y.Z`(不带 `v`)→ PyPI `argospy` + office dash
|
|
15
|
+
- Dash:`https://argos.saidc.ai`
|
|
16
|
+
|
|
17
|
+
CLI 零第三方依赖。`argos-pack` 依赖 `argospy`。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Releasing argos
|
|
2
|
+
|
|
3
|
+
Tag **`X.Y.Z`**(不带 `v`)触发 [`.github/workflows/release.yml`](.github/workflows/release.yml):
|
|
4
|
+
|
|
5
|
+
1. 校验 tag 与 `pyproject.toml` / `chart/Chart.yaml` 一致
|
|
6
|
+
2. `uv build` → PyPI `argospy`(Trusted Publishing)
|
|
7
|
+
3. buildof 打 dash 镜像并 helm 到 office
|
|
8
|
+
|
|
9
|
+
本机不执行 `uv publish` / helm。
|
|
10
|
+
|
|
11
|
+
## Everyday
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
./scripts/release.sh 0.2.1
|
|
15
|
+
git push origin main --tags
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
脚本只改版本、提交、打 annotated tag。推送后由 Actions 发布。
|
|
19
|
+
|
|
20
|
+
## One-time: PyPI Trusted Publishing
|
|
21
|
+
|
|
22
|
+
不在 GitHub 里存长期 token。绑定一次 OIDC:
|
|
23
|
+
|
|
24
|
+
1. 仓库 Environment **`pypi`**(Settings → Environments)
|
|
25
|
+
2. https://pypi.org/manage/account/publishing/ → **Add a new pending publisher**
|
|
26
|
+
- PyPI project name: `argospy`
|
|
27
|
+
- Owner: `lpythu`
|
|
28
|
+
- Repository: `argos`
|
|
29
|
+
- Workflow: `release.yml`
|
|
30
|
+
- Environment name: `pypi`
|
|
31
|
+
3. 第一次成功的 `uv publish` 会创建项目
|
|
32
|
+
|
|
33
|
+
不要在仓库里放 `UV_PUBLISH_TOKEN`。`uv publish --trusted-publishing always` 只走 OIDC,空 token 会和 trusted publishing 冲突。
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
ONCE = "once"
|
|
7
|
+
SOAK = "soak"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def case_slug(case_id: str) -> str:
|
|
11
|
+
return case_id.replace(":", "-")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_slug(ids: list[str]) -> str:
|
|
15
|
+
if not ids:
|
|
16
|
+
return "empty"
|
|
17
|
+
if len(ids) == 1:
|
|
18
|
+
return case_slug(ids[0])
|
|
19
|
+
groups = {part.split(":", 1)[0] if ":" in part else part for part in ids}
|
|
20
|
+
if len(groups) == 1:
|
|
21
|
+
return next(iter(groups))
|
|
22
|
+
if len(ids) <= 3:
|
|
23
|
+
return "+".join(case_slug(i) for i in ids)
|
|
24
|
+
return f"{len(ids)}-cases"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class Spec:
|
|
29
|
+
id: str
|
|
30
|
+
title: str
|
|
31
|
+
group: str
|
|
32
|
+
tags: tuple[str, ...] = ()
|
|
33
|
+
modes: tuple[str, ...] = (ONCE,)
|
|
34
|
+
pack: str = ""
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def slug(self) -> str:
|
|
38
|
+
return case_slug(self.id)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class Step:
|
|
43
|
+
name: str
|
|
44
|
+
status: str
|
|
45
|
+
detail: str = ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Result:
|
|
50
|
+
spec: Spec
|
|
51
|
+
status: str
|
|
52
|
+
elapsed_s: float
|
|
53
|
+
error: str = ""
|
|
54
|
+
metrics: dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
steps: list[Step] = field(default_factory=list)
|
|
56
|
+
notes: list[str] = field(default_factory=list)
|
|
57
|
+
dest: str = ""
|
|
58
|
+
iteration: int = 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Skip(Exception):
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Fail(Exception):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Context:
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
spec: Spec,
|
|
73
|
+
dest: Path,
|
|
74
|
+
emit: Callable[[dict], None],
|
|
75
|
+
*,
|
|
76
|
+
iteration: int = 0,
|
|
77
|
+
run_dest: Path | None = None,
|
|
78
|
+
mode: str = ONCE,
|
|
79
|
+
):
|
|
80
|
+
self.spec = spec
|
|
81
|
+
self.dest = dest
|
|
82
|
+
self.dest.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
self.iteration = iteration
|
|
84
|
+
self.run_dest = run_dest or dest.parent
|
|
85
|
+
self.mode = mode
|
|
86
|
+
self.metrics: dict[str, Any] = {}
|
|
87
|
+
self.steps: list[Step] = []
|
|
88
|
+
self.notes: list[str] = []
|
|
89
|
+
self._emit = emit
|
|
90
|
+
self.write(
|
|
91
|
+
"case.json",
|
|
92
|
+
{
|
|
93
|
+
"id": spec.id,
|
|
94
|
+
"slug": spec.slug,
|
|
95
|
+
"title": spec.title,
|
|
96
|
+
"group": spec.group,
|
|
97
|
+
"pack": spec.pack,
|
|
98
|
+
"tags": list(spec.tags),
|
|
99
|
+
"modes": list(spec.modes),
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def metric(self, key: str, value: Any) -> None:
|
|
104
|
+
self.metrics[key] = value
|
|
105
|
+
self._dump()
|
|
106
|
+
self._emit({"type": "metric", "id": self.spec.id, "key": key, "value": value})
|
|
107
|
+
|
|
108
|
+
def note(self, message: str) -> None:
|
|
109
|
+
self.notes.append(message)
|
|
110
|
+
self._emit({"type": "note", "id": self.spec.id, "message": message})
|
|
111
|
+
|
|
112
|
+
def step(self, name: str, status: str = "ok", detail: str = "") -> None:
|
|
113
|
+
row = Step(name=name, status=status, detail=detail)
|
|
114
|
+
for i, existing in enumerate(self.steps):
|
|
115
|
+
if existing.name == name:
|
|
116
|
+
self.steps[i] = row
|
|
117
|
+
break
|
|
118
|
+
else:
|
|
119
|
+
self.steps.append(row)
|
|
120
|
+
self._dump()
|
|
121
|
+
self._emit({"type": "step", "id": self.spec.id, "name": name, "status": status, "detail": detail})
|
|
122
|
+
|
|
123
|
+
def check(self, ok: bool, message: str) -> None:
|
|
124
|
+
if not ok:
|
|
125
|
+
raise Fail(message)
|
|
126
|
+
|
|
127
|
+
def skip(self, message: str) -> None:
|
|
128
|
+
raise Skip(message)
|
|
129
|
+
|
|
130
|
+
def fail(self, message: str) -> None:
|
|
131
|
+
raise Fail(message)
|
|
132
|
+
|
|
133
|
+
def own(self, resource_id: str, catalog_id: str = "") -> None:
|
|
134
|
+
path = self.run_dest / "owned.json"
|
|
135
|
+
rows: list[dict[str, str]] = []
|
|
136
|
+
if path.exists():
|
|
137
|
+
raw = json.loads(path.read_text())
|
|
138
|
+
if isinstance(raw, list):
|
|
139
|
+
rows = [row for row in raw if isinstance(row, dict)]
|
|
140
|
+
if any(row.get("id") == resource_id for row in rows):
|
|
141
|
+
return
|
|
142
|
+
rows.append({"id": resource_id, "catalog_id": catalog_id})
|
|
143
|
+
path.write_text(json.dumps(rows, indent=2, ensure_ascii=False) + "\n")
|
|
144
|
+
|
|
145
|
+
def write(self, name: str, obj: Any) -> Path:
|
|
146
|
+
path = self.dest / name
|
|
147
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
148
|
+
path.write_bytes(obj)
|
|
149
|
+
elif isinstance(obj, str):
|
|
150
|
+
path.write_text(obj)
|
|
151
|
+
else:
|
|
152
|
+
path.write_text(json.dumps(obj, indent=2, ensure_ascii=False) + "\n")
|
|
153
|
+
return path
|
|
154
|
+
|
|
155
|
+
def _dump(self) -> None:
|
|
156
|
+
self.write(
|
|
157
|
+
"progress.json",
|
|
158
|
+
{
|
|
159
|
+
"id": self.spec.id,
|
|
160
|
+
"slug": self.spec.slug,
|
|
161
|
+
"title": self.spec.title,
|
|
162
|
+
"metrics": self.metrics,
|
|
163
|
+
"steps": [s.__dict__ for s in self.steps],
|
|
164
|
+
"notes": self.notes,
|
|
165
|
+
},
|
|
166
|
+
)
|