Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ _build/
/.ghtopdep_cache/
/worktrees/
/.ruff_cache/
__pycache__/
8 changes: 7 additions & 1 deletion pytest_examples/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations as _annotations

import hashlib
import re
import tempfile
from dataclasses import dataclass
from pathlib import Path
Expand All @@ -21,7 +22,7 @@ class ExamplesConfig:
line_length: int = DEFAULT_LINE_LENGTH
quotes: Literal['single', 'double', 'either'] = 'either'
magic_trailing_comma: bool = True
target_version: Literal['py37', 'py38', 'py39', 'py310', 'py311'] = 'py37'
target_version: str | None = 'py37'
upgrade: bool = False
isort: bool = False
ruff_line_length: int | None = None
Expand All @@ -30,6 +31,11 @@ class ExamplesConfig:
white_space_dot: bool = False
"""If True, replace spaces with `·` in example diffs."""

def __post_init__(self):
"""Validate the configuration after initialization."""
if self.target_version and not re.match(r'py\d{2,}$', self.target_version):
raise ValueError(f'Invalid target version: {self.target_version!r}, must be like "py37"')

def black_mode(self):
return BlackMode(
line_length=self.line_length,
Expand Down
4 changes: 2 additions & 2 deletions pytest_examples/eval_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def set_config(
line_length: int = DEFAULT_LINE_LENGTH,
quotes: Literal['single', 'double', 'either'] = 'either',
magic_trailing_comma: bool = True,
target_version: Literal['py37', 'py38', 'py39', 'py310', 'py310'] = 'py37',
target_version: str | None = 'py37',
upgrade: bool = False,
isort: bool = False,
ruff_line_length: int | None = None,
Expand All @@ -50,7 +50,7 @@ def set_config(
line_length: The line length to use when wrapping print statements, defaults to 88.
quotes: The quote to use, defaults to "either".
magic_trailing_comma: If True, add a trailing comma to magic methods, defaults to True.
target_version: The target version to use when upgrading code, defaults to "py37".
target_version: The target version to use when checking and formatting code, defaults to "py37".
upgrade: If True, upgrade the code to the target version, defaults to False.
isort: If True, run ruff's isort extension on the code, defaults to False.
ruff_line_length: In general, we disable line-length checks in ruff, to let black take care of them.
Expand Down
2 changes: 1 addition & 1 deletion pytest_examples/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def ruff_format(
*,
ignore_errors: bool = False,
) -> str:
args = ('--fix',)
args: tuple[str, ...] = ('--fix',)
if ignore_errors:
args += ('--exit-zero',)
try:
Expand Down
72 changes: 72 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from dataclasses import dataclass
from typing import Any

import pytest

from pytest_examples.config import ExamplesConfig


@dataclass
class TargetVersionTestCase:
id: str
target_version: Any


@pytest.mark.parametrize(
'case',
[
# Valid target versions
TargetVersionTestCase('py37', 'py37'),
TargetVersionTestCase('py38', 'py38'),
TargetVersionTestCase('py39', 'py39'),
TargetVersionTestCase('py310', 'py310'),
TargetVersionTestCase('py311', 'py311'),
TargetVersionTestCase('py312', 'py312'),
TargetVersionTestCase('py313', 'py313'),
TargetVersionTestCase('py314', 'py314'),
TargetVersionTestCase('py3100', 'py3100'),
],
ids=lambda case: case.id,
)
def test_examples_config_valid_target_version(case: TargetVersionTestCase):
"""Test that ExamplesConfig validates target_version correctly during initialization."""
config = ExamplesConfig(target_version=case.target_version)
assert config.target_version == case.target_version


@pytest.mark.parametrize(
'case',
[
TargetVersionTestCase('missing_py', '37'),
TargetVersionTestCase('python_word', 'python37'),
TargetVersionTestCase('single_digit', 'py3'),
TargetVersionTestCase('dots', 'py3.7'),
TargetVersionTestCase('spaces', 'py 37'),
TargetVersionTestCase('uppercase', 'PY37'),
TargetVersionTestCase('mixed_case', 'Py37'),
TargetVersionTestCase('letters_before_digits', 'py3a7'),
TargetVersionTestCase('hyphen', 'py-37'),
TargetVersionTestCase('underscore', 'py_37'),
TargetVersionTestCase('suffix', 'py37!'),
TargetVersionTestCase('text_suffix', 'py37abc'),
],
ids=lambda case: case.id,
)
def test_examples_config_invalid_target_version(case: TargetVersionTestCase):
"""Test that ExamplesConfig validates target_version correctly during initialization."""
with pytest.raises(ValueError, match=f'Invalid target version: {case.target_version!r}'):
ExamplesConfig(target_version=case.target_version)


def test_examples_config_empty_string_target_version():
"""Test that empty string target_version is accepted without validation."""
# Based on the validation logic, empty string should not raise an error
# because the check is 'if self.target_version' which is falsy for empty string
config = ExamplesConfig(target_version='')
assert config.target_version == ''


def test_examples_config_target_version_error_message():
"""Test that the error message includes the expected format."""
with pytest.raises(ValueError, match='must be like "py37"'):
ExamplesConfig(target_version='invalid')
Loading