Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate black, flake8, isort, pyupgrade linters and formatters to ruff #758

Merged
merged 11 commits into from
Jan 2, 2025
Merged
39 changes: 11 additions & 28 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/psf/black
# If you update the version here, also update it in tox.ini (py*-pytestlatest-linters)
rev: 24.10.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.13.2
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: f0b5944bef86f50d875305821a0ab0d8c601e465 # frozen: v0.8.4
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
hooks:
- id: pyupgrade
args: ["--py39-plus"]
- repo: https://github.com/pycqa/flake8
rev: "7.1.1"
hooks:
- id: flake8
additional_dependencies: [
"flake8-pyproject",
"flake8-bugbear",
]
- id: ruff
args: [ --fix ]
- id: ruff-format
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"charliermarsh.ruff"
]
}
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
from __future__ import annotations

from importlib import metadata as _metadata

Expand Down
36 changes: 22 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,29 @@ sphinx-autobuild = "*"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.black]
[tool.ruff]
line-length = 120
target-version = ["py39", "py310", "py311", "py312", "py313"]

[tool.flake8]
# E1: indentation: already covered by `black`
# E2: whitespace: already covered by `black`
# E3: blank line: already covered by `black`
# E501: line length: already covered by `black`
extend-ignore = "E1,E2,E3,E501"

[tool.isort]
profile = "black"
line_length = 120
multi_line_output = 3
target-version = "py39"
lint.select = [
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"E4", # pycodestyle - error - import
"E7", # pycodestyle - error - statement
"E9", # pycodestyle - error - runtime
"F", # pyflakes
"I", # isort
"ISC", # flake8-implicit-str-concat
"PERF", # perflint
"UP", # pyupgrade
]
lint.ignore = [
# Covered by formatter
"ISC001" # single-line-implicit-string-concatenation
]
lint.isort.required-imports = [
"from __future__ import annotations",
]

[tool.coverage.report]
exclude_lines = [
Expand Down
7 changes: 3 additions & 4 deletions src/pytest_bdd/gherkin_terminal_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ def configure(config: Config) -> None:
raise Exception(
"gherkin-terminal-reporter is not compatible with any other terminal reporter."
"You can use only one terminal reporter."
"Currently '{0}' is used."
"Please decide to use one by deactivating {0} or gherkin-terminal-reporter.".format(
current_reporter.__class__
)
f"Currently '{current_reporter.__class__}' is used."
f"Please decide to use one by deactivating {current_reporter.__class__} "
"or gherkin-terminal-reporter."
)
gherkin_reporter = GherkinTerminalReporter(config)
config.pluginmanager.unregister(current_reporter)
Expand Down
44 changes: 21 additions & 23 deletions src/pytest_bdd/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@

from .exceptions import StepError
from .gherkin_parser import Background as GherkinBackground
from .gherkin_parser import DataTable
from .gherkin_parser import DataTable, GherkinDocument, get_gherkin_document
from .gherkin_parser import Feature as GherkinFeature
from .gherkin_parser import GherkinDocument
from .gherkin_parser import Rule as GherkinRule
from .gherkin_parser import Scenario as GherkinScenario
from .gherkin_parser import Step as GherkinStep
from .gherkin_parser import Tag as GherkinTag
from .gherkin_parser import get_gherkin_document
from .types import STEP_TYPE_BY_PARSER_KEYWORD

PARAM_RE = re.compile(r"<(.+?)>")
Expand Down Expand Up @@ -48,7 +46,7 @@ def get_tag_names(tag_data: list[GherkinTag]) -> set[str]:
"""Extract tag names from tag data.

Args:
tag_data (List[dict]): The tag data to extract names from.
tag_data (list[dict]): The tag data to extract names from.

Returns:
set[str]: A set of tag names.
Expand All @@ -66,7 +64,7 @@ class Feature:
rel_filename (str): The relative path of the feature file.
name (str): The name of the feature.
tags (set[str]): A set of tags associated with the feature.
background (Optional[Background]): The background steps for the feature, if any.
background (Background | None): The background steps for the feature, if any.
line_number (int): The line number where the feature starts in the file.
description (str): The description of the feature.
"""
Expand All @@ -88,10 +86,10 @@ class Examples:
"""Represents examples used in scenarios for parameterization.

Attributes:
line_number (Optional[int]): The line number where the examples start.
name (Optional[str]): The name of the examples.
example_params (List[str]): The names of the parameters for the examples.
examples (List[Sequence[str]]): The list of example rows.
line_number (int | None): The line number where the examples start.
name (str | None): The name of the examples.
example_params (list[str]): The names of the parameters for the examples.
examples (list[Sequence[str]]): The list of example rows.
"""

line_number: int | None = None
Expand Down Expand Up @@ -154,11 +152,11 @@ class ScenarioTemplate:
name (str): The name of the scenario.
line_number (int): The line number where the scenario starts in the file.
templated (bool): Whether the scenario is templated.
description (Optional[str]): The description of the scenario.
description (str | None): The description of the scenario.
tags (set[str]): A set of tags associated with the scenario.
_steps (List[Step]): The list of steps in the scenario (internal use only).
examples (Optional[Examples]): The examples used for parameterization in the scenario.
rule (Optional[Rule]): The rule to which the scenario may belong (None = no rule).
_steps (list[Step]): The list of steps in the scenario (internal use only).
examples (Examples | None): The examples used for parameterization in the scenario.
rule (Rule | None): The rule to which the scenario may belong (None = no rule).
"""

feature: Feature
Expand Down Expand Up @@ -197,7 +195,7 @@ def steps(self) -> list[Step]:
"""Get all steps for the scenario, including background steps.

Returns:
List[Step]: A list of steps, including any background steps from the feature.
list[Step]: A list of steps, including any background steps from the feature.
"""
return self.all_background_steps + self._steps

Expand Down Expand Up @@ -244,8 +242,8 @@ class Scenario:
keyword (str): The keyword used to define the scenario.
name (str): The name of the scenario.
line_number (int): The line number where the scenario starts in the file.
steps (List[Step]): The list of steps in the scenario.
description (Optional[str]): The description of the scenario.
steps (list[Step]): The list of steps in the scenario.
description (str | None): The description of the scenario.
tags (set[str]): A set of tags associated with the scenario.
"""

Expand All @@ -270,8 +268,8 @@ class Step:
indent (int): The indentation level of the step.
keyword (str): The keyword used for the step (e.g., 'Given', 'When', 'Then').
failed (bool): Whether the step has failed (internal use only).
scenario (Optional[ScenarioTemplate]): The scenario to which this step belongs (internal use only).
background (Optional[Background]): The background to which this step belongs (internal use only).
scenario (ScenarioTemplate | None): The scenario to which this step belongs (internal use only).
background (Background | None): The background to which this step belongs (internal use only).
"""

type: str
Expand Down Expand Up @@ -346,7 +344,7 @@ class Background:

Attributes:
line_number (int): The line number where the background starts in the file.
steps (List[Step]): The list of steps in the background.
steps (list[Step]): The list of steps in the background.
"""

line_number: int
Expand All @@ -371,7 +369,7 @@ class FeatureParser:
encoding (str): File encoding of the feature file to parse.
"""

def __init__(self, basedir: str, filename: str, encoding: str = "utf-8"):
def __init__(self, basedir: str, filename: str, encoding: str = "utf-8") -> None:
self.abs_filename = os.path.abspath(os.path.join(basedir, filename))
self.rel_filename = os.path.join(os.path.basename(basedir), filename)
self.encoding = encoding
Expand All @@ -380,10 +378,10 @@ def parse_steps(self, steps_data: list[GherkinStep]) -> list[Step]:
"""Parse a list of step data into Step objects.

Args:
steps_data (List[dict]): The list of step data.
steps_data (list[dict]): The list of step data.

Returns:
List[Step]: A list of Step objects.
list[Step]: A list of Step objects.
"""

if not steps_data:
Expand Down Expand Up @@ -423,7 +421,7 @@ def parse_scenario(
Args:
scenario_data (dict): The dictionary containing scenario data.
feature (Feature): The feature to which this scenario belongs.
rule (Optional[Rule]): The rule to which this scenario may belong. (None = no rule)
rule (Rule | None): The rule to which this scenario may belong. (None = no rule)

Returns:
ScenarioTemplate: A ScenarioTemplate object representing the parsed scenario.
Expand Down
8 changes: 4 additions & 4 deletions src/pytest_bdd/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ def parse_step_arguments(step: Step, context: StepFunctionContext) -> dict[str,
"""Parse step arguments."""
parsed_args = context.parser.parse_arguments(step.name)

assert parsed_args is not None, (
f"Unexpected `NoneType` returned from " f"parse_arguments(...) in parser: {context.parser!r}"
)
assert (
parsed_args is not None
), f"Unexpected `NoneType` returned from parse_arguments(...) in parser: {context.parser!r}"

reserved_args = set(parsed_args.keys()) & STEP_ARGUMENTS_RESERVED_NAMES
if reserved_args:
Expand Down Expand Up @@ -386,7 +386,7 @@ def scenario(
feature_name = feature.name or "[Empty]"
raise exceptions.ScenarioNotFound(
f'Scenario "{scenario_name}" in feature "{feature_name}" in {feature.filename} is not found.'
)
) from None

return _get_scenario_decorator(
feature=feature, feature_name=feature_name, templated_scenario=scenario, scenario_name=scenario_name
Expand Down
2 changes: 2 additions & 0 deletions tests/args/cfparse/test_args.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Step arguments tests."""

from __future__ import annotations

import textwrap


Expand Down
2 changes: 2 additions & 0 deletions tests/args/parse/test_args.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Step arguments tests."""

from __future__ import annotations

import textwrap


Expand Down
2 changes: 2 additions & 0 deletions tests/args/regex/test_args.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Step arguments tests."""

from __future__ import annotations

import textwrap


Expand Down
2 changes: 2 additions & 0 deletions tests/args/test_common.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import textwrap

from pytest_bdd.utils import collect_dumped_objects
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import pytest

pytest_plugins = "pytester"
Expand Down
2 changes: 2 additions & 0 deletions tests/datatable/test_datatable.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import textwrap

from src.pytest_bdd.utils import collect_dumped_objects
Expand Down
2 changes: 2 additions & 0 deletions tests/feature/test_alias.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test step alias when decorated multiple times."""

from __future__ import annotations

import textwrap


Expand Down
2 changes: 2 additions & 0 deletions tests/feature/test_background.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test feature background."""

from __future__ import annotations

import textwrap

FEATURE = '''\
Expand Down
2 changes: 2 additions & 0 deletions tests/feature/test_description.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test descriptions."""

from __future__ import annotations

import textwrap


Expand Down
18 changes: 8 additions & 10 deletions tests/feature/test_feature_base_dir.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test feature base dir."""

from __future__ import annotations

import os

import pytest
Expand Down Expand Up @@ -59,12 +61,10 @@ def test_feature_path_by_param_ok(pytester, base_dir):

def prepare_testdir(pytester, ini_base_dir):
pytester.makeini(
"""
f"""
[pytest]
bdd_features_base_dir={}
""".format(
ini_base_dir
)
bdd_features_base_dir={ini_base_dir}
"""
)

feature_file = pytester.mkdir("features").joinpath("steps.feature")
Expand All @@ -77,7 +77,7 @@ def prepare_testdir(pytester, ini_base_dir):
)

pytester.makepyfile(
"""
f"""
import os.path

import pytest
Expand All @@ -103,7 +103,7 @@ def test_not_found_by_ini(scenario_name, multiple):
scenarios(FEATURE)
else:
scenario(FEATURE, scenario_name)
assert os.path.abspath(os.path.join('{}', FEATURE)) in str(exc.value)
assert os.path.abspath(os.path.join('{ini_base_dir}', FEATURE)) in str(exc.value)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -145,7 +145,5 @@ def test_ok_by_param(scenario_name, multiple):
else:
scenario(FEATURE, scenario_name, features_base_dir='features')

""".format(
ini_base_dir
)
"""
)
Loading
Loading