Skip to content

Commit b99d9e1

Browse files
committed
refactor: ruff Python linting over flake8 and black
Run linting workflow through uv and Python 3.13 with a cache.
1 parent d2ec63d commit b99d9e1

26 files changed

+137
-439
lines changed

.github/workflows/test-python.yml

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,25 @@ on:
1111

1212
jobs:
1313
test-python-linting:
14+
runs-on: "ubuntu-latest"
15+
strategy:
16+
fail-fast: false
1417
defaults:
1518
run:
1619
working-directory: python
17-
runs-on: ${{ matrix.os }}
18-
strategy:
19-
fail-fast: false
20-
matrix:
21-
os:
22-
- ubuntu-latest
23-
python-version: ["3.10"]
2420
steps:
2521
- uses: actions/checkout@v5
26-
- name: Set up Python ${{ matrix.python-version }}
27-
uses: actions/setup-python@v6
22+
23+
- name: Install uv and set the python version
24+
uses: astral-sh/setup-uv@v6
2825
with:
29-
python-version: ${{ matrix.python-version }}
26+
enable-cache: true
27+
python-version: "3.13"
28+
version: "0.8.17"
29+
3030
- name: Linting
31-
run: |
32-
python -m pip install pre-commit
33-
pre-commit run --all-files
34-
31+
run: uvx pre-commit run --all-files
32+
3533
test-python:
3634
runs-on: ${{ matrix.os }}
3735
defaults:

python/.pre-commit-config.yaml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
repos:
2-
- repo: https://github.com/psf/black
3-
rev: 22.3.0
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.13.0
44
hooks:
5-
- id: black
6-
- repo: https://github.com/PyCQA/flake8
7-
rev: 4.0.1
8-
hooks:
9-
- id: flake8
10-
args: ['--max-line-length=130','--ignore=E203,W503']
5+
- id: ruff-check
6+
- id: ruff-format

python/cucumber_expressions/argument.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ def __init__(self, group, parameter_type):
1414
self.parameter_type: ParameterType = parameter_type
1515

1616
@staticmethod
17-
def build(
18-
tree_regexp: TreeRegexp, text: str, parameter_types: List
19-
) -> Optional[List[Argument]]:
17+
def build(tree_regexp: TreeRegexp, text: str, parameter_types: List) -> Optional[List[Argument]]:
2018
match_group = tree_regexp.match(text)
2119
if not match_group:
2220
return None
@@ -28,10 +26,7 @@ def build(
2826
f"Group has {len(arg_groups)} capture groups, but there were {len(parameter_types)} parameter types"
2927
)
3028

31-
return [
32-
Argument(arg_group, parameter_type)
33-
for parameter_type, arg_group in zip(parameter_types, arg_groups)
34-
]
29+
return [Argument(arg_group, parameter_type) for parameter_type, arg_group in zip(parameter_types, arg_groups)]
3530

3631
@property
3732
def value(self):

python/cucumber_expressions/ast.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,13 @@ def can_escape(char: str) -> bool:
123123

124124
_escape_chars = [e.value for e in EscapeCharacters]
125125
_demarcation_chars = [e.value for e in DemarcationCharacters]
126-
return any(
127-
char == escape_char for escape_char in _escape_chars + _demarcation_chars
128-
)
126+
return any(char == escape_char for escape_char in _escape_chars + _demarcation_chars)
129127

130128
@staticmethod
131129
def type_of(char: str) -> TokenType:
132130
if char.isspace():
133131
return TokenType.WHITE_SPACE
134-
possible_demarcation_match = [
135-
e.name for e in DemarcationCharacters if e.value == char
136-
]
132+
possible_demarcation_match = [e.name for e in DemarcationCharacters if e.value == char]
137133
if possible_demarcation_match:
138134
_key = possible_demarcation_match[0].replace("_CHARACTER", "")
139135
return TokenType(_key)
@@ -142,11 +138,7 @@ def type_of(char: str) -> TokenType:
142138
@staticmethod
143139
def symbol_of(token: TokenType):
144140
possible_token_character_key = token.name + "_CHARACTER"
145-
if any(
146-
e.name
147-
for e in DemarcationCharacters
148-
if e.name == possible_token_character_key
149-
):
141+
if any(e.name for e in DemarcationCharacters if e.name == possible_token_character_key):
150142
return DemarcationCharacters[possible_token_character_key].value
151143
return ""
152144

python/cucumber_expressions/combinatorial_generated_expression_factory.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,11 @@ def generate_permutations(
2626
if len(generated_expressions) >= MAX_EXPRESSIONS:
2727
return
2828
if depth == len(self.parameter_type_combinations):
29-
generated_expressions.append(
30-
GeneratedExpression(self.expression_template, current_parameter_types)
31-
)
29+
generated_expressions.append(GeneratedExpression(self.expression_template, current_parameter_types))
3230
return
3331
for parameter_type_combination in self.parameter_type_combinations[depth]:
3432
if len(generated_expressions) >= MAX_EXPRESSIONS:
3533
return
3634
new_current_parameter_types = current_parameter_types.copy()
3735
new_current_parameter_types.append(parameter_type_combination)
38-
self.generate_permutations(
39-
generated_expressions, depth + 1, new_current_parameter_types
40-
)
36+
self.generate_permutations(generated_expressions, depth + 1, new_current_parameter_types)

python/cucumber_expressions/errors.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from cucumber_expressions.ast import Token
22

33

4-
def generate_message(
5-
index: int, expression: str, pointer: str, problem: str, solution: str
6-
):
4+
def generate_message(index: int, expression: str, pointer: str, problem: str, solution: str):
75
return "\n".join(
86
[
97
f"This Cucumber Expression has a problem at column {index + 1}:",

python/cucumber_expressions/expression.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ def __init__(self, expression, parameter_type_registry):
2222
self.expression = expression
2323
self.parameter_type_registry = parameter_type_registry
2424
self.parameter_types: List[ParameterType] = []
25-
self.tree_regexp = TreeRegexp(
26-
self.rewrite_to_regex(CucumberExpressionParser().parse(self.expression))
27-
)
25+
self.tree_regexp = TreeRegexp(self.rewrite_to_regex(CucumberExpressionParser().parse(self.expression)))
2826

2927
def match(self, text: str) -> Optional[List[Argument]]:
3028
return Argument.build(self.tree_regexp, text, self.parameter_types)
@@ -60,14 +58,10 @@ def escape_regex(expression) -> str:
6058
def rewrite_optional(self, node: Node):
6159
_possible_node_with_params = self.get_possible_node_with_parameters(node)
6260
if _possible_node_with_params:
63-
raise ParameterIsNotAllowedInOptional(
64-
_possible_node_with_params, self.expression
65-
)
61+
raise ParameterIsNotAllowedInOptional(_possible_node_with_params, self.expression)
6662
_possible_node_with_optionals = self.get_possible_node_with_optionals(node)
6763
if _possible_node_with_optionals:
68-
raise OptionalIsNotAllowedInOptional(
69-
_possible_node_with_optionals, self.expression
70-
)
64+
raise OptionalIsNotAllowedInOptional(_possible_node_with_optionals, self.expression)
7165
if self.are_nodes_empty(node):
7266
raise OptionalMayNotBeEmpty(node, self.expression)
7367
regex = "".join([self.rewrite_to_regex(_node) for _node in node.nodes])
@@ -78,9 +72,7 @@ def rewrite_alternation(self, node: Node):
7872
if not alternative.nodes:
7973
raise AlternativeMayNotBeEmpty(alternative, self.expression)
8074
if self.are_nodes_empty(alternative):
81-
raise AlternativeMayNotExclusivelyContainOptionals(
82-
alternative, self.expression
83-
)
75+
raise AlternativeMayNotExclusivelyContainOptionals(alternative, self.expression)
8476
regex = "|".join([self.rewrite_to_regex(_node) for _node in node.nodes])
8577
return f"(?:{regex})"
8678

python/cucumber_expressions/expression_generator.py

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,46 +24,28 @@ def generate_expressions(self, text: str) -> List[GeneratedExpression]:
2424

2525
def get_matching_parameter_type_matchers():
2626
for parameter_type_matcher in parameter_type_matchers:
27-
advanced_parameter_type_matcher = parameter_type_matcher.advance_to(
28-
pos
29-
)
27+
advanced_parameter_type_matcher = parameter_type_matcher.advance_to(pos)
3028
if advanced_parameter_type_matcher.find:
3129
yield advanced_parameter_type_matcher
3230

33-
matching_parameter_type_matchers = list(
34-
get_matching_parameter_type_matchers()
35-
)
31+
matching_parameter_type_matchers = list(get_matching_parameter_type_matchers())
3632
if not matching_parameter_type_matchers:
3733
break
3834

39-
matching_parameter_type_matchers.sort(
40-
key=functools.cmp_to_key(ParameterTypeMatcher.compare)
41-
)
35+
matching_parameter_type_matchers.sort(key=functools.cmp_to_key(ParameterTypeMatcher.compare))
4236
best_parameter_type_matcher = matching_parameter_type_matchers[0]
4337
best_parameter_type_matchers = list(
4438
filter(
45-
lambda m: ParameterTypeMatcher.compare(
46-
m, best_parameter_type_matcher
47-
)
48-
== 0,
39+
lambda m: ParameterTypeMatcher.compare(m, best_parameter_type_matcher) == 0,
4940
matching_parameter_type_matchers,
5041
)
5142
)
5243
# Filter duplicates
53-
parameter_types = {
54-
_parameter_type_matcher.parameter_type
55-
for _parameter_type_matcher in best_parameter_type_matchers
56-
}
57-
parameter_type_combinations.append(
58-
sorted(parameter_types, key=functools.cmp_to_key(ParameterType.compare))
59-
)
60-
expression_template.append(
61-
self.escape(text[pos : best_parameter_type_matcher.start])
62-
)
44+
parameter_types = {_parameter_type_matcher.parameter_type for _parameter_type_matcher in best_parameter_type_matchers}
45+
parameter_type_combinations.append(sorted(parameter_types, key=functools.cmp_to_key(ParameterType.compare)))
46+
expression_template.append(self.escape(text[pos : best_parameter_type_matcher.start]))
6347
expression_template.append("{%s}")
64-
pos = best_parameter_type_matcher.start + len(
65-
best_parameter_type_matcher.group
66-
)
48+
pos = best_parameter_type_matcher.start + len(best_parameter_type_matcher.group)
6749
if pos >= len(text):
6850
break
6951
expression_template.append(self.escape(text[pos:]))
@@ -73,27 +55,15 @@ def get_matching_parameter_type_matchers():
7355

7456
@staticmethod
7557
def escape(string: str) -> str:
76-
return (
77-
string.replace("%", "%%")
78-
.replace(r"(", "\\(")
79-
.replace(r"{", "\\{")
80-
.replace(r"/", "\\/")
81-
)
58+
return string.replace("%", "%%").replace(r"(", "\\(").replace(r"{", "\\{").replace(r"/", "\\/")
8259

8360
def create_parameter_type_matchers(self, text) -> List[ParameterTypeMatcher]:
8461
parameter_type_matchers = []
8562
for parameter_type in self.parameter_type_registry.parameter_types:
8663
if parameter_type.use_for_snippets:
87-
parameter_type_matchers.extend(
88-
self.create_parameter_type_matchers_with_type(parameter_type, text)
89-
)
64+
parameter_type_matchers.extend(self.create_parameter_type_matchers_with_type(parameter_type, text))
9065
return parameter_type_matchers
9166

9267
@staticmethod
93-
def create_parameter_type_matchers_with_type(
94-
parameter_type, text
95-
) -> List[ParameterTypeMatcher]:
96-
return [
97-
ParameterTypeMatcher(parameter_type, re.compile(f"({regexp})"), text, 0)
98-
for regexp in parameter_type.regexps
99-
]
68+
def create_parameter_type_matchers_with_type(parameter_type, text) -> List[ParameterTypeMatcher]:
69+
return [ParameterTypeMatcher(parameter_type, re.compile(f"({regexp})"), text, 0) for regexp in parameter_type.regexps]

python/cucumber_expressions/expression_parser.py

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ class Parser(NamedTuple):
2323

2424

2525
class CucumberExpressionParser:
26-
2726
# text == whitespace | ')' | '}' | .
2827
@staticmethod
2928
def parse_text(parser: Parser):
@@ -34,9 +33,7 @@ def parse_text(parser: Parser):
3433
TokenType.END_PARAMETER,
3534
TokenType.END_OPTIONAL,
3635
]:
37-
return Result(
38-
1, Node(NodeType.TEXT, None, token.text, token.start, token.end)
39-
)
36+
return Result(1, Node(NodeType.TEXT, None, token.text, token.start, token.end))
4037
if token.ast_type == TokenType.ALTERNATION:
4138
raise AlternationNotAllowedInOptional(parser.expression, token)
4239
# If configured correctly this will never happen
@@ -47,9 +44,7 @@ def parse_text(parser: Parser):
4744
def parse_name(parser: Parser):
4845
token = parser.tokens[parser.current]
4946
if token.ast_type in [TokenType.WHITE_SPACE, TokenType.TEXT]:
50-
return Result(
51-
1, Node(NodeType.TEXT, None, token.text, token.start, token.end)
52-
)
47+
return Result(1, Node(NodeType.TEXT, None, token.text, token.start, token.end))
5348
if token.ast_type in [
5449
TokenType.BEGIN_PARAMETER,
5550
TokenType.END_PARAMETER,
@@ -90,9 +85,7 @@ def parse_alternative_separator(parser: Parser) -> Result:
9085
if not self.looking_at(tokens, parser.current, TokenType.ALTERNATION):
9186
return Result(0, None)
9287
token = tokens[parser.current]
93-
return Result(
94-
1, Node(NodeType.ALTERNATIVE, None, token.text, token.start, token.end)
95-
)
88+
return Result(1, Node(NodeType.ALTERNATIVE, None, token.text, token.start, token.end))
9689

9790
alternative_parsers = [
9891
parse_alternative_separator,
@@ -130,11 +123,7 @@ def parse_alternation(parser: Parser) -> Result:
130123
],
131124
)
132125
sub_current = parser.current + consumed
133-
if not [
134-
_ast_node.ast_type
135-
for _ast_node in ast_nodes
136-
if _ast_node.ast_type == NodeType.ALTERNATIVE
137-
]:
126+
if not [_ast_node.ast_type for _ast_node in ast_nodes if _ast_node.ast_type == NodeType.ALTERNATIVE]:
138127
return Result(0, None)
139128

140129
start = tokens[parser.current].start
@@ -204,9 +193,7 @@ def _parse_between(parser: Parser):
204193
return _parse_between
205194

206195
@staticmethod
207-
def parse_token(
208-
expression, parsers: List, tokens: List[Token], start_at: int
209-
) -> Result:
196+
def parse_token(expression, parsers: List, tokens: List[Token], start_at: int) -> Result:
210197
for parser in parsers:
211198
consumed, ast = parser(Parser(expression, tokens, start_at))
212199
if consumed:
@@ -237,12 +224,8 @@ def parse_tokens_until(
237224
ast.append(result.ast_node)
238225
return current - start_at, ast
239226

240-
def looking_at_any(
241-
self, tokens: List[Token], position: int, token_types: List[TokenType]
242-
) -> bool:
243-
return any(
244-
self.looking_at(tokens, position, token_type) for token_type in token_types
245-
)
227+
def looking_at_any(self, tokens: List[Token], position: int, token_types: List[TokenType]) -> bool:
228+
return any(self.looking_at(tokens, position, token_type) for token_type in token_types)
246229

247230
@staticmethod
248231
def looking_at(tokens: List[Token], position: int, token_type: TokenType) -> bool:
@@ -254,9 +237,7 @@ def looking_at(tokens: List[Token], position: int, token_type: TokenType) -> boo
254237
return token_type == TokenType.END_OF_LINE
255238
return tokens[position].ast_type == token_type
256239

257-
def split_alternatives(
258-
self, start: int, end: int, alternation: List[Node]
259-
) -> List[Node]:
240+
def split_alternatives(self, start: int, end: int, alternation: List[Node]) -> List[Node]:
260241
separators = []
261242
alternatives = []
262243
alternative = []
@@ -271,9 +252,7 @@ def split_alternatives(
271252
return list(self.create_alternative_nodes(start, end, separators, alternatives))
272253

273254
@staticmethod
274-
def create_alternative_nodes(
275-
start: int, end: int, separators: List, alternatives: List
276-
) -> List[Node]:
255+
def create_alternative_nodes(start: int, end: int, separators: List, alternatives: List) -> List[Node]:
277256
for index, alternative in enumerate(alternatives):
278257
if index == 0:
279258
right_separator = separators[index]
@@ -286,9 +265,7 @@ def create_alternative_nodes(
286265
)
287266
elif index == len(alternatives) - 1:
288267
left_separator = separators[index - 1]
289-
yield Node(
290-
NodeType.ALTERNATIVE, alternative, None, left_separator.end, end
291-
)
268+
yield Node(NodeType.ALTERNATIVE, alternative, None, left_separator.end, end)
292269
else:
293270
left_separator = separators[index - 1]
294271
right_separator = separators[index]

python/cucumber_expressions/expression_tokenizer.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,5 @@ def token_type_of(self, char: str, treat_as_text) -> TokenType:
9292
)
9393

9494
@staticmethod
95-
def should_create_new_token(
96-
previous_token_type: TokenType, current_token_type: TokenType
97-
):
98-
return current_token_type != previous_token_type or (
99-
current_token_type not in [TokenType.WHITE_SPACE, TokenType.TEXT]
100-
)
95+
def should_create_new_token(previous_token_type: TokenType, current_token_type: TokenType):
96+
return current_token_type != previous_token_type or (current_token_type not in [TokenType.WHITE_SPACE, TokenType.TEXT])

0 commit comments

Comments
 (0)