Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to skip normalising requirement versions #150

Merged
merged 7 commits into from
Nov 1, 2023
Merged
13 changes: 12 additions & 1 deletion src/pyproject_fmt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@ class PyProjectFmtNamespace(Namespace):
stdout: bool
indent: int
check: bool
keep_full_version: bool

@property
def configs(self) -> list[Config]:
""":return: configurations"""
return [Config(toml, toml.read_text(encoding="utf-8"), self.indent) for toml in self.inputs]
return [
Config(
pyproject_toml=toml,
toml=toml.read_text(encoding="utf-8"),
indent=self.indent,
keep_full_version=self.keep_full_version,
)
for toml in self.inputs
]


def pyproject_toml_path_creator(argument: str) -> Path:
Expand Down Expand Up @@ -71,6 +80,8 @@ def _build_cli() -> ArgumentParser:
group.add_argument("-s", "--stdout", action="store_true", help=msg)
msg = "check and fail if any input would be formatted, printing any diffs"
group.add_argument("--check", action="store_true", help=msg)
msg = "keep full dependency versions. For example do not change version 1.0.0 to 1"
group.add_argument("--keep-full-version", action="store_true", help=msg)
parser.add_argument(
"--indent",
type=int,
Expand Down
6 changes: 5 additions & 1 deletion src/pyproject_fmt/formatter/build_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ def fmt_build_system(parsed: TOMLDocument, conf: Config) -> None:
"""
system = cast(Optional[Table], parsed.get("build-system"))
if system is not None:
normalize_pep508_array(cast(Optional[Array], system.get("requires")), conf.indent)
normalize_pep508_array(
requires_array=cast(Optional[Array], system.get("requires")),
indent=conf.indent,
keep_full_version=conf.keep_full_version,
)
sorted_array(cast(Optional[Array], system.get("backend-path")), indent=conf.indent)
order_keys(system, ("build-backend", "requires", "backend-path"))
ensure_newline_at_end(system)
Expand Down
1 change: 1 addition & 0 deletions src/pyproject_fmt/formatter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Config:
pyproject_toml: Path
toml: str #: the text to format
indent: int = DEFAULT_INDENT #: indentation to apply
keep_full_version: bool = False


__all__ = [
Expand Down
7 changes: 5 additions & 2 deletions src/pyproject_fmt/formatter/pep508.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ def _best_effort_string_repr(req: str) -> String:
return toml_string(req)


def normalize_pep508_array(requires_array: Array | None, indent: int) -> None:
def normalize_pep508_array(requires_array: Array | None, indent: int, *, keep_full_version: bool) -> None:
"""
Normalize a TOML array via PEP-508.

:param requires_array: the input array
:param indent: indentation level
:param keep_full_version: whether to preserve, and therefore not normalize, requirements
"""
if requires_array is None:
return
# first normalize values
for at in range(len(requires_array)):
normalized = _best_effort_string_repr(normalize_req(str(requires_array[at])))
initial_requirement_string = str(requires_array[at])
req_string = initial_requirement_string if keep_full_version else normalize_req(initial_requirement_string)
normalized = _best_effort_string_repr(req=req_string)
requires_array[at] = normalized
# then sort
sorted_array(requires_array, indent, key=lambda e: Requirement(e.text).name.lower())
Expand Down
12 changes: 10 additions & 2 deletions src/pyproject_fmt/formatter/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,19 @@ def fmt_project(parsed: TOMLDocument, conf: Config) -> None: # noqa: C901

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

normalize_pep508_array(cast(Optional[Array], project.get("dependencies")), conf.indent)
normalize_pep508_array(
requires_array=cast(Optional[Array], project.get("dependencies")),
indent=conf.indent,
keep_full_version=conf.keep_full_version,
)
if "optional-dependencies" in project:
opt_deps = cast(Table, project["optional-dependencies"])
for value in opt_deps.values():
normalize_pep508_array(cast(Array, value), conf.indent)
normalize_pep508_array(
requires_array=cast(Array, value),
indent=conf.indent,
keep_full_version=conf.keep_full_version,
)
order_keys(opt_deps, (), sort_key=lambda k: k[0]) # pragma: no branch

for of_type in ("scripts", "gui-scripts", "entry-points", "urls"):
Expand Down
28 changes: 28 additions & 0 deletions tests/formatter/test_build_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,31 @@ def test_indent(fmt: Fmt, indent: int) -> None:
"""
config = Config(pyproject_toml=Path(), toml=dedent(txt), indent=indent)
fmt(fmt_build_system, config, expected)


def test_keep_full_version_on(fmt: Fmt) -> None:
txt = """
[build-system]
requires = [
"A==1.0.0",
]
"""
config = Config(pyproject_toml=Path(), toml=dedent(txt), indent=2, keep_full_version=True)
fmt(fmt_build_system, config, txt)


def test_keep_full_version_off(fmt: Fmt) -> None:
txt = """
[build-system]
requires = [
"A==1.0.0",
]
"""
expected = """
[build-system]
requires = [
"A==1",
]
"""
config = Config(pyproject_toml=Path(), toml=dedent(txt), indent=2, keep_full_version=False)
fmt(fmt_build_system, config, expected)
22 changes: 21 additions & 1 deletion tests/formatter/test_pep508.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ def test_normalize_pep508_array(indent: int) -> None:
"""
parsed = parse(toml_document_string)
dependencies = parsed["requirements"]
normalize_pep508_array(requires_array=cast(Array, dependencies), indent=indent)
normalize_pep508_array(
requires_array=cast(Array, dependencies),
indent=indent,
keep_full_version=False,
)
assert dependencies == ["zzz>=1.1.1", "pytest==6"]
expected_string = dedent(
f"""\
Expand All @@ -66,3 +70,19 @@ def test_normalize_pep508_array(indent: int) -> None:
""",
).strip()
assert dependencies.as_string() == expected_string


def test_normalize_pep508_array_keep_versions() -> None:
toml_document_string = """
requirements = [
"pytest==6.0.0",
]
"""
parsed = parse(toml_document_string)
dependencies = parsed["requirements"]
normalize_pep508_array(
requires_array=cast(Array, dependencies),
indent=2,
keep_full_version=True,
)
assert dependencies == ["pytest==6.0.0"]
40 changes: 40 additions & 0 deletions tests/formatter/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,43 @@ def test_indent(fmt: Fmt, indent: int) -> None:
"""
config = Config(pyproject_toml=Path(), toml=dedent(txt), indent=indent)
fmt(fmt_project, config, expected)


def test_keep_full_version_on(fmt: Fmt) -> None:
txt = """
[project]
dependencies = [
"A==1.0.0",
]
[project.optional-dependencies]
docs = [
"B==2.0.0",
]
"""
config = Config(pyproject_toml=Path(), toml=dedent(txt), indent=2, keep_full_version=True)
fmt(fmt_project, config, txt)


def test_keep_full_version_off(fmt: Fmt) -> None:
txt = """
[project]
dependencies = [
"A==1.0.0",
]
[project.optional-dependencies]
docs = [
"B==2.0.0",
]
"""
expected = """
[project]
dependencies = [
"A==1",
]
[project.optional-dependencies]
docs = [
"B==2",
]
"""
config = Config(pyproject_toml=Path(), toml=dedent(txt), indent=2, keep_full_version=False)
fmt(fmt_project, config, expected)
24 changes: 24 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,27 @@ def test_indent(tmp_path: Path, indent: int) -> None:
run(args)
output = pyproject_toml.read_text()
assert output == dedent(expected)


def test_keep_full_version_cli(tmp_path: Path) -> None:
start = """\
[build-system]
requires = [
"A==1.0.0",
]

[project]
dependencies = [
"A==1.0.0",
]
[project.optional-dependencies]
docs = [
"B==2.0.0",
]
"""
pyproject_toml = tmp_path / "pyproject.toml"
pyproject_toml.write_text(dedent(start))
args = [str(pyproject_toml), "--keep-full-version"]
run(args)
output = pyproject_toml.read_text()
assert output == dedent(start)