Skip to content

Commit 39b9401

Browse files
committed
fix: target python version
Allow any valid python version to be set as the target version so that users don't need to install a new package version each time a new Python version is released. Add tests for validating the target version configuration. Clarify the meaning of the target version in the doc comment. Fixes: #63
1 parent 7221ea6 commit 39b9401

File tree

5 files changed

+83
-4
lines changed

5 files changed

+83
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ _build/
2424
/.ghtopdep_cache/
2525
/worktrees/
2626
/.ruff_cache/
27+
__pycache__/

pytest_examples/config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations as _annotations
22

33
import hashlib
4+
import re
45
import tempfile
56
from dataclasses import dataclass
67
from pathlib import Path
@@ -21,7 +22,7 @@ class ExamplesConfig:
2122
line_length: int = DEFAULT_LINE_LENGTH
2223
quotes: Literal['single', 'double', 'either'] = 'either'
2324
magic_trailing_comma: bool = True
24-
target_version: Literal['py37', 'py38', 'py39', 'py310', 'py311'] = 'py37'
25+
target_version: str | None = 'py37'
2526
upgrade: bool = False
2627
isort: bool = False
2728
ruff_line_length: int | None = None
@@ -30,6 +31,11 @@ class ExamplesConfig:
3031
white_space_dot: bool = False
3132
"""If True, replace spaces with `·` in example diffs."""
3233

34+
def __post_init__(self):
35+
"""Validate the configuration after initialization."""
36+
if self.target_version and not re.match(r'py\d{2,}$', self.target_version):
37+
raise ValueError(f'Invalid target version: {self.target_version!r}, must be like "py37"')
38+
3339
def black_mode(self):
3440
return BlackMode(
3541
line_length=self.line_length,

pytest_examples/eval_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def set_config(
3737
line_length: int = DEFAULT_LINE_LENGTH,
3838
quotes: Literal['single', 'double', 'either'] = 'either',
3939
magic_trailing_comma: bool = True,
40-
target_version: Literal['py37', 'py38', 'py39', 'py310', 'py310'] = 'py37',
40+
target_version: str | None = 'py37',
4141
upgrade: bool = False,
4242
isort: bool = False,
4343
ruff_line_length: int | None = None,
@@ -50,7 +50,7 @@ def set_config(
5050
line_length: The line length to use when wrapping print statements, defaults to 88.
5151
quotes: The quote to use, defaults to "either".
5252
magic_trailing_comma: If True, add a trailing comma to magic methods, defaults to True.
53-
target_version: The target version to use when upgrading code, defaults to "py37".
53+
target_version: The target version to use when checking and formatting code, defaults to "py37".
5454
upgrade: If True, upgrade the code to the target version, defaults to False.
5555
isort: If True, run ruff's isort extension on the code, defaults to False.
5656
ruff_line_length: In general, we disable line-length checks in ruff, to let black take care of them.

pytest_examples/lint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def ruff_format(
2727
*,
2828
ignore_errors: bool = False,
2929
) -> str:
30-
args = ('--fix',)
30+
args: tuple[str, ...] = ('--fix',)
3131
if ignore_errors:
3232
args += ('--exit-zero',)
3333
try:

tests/test_config.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from dataclasses import dataclass
2+
from typing import Any
3+
4+
import pytest
5+
6+
from pytest_examples.config import ExamplesConfig
7+
8+
9+
@dataclass
10+
class TargetVersionTestCase:
11+
id: str
12+
target_version: Any
13+
14+
15+
@pytest.mark.parametrize(
16+
'case',
17+
[
18+
# Valid target versions
19+
TargetVersionTestCase('py37', 'py37'),
20+
TargetVersionTestCase('py38', 'py38'),
21+
TargetVersionTestCase('py39', 'py39'),
22+
TargetVersionTestCase('py310', 'py310'),
23+
TargetVersionTestCase('py311', 'py311'),
24+
TargetVersionTestCase('py312', 'py312'),
25+
TargetVersionTestCase('py313', 'py313'),
26+
TargetVersionTestCase('py314', 'py314'),
27+
TargetVersionTestCase('py3100', 'py3100'),
28+
],
29+
ids=lambda case: case.id,
30+
)
31+
def test_examples_config_valid_target_version(case: TargetVersionTestCase):
32+
"""Test that ExamplesConfig validates target_version correctly during initialization."""
33+
config = ExamplesConfig(target_version=case.target_version)
34+
assert config.target_version == case.target_version
35+
36+
37+
@pytest.mark.parametrize(
38+
'case',
39+
[
40+
TargetVersionTestCase('missing_py', '37'),
41+
TargetVersionTestCase('python_word', 'python37'),
42+
TargetVersionTestCase('single_digit', 'py3'),
43+
TargetVersionTestCase('dots', 'py3.7'),
44+
TargetVersionTestCase('spaces', 'py 37'),
45+
TargetVersionTestCase('uppercase', 'PY37'),
46+
TargetVersionTestCase('mixed_case', 'Py37'),
47+
TargetVersionTestCase('letters_before_digits', 'py3a7'),
48+
TargetVersionTestCase('hyphen', 'py-37'),
49+
TargetVersionTestCase('underscore', 'py_37'),
50+
TargetVersionTestCase('suffix', 'py37!'),
51+
TargetVersionTestCase('text_suffix', 'py37abc'),
52+
],
53+
ids=lambda case: case.id,
54+
)
55+
def test_examples_config_invalid_target_version(case: TargetVersionTestCase):
56+
"""Test that ExamplesConfig validates target_version correctly during initialization."""
57+
with pytest.raises(ValueError, match=f'Invalid target version: {case.target_version!r}'):
58+
ExamplesConfig(target_version=case.target_version)
59+
60+
61+
def test_examples_config_empty_string_target_version():
62+
"""Test that empty string target_version is accepted without validation."""
63+
# Based on the validation logic, empty string should not raise an error
64+
# because the check is 'if self.target_version' which is falsy for empty string
65+
config = ExamplesConfig(target_version='')
66+
assert config.target_version == ''
67+
68+
69+
def test_examples_config_target_version_error_message():
70+
"""Test that the error message includes the expected format."""
71+
with pytest.raises(ValueError, match='must be like "py37"'):
72+
ExamplesConfig(target_version='invalid')

0 commit comments

Comments
 (0)