pytest-digline 0.1.0__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,45 @@
1
+ # Python bytecode and build output
2
+ __pycache__/
3
+ *.py[cod]
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+
8
+ # Environment. Recreated by `uv sync`; it pins absolute paths, so it must
9
+ # never be committed.
10
+ .venv/
11
+ .env
12
+
13
+ # Tool caches. Each already drops its own `.gitignore`; listed here so a
14
+ # fresh clone is clean before the tools have run once.
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .mypy_cache/
18
+
19
+ # IDE. Excluded because the project files carry machine-specific SDK paths
20
+ # (`digline.iml` names the interpreter by absolute path). Drop these two lines
21
+ # to version the shared part, and keep ignoring `.idea/workspace.xml`.
22
+ .idea/
23
+ *.iml
24
+
25
+ # Local Claude Code settings. `.claude/settings.json`, if it appears, is shared
26
+ # and stays versioned. `CLAUDE.local.md` is the personal working agreement —
27
+ # how I want to be worked with — as against `CLAUDE.md`, which is the project.
28
+ .claude/settings.local.json
29
+ CLAUDE.local.md
30
+
31
+ # macOS
32
+ .DS_Store
33
+
34
+ # Working material that stays local and is not part of the package.
35
+ private/
36
+
37
+ # NOT ignored: `.digline/`. Decision 2 — baselines are versioned, run
38
+ # artifacts are not — and the split is enforced one level down, by the
39
+ # `.gitignore` the store itself writes into `.digline/` (`*/runs/`).
40
+ # Ignoring `.digline/` here would take the baselines out of git with it.
41
+ to-publish/
42
+
43
+ # The site checkout that ci.yml's `docs` job makes, and that RELEASING
44
+ # tells you to make to reproduce it. Never committed here.
45
+ _site/
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.5
2
+ Name: pytest-digline
3
+ Version: 0.1.0
4
+ Summary: Gate a pytest run on a digline comparison: one row per check, against the baseline committed in your repo.
5
+ Project-URL: Homepage, https://digline.dev/
6
+ Project-URL: Documentation, https://digline.dev/product/pytest/
7
+ Project-URL: Changelog, https://digline.dev/product/changelog/
8
+ Project-URL: Repository, https://github.com/digline/digline
9
+ Project-URL: Issues, https://github.com/digline/digline/issues
10
+ Author-email: Alessandro Prandini <alessandro.prandini@ict-group.it>
11
+ License-Expression: Apache-2.0
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: digline>=0.9.0
21
+ Requires-Dist: pytest>=8.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # pytest-digline
25
+
26
+ The digline comparison, as rows in pytest's own report.
27
+
28
+ ```sh
29
+ pip install pytest-digline
30
+ pytest --digline-suite eval/suite.py
31
+ ```
32
+
33
+ ```
34
+ digline: comparing 1 suite(s) against the committed baseline
35
+
36
+ eval/suite.py::alpha::contains .
37
+ eval/suite.py::alpha::llm_rubric F
38
+ eval/suite.py::beta::contains .
39
+ eval/suite.py::gamma s
40
+
41
+ =================================== FAILURES ===================================
42
+ digline: alpha · llm_rubric
43
+ dropped from 0.910000 to 0.640000, below its threshold of 0.700000,
44
+ and beyond the 0.880000–0.950000 this check measured across 5 samples
45
+ reason: signed=True, concise=False
46
+
47
+ ==================================== digline ===================================
48
+ 1 check got worse; every case could be judged; 1 case is suspended; the rules
49
+ are unchanged.
50
+ ```
51
+
52
+ One row per **check** — one assertion on one case — because that is digline's
53
+ unit of verdict. `-k` and `--last-failed` select them like any other test.
54
+
55
+ ## What it does, and what it costs
56
+
57
+ By default it **compares and never runs**: it reads the latest stored run of
58
+ each suite, holds it against the baseline committed in your repository, and
59
+ makes no network call at all. Producing a run stays a separate act —
60
+ `digline run`, the official image, the GitHub Action.
61
+
62
+ `--digline-run` runs each suite first. That calls your provider and spends
63
+ money, so it is a flag on the command line where the cost is visible, it prints
64
+ the planned call count before the first call, and it **refuses under
65
+ `--collect-only`**: a command whose job is to list test names must never be able
66
+ to spend a hundred model calls.
67
+
68
+ ## The four states
69
+
70
+ | digline | pytest |
71
+ |---|---|
72
+ | fine | passed |
73
+ | got worse | **FAILED** |
74
+ | could not be judged | **ERROR** — an error is neither green nor a regression |
75
+ | suspended | **SKIPPED**, with the reason the suite declared |
76
+
77
+ The suspension is the one digline state an exit code cannot express: it never
78
+ fails, so it disappears into `0`. pytest has had a state for *a decision rather
79
+ than an outcome* since it was written, and this is it.
80
+
81
+ **What pytest cannot carry**: the process exit code. `digline compare` exits `1`
82
+ for a regression and `2` for a run it could not judge; a pytest run exits `1`
83
+ for either. The distinction survives in the report — `F` and `E` are counted
84
+ separately, `-rE` lists the errored rows — and in `--junit-xml`. A job that
85
+ needs `1` versus `2` runs `digline compare`, which is one command away.
86
+
87
+ ## Naming a suite
88
+
89
+ Nothing is discovered by convention: a suite is a file that *executes*, and a
90
+ file found by convention is a file that runs by accident. Name it, either way:
91
+
92
+ ```sh
93
+ pytest --digline-suite eval/suite.py --digline-suite eval/billing.toml
94
+ ```
95
+
96
+ ```ini
97
+ # pyproject.toml
98
+ [tool.pytest.ini_options]
99
+ digline_suites = ["eval/suite.py"]
100
+ ```
101
+
102
+ The command line replaces the ini list rather than adding to it, so narrowing a
103
+ run to one suite is expressible. `--digline-root` names the perimeter holding
104
+ `.digline/` and defaults to pytest's rootdir.
105
+
106
+ Select rows with `-k`; a node id passed as an argument is not supported.
107
+
108
+ ## What it will not do
109
+
110
+ **It does not promote.** There is no flag, no fixture and no marker that makes
111
+ a run the new baseline — not refused, *absent*, and a test in this package
112
+ sweeps the sources to keep it that way. A baseline is an approved reference;
113
+ the approval is a person's, and a green pytest run is the single most likely
114
+ place for a promotion to happen by accident.
115
+
116
+ **It has no thresholds of its own.** Every bar it reports against was declared
117
+ in your suite and frozen in the baseline somebody promoted and committed.
118
+
119
+ **It depends on digline and pytest, and nothing else.**
120
+
121
+ Turn it off for one run with `-p no:digline`. With no suite named it collects
122
+ nothing, prints nothing and adds no header.
123
+
124
+ The reasoning is [ADR 0013](https://digline.dev/product/adr/0013-the-pytest-plugin/).
@@ -0,0 +1,101 @@
1
+ # pytest-digline
2
+
3
+ The digline comparison, as rows in pytest's own report.
4
+
5
+ ```sh
6
+ pip install pytest-digline
7
+ pytest --digline-suite eval/suite.py
8
+ ```
9
+
10
+ ```
11
+ digline: comparing 1 suite(s) against the committed baseline
12
+
13
+ eval/suite.py::alpha::contains .
14
+ eval/suite.py::alpha::llm_rubric F
15
+ eval/suite.py::beta::contains .
16
+ eval/suite.py::gamma s
17
+
18
+ =================================== FAILURES ===================================
19
+ digline: alpha · llm_rubric
20
+ dropped from 0.910000 to 0.640000, below its threshold of 0.700000,
21
+ and beyond the 0.880000–0.950000 this check measured across 5 samples
22
+ reason: signed=True, concise=False
23
+
24
+ ==================================== digline ===================================
25
+ 1 check got worse; every case could be judged; 1 case is suspended; the rules
26
+ are unchanged.
27
+ ```
28
+
29
+ One row per **check** — one assertion on one case — because that is digline's
30
+ unit of verdict. `-k` and `--last-failed` select them like any other test.
31
+
32
+ ## What it does, and what it costs
33
+
34
+ By default it **compares and never runs**: it reads the latest stored run of
35
+ each suite, holds it against the baseline committed in your repository, and
36
+ makes no network call at all. Producing a run stays a separate act —
37
+ `digline run`, the official image, the GitHub Action.
38
+
39
+ `--digline-run` runs each suite first. That calls your provider and spends
40
+ money, so it is a flag on the command line where the cost is visible, it prints
41
+ the planned call count before the first call, and it **refuses under
42
+ `--collect-only`**: a command whose job is to list test names must never be able
43
+ to spend a hundred model calls.
44
+
45
+ ## The four states
46
+
47
+ | digline | pytest |
48
+ |---|---|
49
+ | fine | passed |
50
+ | got worse | **FAILED** |
51
+ | could not be judged | **ERROR** — an error is neither green nor a regression |
52
+ | suspended | **SKIPPED**, with the reason the suite declared |
53
+
54
+ The suspension is the one digline state an exit code cannot express: it never
55
+ fails, so it disappears into `0`. pytest has had a state for *a decision rather
56
+ than an outcome* since it was written, and this is it.
57
+
58
+ **What pytest cannot carry**: the process exit code. `digline compare` exits `1`
59
+ for a regression and `2` for a run it could not judge; a pytest run exits `1`
60
+ for either. The distinction survives in the report — `F` and `E` are counted
61
+ separately, `-rE` lists the errored rows — and in `--junit-xml`. A job that
62
+ needs `1` versus `2` runs `digline compare`, which is one command away.
63
+
64
+ ## Naming a suite
65
+
66
+ Nothing is discovered by convention: a suite is a file that *executes*, and a
67
+ file found by convention is a file that runs by accident. Name it, either way:
68
+
69
+ ```sh
70
+ pytest --digline-suite eval/suite.py --digline-suite eval/billing.toml
71
+ ```
72
+
73
+ ```ini
74
+ # pyproject.toml
75
+ [tool.pytest.ini_options]
76
+ digline_suites = ["eval/suite.py"]
77
+ ```
78
+
79
+ The command line replaces the ini list rather than adding to it, so narrowing a
80
+ run to one suite is expressible. `--digline-root` names the perimeter holding
81
+ `.digline/` and defaults to pytest's rootdir.
82
+
83
+ Select rows with `-k`; a node id passed as an argument is not supported.
84
+
85
+ ## What it will not do
86
+
87
+ **It does not promote.** There is no flag, no fixture and no marker that makes
88
+ a run the new baseline — not refused, *absent*, and a test in this package
89
+ sweeps the sources to keep it that way. A baseline is an approved reference;
90
+ the approval is a person's, and a green pytest run is the single most likely
91
+ place for a promotion to happen by accident.
92
+
93
+ **It has no thresholds of its own.** Every bar it reports against was declared
94
+ in your suite and frozen in the baseline somebody promoted and committed.
95
+
96
+ **It depends on digline and pytest, and nothing else.**
97
+
98
+ Turn it off for one run with `-p no:digline`. With no suite named it collects
99
+ nothing, prints nothing and adds no header.
100
+
101
+ The reasoning is [ADR 0013](https://digline.dev/product/adr/0013-the-pytest-plugin/).
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "pytest-digline"
3
+ version = "0.1.0"
4
+ description = "Gate a pytest run on a digline comparison: one row per check, against the baseline committed in your repo."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = "Apache-2.0"
8
+ authors = [
9
+ { name = "Alessandro Prandini", email = "alessandro.prandini@ict-group.it" },
10
+ ]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Framework :: Pytest",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3 :: Only",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Topic :: Software Development :: Testing",
18
+ "Typing :: Typed",
19
+ ]
20
+ dependencies = [
21
+ # 0.9.0 is the release that first publishes `report.check_line`, the
22
+ # per-delta sentence this package prints on a failing row (ADR 0013 §6).
23
+ # Held there by `tests/test_plugin_floors.py`, which computes the floor
24
+ # from the newest name these sources import rather than taking it on trust.
25
+ "digline>=0.9.0",
26
+ # A floor and no cap, unlike `digline-mcp`'s `mcp<3`. That cap records a
27
+ # rename that actually happened; pytest has not moved the `pytest11` entry
28
+ # point in a decade, and a cap here would fight the one dependency the user
29
+ # certainly already has. 8.0 is where `@pytest.hookimpl(wrapper=True)` — the
30
+ # hook that keeps an ERROR row free of a traceback — became available.
31
+ "pytest>=8.0",
32
+ ]
33
+
34
+ # The same storefront as `digline` itself: a plugin's PyPI page is where
35
+ # somebody meets it first, so it points at the same site and the same repo
36
+ # rather than being a page of no links at all.
37
+ [project.urls]
38
+ Homepage = "https://digline.dev/"
39
+ Documentation = "https://digline.dev/product/pytest/"
40
+ Changelog = "https://digline.dev/product/changelog/"
41
+ Repository = "https://github.com/digline/digline"
42
+ Issues = "https://github.com/digline/digline/issues"
43
+
44
+ # Named `digline` and not `pytest_digline`: this is the name a user types to
45
+ # switch it off — `pytest -p no:digline` — and it is what the header prints.
46
+ [project.entry-points.pytest11]
47
+ digline = "pytest_digline.plugin"
48
+
49
+ [build-system]
50
+ requires = ["hatchling>=1.27"]
51
+ build-backend = "hatchling.build"
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/pytest_digline"]
55
+
56
+ [tool.uv.sources]
57
+ digline = { workspace = true }
@@ -0,0 +1,15 @@
1
+ """`pytest-digline`: the comparison, as rows in pytest's own report.
2
+
3
+ The plugin lives in `pytest_digline.plugin`, which is what the `pytest11` entry
4
+ point names. Nothing is exported here: a pytest plugin is not a library, and a
5
+ name importable from the package root is a surface somebody would write against.
6
+ """
7
+
8
+ from importlib.metadata import version as _distribution_version
9
+
10
+ # Read from the installed distribution rather than written here, for the reason
11
+ # `digline.__version__` is: a hand-written copy is neither derived nor gated,
12
+ # and `tests/test_versions.py` sweeps these sources for exactly that.
13
+ __version__ = _distribution_version("pytest-digline")
14
+
15
+ __all__ = ["__version__"]