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.
Files changed (81) hide show
  1. argospy-0.2.2/.dockerignore +7 -0
  2. argospy-0.2.2/.github/workflows/ci.yml +42 -0
  3. argospy-0.2.2/.github/workflows/release.yml +105 -0
  4. argospy-0.2.2/.gitignore +19 -0
  5. argospy-0.2.2/Dockerfile +25 -0
  6. argospy-0.2.2/PKG-INFO +28 -0
  7. argospy-0.2.2/README.md +17 -0
  8. argospy-0.2.2/RELEASING.md +33 -0
  9. argospy-0.2.2/argos/case.py +166 -0
  10. argospy-0.2.2/argos/cli.py +351 -0
  11. argospy-0.2.2/argos/client.py +111 -0
  12. argospy-0.2.2/argos/duration.py +32 -0
  13. argospy-0.2.2/argos/pack.py +59 -0
  14. argospy-0.2.2/argos/paths.py +9 -0
  15. argospy-0.2.2/argos/report.py +127 -0
  16. argospy-0.2.2/argos/runner.py +156 -0
  17. argospy-0.2.2/argos/secrets.py +49 -0
  18. argospy-0.2.2/argos/select.py +33 -0
  19. argospy-0.2.2/argos/suite.py +97 -0
  20. argospy-0.2.2/argos/term.py +164 -0
  21. argospy-0.2.2/chart/Chart.yaml +6 -0
  22. argospy-0.2.2/chart/templates/deployment.yaml +95 -0
  23. argospy-0.2.2/chart/templates/ingress.yaml +39 -0
  24. argospy-0.2.2/chart/templates/secret.yaml +12 -0
  25. argospy-0.2.2/chart/values-office.yaml +22 -0
  26. argospy-0.2.2/chart/values.yaml +22 -0
  27. argospy-0.2.2/dash/alembic/env.py +63 -0
  28. argospy-0.2.2/dash/alembic/script.py.mako +25 -0
  29. argospy-0.2.2/dash/alembic/versions/20260914_0001_baseline.py +78 -0
  30. argospy-0.2.2/dash/alembic.ini +38 -0
  31. argospy-0.2.2/dash/analyze.py +337 -0
  32. argospy-0.2.2/dash/auth.py +73 -0
  33. argospy-0.2.2/dash/config.py +49 -0
  34. argospy-0.2.2/dash/database.py +18 -0
  35. argospy-0.2.2/dash/entrypoint.sh +5 -0
  36. argospy-0.2.2/dash/main.py +69 -0
  37. argospy-0.2.2/dash/models.py +74 -0
  38. argospy-0.2.2/dash/pyproject.toml +20 -0
  39. argospy-0.2.2/dash/routers/auth.py +34 -0
  40. argospy-0.2.2/dash/routers/comments.py +61 -0
  41. argospy-0.2.2/dash/routers/runs.py +361 -0
  42. argospy-0.2.2/dash/skill.md +74 -0
  43. argospy-0.2.2/dash/skill.py +36 -0
  44. argospy-0.2.2/dash/ui/components.json +21 -0
  45. argospy-0.2.2/dash/ui/index.html +12 -0
  46. argospy-0.2.2/dash/ui/package-lock.json +7096 -0
  47. argospy-0.2.2/dash/ui/package.json +37 -0
  48. argospy-0.2.2/dash/ui/src/App.tsx +29 -0
  49. argospy-0.2.2/dash/ui/src/components/field.tsx +15 -0
  50. argospy-0.2.2/dash/ui/src/components/iter-strip.tsx +24 -0
  51. argospy-0.2.2/dash/ui/src/components/pass-bar.tsx +18 -0
  52. argospy-0.2.2/dash/ui/src/components/spark.tsx +27 -0
  53. argospy-0.2.2/dash/ui/src/components/ui/badge.tsx +31 -0
  54. argospy-0.2.2/dash/ui/src/components/ui/button.tsx +32 -0
  55. argospy-0.2.2/dash/ui/src/components/ui/card.tsx +12 -0
  56. argospy-0.2.2/dash/ui/src/components/ui/input.tsx +18 -0
  57. argospy-0.2.2/dash/ui/src/components/ui/textarea.tsx +16 -0
  58. argospy-0.2.2/dash/ui/src/index.css +75 -0
  59. argospy-0.2.2/dash/ui/src/lib/api.ts +163 -0
  60. argospy-0.2.2/dash/ui/src/lib/fmt.ts +32 -0
  61. argospy-0.2.2/dash/ui/src/lib/i18n.ts +165 -0
  62. argospy-0.2.2/dash/ui/src/lib/plan.ts +68 -0
  63. argospy-0.2.2/dash/ui/src/lib/status.ts +13 -0
  64. argospy-0.2.2/dash/ui/src/lib/utils.ts +1 -0
  65. argospy-0.2.2/dash/ui/src/main.tsx +11 -0
  66. argospy-0.2.2/dash/ui/src/pages/cases.tsx +116 -0
  67. argospy-0.2.2/dash/ui/src/pages/home.tsx +298 -0
  68. argospy-0.2.2/dash/ui/src/pages/login.tsx +48 -0
  69. argospy-0.2.2/dash/ui/src/pages/plan.tsx +133 -0
  70. argospy-0.2.2/dash/ui/src/pages/run-detail.tsx +356 -0
  71. argospy-0.2.2/dash/ui/src/pages/runs.tsx +108 -0
  72. argospy-0.2.2/dash/ui/src/pages/shell.tsx +83 -0
  73. argospy-0.2.2/dash/ui/src/pages/skill.tsx +44 -0
  74. argospy-0.2.2/dash/ui/src/vite-env.d.ts +1 -0
  75. argospy-0.2.2/dash/ui/tsconfig.app.json +23 -0
  76. argospy-0.2.2/dash/ui/tsconfig.json +8 -0
  77. argospy-0.2.2/dash/ui/tsconfig.node.json +17 -0
  78. argospy-0.2.2/dash/ui/vite.config.ts +26 -0
  79. argospy-0.2.2/pyproject.toml +23 -0
  80. argospy-0.2.2/scripts/dash-release.sh +61 -0
  81. argospy-0.2.2/scripts/release.sh +68 -0
@@ -0,0 +1,7 @@
1
+ **/.venv
2
+ **/__pycache__
3
+ **/node_modules
4
+ **/out
5
+ **/.git
6
+ dash/ui/dist
7
+ *.egg-info
@@ -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
@@ -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/
@@ -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`。
@@ -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
+ )