-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 setuptools.get_version(path, field='__version__') #1679
Conversation
This allows people who use setuptools to maintain project versions in a single place, for example as attributes in their Python source. Closes pypa#1316 Closes pypa/pip#6004 Closes pypa/packaging.python.org#571
I am not sure why this function exists, it seems like it is designed to pull the version string from [metadata]
name=foo
version=0.0.1 Why would you specify it in In general, I think we are more interested in going the other way - we don't want to encourage people to use Additionally, #1316 is already closed and you can use |
The function is designed to pull the version string from any file including various https://github.com/pypa/pip/blob/fb944ab62f755e71204020bc5b9881367803e5ec/setup.py#L18 And by a will of
It even can save you some time to update version in both |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still on the fence with this one. On one hand, this functionality provides a convenience for packages already dependent on this behavior. In doing so, it also provides a hook where such behavior could be discouraged or deprecated.
On the other hand, we're already discouraging use of imperative code in setup.py. Most importantly, this behavior doesn't have a comparable syntax in setup.cfg. That is, one cannot specify to load the version from a field in a file through setup.cfg (except for an empty file), meaning it becomes yet another feature that's preventing the adoption of declarative config.
For me, that's the deal-breaker. I'd be much more inclined to accept a pull request that enabled the metadata.version
(declarative config) to specify a path and field from which the version would be extracted.
Indeed, setuptools almost has that already with:
# setup.cfg
[metadata]
version = file:pkg/version.txt
And then in pkg/__init__.py
, set __version__ = open(here / 'version.txt').read()
.
I seem to recall someone was working on a technique to specify an extracted version in declarative config, but I can't find that now. It's possible there were good reasons why that was not done.
So between this being a deprecated pattern, there being several other viable patterns, and because this pattern impedes the best recommended patterns, I'm strongly disinclined to accept this change. If we were to accept it, I think my first action would be to deprecate it... which suggests it shouldn't be accepted at all.
@@ -89,7 +89,7 @@ def pypi_link(pkg_filename): | |||
|
|||
setup_params = dict( | |||
name="setuptools", | |||
version="40.8.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although you could use get_version
here, this usage would be discouraged. Better would be to store the value in setup.cfg under metadata.version
(even if in duplicate), although doing so leads to a limitation in bump2version. What's more concerning about this change is the issues it introduces if setuptools were to move to a declarative config (something that's definitely desirable)... and this speaks to the resistance to accepting this change--it's encouraging patterns that run contrary to the best practices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what is the ETA to introduce the best practices to pip
? Why doesn't it use best practices?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Support for declarative config is relatively recent and I haven't spent much time evangelizing it, so it's probably no big surprise that some projects haven't adopted it. Even setuptools itself, which because it installs itself, would have had no minimum dependency, only adopted declarative config many months after it was available. It's just a matter of priorities and resources. Perhaps that team would accept a pull request to use declarative config; I don't know.
|
||
def test_non_utf8_python_file(self): | ||
path = os.path.join(self.tmpdir, 'russian.py') | ||
with open(path, 'wb') as fp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use io.open(..., encoding='cp1251')
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is called non_ut8
. Python doesn't know that it is russuan or not - filename can be any.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I should have asked is if you could open this file as text with an encoding (cp1251) and then write unicode text to it.
# Using binary matching to avoid possible encoding problems | ||
# when reading arbitrary text files | ||
if type(field) is not bytes: | ||
field = field.encode('utf-8') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This encode should happen unconditionally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if it is already bytes
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should not accept bytes on Python 3. On Python 2, if field is a str
, it should be decodable through the default encoding, causing an implied decoding and explicit encoding using UTF-8.
I'm actually thinking now though that this function should only support files that are UTF-8 or where an alternate encoding is specified.
from "key = value" format with key matched at the beginning of | ||
a line and specified by `field` parameter. Returns string. | ||
""" | ||
version_file = os.path.abspath(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this line has no useful effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be. Just a safe practice, useful for debugging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, let's omit it except when debugging.
I'm increasingly strongly against this change. It seems very fragile compared to many of the other single-sourcing options out there, and it encourages a pattern that I would prefer to discourage. The existing users of this pattern already have something that works for them. I agree with you that to the extent that a change needs to be made, it should be adding more options for single-sourcing your versions in the |
Single sourcing the package version from the variable As of today it is not possible to use The future might be a pure |
@lmittmann I don't see how this is a great improvement. Generally speaking we don't want people using the imperative build system for super common patterns because it's unnecessarily powerful and error-prone. This patch itself is very error prone and makes some very specific assumptions about how the version will be specified. For example, if someone specifies their version like this: VERSION_MAJOR = 1
VERSION_MINOR = 2
VERSION_PATCH = 0
__version__ = '.'.join(map(str, (VERSION_MAJOR,
VERSION_MINOR,
VERSION_PATCH))) This __version__ = '0.2.4'
if is_dev():
__version__ += ".dev" + get_dev_version() I'll also note that this function doesn't even write one test using it to enable the pattern of "pull the version from It is very difficult to write truly generic functions that work in a wide variety of circumstances, but it's very easy to write functions that work for your particular situation. Given that we're really hoping to get people away from using the imperative build configuration ( |
Thanks for the contrib, but the consensus is we will decline. |
@pganssle your story is very far-fetched. It could make sense if @jaraco if #1687 solves single sourcing problem, why it is not possible to remove |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've commented on the responses above (sorry for the delayed response)
from "key = value" format with key matched at the beginning of | ||
a line and specified by `field` parameter. Returns string. | ||
""" | ||
version_file = os.path.abspath(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, let's omit it except when debugging.
# Using binary matching to avoid possible encoding problems | ||
# when reading arbitrary text files | ||
if type(field) is not bytes: | ||
field = field.encode('utf-8') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should not accept bytes on Python 3. On Python 2, if field is a str
, it should be decodable through the default encoding, causing an implied decoding and explicit encoding using UTF-8.
I'm actually thinking now though that this function should only support files that are UTF-8 or where an alternate encoding is specified.
|
||
def test_non_utf8_python_file(self): | ||
path = os.path.join(self.tmpdir, 'russian.py') | ||
with open(path, 'wb') as fp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I should have asked is if you could open this file as text with an encoding (cp1251) and then write unicode text to it.
@@ -89,7 +89,7 @@ def pypi_link(pkg_filename): | |||
|
|||
setup_params = dict( | |||
name="setuptools", | |||
version="40.8.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Support for declarative config is relatively recent and I haven't spent much time evangelizing it, so it's probably no big surprise that some projects haven't adopted it. Even setuptools itself, which because it installs itself, would have had no minimum dependency, only adopted declarative config many months after it was available. It's just a matter of priorities and resources. Perhaps that team would accept a pull request to use declarative config; I don't know.
Summary of changes
This allows people who use setuptools to maintain project versions
in a single place, for example as attributes in their Python source.
Closes #1316
Closes pypa/pip#6004
Closes pypa/packaging.python.org#571
Pull Request Checklist