pytest_beehave.hatch
Hatch command — generate bee-themed example features directory.
1"""Hatch command — generate bee-themed example features directory.""" 2 3from __future__ import annotations 4 5import secrets 6from dataclasses import dataclass 7from pathlib import Path 8 9_FEATURE_NAMES = [ 10 "The Forager's Journey", 11 "Queen's Decree", 12 "Drone Assembly Protocol", 13 "Worker Bee Orientation", 14 "Nectar Collection Workflow", 15 "Hive Temperature Regulation", 16 "Pollen Scout Dispatch", 17 "Royal Jelly Production", 18 "Swarm Formation Ritual", 19 "Honeycomb Architecture Review", 20] 21 22_BEES = [ 23 "Beatrice", 24 "Boris", 25 "Belinda", 26 "Bruno", 27 "Blossom", 28 "Barnaby", 29 "Bridget", 30 "Bertram", 31] 32_HIVES = ["the Golden Hive", "the Amber Hive", "the Crystal Hive", "the Obsidian Hive"] 33 34_BACKLOG_CONTENT = """\ 35Feature: {feature_name} 36 37 As {bee}, a worker bee in {hive} 38 I want to complete my assigned foraging route 39 So that the colony has enough nectar for the season 40 41 Rule: Forager readiness 42 43 @id:hatch001 44 Example: Forager departs when pollen reserve is below threshold 45 Given the pollen reserve is below 30 percent 46 When the forager sensor detects the shortage 47 Then {bee} departs for the meadow within one waggle cycle 48 49 Example: Untagged scenario triggers auto-ID assignment 50 Given the hive registers a new forager 51 When the forager completes orientation 52 Then the forager is assigned a unique scout ID 53 54 @deprecated 55 Example: Legacy hive-entry handshake (deprecated) 56 Given an older forager approaches the hive entrance 57 When the guard bee checks the legacy handshake 58 Then the handshake is accepted but logged as deprecated 59 60 Rule: Nectar quality control 61 62 @id:hatch002 63 Example: Low-quality nectar is rejected at the gate 64 Given a forager returns with nectar of quality below 0.4 brix 65 When the gate inspector evaluates the sample 66 Then the nectar is rejected and the forager is sent to a higher-quality source 67""" 68 69_IN_PROGRESS_CONTENT = """\ 70# language: en 71Feature: Waggle Dance Communication 72 73 Background: 74 Given the hive is in active foraging mode 75 And the dance floor is clear of obstacles 76 77 Rule: Direction encoding 78 79 @id:hatch003 80 Example: Scout encodes flower direction in waggle run angle 81 Given a scout has located flowers 200 metres to the north-east 82 When the scout performs the waggle dance 83 Then the waggle run angle matches the sun-relative bearing to the flowers 84 85 Rule: Distance encoding 86 87 @id:hatch004 88 Scenario Outline: Scout encodes distance via waggle run duration 89 Given a scout has located flowers at <distance> metres 90 When the scout performs the waggle dance 91 Then the waggle run lasts approximately <duration> milliseconds 92 93 Examples: 94 | distance | duration | 95 | 100 | 250 | 96 | 500 | 875 | 97 | 1000 | 1500 | 98 99 @id:hatch005 100 Example: Scout provides a data table of visited flower patches 101 Given the scout returns from a multi-patch forage 102 When the scout performs the waggle dance 103 Then the flower patch register contains the following entries: 104 | patch_id | species | quality | 105 | P-001 | Lavender | 0.92 | 106 | P-002 | Clover | 0.85 | 107 | P-003 | Sunflower | 0.78 | 108""" 109 110_COMPLETED_CONTENT = """\ 111Feature: Colony Winter Preparation 112 113 As {bee}, the winter logistics coordinator in {hive} 114 I want to ensure honey stores are sufficient before the first frost 115 So that the colony survives the winter without starvation 116 117 Rule: Honey reserve verification 118 119 @id:hatch006 120 Example: Winter preparation passes when honey reserve exceeds minimum 121 Given the honey reserve is at 85 percent capacity 122 When the winter readiness check is performed 123 Then the colony status is set to WINTER-READY 124 125 @id:hatch007 126 Example: Winter preparation fails when honey reserve is insufficient 127 Given the honey reserve is below 60 percent capacity 128 When the winter readiness check is performed 129 Then the colony status is set to AT-RISK and an alert is raised for {bee} 130""" 131 132 133@dataclass(frozen=True, slots=True) 134class HatchFile: 135 """A single generated .feature file to be written. 136 137 Attributes: 138 relative_path: Path relative to the features root (e.g. ``backlog/x.feature``). 139 content: The full Gherkin text to write. 140 """ 141 142 relative_path: str 143 content: str 144 145 146def generate_hatch_files() -> list[HatchFile]: 147 """Generate bee-themed example .feature files using stdlib randomisation. 148 149 Returns: 150 A list of HatchFile objects ready to be written to disk. 151 """ 152 feature_name = secrets.choice(_FEATURE_NAMES) 153 bee = secrets.choice(_BEES) 154 hive = secrets.choice(_HIVES) 155 156 return [ 157 HatchFile( 158 relative_path="backlog/forager-journey.feature", 159 content=_BACKLOG_CONTENT.format( 160 feature_name=feature_name, bee=bee, hive=hive 161 ), 162 ), 163 HatchFile( 164 relative_path="in-progress/waggle-dance.feature", 165 content=_IN_PROGRESS_CONTENT, 166 ), 167 HatchFile( 168 relative_path="completed/winter-preparation.feature", 169 content=_COMPLETED_CONTENT.format(bee=bee, hive=hive), 170 ), 171 ] 172 173 174def write_hatch(features_root: Path, files: list[HatchFile]) -> list[str]: 175 """Write HatchFile objects to disk under features_root. 176 177 Args: 178 features_root: The root features directory to write into. 179 files: The list of HatchFile objects to write. 180 181 Returns: 182 List of written file paths as strings (relative to features_root). 183 """ 184 written: list[str] = [] 185 for hatch_file in files: 186 dest = features_root / hatch_file.relative_path 187 dest.parent.mkdir(parents=True, exist_ok=True) 188 dest.write_text(hatch_file.content, encoding="utf-8") 189 written.append(hatch_file.relative_path) 190 return written 191 192 193def run_hatch(features_root: Path, force: bool) -> list[str]: 194 """Run the hatch command: check for conflicts, generate, and write files. 195 196 Args: 197 features_root: The root features directory to populate. 198 force: If True, overwrite existing content without error. 199 200 Returns: 201 List of written file paths as strings. 202 203 Raises: 204 SystemExit: If the directory contains .feature files and force is False. 205 """ 206 existing = list(features_root.rglob("*.feature")) if features_root.exists() else [] 207 if existing and not force: 208 conflict = existing[0] 209 raise SystemExit( 210 f"[beehave] hatch aborted: existing .feature files found at {conflict}. " 211 "Use --beehave-hatch-force to overwrite." 212 ) 213 for old_file in existing: 214 old_file.unlink() 215 return write_hatch(features_root, generate_hatch_files())
134@dataclass(frozen=True, slots=True) 135class HatchFile: 136 """A single generated .feature file to be written. 137 138 Attributes: 139 relative_path: Path relative to the features root (e.g. ``backlog/x.feature``). 140 content: The full Gherkin text to write. 141 """ 142 143 relative_path: str 144 content: str
A single generated .feature file to be written.
Attributes:
relative_path: Path relative to the features root (e.g. backlog/x.feature).
content: The full Gherkin text to write.
147def generate_hatch_files() -> list[HatchFile]: 148 """Generate bee-themed example .feature files using stdlib randomisation. 149 150 Returns: 151 A list of HatchFile objects ready to be written to disk. 152 """ 153 feature_name = secrets.choice(_FEATURE_NAMES) 154 bee = secrets.choice(_BEES) 155 hive = secrets.choice(_HIVES) 156 157 return [ 158 HatchFile( 159 relative_path="backlog/forager-journey.feature", 160 content=_BACKLOG_CONTENT.format( 161 feature_name=feature_name, bee=bee, hive=hive 162 ), 163 ), 164 HatchFile( 165 relative_path="in-progress/waggle-dance.feature", 166 content=_IN_PROGRESS_CONTENT, 167 ), 168 HatchFile( 169 relative_path="completed/winter-preparation.feature", 170 content=_COMPLETED_CONTENT.format(bee=bee, hive=hive), 171 ), 172 ]
Generate bee-themed example .feature files using stdlib randomisation.
Returns: A list of HatchFile objects ready to be written to disk.
175def write_hatch(features_root: Path, files: list[HatchFile]) -> list[str]: 176 """Write HatchFile objects to disk under features_root. 177 178 Args: 179 features_root: The root features directory to write into. 180 files: The list of HatchFile objects to write. 181 182 Returns: 183 List of written file paths as strings (relative to features_root). 184 """ 185 written: list[str] = [] 186 for hatch_file in files: 187 dest = features_root / hatch_file.relative_path 188 dest.parent.mkdir(parents=True, exist_ok=True) 189 dest.write_text(hatch_file.content, encoding="utf-8") 190 written.append(hatch_file.relative_path) 191 return written
Write HatchFile objects to disk under features_root.
Args: features_root: The root features directory to write into. files: The list of HatchFile objects to write.
Returns: List of written file paths as strings (relative to features_root).
194def run_hatch(features_root: Path, force: bool) -> list[str]: 195 """Run the hatch command: check for conflicts, generate, and write files. 196 197 Args: 198 features_root: The root features directory to populate. 199 force: If True, overwrite existing content without error. 200 201 Returns: 202 List of written file paths as strings. 203 204 Raises: 205 SystemExit: If the directory contains .feature files and force is False. 206 """ 207 existing = list(features_root.rglob("*.feature")) if features_root.exists() else [] 208 if existing and not force: 209 conflict = existing[0] 210 raise SystemExit( 211 f"[beehave] hatch aborted: existing .feature files found at {conflict}. " 212 "Use --beehave-hatch-force to overwrite." 213 ) 214 for old_file in existing: 215 old_file.unlink() 216 return write_hatch(features_root, generate_hatch_files())
Run the hatch command: check for conflicts, generate, and write files.
Args: features_root: The root features directory to populate. force: If True, overwrite existing content without error.
Returns: List of written file paths as strings.
Raises: SystemExit: If the directory contains .feature files and force is False.