Skip to content

fix: target python version#64

Open
stevenh wants to merge 1 commit into
pydantic:mainfrom
stevenh:fix/target-version
Open

fix: target python version#64
stevenh wants to merge 1 commit into
pydantic:mainfrom
stevenh:fix/target-version

Conversation

@stevenh

@stevenh stevenh commented Jun 24, 2025

Copy link
Copy Markdown

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

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: pydantic#63
@hramezani

Copy link
Copy Markdown

Sorry for the delay in reviewing this 🙏

Nice fix for the set_config/ExamplesConfig drift and the py310 typo. A few things I think are needed before this is complete:

  1. Validate against actual Black support, not a regex. black_mode() does BlackTargetVersion[target_version.upper()], so any well-formed-but-unsupported value still KeyErrors at format time. py315, py3100, and py99 all pass py\d{2,}$ but crash Black — and the test suite currently asserts py3100 is valid, which codifies that. Checking membership in BlackTargetVersion fixes it and auto-tracks new versions without maintenance:
    valid = {v.name.lower() for v in BlackTargetVersion}
    if self.target_version and self.target_version.lower() not in valid:
        raise ValueError(f'Invalid target_version {self.target_version!r}; supported: {", ".join(sorted(valid))}')
  2. Regex edge case: $ matches before a trailing newline, so 'py37\n' passes. Moot if you switch to the membership check.
  3. Keep Literal[...] | str rather than plain str — type checkers still autocomplete the known versions while accepting new ones, which keeps the discoverability the Literal gave.
  4. Add a version-behavior test, not just validation. With upgrade=True, ruff's UP007 (Use X | Y) only fires at py310+, so the same snippet formats differently across versions — a parametrized test locks that in and proves py312/py313 actually flow through Black+ruff:
    union_code = 'from typing import Union\n\n\ndef foo(x: Union[int, str]):\n    pass\n'
    
    @pytest.mark.parametrize('v', ['py37', 'py38', 'py39'])
    def test_upgrade_keeps_union_pre_py310(v):
        ruff_check(CodeExample.create(union_code), ExamplesConfig(target_version=v, upgrade=True))
    
    @pytest.mark.parametrize('v', ['py310', 'py311', 'py312', 'py313'])
    def test_upgrade_rewrites_union_py310_plus(v):
        with pytest.raises(FormatError, match='UP007 .* Use `X | Y`'):
            ruff_check(CodeExample.create(union_code), ExamplesConfig(target_version=v, upgrade=True))

No pressure on turnaround — if you don't have time to make these changes, I'm happy to take care of them in a separate follow-up PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

target_versions are out of date

2 participants