On Tue, Sep 11, 2012 at 3:19 PM, Daniel Holth <dholth@gmail.com> wrote:

On Tue, Sep 11, 2012 at 3:11 PM, Erik Bray <erik.m.bray@gmail.com> wrote:

On Tue, Sep 11, 2012 at 2:35 PM, Donald Stufft <donald.stufft@gmail.com> wrote:

I was digging through PEP386 & PEP345 today, and I noticed something odd about the wording of PEP345.

It states:

When a version is provided, it always includes all versions that starts with the same value. For example the "2.5" version of Python will include versions like "2.5.2" or "2.5.3". Pre and post releases in that case are excluded. So in our example, versions like "2.5a1" are not included when "2.5" is used. If the first version of the range is required, it has to be explicitly given. In our example, it will be "2.5.0".

It also states:

In that case, "2.5.0" will have to be explicitly used to avoid any confusion between the "2.5" notation that represents the full range. It is a recommended practice to use schemes of the same length for a series to completely avoid this problem.

This effectively translates to an inability to pin to an exact version. Even in the case of specifying == it checks that the version "starts with" the value you selected. So if you pin to "2.5", and the author then releases "2.5.1", that will count as ==2.5. If you try to then pin to "2.5.0", and the author releases "2.5.0.1", then that will count as ==2.5.0.

Essentially this translates to:

==2.5 -> >=2.5<2.6 ==2.5.0 -> >=2.5.0<2.5.1 ==2.5.0.0 -> >=2.5.0.0<2.5.0.1

Which means that version specifiers are _always_ ranges and are never exact versions. The PEP as written relies on authors to decide beforehand how many digits they are going to use in their versions, and for them to never increase or decrease that number.

I also checked to see if Distutils2/packaging implemented VersionPredicates that way or if they allowed specifying an exact version. It turned out that it implements the PEP as written:

from distutils2 import version predicate = version.VersionPredicate("foo (==2.5)") print predicate foo (==2.5) predicate.match("2.5") True predicate.match("2.5.0") True predicate.match("2.5.0.0") True predicate.mach("2.5.0.5") True

That's kind of annoying. Does anyone know if this is by design?

FWIW there is a workaround. For example if you want to pin to exactly 2.5.1:

predicate = version.VersionPredicate("foo (==2.5.1,<2.5.1.1)") predicate.match('2.5.1') True predicate.match('2.5.2') False predicate.match('2.5.1.0') True predicate.match('2.5.1.1')

But you could still release 2.5.1.0.0? I suppose we limit the number of version parts these days.

Why don't we update the spec so that (2.0) means (2.0) the range, and (==2.0) means 2.0 (exactly).

Daniel

The PEP is ambiguous on this, but you could get away with reading it as "When a version is provided (without a conditional operator) it always includes all versions that start with the same value". Although it's unwritten in the PEP exactly how the operators are meant to be interpreted, I would say they should be interpreted strictly. Erik