beehave 0.2.20250421__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,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026, eol
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.
22
+
@@ -0,0 +1,392 @@
1
+ Metadata-Version: 2.4
2
+ Name: beehave
3
+ Version: 0.2.20250421
4
+ Summary: A pytest plugin that runs acceptance criteria stub generation as part of the pytest lifecycle, with auto-ID assignment and generic step docstrings
5
+ Author-email: eol <nullhack@users.noreply.github.com>
6
+ Maintainer-email: eol <nullhack@users.noreply.github.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026, eol
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+
30
+ Project-URL: Repository, https://github.com/nullhack/beehave
31
+ Project-URL: Documentation, https://github.com/nullhack/beehave/tree/main/docs/api/
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Framework :: Pytest
34
+ Requires-Python: >=3.13
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: fire>=0.7.1
38
+ Requires-Dist: gherkin-official>=39.0.0
39
+ Provides-Extra: html
40
+ Requires-Dist: pytest-html>=4.1.1; extra == "html"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pdoc>=14.0; extra == "dev"
43
+ Requires-Dist: pytest>=9.0.3; extra == "dev"
44
+ Requires-Dist: pytest-cov>=6.1.1; extra == "dev"
45
+ Requires-Dist: pytest-html>=4.1.1; extra == "dev"
46
+ Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
47
+ Requires-Dist: ruff>=0.11.5; extra == "dev"
48
+ Requires-Dist: taskipy>=1.14.1; extra == "dev"
49
+ Requires-Dist: hypothesis>=6.148.4; extra == "dev"
50
+ Requires-Dist: pyright>=1.1.407; extra == "dev"
51
+ Requires-Dist: ghp-import>=2.1.0; extra == "dev"
52
+ Dynamic: license-file
53
+
54
+ <div align="center">
55
+ <img src="docs/images/banner.svg" alt="pytest-beehave" width="860"/>
56
+
57
+ <br><br>
58
+
59
+ <p><strong>Keeps your Gherkin acceptance criteria and test stubs in sync — automatically, every time pytest runs.</strong></p>
60
+
61
+ [![PyPI][pypi-shield]][pypi-url]
62
+ [![Contributors][contributors-shield]][contributors-url]
63
+ [![Forks][forks-shield]][forks-url]
64
+ [![Stargazers][stars-shield]][stars-url]
65
+ [![Issues][issues-shield]][issues-url]
66
+ [![MIT License][license-shield]][license-url]
67
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?style=for-the-badge)](https://nullhack.github.io/pytest-beehave/coverage/)
68
+ [![CI](https://img.shields.io/github/actions/workflow/status/nullhack/pytest-beehave/ci.yml?style=for-the-badge&label=CI)](https://github.com/nullhack/pytest-beehave/actions/workflows/ci.yml)
69
+ [![Python](https://img.shields.io/badge/python-3.13-blue?style=for-the-badge)](https://www.python.org/downloads/)
70
+ </div>
71
+
72
+ ---
73
+
74
+ ## What it does
75
+
76
+ pytest-beehave is a pytest plugin. Every time you run `pytest`, it reads your Gherkin `.feature` files and makes sure the corresponding test stub files are current:
77
+
78
+ - **No stub for a new `Example:`?** It creates one — a typed, skipped test function with the Given/When/Then steps as its docstring.
79
+ - **Steps changed in the feature file?** It updates the docstring. Your test body is never touched.
80
+ - **`Example:` missing an `@id` tag?** It writes one back into the feature file in-place.
81
+ - **`@id` disappeared from the feature file?** It marks the test `skip(reason="orphan")` so nothing runs silently.
82
+ - **`@deprecated` tag on a feature or rule?** The `deprecated` pytest marker propagates down to every affected test.
83
+
84
+ All of this happens in `pytest_configure` — before pytest collects a single test.
85
+
86
+ ---
87
+
88
+ ## Why pytest-beehave?
89
+
90
+ BDD frameworks sold a compelling promise: human-readable specifications that live alongside your tests, kept honest by the test suite itself. The promise is real. The implementation is the problem. Every scenario explodes into a constellation of `@given`, `@when`, and `@then` step functions scattered across multiple files, wired together by fragile string matching. Refactor one step and you're hunting across the codebase. Add a new scenario and you're registering glue code. The ceremony grows with every feature, and the spec drifts from reality anyway — silently in unused step definitions, loudly in broken ones, always painfully. Plain pytest, on the other hand, is refreshingly direct. But there's no business-readable layer: acceptance criteria live in tickets or comments, never in code, and nothing machine-enforces that what the stakeholder approved is what the test exercises.
91
+
92
+ pytest-beehave is the middle ground. Write your acceptance criteria in plain Gherkin — business-readable, version-controlled, owned by the team. The plugin does the worker-bee work: generating test stubs, keeping docstrings in sync with your steps, assigning stable IDs, and flagging drift before it silently rots. You implement the test body however you like, in plain pytest, with no step files and no glue. The hive stays in order automatically — that tedious, thankless, essential synchronisation work is handled so you never have to think about it again.
93
+
94
+ ---
95
+
96
+ ## Installation
97
+
98
+ Available on [PyPI](https://pypi.org/project/pytest-beehave/):
99
+
100
+ ```bash
101
+ pip install pytest-beehave
102
+ ```
103
+
104
+ No `conftest.py` changes required. The plugin registers itself via pytest's entry-point system.
105
+
106
+ ---
107
+
108
+ ## Quick start
109
+
110
+ **1. Write a feature file with an untagged `Example:`:**
111
+
112
+ ```gherkin
113
+ # docs/features/in-progress/checkout.feature
114
+ Feature: Checkout
115
+
116
+ Rule: Tax calculation
117
+
118
+ Example: VAT is applied at the correct rate
119
+ Given a cart with items totalling £100
120
+ When the buyer is in the UK
121
+ Then the order total is £120
122
+ ```
123
+
124
+ **2. Run pytest:**
125
+
126
+ ```bash
127
+ pytest
128
+ ```
129
+
130
+ **3. Two things just happened automatically:**
131
+
132
+ The feature file was updated with a stable ID:
133
+
134
+ ```gherkin
135
+ @id:a3f2b1c4
136
+ Example: VAT is applied at the correct rate
137
+ ```
138
+
139
+ And a test stub was created at `tests/features/checkout/tax_calculation_test.py`:
140
+
141
+ ```python
142
+ import pytest
143
+
144
+ @pytest.mark.skip(reason="not yet implemented")
145
+ def test_checkout_a3f2b1c4() -> None:
146
+ """
147
+ Given: a cart with items totalling £100
148
+ When: the buyer is in the UK
149
+ Then: the order total is £120
150
+ """
151
+ ```
152
+
153
+ **4. Implement the test and ship.**
154
+
155
+ The stub is already in the right place with the right name. Fill in the body and remove the `skip`.
156
+
157
+ ---
158
+
159
+ ## See it in 2 minutes
160
+
161
+ No feature files yet? Generate a working example project in one command:
162
+
163
+ ```
164
+ $ pytest --beehave-hatch
165
+
166
+ [beehave] HATCH backlog/forager-journey.feature
167
+ [beehave] HATCH in-progress/waggle-dance.feature
168
+ [beehave] HATCH completed/winter-preparation.feature
169
+ [beehave] hatch complete
170
+ ```
171
+
172
+ Three bee-themed `.feature` files land under `docs/features/`, covering every Gherkin construct the plugin supports: `Background`, `Rule`, `Example`, `Scenario Outline` with an `Examples` table, data tables, untagged scenarios (to trigger auto-ID), and `@deprecated`.
173
+
174
+ The `in-progress/waggle-dance.feature` file looks like this:
175
+
176
+ ```gherkin
177
+ # language: en
178
+ Feature: Waggle Dance Communication
179
+
180
+ Background:
181
+ Given the hive is in active foraging mode
182
+ And the dance floor is clear of obstacles
183
+
184
+ Rule: Direction encoding
185
+
186
+ @id:hatch003
187
+ Example: Scout encodes flower direction in waggle run angle
188
+ Given a scout has located flowers 200 metres to the north-east
189
+ When the scout performs the waggle dance
190
+ Then the waggle run angle matches the sun-relative bearing to the flowers
191
+
192
+ Rule: Distance encoding
193
+
194
+ @id:hatch004
195
+ Scenario Outline: Scout encodes distance via waggle run duration
196
+ Given a scout has located flowers at <distance> metres
197
+ When the scout performs the waggle dance
198
+ Then the waggle run lasts approximately <duration> milliseconds
199
+
200
+ Examples:
201
+ | distance | duration |
202
+ | 100 | 250 |
203
+ | 500 | 875 |
204
+ | 1000 | 1500 |
205
+
206
+ @id:hatch005
207
+ Example: Scout provides a data table of visited flower patches
208
+ Given the scout returns from a multi-patch forage
209
+ When the scout performs the waggle dance
210
+ Then the flower patch register contains the following entries:
211
+ | patch_id | species | quality |
212
+ | P-001 | Lavender | 0.92 |
213
+ | P-002 | Clover | 0.85 |
214
+ | P-003 | Sunflower | 0.78 |
215
+ ```
216
+
217
+ Now run pytest:
218
+
219
+ ```
220
+ $ pytest
221
+
222
+ [beehave] CREATE tests/features/forager_journey/forager_readiness_test.py
223
+ [beehave] CREATE tests/features/forager_journey/nectar_quality_control_test.py
224
+ [beehave] CREATE tests/features/waggle_dance/direction_encoding_test.py
225
+ [beehave] CREATE tests/features/waggle_dance/distance_encoding_test.py
226
+ ```
227
+
228
+ The untagged `Example:` in `forager-journey.feature` got an `@id` written back in-place. Every stub is already in the right file with the right name:
229
+
230
+ ```python
231
+ # tests/features/waggle_dance/distance_encoding_test.py
232
+
233
+ import pytest
234
+
235
+
236
+ class TestDistanceEncoding:
237
+ @pytest.mark.skip(reason="not yet implemented")
238
+ def test_waggle_dance_hatch004() -> None:
239
+ """
240
+ Background:
241
+ Given: the hive is in active foraging mode
242
+ And: the dance floor is clear of obstacles
243
+ Given: a scout has located flowers at <distance> metres
244
+ When: the scout performs the waggle dance
245
+ Then: the waggle run lasts approximately <duration> milliseconds
246
+ """
247
+ raise NotImplementedError
248
+
249
+ @pytest.mark.skip(reason="not yet implemented")
250
+ def test_waggle_dance_hatch005() -> None:
251
+ """
252
+ Background:
253
+ Given: the hive is in active foraging mode
254
+ And: the dance floor is clear of obstacles
255
+ Given: the scout returns from a multi-patch forage
256
+ When: the scout performs the waggle dance
257
+ Then: the flower patch register contains the following entries:
258
+ | patch_id | species | quality |
259
+ | P-001 | Lavender | 0.92 |
260
+ | P-002 | Clover | 0.85 |
261
+ | P-003 | Sunflower | 0.78 |
262
+ """
263
+ raise NotImplementedError
264
+ ```
265
+
266
+ Remove the `skip`, implement the test body, run `pytest` again. The hive stays in sync from here on automatically.
267
+
268
+ ---
269
+
270
+ ## How it works
271
+
272
+ pytest-beehave hooks into `pytest_configure`, the earliest possible entry point. Every stub exists on disk before pytest begins collection.
273
+
274
+ ```
275
+ pytest invoked
276
+ └─ pytest_configure fires
277
+ ├─ Bootstrap — create docs/features/{backlog,in-progress,completed}/ if missing
278
+ ├─ Assign IDs — write @id tags to untagged Examples (or fail loudly in CI)
279
+ └─ Sync stubs
280
+ ├─ Create stubs for new Examples
281
+ ├─ Update docstrings when steps change
282
+ ├─ Rename functions when the feature slug changes
283
+ ├─ Mark orphaned tests (criterion deleted from feature file)
284
+ ├─ Redirect non-conforming tests to canonical locations
285
+ └─ Propagate @deprecated markers from Gherkin tags
286
+ └─ Collection begins — every stub is already present
287
+ └─ Tests run
288
+ ```
289
+
290
+ ---
291
+
292
+ ## File layout
293
+
294
+ Beehave expects — and will create — this structure:
295
+
296
+ ```
297
+ docs/features/
298
+ backlog/ ← criteria waiting to be built
299
+ in-progress/ ← criteria actively being implemented
300
+ completed/ ← shipped criteria (orphan detection only; no stub updates)
301
+
302
+ tests/features/
303
+ <feature-name>/
304
+ <rule-slug>_test.py ← one file per Rule: block
305
+ ```
306
+
307
+ Every test function name encodes its criterion:
308
+
309
+ ```
310
+ test_<feature_slug>_<@id>
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Markers
316
+
317
+ pytest-beehave manages four markers. Your own markers (`slow`, `unit`, `integration`) are never touched.
318
+
319
+ | Marker | Meaning |
320
+ |---|---|
321
+ | `skip(reason="not yet implemented")` | Stub created, not yet implemented |
322
+ | `skip(reason="orphan: ...")` | The `@id` no longer exists in any feature file |
323
+ | `skip(reason="non-conforming: moved to ...")` | Test was in the wrong file; canonical stub created |
324
+ | `deprecated` | Criterion retired via `@deprecated` Gherkin tag |
325
+
326
+ ---
327
+
328
+ ## Configuration
329
+
330
+ ```toml
331
+ # pyproject.toml
332
+ [tool.beehave]
333
+ features_path = "docs/features" # default; omit if this matches your layout
334
+ ```
335
+
336
+ If `features_path` is set but the directory does not exist, pytest-beehave exits immediately with a clear error.
337
+
338
+ ---
339
+
340
+ ## CI behaviour
341
+
342
+ On a read-only filesystem (CI), pytest-beehave skips all write operations and instead **fails the run** if it finds any `Example:` without an `@id` tag. This enforces that IDs are always committed — drift is caught at the PR gate, not after merge.
343
+
344
+ ---
345
+
346
+ ## Requirements
347
+
348
+ | | Version |
349
+ |---|---|
350
+ | Python | ≥ 3.13 |
351
+ | pytest | ≥ 6.0 |
352
+
353
+ Optional: install `pytest-beehave[html]` for acceptance-criteria columns in pytest-html reports.
354
+
355
+ ```bash
356
+ pip install "pytest-beehave[html]"
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Contributing
362
+
363
+ ```bash
364
+ git clone https://github.com/nullhack/pytest-beehave
365
+ cd pytest-beehave
366
+ uv sync --all-extras
367
+ uv run task test && uv run task lint && uv run task static-check
368
+ ```
369
+
370
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/nullhack/pytest-beehave/issues).
371
+
372
+ ---
373
+
374
+ ## License
375
+
376
+ MIT — see [LICENSE](LICENSE).
377
+
378
+ **Author:** eol ([@nullhack](https://github.com/nullhack)) · [Documentation](https://nullhack.github.io/pytest-beehave)
379
+
380
+ <!-- MARKDOWN LINKS & IMAGES -->
381
+ [pypi-shield]: https://img.shields.io/pypi/v/pytest-beehave?style=for-the-badge&color=orange
382
+ [pypi-url]: https://pypi.org/project/pytest-beehave/
383
+ [contributors-shield]: https://img.shields.io/github/contributors/nullhack/pytest-beehave.svg?style=for-the-badge
384
+ [contributors-url]: https://github.com/nullhack/pytest-beehave/graphs/contributors
385
+ [forks-shield]: https://img.shields.io/github/forks/nullhack/pytest-beehave.svg?style=for-the-badge
386
+ [forks-url]: https://github.com/nullhack/pytest-beehave/network/members
387
+ [stars-shield]: https://img.shields.io/github/stars/nullhack/pytest-beehave.svg?style=for-the-badge
388
+ [stars-url]: https://github.com/nullhack/pytest-beehave/stargazers
389
+ [issues-shield]: https://img.shields.io/github/issues/nullhack/pytest-beehave.svg?style=for-the-badge
390
+ [issues-url]: https://github.com/nullhack/pytest-beehave/issues
391
+ [license-shield]: https://img.shields.io/badge/license-MIT-green?style=for-the-badge
392
+ [license-url]: https://github.com/nullhack/pytest-beehave/blob/main/LICENSE