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 support for version compare and for checking for version part argument #20

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

arcanefoam
Copy link

Version compare is important for other scripts that use bumpversion as a library, e.g. in my big osgi project I want to make sure all projects are at the same version, find which ones are ahead/behind.

I also took another approach to checking the version part argument. My implementation checks against the current VersionConfiguration (not hardcoded to specific names).

Both changes have proper tests (I think) in place.

Copy link
Collaborator

@ekohl ekohl left a comment

Choose a reason for hiding this comment

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

This is just an superficial review. I didn't look into all the details yet.

if not isinstance(other, Version):
return False
try:
return all(v == other[k] for k, v in self.items())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Doesn't this ignore the case there len(self.items()) < len(other.items())?

Copy link
Author

@arcanefoam arcanefoam Jun 4, 2018

Choose a reason for hiding this comment

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

It does, we could do a len compare first to make sure both have the same number of parts. I will add this.

Copy link
Author

@arcanefoam arcanefoam Jul 13, 2018

Choose a reason for hiding this comment

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

On a second thought, I think equality should be strict, that is: 1.4 != 1.4.0, by assuming they are the same we ignore the version pattern defined by parse and serialize. If both versions where generated by bumpversion then this highlights that the user has an error in the part configuration.

for vals in ((v, other[k]) for k, v in self.items()):
if vals[0] == vals[1]:
continue
if method(vals[0], vals[1]):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can be return method(vals[0], vals[1]) but then I also prefer to rewrite it a bit:

if vals[0] != vals[1]:
    return method(vals[0], vals[1])

Copy link
Author

Choose a reason for hiding this comment

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

yes, cant really remember why did I do a if to return true/false.

return hash(self.value)

def _compare(self, other, method):
if self.config.function_cls is not other.config.function_cls:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Odd indenting here

@arcanefoam
Copy link
Author

Sorry for the delay on this. Have pushed the requested changes, except for the equals (see my comments).

Copy link
Collaborator

@ekohl ekohl left a comment

Choose a reason for hiding this comment

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

It took a while to get back to this. Some inline comments.

@@ -336,7 +342,59 @@ def __iter__(self):
return iter(self._values)

def __repr__(self):
return '<bumpversion.Version:{}>'.format(keyvaluestring(self._values))
by_part = ", ".join("{}={}".format(k, v) for k, v in self.items())
return '<bumpversion.Version:{}>'.format(by_part)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably cleaner to use self.__class__ to show the class name rather than hardcoding.

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense. Do notice that this is how it was done in the past, I just added the by_part line ;)

return not self.__eq__(other)

def __le__(self, other):
return self._compare(other, lambda s, o: o > s, False) # Note the change of order in operands
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can also use operator.lt instead of the lambda. There's also operator.gt

Copy link
Author

Choose a reason for hiding this comment

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

Agreed

def __hash__(self):
return hash(tuple((k, v) for k, v in self.items()))

def _compare(self, other, method, strict=True):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function is really hard to understand at first sight. The strict parameter should be named something clearer, perhaps allow_equal?

Copy link
Author

Choose a reason for hiding this comment

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

Indeed, I have changed the logic and improved the docs so the intent is more clear

def keyvaluestring(d):
return ", ".join("{}={}".format(k, v) for k, v in sorted(d.items()))

class Version(object):

def __init__(self, values, original=None):
def __init__(self, values, config, original=None):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not a fan of the config here. It doesn't feel very logical that a version has a config, especially if it's only used for order.

Copy link
Author

Choose a reason for hiding this comment

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

Agreed, will only use the order

self._values = dict(values)
self.config = config
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is IMHO an internal property so should be named self._config

Copy link
Author

Choose a reason for hiding this comment

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

Config is now the order and it has to be public because when comparing we first compare the orders cause if they are different the versions can not be comapred

def __eq__(self, other):
return self.value == other.value
return self._compare(other, lambda s, o: s == o)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have a look at the operator module here as well.

Copy link
Author

Choose a reason for hiding this comment

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

OK

return self._compare(other, lambda s, o: s > o)

def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

OK

try:
for vals in ((v, other[k]) for k, v in self.items()):
if vals[0] == vals[1]:
continue
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMHO it's cleaner to use avoid continue

Copy link
Author

Choose a reason for hiding this comment

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

See previous comment on this method

Copy link
Author

Choose a reason for hiding this comment

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

On a second pass I can not find a way to avoid the continue :/

Copy link
Collaborator

@florisla florisla Dec 7, 2018

Choose a reason for hiding this comment

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

@arcanefoam Since the only purpose of the continue is to prevent calling return, you can just invert the logic. Instead of

for vals in ((v, other[k]) for k, v in self.items()):
    if vals[0] == vals[1]:
        continue
    return method(vals[0], vals[1])

Do

for vals in ((v, other[k]) for k, v in self.items()):
    if vals[0] != vals[1]:
        return method(vals[0], vals[1])

I would also improve the readability by changing this to

for key, value in self.items():
    other_value = other[key]
    if value != other_value:
        return method(value, other_value)

raise TypeError("Versions use different parts, cant compare them.")

def __ne__(self, other):
return not self.__eq__(other)
Copy link
Collaborator

Choose a reason for hiding this comment

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

https://stackoverflow.com/questions/4352244/python-should-i-implement-ne-operator-based-on-eq#30676267 recommends avoiding this but rather write it as return not self == other. This is the default in Python 3 (but not 2).

Copy link
Author

Choose a reason for hiding this comment

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

OK

@c4urself
Copy link
Owner

@arcanefoam any movement here?

@arcanefoam
Copy link
Author

@c4urself sorry for the radio silence, had been working on unrelated projects. I agree with most of the comments from @ekohl, I will reply to them and commit some changes during this week

@florisla
Copy link
Collaborator

This branch needs a rebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants