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

Allow modifying versions #698

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

hauntsaninja
Copy link
Contributor

@hauntsaninja hauntsaninja commented Jun 16, 2023

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 since other parts don't get reset.
  • This is why I don't allow replacing epoch. One could make an argument that we shouldn't allow replacing release either, thoughts? (E.g. when would it make sense to replace the release but keep the pre?)
  • 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.

Fixes pypa#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.
Comment on lines +228 to +235
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":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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":
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":

Let’s make these keyword-only, I don’t think using those as positional makes sense.

Comment on lines +262 to +264
ret = Version.__new__(Version)
ret._version = version
ret._set_key()
Copy link
Member

@uranusjr uranusjr Jun 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC pathlib (?) has a pattern like

new = self.__new__(Version)
new._set_version(version)

This would probably be a bit less awkward.

@brettcannon brettcannon marked this pull request as draft June 23, 2023 23:12
@brettcannon
Copy link
Member

The general approach looks okay to me (with Tzu-ping's comments). @pradyunsg ?

FYI I converted this to a draft until there are tests and documentation.


version = self._version
if release is not _SENTINEL:
version = version._replace(release=release)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to use a kwargs dict or a partial here instead of repeatedly recreating the version tuple?

local=_parse_local_version(local) if local is not None else None
)

ret = Version.__new__(Version)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that replace would bypass validation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What validation are you thinking of?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation that happens when the version string is parsed: https://github.com/hauntsaninja/packaging/blob/1fbd2d4ba66dfa6908c59d10ace94524c295cd55/src/packaging/version.py#L200-L202. For example, you could set pre='abc', but that's not a valid pre-release, and it would raise an error if the version was stringified and reparsed.

@pradyunsg
Copy link
Member

@pradyunsg ?

Same. I'm on board for this approach, assuming tests + docs + existing review comments are addressed. :)

@doc-sheet
Copy link

Hello.
If it is not intentional to keep props readonly why not just add setters for smooth increment?
e.g.

a = Version('1.2.3')
a.minor += 1

@uranusjr
Copy link
Member

If it is not intentional to keep props readonly

Where do you get this impression?

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.

Modifying versions
6 participants