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

Wishlist: update __version__ in a specified .py file #37

Closed
mgedmin opened this Issue Mar 6, 2013 · 19 comments

Comments

Projects
None yet
4 participants
@mgedmin
Copy link
Contributor

mgedmin commented Mar 6, 2013

For various reasons I often have a statement like this

__version__ = '2.4.frog-knows'

in one of my *.py files, and a setup.py that extracts it from there. If I try to run fullrelease in such a package, it fails to update the version number and tries to create incorrect tags etc.

I'd like to be able to tell zest.releaser where the version is defined, perhaps in setup.cfg? Something like

[zest.releaser]
version-file = src/gtimelog/__init__.py
@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 6, 2013

Useful addition, possibly. I might even start to use it myself :-) Some questions though:

  • How does the setup.py look in your case. I mean, how reliably can it grab the version number from the package it is about to install?
  • Do you have to take care with imports in the file where you define the version? Only stdlib imports?
  • A quick pkginfo version call in your code is also possible. That could keep the version nicely with the other packaging data in the setup.py.
  • Importing in setup.py works now as it is python. But new packaging stuff moves to non-python config files. Do you know if these also support versions-in-python-files?
  • Is a double underscore the most common format for the version? I think so, but...
@mgedmin

This comment has been minimized.

Copy link
Contributor Author

mgedmin commented Mar 6, 2013

I have different variations (I'm still, uh, call it researching the best way to do that), roughly from oldest to newest:

  • direct relative import (eazysvn) because it's a single-module package and the single module lives in the same directory as setup.py
  • sys.path manipulation + direct import (pyspacewar)
  • execfile into an empty namespace (zodbbrowser, gtimelog, irclog2html)
  • exec() of just the single assignment statement into an empty namespace (restview)
  • regexp matching (objgraph)
  • regexp matching + ast.literal_eval (check-manifest)

I think the issue with non-stdlib imports is what made me abandon the execfile approach.

I'm not familiar with pkginfo. I've considered using pkg_resources to get the version number of the installed package, but rejected it for the following reason: I try to make my software work directly from a source checkout, without an explicit installation step, to lower the barrier of entry for new users (and reduce the hassle for myself as well).

I've no idea what the new packaging metadata efforts will do to my desire to not repeat myself. I'll cross that bridge when I reach it. Worst case, I'll have to duplicate the version number -- in which case I'll have a desire for zest.releaser to be able to update both numbers!

I'm not sure about the conventions. This StackOverflow post recommends version and links to epydoc for justification. PEP-0008 documents version, but I've never seen that particular variation in the wild.

My own modules vary between version, VERSION, and __version__, but I'm willing to normalize them to a single spelling, if there are tools that support that spelling. I'm leaning towards __version__ because it matches the other special variables like __author__, __licence__, and __url__ that I've seen in the wild (and, I think, in the stdlib).

@mgedmin

This comment has been minimized.

Copy link
Contributor Author

mgedmin commented Mar 6, 2013

Oh, look, PEP 396 recommends __version__ too!

There's also this interesting recommendation:

  1. The version attribute in a classic distutils setup.py file, or the PEP 345 [7] Version metadata field SHOULD be derived from the version field, or vice versa.

although PEP 345, AFAICS, doesn't provide any way for its Version metadata field to be derived from __version__, which only leaves the "vice versa" option.

@mgedmin

This comment has been minimized.

Copy link
Contributor Author

mgedmin commented Mar 6, 2013

Here's another example of a setup.py that uses a nice regexp-based oneliner to extract metadata from double-underscore variables: pythonjabberbot

@florentx

This comment has been minimized.

Copy link

florentx commented Mar 6, 2013

Good idea, even if i am not a regular user of zest.releaser.

Usually I avoid to import the module to retrieve the version, in order to avoid side-effects.
When I am bored of releasing with mismatched versions between the module and the setup.py, I call an eval() on the line containing the version number:
https://github.com/jcrocholl/pep8/blob/master/setup.py#L6:L10

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 7, 2013

Zest.releaser already has two ways to look for the version number. First is a setup.py, second a version.txt file like existed in older zope projects (and which is still handy for releasing non-python-package projects!). So one more can't hurt too much :-)

Perhaps a pointer in setup.cfg to point zest.releaser at the proper python file? We need to include a hint on how to grab the version in the setup.py, then, from the version attribute.

At the same time, also include a hint on how to grab the version from the package metadata if you want to keep the version in setup.py. This would be the easiest way, as you don't need to point zest.releaser at your version file.

You could also stick a version.txt in your package and read it into version and pick it up from setup.py. zest.releaser would need to change for that.

TODO:

  • Read optional setting from setup.cfg about version location. Or can we assume that to always be in the package's __init__.py?
  • Add regex to extract the version.
  • Write back the version reliably.
  • Add documentation on the various scenarios.
@mgedmin

This comment has been minimized.

Copy link
Contributor Author

mgedmin commented Mar 7, 2013

About version.txt: does it have to be at the project root, or can it live in src/my/package? I could make the second work; the first is not an option because it wouldn't let installed software access its own version number.

Yes, a pointer in setup.cfg is what I had in mind. As you may have seen, some of my projects have no packages (directories with __init__.py), just a single module. Using init.py may be a useful default, but I suspect you'll find it's not trivial to figure out from setup.py what the right package directory is. Also it smacks of guessing in the face of ambiguity.

I'd say if there's no version.txt, and setup.py has no version='x.x.x', and setup.cfg has no version-file option, emit a warning saying that zest.releaser couldn't adjust the version, and informing the user about the setup.cfg option name.

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 7, 2013

version.txt: yes, it looks in subdirectories. There are a couple of rules to prevent it from finding one in parts/omelette/my.unrelated.package/, though :-)

@miohtama

This comment has been minimized.

Copy link

miohtama commented Mar 17, 2013

I did the opposite:

I made the Python code to look version supplied by setup.py: I consider setup.py version definitive one, so all modules should look into this, and not declare it elsewhere.

This is how I did it:

def _GetApiWrapperVersion(self):
    import pkg_resources
    return pkg_resources.get_distribution("Skype4Py").version
@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 18, 2013

@miohtama, I do think it needs to be called version, convention-wise.

My initial reaction also was "use pkg_resources as it is definitive".

Does anyone know if having a __version__() function instead of an attribute is acceptable? That way an "import pkg_resources" inside the function could work around the not-installed-with-setuptools scenario that @mgedmin wants to support. Not having a version number isn't that bad.

(I'm exploring options right now)

@florentx

This comment has been minimized.

Copy link

florentx commented Mar 18, 2013

With the principle of least astonishment, I tend to look for the __version__ constant either in my_module.py or my_package/__init__.py.

For example both pep8 and flake8 do like this.
This is how it is integrated with Sphinx (through import).
https://github.com/jcrocholl/pep8/blob/master/docs/conf.py#L55:L59

At the end, the only other place where I need to manually change the version is the Changelog.

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 18, 2013

Hm, looking at the version from within the sphinx config is also possible, of course. Didn't even think about that :-)

Least astonishment: I tend to look at the setup.py. That one has to have a version; the code not necessarily so. OTOH, I'm planning to add version to my packages.

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 18, 2013

I'd personally go for the pkg_resources way, which wouldn't need a zest.releaser change, but apparently that's not practical in all cases.

But, in the end, zest.releaser really only has to look at setup.py (or version.txt) and conclude that there's no version there. Then it looks in the applicable __init__.py for a proper version string and uses that one.

How the package gets it back into the setup.py (before the package as such has been installed) isn't really zest.releaser's concern.

We do have to add documentation one or two strategies for dealing with versions.

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 20, 2013

@miohtama

This comment has been minimized.

Copy link

miohtama commented Mar 20, 2013

I might be little too late for this, but maybe making it generic: why just give a Python expression as input. This would return the version number by eval(). You could basically give in any valid Python dotted object you import or function as input. Would make the codebase a lot of simpler, too :)

Because I think making version special case is little bit narrow and people have weird methods of stamping out version numbers in their packages.

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 20, 2013

A python expression in setup.cfg? That's a little too free-form for my taste. __version__ seems to be the default, so I'm all in favour of enforcing that :-)

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 20, 2013

I've finished my pull request: #39.

Best way to see what's changed is to look at the documentation on versions: https://github.com/zestsoftware/zest.releaser/blob/reinout-version-attribute/doc/source/versions.rst

Ping to @mauritsvanrees, can you take a look at it?

@mgedmin

This comment has been minimized.

Copy link
Contributor Author

mgedmin commented Mar 21, 2013

It sounds excelent!

@reinout

This comment has been minimized.

Copy link
Collaborator

reinout commented Mar 21, 2013

Done! Version 3.44 includes the functionality. See http://zestreleaser.readthedocs.org/en/latest/versions.html for the documentation.

(I'll have to do some more work on the documentation later on).

Please try it out and see if it fits your usecase!

@reinout reinout closed this Mar 21, 2013

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