Skip to content

Commit 4e17ccb

Browse files
committed
Move the calculating of given/when/then to pydantic models, as well as removing tabbing from docstring in steps (aka multiline steps)
1 parent cc9b37f commit 4e17ccb

File tree

2 files changed

+47
-15
lines changed

2 files changed

+47
-15
lines changed

src/pytest_bdd/gherkin_parser.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import linecache
2+
import textwrap
23
from pathlib import Path
3-
from typing import List, Optional, Union
4+
from typing import List, Optional
45

56
from gherkin.errors import CompositeParserException
67
from gherkin.parser import Parser
78
from gherkin.token_scanner import TokenScanner
8-
from pydantic import BaseModel
9+
from pydantic import BaseModel, field_validator, model_validator
10+
11+
from . import exceptions
12+
from .types import STEP_TYPES
913

1014

1115
class Location(BaseModel):
@@ -41,6 +45,10 @@ class DocString(BaseModel):
4145
delimiter: str
4246
location: Location
4347

48+
@field_validator("content", mode="before")
49+
def dedent_content(cls, value: str) -> str:
50+
return textwrap.dedent(value)
51+
4452

4553
class Step(BaseModel):
4654
id: str
@@ -51,6 +59,18 @@ class Step(BaseModel):
5159
dataTable: Optional[DataTable] = None
5260
docString: Optional[DocString] = None
5361

62+
@field_validator("keyword", mode="before")
63+
def normalize_keyword(cls, value: str) -> str:
64+
return value.lower().strip()
65+
66+
@property
67+
def given_when_then(self) -> str:
68+
return self._gwt
69+
70+
@given_when_then.setter
71+
def given_when_then(self, gwt: str) -> None:
72+
self._gwt = gwt
73+
5474

5575
class Tag(BaseModel):
5676
id: str
@@ -68,6 +88,12 @@ class Scenario(BaseModel):
6888
tags: List[Tag]
6989
examples: Optional[List[DataTable]] = None
7090

91+
@model_validator(mode="after")
92+
def process_steps(cls, instance):
93+
steps = instance.steps
94+
instance.steps = _compute_given_when_then(steps)
95+
return instance
96+
7197

7298
class Rule(BaseModel):
7399
id: str
@@ -87,6 +113,12 @@ class Background(BaseModel):
87113
description: str
88114
steps: List[Step]
89115

116+
@model_validator(mode="after")
117+
def process_steps(cls, instance):
118+
steps = instance.steps
119+
instance.steps = _compute_given_when_then(steps)
120+
return instance
121+
90122

91123
class Child(BaseModel):
92124
background: Optional[Background] = None
@@ -108,6 +140,15 @@ class GherkinDocument(BaseModel):
108140
comments: List[Comment]
109141

110142

143+
def _compute_given_when_then(steps: list[Step]) -> list[Step]:
144+
last_gwt = None
145+
for step in steps:
146+
if step.keyword in STEP_TYPES:
147+
last_gwt = step.keyword
148+
step.given_when_then = last_gwt
149+
return steps
150+
151+
111152
class GherkinParser:
112153
def __init__(self, abs_filename: str = None, encoding: str = "utf-8"):
113154
self.abs_filename = Path(abs_filename) if abs_filename else None
@@ -118,8 +159,6 @@ def __init__(self, abs_filename: str = None, encoding: str = "utf-8"):
118159
try:
119160
self.gherkin_data = Parser().parse(TokenScanner(self.feature_file_text))
120161
except CompositeParserException as e:
121-
from src.pytest_bdd import exceptions
122-
123162
raise exceptions.FeatureError(
124163
e.args[0],
125164
e.errors[0].location["line"],

src/pytest_bdd/parser.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ class Step:
220220
"""
221221

222222
type: str
223-
_name: str
223+
name: str
224224
line_number: int
225225
indent: int
226226
keyword: str
@@ -356,21 +356,17 @@ def parse_steps(self, steps_data: list[GherkinStep]) -> list[Step]:
356356
List[Step]: A list of Step objects.
357357
"""
358358
steps = []
359-
current_step_type = None
360359
for step_data in steps_data:
361-
keyword = step_data.keyword.strip().lower()
362-
current_step_type = self.get_step_type(keyword) or current_step_type
363360
name = strip_comments(step_data.text)
364361
if step_data.docString:
365-
doc_string = textwrap.dedent(step_data.docString.content)
366-
name = f"{name}\n{doc_string}"
362+
name = f"{name}\n{step_data.docString.content}"
367363
steps.append(
368364
Step(
369365
name=name,
370-
type=current_step_type,
366+
type=step_data.given_when_then,
371367
indent=step_data.location.column - 1,
372368
line_number=step_data.location.line,
373-
keyword=keyword.title(),
369+
keyword=step_data.keyword.title(),
374370
)
375371
)
376372
return steps
@@ -424,9 +420,6 @@ def _parse_feature_file(self) -> GherkinDocument:
424420
425421
Returns:
426422
Dict: A Gherkin document representation of the feature file.
427-
428-
Raises:
429-
FeatureError: If there is an error parsing the feature file.
430423
"""
431424
return GherkinParser(self.abs_filename, self.encoding).to_gherkin_document()
432425

0 commit comments

Comments
 (0)