From 1fbd2d4ba66dfa6908c59d10ace94524c295cd55 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 16 Jun 2023 16:48:09 -0700 Subject: [PATCH] Allow modifying versions Fixes #659 Let me know if something like this seems acceptable to you and I'll add tests and documentation. Some notes: - My use case was adding a local version to an existing version - I don't think it would make sense to allow replacing parts of the release, e.g. "major", since I don't really see a use case. Things like `v.replace(major=v.major+1)` are probably just mistakes. - This is why I don't allow replacing `epoch` either - `Version.__new__(Version)` is a little gross, as is the munging. The munging is designed to line up with the corresponding properties, so `v.replace(xyz=v.xyz)` always works. --- src/packaging/version.py | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/packaging/version.py b/src/packaging/version.py index 7ee73628..55d64ffa 100644 --- a/src/packaging/version.py +++ b/src/packaging/version.py @@ -155,6 +155,8 @@ def __ne__(self, other: object) -> bool: :meta hide-value: """ +_SENTINEL: Any = object() + class Version(_BaseVersion): """This class abstracts handling of a project's versions. @@ -210,7 +212,9 @@ def __init__(self, version: str) -> None: dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), local=_parse_local_version(match.group("local")), ) + self._set_key() + def _set_key(self) -> None: # Generate a key which will be used for sorting self._key = _cmpkey( self._version.epoch, @@ -221,6 +225,45 @@ def __init__(self, version: str) -> None: self._version.local, ) + def replace( + self, + release: Tuple[int, ...] = _SENTINEL, + pre: Optional[Tuple[str, int]] = _SENTINEL, + post: Optional[int] = _SENTINEL, + dev: Optional[int] = _SENTINEL, + local: Optional[str] = _SENTINEL, + ) -> "Version": + """Return a new Version object with the given version parts replaced. + + >>> Version("1.0a1").replace(pre="b2") + + >>> Version("1.0").replace(dev="0") + + >>> Version("1.0").replace(local="foo") + + """ + + version = self._version + if release is not _SENTINEL: + version = version._replace(release=release) + if pre is not _SENTINEL: + version = version._replace(pre=pre) + if post is not _SENTINEL: + version = version._replace( + post=("post", post) if post is not None else None + ) + if dev is not _SENTINEL: + version = version._replace(dev=("dev", dev) if dev is not None else None) + if local is not _SENTINEL: + version = version._replace( + local=_parse_local_version(local) if local is not None else None + ) + + ret = Version.__new__(Version) + ret._version = version + ret._set_key() + return ret + def __repr__(self) -> str: """A representation of the Version that shows all internal state.