Skip to content

Commit

Permalink
Support adding the classifier for the current Python prerelease (#162)
Browse files Browse the repository at this point in the history
Co-authored-by: Bernát Gábor <[email protected]>
  • Loading branch information
edgarrmondragon and gaborbernat committed Jan 10, 2024
1 parent 1fa8cdc commit e1ccea9
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 9 deletions.
12 changes: 11 additions & 1 deletion src/pyproject_fmt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
from pathlib import Path
from typing import Sequence

from packaging.version import Version

from ._version import __version__
from .formatter.config import DEFAULT_INDENT, Config
from .formatter.config import DEFAULT_INDENT, DEFAULT_MAX_SUPPORTED_PYTHON, Config


class PyProjectFmtNamespace(Namespace):
Expand All @@ -23,6 +25,7 @@ class PyProjectFmtNamespace(Namespace):
indent: int
check: bool
keep_full_version: bool
max_supported_python: Version

@property
def configs(self) -> list[Config]:
Expand All @@ -33,6 +36,7 @@ def configs(self) -> list[Config]:
toml=toml.read_text(encoding="utf-8"),
indent=self.indent,
keep_full_version=self.keep_full_version,
max_supported_python=self.max_supported_python,
)
for toml in self.inputs
]
Expand Down Expand Up @@ -88,6 +92,12 @@ def _build_cli() -> ArgumentParser:
default=DEFAULT_INDENT,
help="number of spaces to indent",
)
parser.add_argument(
"--max-supported-python",
type=Version,
default=DEFAULT_MAX_SUPPORTED_PYTHON,
help="latest Python version the project supports (e.g. 3.13)",
)
msg = "pyproject.toml file(s) to format"
parser.add_argument("inputs", nargs="+", type=pyproject_toml_path_creator, help=msg)
return parser
Expand Down
7 changes: 6 additions & 1 deletion src/pyproject_fmt/formatter/config.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""Defines configuration for the formatter."""
from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from packaging.version import Version

if TYPE_CHECKING:
from pathlib import Path
from typing import Final


DEFAULT_INDENT: Final[int] = 2 #: default indentation level
DEFAULT_MAX_SUPPORTED_PYTHON: Final[str] = "3.12" #: default maximum supported Python version


@dataclass(frozen=True)
Expand All @@ -20,9 +23,11 @@ class Config:
toml: str #: the text to format
indent: int = DEFAULT_INDENT #: indentation to apply
keep_full_version: bool = False
max_supported_python: Version = field(default_factory=lambda: Version(DEFAULT_MAX_SUPPORTED_PYTHON))


__all__ = [
"Config",
"DEFAULT_INDENT",
"DEFAULT_MAX_SUPPORTED_PYTHON",
]
14 changes: 7 additions & 7 deletions src/pyproject_fmt/formatter/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from .config import Config

_PY_MIN_VERSION: int = 7
_PY_MAX_VERSION: int = 12


def fmt_project(parsed: TOMLDocument, conf: Config) -> None: # noqa: C901
Expand All @@ -45,7 +44,7 @@ def fmt_project(parsed: TOMLDocument, conf: Config) -> None: # noqa: C901
sorted_array(cast(Optional[Array], project.get("dynamic")), indent=conf.indent)

if "requires-python" in project:
_add_py_classifiers(project)
_add_py_classifiers(project, py_max_version=conf.max_supported_python)

sorted_array(cast(Optional[Array], project.get("classifiers")), indent=conf.indent, custom_sort="natsort")

Expand Down Expand Up @@ -105,11 +104,11 @@ def fmt_project(parsed: TOMLDocument, conf: Config) -> None: # noqa: C901
ensure_newline_at_end(project)


def _add_py_classifiers(project: Table) -> None:
def _add_py_classifiers(project: Table, *, py_max_version: Version) -> None:
specifiers = SpecifierSet(project.get("requires-python", f">=3.{_PY_MIN_VERSION}"))

min_version = _get_min_version_classifier(specifiers)
max_version = _get_max_version_classifier(specifiers)
max_version = _get_max_version_classifier(specifiers, py_max_version=py_max_version)

allowed_versions = list(specifiers.filter(f"3.{v}" for v in range(min_version, max_version + 1)))

Expand Down Expand Up @@ -142,18 +141,19 @@ def _get_min_version_classifier(specifiers: SpecifierSet) -> int:
min_version.append(Version(specifier.version).minor)
if specifier.operator == ">":
min_version.append(Version(specifier.version).minor + 1)
return min(min_version) if min_version else _PY_MIN_VERSION
return min(min_version, default=_PY_MIN_VERSION)


def _get_max_version_classifier(specifiers: SpecifierSet) -> int:
def _get_max_version_classifier(specifiers: SpecifierSet, *, py_max_version: Version) -> int:
max_version: list[int] = []

for specifier in specifiers:
if specifier.operator == "<=":
max_version.append(Version(specifier.version).minor)
if specifier.operator == "<":
max_version.append(Version(specifier.version).minor - 1)
return max(max_version) if max_version else (_get_max_version_tox() or _PY_MAX_VERSION)

return max(max_version) if max_version else (_get_max_version_tox() or py_max_version.minor)


def _get_max_version_tox() -> int | None:
Expand Down
35 changes: 35 additions & 0 deletions tests/formatter/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import TYPE_CHECKING

import pytest
from packaging.version import Version

from pyproject_fmt.formatter.config import Config
from pyproject_fmt.formatter.project import fmt_project
Expand Down Expand Up @@ -373,6 +374,40 @@ def test_classifier_two_upper_bounds(fmt: Fmt) -> None:
fmt(fmt_project, start, expected)


def test_classifier_prerelease(fmt: Fmt) -> None:
txt = """
[project]
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
"""
expected = """
[project]
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
]
"""
config = Config(
pyproject_toml=Path(),
toml=dedent(txt),
max_supported_python=Version("3.15"),
)

fmt(fmt_project, config, expected)


def test_classifier_gt_tox(fmt: Fmt, tmp_path: Path) -> None:
(tmp_path / "tox.ini").write_text("[tox]\nenv_list = py{311,312}-{magic}")
start = """
Expand Down

0 comments on commit e1ccea9

Please sign in to comment.