verifaied 0.0.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.
@@ -0,0 +1,52 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ .venv/
8
+ venv/
9
+ env/
10
+ *.egg-info/
11
+ dist/
12
+ build/
13
+
14
+ # Node
15
+ node_modules/
16
+ frontend/dist/
17
+ frontend/coverage/
18
+
19
+ # IDE
20
+ .idea/
21
+ .vscode/
22
+ *.swp
23
+ *.swo
24
+
25
+ # Environment
26
+ **/.env
27
+ **/.env.*
28
+
29
+ # Docker
30
+ pgdata/
31
+
32
+ # OS
33
+ .DS_Store
34
+ Thumbs.db
35
+
36
+ # Coverage
37
+ .coverage
38
+ coverage.json
39
+ htmlcov/
40
+
41
+ # Keys
42
+ *.pem
43
+
44
+ TODO.txt
45
+
46
+ # Terraform
47
+ **/.terraform/
48
+ *.tfstate
49
+ *.tfstate.backup
50
+ # Auto-loaded tfvars files hold env-specific secrets/IPs (e.g. allowlist.auto.tfvars).
51
+ # Plain terraform.tfvars is checked in — keep it free of sensitive values.
52
+ *.auto.tfvars
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: verifaied
3
+ Version: 0.0.1
4
+ Summary: Upload local pytest coverage to verifAIed for instant feedback
5
+ Project-URL: Homepage, https://pypi.org/project/verifaied/
6
+ Author: Kyle Richards
7
+ License: MIT
8
+ Keywords: ai,coverage,llm,pytest,testing
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Software Development :: Testing
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: httpx>=0.27.0
15
+ Requires-Dist: rich>=13.7.0
16
+ Requires-Dist: typer>=0.12.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # verifaied
20
+
21
+ CLI for uploading local pytest coverage to verifAIed so an LLM (or you) can see what's untested without waiting for CI.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ uv tool install verifaied # or
27
+ pipx install verifaied
28
+ ```
29
+
30
+ ## Configure
31
+
32
+ Set the API token (mint one from the verifAIed app) and, for self-hosted
33
+ or local backends, the API URL:
34
+
35
+ ```bash
36
+ export VERIFAIED_API_TOKEN=vr_live_...
37
+ export VERIFAIED_API_URL=http://localhost:8000 # default
38
+ ```
39
+
40
+ ## Use
41
+
42
+ After a pytest run that emits `coverage.json`:
43
+
44
+ ```bash
45
+ pytest --cov --cov-report=json --junitxml=junit.xml
46
+ verifaied upload --repo <repository-id>
47
+ ```
48
+
49
+ The CLI reads `coverage.json`, pulls the source for every file it
50
+ references from your working tree, and posts everything to
51
+ `/repositories/<id>/local-coverage`. The response prints a summary of
52
+ untested / partial / failing functions so you (or your LLM) can fix
53
+ them on the next iteration.
54
+
55
+ ### Flags
56
+
57
+ - `--repo / -r <UUID>` — repository id (required)
58
+ - `--branch / -b <name>` — branch to attach the upload to (default:
59
+ `git branch --show-current`, then `local`)
60
+ - `--coverage <path>` — path to coverage.json (default: `./coverage.json`)
61
+ - `--junit <path>` — optional JUnit XML for failing-test detail
62
+ - `--commit-sha <sha>` — commit sha for display (default: `git rev-parse HEAD`)
63
+ - `--root <path>` — root the coverage paths are relative to (default: cwd)
64
+ - `--api-url <url>` — backend base URL (overrides `VERIFAIED_API_URL`)
65
+ - `--token <token>` — API token (overrides `VERIFAIED_API_TOKEN`)
@@ -0,0 +1,47 @@
1
+ # verifaied
2
+
3
+ CLI for uploading local pytest coverage to verifAIed so an LLM (or you) can see what's untested without waiting for CI.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv tool install verifaied # or
9
+ pipx install verifaied
10
+ ```
11
+
12
+ ## Configure
13
+
14
+ Set the API token (mint one from the verifAIed app) and, for self-hosted
15
+ or local backends, the API URL:
16
+
17
+ ```bash
18
+ export VERIFAIED_API_TOKEN=vr_live_...
19
+ export VERIFAIED_API_URL=http://localhost:8000 # default
20
+ ```
21
+
22
+ ## Use
23
+
24
+ After a pytest run that emits `coverage.json`:
25
+
26
+ ```bash
27
+ pytest --cov --cov-report=json --junitxml=junit.xml
28
+ verifaied upload --repo <repository-id>
29
+ ```
30
+
31
+ The CLI reads `coverage.json`, pulls the source for every file it
32
+ references from your working tree, and posts everything to
33
+ `/repositories/<id>/local-coverage`. The response prints a summary of
34
+ untested / partial / failing functions so you (or your LLM) can fix
35
+ them on the next iteration.
36
+
37
+ ### Flags
38
+
39
+ - `--repo / -r <UUID>` — repository id (required)
40
+ - `--branch / -b <name>` — branch to attach the upload to (default:
41
+ `git branch --show-current`, then `local`)
42
+ - `--coverage <path>` — path to coverage.json (default: `./coverage.json`)
43
+ - `--junit <path>` — optional JUnit XML for failing-test detail
44
+ - `--commit-sha <sha>` — commit sha for display (default: `git rev-parse HEAD`)
45
+ - `--root <path>` — root the coverage paths are relative to (default: cwd)
46
+ - `--api-url <url>` — backend base URL (overrides `VERIFAIED_API_URL`)
47
+ - `--token <token>` — API token (overrides `VERIFAIED_API_TOKEN`)
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "verifaied"
3
+ version = "0.0.1"
4
+ description = "Upload local pytest coverage to verifAIed for instant feedback"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Kyle Richards" }]
9
+ keywords = ["pytest", "coverage", "testing", "ai", "llm"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Environment :: Console",
13
+ "Intended Audience :: Developers",
14
+ "Topic :: Software Development :: Testing",
15
+ ]
16
+ dependencies = [
17
+ "httpx>=0.27.0",
18
+ "typer>=0.12.0",
19
+ "rich>=13.7.0",
20
+ ]
21
+
22
+ [project.scripts]
23
+ verifaied = "verifaied.cli:app"
24
+
25
+ [project.urls]
26
+ Homepage = "https://pypi.org/project/verifaied/"
27
+
28
+ [build-system]
29
+ requires = ["hatchling"]
30
+ build-backend = "hatchling.build"
31
+
32
+ [tool.hatch.build.targets.wheel]
33
+ packages = ["src/verifaied"]
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "pytest>=8.0.0",
38
+ "pytest-httpx>=0.30.0",
39
+ "ruff>=0.8.0",
40
+ ]
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+
45
+ [tool.ruff.lint]
46
+ select = ["E", "F", "I", "B", "UP"]
47
+ # Typer's idiomatic API is `def cmd(arg: T = typer.Option(...))`. Ruff's
48
+ # B008 flags any function-call default, which would force us to wrap
49
+ # every option — overwhelming the actual signal. Disable for the file
50
+ # that uses Typer's surface; keep B008 enabled everywhere else.
51
+ [tool.ruff.lint.per-file-ignores]
52
+ "src/verifaied/cli.py" = ["B008"]
@@ -0,0 +1,3 @@
1
+ """verifAIed CLI: upload local pytest coverage for instant feedback."""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,4 @@
1
+ from verifaied.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1,189 @@
1
+ """Typer entry point: `verifaied upload`.
2
+
3
+ The CLI surface is intentionally tiny — one verb, all options also
4
+ available as env vars. Exit codes are meaningful so CI pipelines can
5
+ fail builds on missing coverage:
6
+
7
+ 0 success
8
+ 1 usage / config / preflight error (missing files, bad json, no token)
9
+ 2 HTTP/API error (auth, 404, 413, network)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+ from uuid import UUID
16
+
17
+ import typer
18
+ from rich.console import Console
19
+ from rich.table import Table
20
+
21
+ from verifaied import __version__
22
+ from verifaied.client import ApiError, UploadResult, upload
23
+ from verifaied.config import (
24
+ DEFAULT_API_URL,
25
+ ENV_API_TOKEN,
26
+ ENV_API_URL,
27
+ resolved_api_token,
28
+ resolved_api_url,
29
+ )
30
+ from verifaied.uploader import UploaderError, build_payload
31
+
32
+ app = typer.Typer(
33
+ add_completion=False,
34
+ no_args_is_help=True,
35
+ help="Upload local pytest coverage to verifAIed.",
36
+ )
37
+
38
+ console = Console()
39
+ err_console = Console(stderr=True)
40
+
41
+
42
+ def _version_callback(value: bool) -> None:
43
+ if value:
44
+ console.print(f"verifaied {__version__}")
45
+ raise typer.Exit()
46
+
47
+
48
+ @app.callback()
49
+ def main(
50
+ version: bool = typer.Option(
51
+ False,
52
+ "--version",
53
+ callback=_version_callback,
54
+ is_eager=True,
55
+ help="Show the CLI version and exit.",
56
+ ),
57
+ ) -> None:
58
+ """verifAIed — instant coverage feedback for AI-written tests."""
59
+
60
+
61
+ @app.command("upload")
62
+ def upload_command(
63
+ repo: UUID = typer.Option(
64
+ ...,
65
+ "--repo",
66
+ "-r",
67
+ help="Repository id (UUID) from the verifAIed dashboard.",
68
+ ),
69
+ branch: str | None = typer.Option(
70
+ None,
71
+ "--branch",
72
+ "-b",
73
+ help="Branch name. Defaults to `git branch --show-current`.",
74
+ ),
75
+ coverage: Path | None = typer.Option(
76
+ None,
77
+ "--coverage",
78
+ help="Path to the pytest-cov json report (default: ./coverage.json).",
79
+ ),
80
+ junit: Path | None = typer.Option(
81
+ None,
82
+ "--junit",
83
+ help="Optional JUnit XML for failing-test detail.",
84
+ ),
85
+ commit_sha: str | None = typer.Option(
86
+ None,
87
+ "--commit-sha",
88
+ help="Commit sha for display. Defaults to `git rev-parse HEAD`.",
89
+ ),
90
+ root: Path | None = typer.Option(
91
+ None,
92
+ "--root",
93
+ help="Root the coverage paths are relative to (default: cwd).",
94
+ ),
95
+ api_url: str | None = typer.Option(
96
+ None,
97
+ "--api-url",
98
+ help=f"Backend base URL. Defaults to ${ENV_API_URL} or {DEFAULT_API_URL}.",
99
+ ),
100
+ token: str | None = typer.Option(
101
+ None,
102
+ "--token",
103
+ help=f"API token (vr_live_...). Defaults to ${ENV_API_TOKEN}.",
104
+ ),
105
+ ) -> None:
106
+ """Read coverage.json + working-tree source, POST to verifAIed."""
107
+ resolved_url = resolved_api_url(api_url)
108
+ resolved_token = resolved_api_token(token)
109
+ if not resolved_token:
110
+ err_console.print(
111
+ f"[red]error[/red]: no API token (set ${ENV_API_TOKEN} or pass --token)"
112
+ )
113
+ raise typer.Exit(code=1)
114
+
115
+ coverage_path = coverage or Path("coverage.json")
116
+ root_path = root or Path.cwd()
117
+
118
+ try:
119
+ payload = build_payload(
120
+ coverage_path=coverage_path,
121
+ root=root_path,
122
+ branch=branch,
123
+ commit_sha=commit_sha,
124
+ junit_path=junit,
125
+ )
126
+ except UploaderError as e:
127
+ err_console.print(f"[red]error[/red]: {e}")
128
+ raise typer.Exit(code=1) from e
129
+
130
+ try:
131
+ result = upload(
132
+ api_url=resolved_url,
133
+ token=resolved_token,
134
+ repo_id=repo,
135
+ payload=payload,
136
+ )
137
+ except ApiError as e:
138
+ err_console.print(f"[red]error[/red]: {e.message}")
139
+ raise typer.Exit(code=2) from e
140
+
141
+ _render(result, api_url=resolved_url, repo_id=repo)
142
+
143
+
144
+ def _render(result: UploadResult, *, api_url: str, repo_id: UUID) -> None:
145
+ """Print a compact, scannable summary. Designed for LLM tail-reads
146
+ too: the counts come first so the model sees the deltas without
147
+ scrolling past a function list."""
148
+ table = Table(show_header=False, box=None, padding=(0, 1))
149
+ table.add_row("branch", result.branch)
150
+ table.add_row("files received", str(result.files_received))
151
+ table.add_row("functions matched", str(result.functions_matched))
152
+ table.add_row("untested", str(len(result.untested)))
153
+ table.add_row("partial", str(len(result.partial)))
154
+ table.add_row("failing tests", str(len(result.failing_tests)))
155
+ console.print(table)
156
+
157
+ if result.functions_matched == 0:
158
+ err_console.print(
159
+ "[yellow]warning[/yellow]: 0 functions matched — your `files` keys "
160
+ "likely don't match the paths in coverage.json. Try --root."
161
+ )
162
+
163
+ _print_function_list("untested", result.untested)
164
+ _print_function_list("partial", result.partial)
165
+
166
+ if result.failing_tests:
167
+ console.print("\n[bold]failing tests[/bold]")
168
+ for t in result.failing_tests[:10]:
169
+ node = t.get("node_id") or t.get("name") or "?"
170
+ console.print(f" - {node}")
171
+ if len(result.failing_tests) > 10:
172
+ console.print(f" ... and {len(result.failing_tests) - 10} more")
173
+
174
+ console.print(
175
+ f"\n[dim]→ {api_url.rstrip('/')}/repos/{repo_id}"
176
+ f"/branches/{result.branch}?analysisId={result.analysis_id}[/dim]"
177
+ )
178
+
179
+
180
+ def _print_function_list(label: str, fns: list[dict]) -> None:
181
+ if not fns:
182
+ return
183
+ console.print(f"\n[bold]{label}[/bold]")
184
+ for fn in fns[:10]:
185
+ path = fn.get("file_path", "?")
186
+ name = fn.get("name", "?")
187
+ console.print(f" - {path}::{name}")
188
+ if len(fns) > 10:
189
+ console.print(f" ... and {len(fns) - 10} more")
@@ -0,0 +1,103 @@
1
+ """Thin httpx wrapper around POST /repositories/{id}/local-coverage.
2
+
3
+ Kept separate from ``uploader.py`` so the file-walking logic can be
4
+ unit-tested without spinning a fake HTTP server, and so the HTTP layer
5
+ can be swapped if/when we add a streaming variant for huge uploads.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+ from uuid import UUID
13
+
14
+ import httpx
15
+
16
+ from verifaied.uploader import Payload
17
+
18
+
19
+ class ApiError(Exception):
20
+ """Raised for any non-2xx response. The CLI maps this to an exit
21
+ code + a friendly message; the raw status + detail are surfaced so
22
+ the user can tell 401 (token) from 404 (wrong repo) from 413 (too
23
+ big) without reading a stack trace."""
24
+
25
+ def __init__(self, status_code: int, message: str) -> None:
26
+ super().__init__(message)
27
+ self.status_code = status_code
28
+ self.message = message
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class UploadResult:
33
+ analysis_id: str
34
+ branch: str
35
+ files_received: int
36
+ functions_matched: int
37
+ untested: list[dict[str, Any]]
38
+ partial: list[dict[str, Any]]
39
+ failing_tests: list[dict[str, Any]]
40
+ message: str
41
+ summary: dict[str, Any]
42
+
43
+
44
+ def upload(
45
+ *,
46
+ api_url: str,
47
+ token: str,
48
+ repo_id: UUID,
49
+ payload: Payload,
50
+ timeout: float = 60.0,
51
+ ) -> UploadResult:
52
+ """POST the payload and return the parsed response.
53
+
54
+ Times out at 60s by default — the synchronous endpoint persists rows
55
+ and re-runs the analyzer inline, so very large repos may push past
56
+ the default httpx 5s. Callers can override for CI use.
57
+ """
58
+ body = {
59
+ "branch": payload.branch,
60
+ "commit_sha": payload.commit_sha,
61
+ "files": payload.files,
62
+ "coverage_json": payload.coverage_json,
63
+ }
64
+ if payload.junit_xml is not None:
65
+ body["junit_xml"] = payload.junit_xml
66
+
67
+ url = f"{api_url.rstrip('/')}/repositories/{repo_id}/local-coverage"
68
+ headers = {"Authorization": f"Bearer {token}"}
69
+
70
+ try:
71
+ response = httpx.post(url, json=body, headers=headers, timeout=timeout)
72
+ except httpx.HTTPError as e:
73
+ raise ApiError(0, f"could not reach {url}: {e}") from e
74
+
75
+ if response.status_code >= 400:
76
+ raise ApiError(response.status_code, _detail(response))
77
+
78
+ data = response.json()
79
+ return UploadResult(
80
+ analysis_id=data["analysis_id"],
81
+ branch=data["branch"],
82
+ files_received=data["files_received"],
83
+ functions_matched=data["functions_matched"],
84
+ untested=data.get("untested") or [],
85
+ partial=data.get("partial") or [],
86
+ failing_tests=data.get("failing_tests") or [],
87
+ message=data.get("message") or "",
88
+ summary=data.get("summary") or {},
89
+ )
90
+
91
+
92
+ def _detail(response: httpx.Response) -> str:
93
+ """Pull a human-readable error message out of the response. Falls
94
+ back to status reason if the body isn't JSON or doesn't carry a
95
+ ``detail`` field."""
96
+ try:
97
+ body = response.json()
98
+ except ValueError:
99
+ return f"{response.status_code} {response.reason_phrase}".strip()
100
+ detail = body.get("detail") if isinstance(body, dict) else None
101
+ if isinstance(detail, str) and detail:
102
+ return detail
103
+ return f"{response.status_code} {response.reason_phrase}".strip()
@@ -0,0 +1,25 @@
1
+ """Env-var defaults for the CLI.
2
+
3
+ Kept thin on purpose — every option is also a CLI flag, so this module
4
+ exists only to centralize the env-var *names* and the dev-friendly
5
+ ``http://localhost:8000`` default. No filesystem config files (yet); we
6
+ follow the 12-factor convention so the CLI behaves the same in shell,
7
+ pre-commit hooks, and CI.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+
14
+ ENV_API_URL = "VERIFAIED_API_URL"
15
+ ENV_API_TOKEN = "VERIFAIED_API_TOKEN"
16
+
17
+ DEFAULT_API_URL = "http://localhost:8000"
18
+
19
+
20
+ def resolved_api_url(override: str | None) -> str:
21
+ return override or os.environ.get(ENV_API_URL) or DEFAULT_API_URL
22
+
23
+
24
+ def resolved_api_token(override: str | None) -> str | None:
25
+ return override or os.environ.get(ENV_API_TOKEN)
@@ -0,0 +1,135 @@
1
+ """Build the local-coverage POST payload from a pytest-cov json file.
2
+
3
+ The single risky thing in the CLI is the file-path round-trip: the
4
+ ``files`` dict in the request must use the same string keys as the
5
+ ``coverage.json`` records, otherwise the backend's AST analyzer can't
6
+ match function definitions to coverage rows and ``functions_matched``
7
+ comes back as zero. We resolve every path *relative to the same root*
8
+ the user gave us, and bail loudly if any source can't be read.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import subprocess
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+
20
+ class UploaderError(Exception):
21
+ """Raised for any pre-flight problem (missing file, malformed json,
22
+ git failure). The CLI catches this and prints a friendly message
23
+ rather than a stack trace."""
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Payload:
28
+ branch: str
29
+ commit_sha: str | None
30
+ files: dict[str, str]
31
+ coverage_json: dict[str, Any]
32
+ junit_xml: str | None
33
+
34
+
35
+ def load_coverage_json(path: Path) -> dict[str, Any]:
36
+ if not path.exists():
37
+ raise UploaderError(
38
+ f"coverage file not found: {path}. Run pytest with "
39
+ "`--cov --cov-report=json` first."
40
+ )
41
+ try:
42
+ data = json.loads(path.read_text())
43
+ except json.JSONDecodeError as e:
44
+ raise UploaderError(f"could not parse {path}: {e}") from e
45
+ if not isinstance(data, dict) or "files" not in data:
46
+ raise UploaderError(
47
+ f"{path} doesn't look like a pytest-cov json report "
48
+ "(missing top-level 'files' key)."
49
+ )
50
+ return data
51
+
52
+
53
+ def load_files_referenced_by(coverage: dict[str, Any], root: Path) -> dict[str, str]:
54
+ """Read each source file the coverage json references.
55
+
56
+ Keys are returned **unchanged** from the coverage json so the upload
57
+ payload matches the backend's expectations exactly. Values are the
58
+ file contents read from ``<root>/<key>``.
59
+ """
60
+ files_block = coverage.get("files") or {}
61
+ if not isinstance(files_block, dict) or not files_block:
62
+ raise UploaderError("coverage json has no 'files' entries — nothing to upload.")
63
+ out: dict[str, str] = {}
64
+ missing: list[str] = []
65
+ for rel_path in files_block.keys():
66
+ source_path = root / rel_path
67
+ if not source_path.is_file():
68
+ missing.append(rel_path)
69
+ continue
70
+ out[rel_path] = source_path.read_text()
71
+ if missing:
72
+ joined = "\n ".join(missing)
73
+ raise UploaderError(
74
+ "could not read source for the following files referenced by "
75
+ f"coverage.json (looking under {root}):\n {joined}\n"
76
+ "Set --root to the directory pytest was run from."
77
+ )
78
+ return out
79
+
80
+
81
+ def load_junit(path: Path | None) -> str | None:
82
+ if path is None:
83
+ return None
84
+ if not path.exists():
85
+ raise UploaderError(f"JUnit file not found: {path}")
86
+ return path.read_text()
87
+
88
+
89
+ def detect_branch() -> str:
90
+ """`git branch --show-current`, or the literal ``"local"`` if we're
91
+ outside a git repo or on a detached HEAD. The backend treats this as
92
+ a plain string; users on detached HEAD can pass --branch explicitly."""
93
+ out = _git("branch", "--show-current")
94
+ return out or "local"
95
+
96
+
97
+ def detect_commit_sha() -> str | None:
98
+ """Short-circuits to None outside a git repo so the upload still
99
+ succeeds — the backend treats commit_sha as optional and uses its
100
+ own content hash for cache keying."""
101
+ return _git("rev-parse", "HEAD") or None
102
+
103
+
104
+ def _git(*args: str) -> str:
105
+ try:
106
+ result = subprocess.run(
107
+ ["git", *args],
108
+ capture_output=True,
109
+ text=True,
110
+ check=False,
111
+ )
112
+ except FileNotFoundError:
113
+ return ""
114
+ if result.returncode != 0:
115
+ return ""
116
+ return result.stdout.strip()
117
+
118
+
119
+ def build_payload(
120
+ *,
121
+ coverage_path: Path,
122
+ root: Path,
123
+ branch: str | None,
124
+ commit_sha: str | None,
125
+ junit_path: Path | None,
126
+ ) -> Payload:
127
+ coverage_json = load_coverage_json(coverage_path)
128
+ files = load_files_referenced_by(coverage_json, root)
129
+ return Payload(
130
+ branch=branch or detect_branch(),
131
+ commit_sha=commit_sha or detect_commit_sha(),
132
+ files=files,
133
+ coverage_json=coverage_json,
134
+ junit_xml=load_junit(junit_path),
135
+ )
File without changes