From 070541d70740fa2a02a3e4194135b9e821eb3604 Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Sat, 20 Aug 2022 11:52:29 +0200 Subject: [PATCH 1/3] Do not use eagerly load into a set if not necessary --- src/pytest_bdd/generation.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pytest_bdd/generation.py b/src/pytest_bdd/generation.py index 9304b4c..2d9cb8e 100644 --- a/src/pytest_bdd/generation.py +++ b/src/pytest_bdd/generation.py @@ -117,7 +117,7 @@ def print_missing_code(scenarios: list[ScenarioTemplate], steps: list[Step]) -> tw.line() features = sorted( - {scenario.feature for scenario in scenarios}, key=lambda feature: feature.name or feature.filename + (scenario.feature for scenario in scenarios), key=lambda feature: feature.name or feature.filename ) code = generate_code(features, scenarios, steps) tw.write(code) @@ -145,9 +145,7 @@ def parse_feature_files(paths: list[str], **kwargs: Any) -> tuple[list[Feature], itertools.chain.from_iterable(feature.scenarios.values() for feature in features), key=lambda scenario: (scenario.feature.name or scenario.feature.filename, scenario.name), ) - steps = sorted( - set(itertools.chain.from_iterable(scenario.steps for scenario in scenarios)), key=lambda step: step.name - ) + steps = sorted((step for scenario in scenarios for step in scenario.steps), key=lambda step: step.name) return features, scenarios, steps From 5ffe96c81d0d93baf4d4288a7ca65687a2dd283a Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Sat, 20 Aug 2022 12:11:45 +0200 Subject: [PATCH 2/3] Simplify code by using dataclasses --- src/pytest_bdd/parser.py | 152 +++++++++++++++------------------------ 1 file changed, 57 insertions(+), 95 deletions(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index 0513465..45e176c 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -5,6 +5,7 @@ import re import textwrap import typing from collections import OrderedDict +from dataclasses import dataclass, field from typing import cast from . import exceptions, types @@ -28,7 +29,7 @@ STEP_PREFIXES = [ ] if typing.TYPE_CHECKING: - from typing import Any, Iterable, Mapping, Match + from typing import Any, Iterable, Mapping, Match, Sequence def split_line(line: str) -> list[str]: @@ -187,49 +188,31 @@ def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Featu return feature +@dataclass class Feature: - """Feature.""" - - def __init__( - self, - scenarios: OrderedDict, - filename: str, - rel_filename: str, - name: str | None, - tags: set, - background: Background | None, - line_number: int, - description: str, - ) -> None: - self.scenarios: dict[str, ScenarioTemplate] = scenarios - self.rel_filename: str = rel_filename - self.filename: str = filename - self.tags: set = tags - self.name: str | None = name - self.line_number: int = line_number - self.description: str = description - self.background: Background | None = background + scenarios: OrderedDict[str, ScenarioTemplate] + filename: str + rel_filename: str + name: str | None + tags: set[str] + background: Background | None + line_number: int + description: str +@dataclass class ScenarioTemplate: """A scenario template. Created when parsing the feature file, it will then be combined with the examples to create a Scenario.""" - def __init__(self, feature: Feature, name: str, line_number: int, templated: bool, tags=None) -> None: - """ - - :param str name: Scenario name. - :param int line_number: Scenario line number. - :param set tags: Set of tags. - """ - self.feature = feature - self.name = name - self._steps: list[Step] = [] - self.examples = Examples() - self.line_number = line_number - self.tags = tags or set() - self.templated = templated + feature: Feature + name: str + line_number: int + templated: bool + tags: set[str] = field(default_factory=set) + examples: Examples | None = field(default_factory=lambda: Examples()) + _steps: list[Step] = field(init=False, default_factory=list) def add_step(self, step: Step) -> None: """Add step to the scenario. @@ -263,50 +246,38 @@ class ScenarioTemplate: return Scenario(feature=self.feature, name=self.name, line_number=self.line_number, steps=steps, tags=self.tags) +@dataclass class Scenario: - - """Scenario.""" - - def __init__(self, feature: Feature, name: str, line_number: int, steps: list[Step] = None, tags=None) -> None: - """Scenario constructor. - - :param pytest_bdd.parser.Feature feature: Feature. - :param str name: Scenario name. - :param int line_number: Scenario line number. - :param set tags: Set of tags. - """ - if steps is None: - steps = [] - self.feature = feature - self.name = name - self.steps = steps - self.line_number = line_number - self.tags = tags or set() - self.failed = False + feature: Feature + name: str + line_number: int + steps: list[Step] + tags: set[str] = field(default_factory=set) +@dataclass class Step: - - """Step.""" + type: str + _name: str + line_number: int + indent: int + keyword: str + failed: bool = field(init=False, default=False) + scenario: ScenarioTemplate | None = field(init=False, default=None) + background: Background | None = field(init=False, default=None) + lines: list[str] = field(init=False, default_factory=list) def __init__(self, name: str, type: str, indent: int, line_number: int, keyword: str) -> None: - """Step constructor. + self.name = name + self.type = type + self.indent = indent + self.line_number = line_number + self.keyword = keyword - :param str name: step name. - :param str type: step type. - :param int indent: step text indent. - :param int line_number: line number. - :param str keyword: step keyword. - """ - self.name: str = name - self.keyword: str = keyword - self.lines: list[str] = [] - self.indent: int = indent - self.type: str = type - self.line_number: int = line_number - self.failed: bool = False - self.scenario: ScenarioTemplate | None = None - self.background: Background | None = None + self.failed = False + self.scenario = None + self.background = None + self.lines = [] def add_line(self, line: str) -> None: """Add line to the multiple step. @@ -345,7 +316,7 @@ class Step: """Get step params.""" return tuple(frozenset(STEP_PARAM_RE.findall(self.name))) - def render(self, context: Mapping[str, Any]): + def render(self, context: Mapping[str, Any]) -> str: def replacer(m: Match): varname = m.group(1) return str(context[varname]) @@ -353,19 +324,11 @@ class Step: return STEP_PARAM_RE.sub(replacer, self.name) +@dataclass class Background: - - """Background.""" - - def __init__(self, feature: Feature, line_number: int) -> None: - """Background constructor. - - :param pytest_bdd.parser.Feature feature: Feature. - :param int line_number: Line number. - """ - self.feature: Feature = feature - self.line_number: int = line_number - self.steps: list[Step] = [] + feature: Feature + line_number: int + steps: list[Step] = field(init=False, default_factory=list) def add_step(self, step: Step) -> None: """Add step to the background.""" @@ -373,25 +336,24 @@ class Background: self.steps.append(step) +@dataclass class Examples: - """Example table.""" - def __init__(self) -> None: - """Initialize examples instance.""" - self.example_params: list[str] = [] - self.examples: list[list[str]] = [] - self.line_number: int | None = None - self.name = None + line_number: int | None = field(default=None) + name: str | None = field(default=None) - def set_param_names(self, keys: list[str]) -> None: + example_params: list[str] = field(init=False, default_factory=list) + examples: list[Sequence[str]] = field(init=False, default_factory=list) + + def set_param_names(self, keys: Iterable[str]) -> None: """Set parameter names. :param names: `list` of `string` parameter names. """ self.example_params = [str(key) for key in keys] - def add_example(self, values: list[str]) -> None: + def add_example(self, values: Sequence[str]) -> None: """Add example. :param values: `list` of `string` parameter values. From bf5817948ad3c1731d3a7812dd6b0c20a93e0f90 Mon Sep 17 00:00:00 2001 From: Alessio Bogon <778703+youtux@users.noreply.github.com> Date: Sat, 20 Aug 2022 12:25:54 +0200 Subject: [PATCH 3/3] Remove useless docstrings --- src/pytest_bdd/parser.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/pytest_bdd/parser.py b/src/pytest_bdd/parser.py index 45e176c..cc996d6 100644 --- a/src/pytest_bdd/parser.py +++ b/src/pytest_bdd/parser.py @@ -204,7 +204,8 @@ class Feature: class ScenarioTemplate: """A scenario template. - Created when parsing the feature file, it will then be combined with the examples to create a Scenario.""" + Created when parsing the feature file, it will then be combined with the examples to create a Scenario. + """ feature: Feature name: str @@ -215,10 +216,6 @@ class ScenarioTemplate: _steps: list[Step] = field(init=False, default_factory=list) def add_step(self, step: Step) -> None: - """Add step to the scenario. - - :param pytest_bdd.parser.Step step: Step. - """ step.scenario = self self._steps.append(step) @@ -288,7 +285,6 @@ class Step: @property def name(self) -> str: - """Get step name.""" multilines_content = textwrap.dedent("\n".join(self.lines)) if self.lines else "" # Remove the multiline quotes, if present. @@ -304,7 +300,6 @@ class Step: @name.setter def name(self, value: str) -> None: - """Set step name.""" self._name = value def __str__(self) -> str: @@ -313,7 +308,6 @@ class Step: @property def params(self) -> tuple[str, ...]: - """Get step params.""" return tuple(frozenset(STEP_PARAM_RE.findall(self.name))) def render(self, context: Mapping[str, Any]) -> str: @@ -347,17 +341,9 @@ class Examples: examples: list[Sequence[str]] = field(init=False, default_factory=list) def set_param_names(self, keys: Iterable[str]) -> None: - """Set parameter names. - - :param names: `list` of `string` parameter names. - """ self.example_params = [str(key) for key in keys] def add_example(self, values: Sequence[str]) -> None: - """Add example. - - :param values: `list` of `string` parameter values. - """ self.examples.append(values) def as_contexts(self) -> Iterable[dict[str, Any]]: @@ -371,7 +357,6 @@ class Examples: yield dict(zip(header, row)) def __bool__(self) -> bool: - """Bool comparison.""" return bool(self.examples)