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
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-24.04
tools:
python: "3.13"
python: "3.14"
commands:
- asdf plugin add uv
- asdf install uv latest
Expand Down
13 changes: 13 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"erbsland.sphinx.ansi",
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.extlinks",
"sphinx.ext.intersphinx",
"sphinx_toolbox.more_autodoc.autotypeddict",
"sphinxcontrib.programoutput",
]

# General information about the project.
Expand Down Expand Up @@ -104,3 +106,14 @@
"python": ("https://docs.python.org/3/", None),
"pypug": ("https://packaging.python.org/", None),
}


# -- Options for programout ----------------------------------------------------------
# https://sphinxcontrib-programoutput.readthedocs.io

programoutput_use_ansi = True

# Needed to ensure color output
# See https://github.com/OpenNTI/sphinxcontrib-programoutput/issues/77
os.environ["FORCE_COLOR"] = "1"
os.environ.pop("NO_COLOR", None)
14 changes: 14 additions & 0 deletions docs/version.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,17 @@ Reference
.. automodule:: packaging.version
:members:
:special-members:


CLI
---

A CLI utility is provided:

.. program-output:: python -m packaging.version --help

You can compare two versions:

.. program-output:: python -m packaging.version compare --help

.. versionadded:: 26.1
13 changes: 11 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ test = [
]
dev = [{ include-group = "test" }]
docs = [
"erbsland-sphinx-ansi; python_version>='3.10'",
"furo",
"sphinx <9", # required by sphinx-toolbox
"sphinx-toolbox",
"typing-extensions>=4.1.0; python_version < '3.9'",
"sphinxcontrib-programoutput >=0.19",
]


Expand All @@ -75,7 +77,14 @@ source_pkgs = ["packaging"]
[tool.coverage.report]
show_missing = true
fail_under = 100
exclude_also = ["@(abc.)?abstractmethod", "@(abc.)?abstractproperty", "if (typing.)?TYPE_CHECKING:", "@(typing.)?overload", "def __dir__()"]
exclude_also = [
"@(abc.)?abstractmethod",
"@(abc.)?abstractproperty",
"if (typing.)?TYPE_CHECKING:",
"@(typing.)?overload",
"def __dir__()",
'if __name__ == "__main__":',
]

[tool.pytest.ini_options]
minversion = "6.2"
Expand Down
54 changes: 54 additions & 0 deletions src/packaging/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from __future__ import annotations

import operator
import re
import sys
import typing
Expand All @@ -26,6 +27,8 @@
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType

if typing.TYPE_CHECKING:
import argparse

from typing_extensions import Self, Unpack

if sys.version_info >= (3, 13): # pragma: no cover
Expand Down Expand Up @@ -929,3 +932,54 @@ def _cmpkey(
)

return epoch, _release, _pre, _post, _dev, _local


_COMPARE_OPERATIONS = {
"lt": operator.lt,
"le": operator.le,
"eq": operator.eq,
"ne": operator.ne,
"ge": operator.ge,
"gt": operator.gt,
"<": operator.lt,
"<=": operator.le,
"==": operator.eq,
"!=": operator.ne,
">=": operator.ge,
">": operator.gt,
}


def _main_compare(args: argparse.Namespace) -> int:
result = _COMPARE_OPERATIONS[args.operator](args.version1, args.version2)
return not result


def main() -> None:
import argparse # noqa: PLC0415

parser = argparse.ArgumentParser(description="Version utilities")
subparsers = parser.add_subparsers(dest="command", required=True)

compare = subparsers.add_parser(
"compare",
help="Compare two semantic versions.",
description="Compare two semantic versions. Return code is 0 or 1.",
)
compare.set_defaults(func=_main_compare)
compare.add_argument("version1", type=Version, help="First version to compare")
compare.add_argument(
"operator",
choices=_COMPARE_OPERATIONS.keys(),
help="Comparison operator",
)
compare.add_argument("version2", type=Version, help="Second version to compare")

args = parser.parse_args()

result = args.func(args)
raise SystemExit(result)


if __name__ == "__main__":
main()
60 changes: 60 additions & 0 deletions tests/test_version_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

from __future__ import annotations

import sys

import pytest

from packaging.version import main


@pytest.mark.parametrize(
("args", "retcode"),
[
("1.2 eq 1.2", 0),
("1.2 eq 1.2.0", 0),
("1.2 eq 1.2dev1", 1),
("1.2 == 1.2", 0),
("1.2 == 1.2.0", 0),
("1.2 == 1.2dev1", 1),
("1.2 ne 1.2.0", 1),
("1.2 ne 1.2dev1", 0),
("1.2 != 1.2.0", 1),
("1.2 != 1.2dev1", 0),
("1.2 lt 1.2.0", 1),
("1.2 lt 1.2dev1", 1),
("1.2 lt 1.3", 0),
("1.2 < 1.2.0", 1),
("1.2 < 1.2dev1", 1),
("1.2 < 1.3", 0),
("1.2 gt 1.2.0", 1),
("1.2 gt 1.2dev1", 0),
("1.2 gt 1.1", 0),
("1.2 > 1.2.0", 1),
("1.2 > 1.2dev1", 0),
("1.2 > 1.1", 0),
("1.2 le 1.2", 0),
("1.2 le 1.3", 0),
("1.2 le 1.1", 1),
("1.2 <= 1.2", 0),
("1.2 <= 1.3", 0),
("1.2 <= 1.1", 1),
("1.2 ge 1.2", 0),
("1.2 ge 1.1", 0),
("1.2 ge 1.3", 1),
("1.2 >= 1.2", 0),
("1.2 >= 1.1", 0),
("1.2 >= 1.3", 1),
("1.2 foo 1.2", 2),
("1.2 == unreal", 2),
],
)
def test_compare(monkeypatch: pytest.MonkeyPatch, args: str, retcode: int) -> None:
monkeypatch.setattr(sys, "argv", ["prog", "compare", *args.split()])
with pytest.raises(SystemExit) as excinfo:
main()

assert excinfo.value.code == retcode
Loading