-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
How to get version from pyproject.toml from python app? #273
Comments
The >>> import pkg_resources
>>> my_version = pkg_resources.get_distribution('my-package-name').version |
Related: Single-sourcing the package version from Python Packaging User Guide |
Thanks. I am using #273 (comment) |
Using For what it's worth, I see that Poetry itself simply duplicates the version. I think there currently isn't a "proper" solution for single-sourcing the application version in a project packaged by Poetry. |
any update on how to do this best? does following how Poetry does it itself really mean updating the version.py file by hand? |
I am using @roywes solution with a fallback to when the package is not installed. |
You can now use It has a convenient function |
@Eric-Arellano Just for clarity, The docs seem to say that a package built by poetry and installed will have the metadata version set from the |
@insysion The metadata is built from the pyproject.toml (into these locations in the sdist: |
It seems both |
@cglacet You could directly parse the TOML in |
@sinoroc Yes I could, but isn't including a package as its own dependency weird? |
@cglacet I am not following. |
@sinoroc That's probably not clear because it's not clear in my head haha. If I need to install a package to get its version, then, to get the current version of the package I'm currently building it needs to install itself (so it can read its own version number). In my case, I want to include the version number in the package documentation (sphinx configuration). And I also want that particular piece of code to run even while in development (because I will build the documentation locally a lot before its built by my CI pipeline) |
Like you said, I could very well parse the |
I know that it is a question that keeps coming up again and again, and there is still no one true answer. It all depends on what the context and conditions are. There are multiple solutions, all with their pros and cons. Since you mention building sphinx documentation I will tell here how it is solved in one of my projects... The
and the
So, yes it means that the project always has to be installed beforehand (at least in editable (aka develop) mode). It is a bit of a shame, I reckon, but it works well, so I leave it like that for now. Although I believe it would be relatively easy, to extract the version string (and other metadata) from a build artefact of the project, instead of from a complete installation. In the case of poetry projects, since the source of truth for the version string is in
This bit, I do not understand what you mean. Where is the issue? Usually libraries or applications do not really need to know their own version string. And if they do have this need, then I would definitely think that it is at run-time, meaning that they are already installed. So using |
Wow, thank you @sinoroc. Your example helped me immensely. |
I'm looking for a solution to run in |
@abolourian What are you trying to do? How is your use case different than others' use cases? What is the |
@sinoroc In a CICD process, I'm using a method in I defined a method in a |
Basically everything I am saying is: don't write a If really you want to do it, fair enough, no problem. You approach with calling Also I would like to mention the project You might also want to read this thread: #693 And take note that there is a project of a real plugin system in poetry, scheduled for v1.2. Which might allow to do such things in a much cleaner fashion. Other discussion on the topic: python/importlib_metadata#248 |
@maresb My point is: this should be solved in the development tooling, this does not belong in the code itself. And it is made even worse by the fact that it is running unconditionally on import. Maybe the Poetry commands such as I also believe that having a |
I don't see how Poetry could address this since a version change in
I find |
Use Maybe a post checkout git hook could help indeed, no idea if it exists. My thinking is that sooner or later you will need to run a poetry command, so maybe poetry could do a quick check to make sure that the editable installation metadata is up-to-date with the content of |
I don't claim to be a Python master by any means, but I wrote this in my import importlib.metadata
import tomllib
try:
with open("pyproject.toml", "rb") as f:
pyproject = tomllib.load(f)
__version__ = pyproject["tool"]["poetry"]["version"]
except Exception as e:
__version__ = importlib.metadata.version('bop') Seems to work for local development when the package is installed in edit mode, and then when built, distributed and pip installed. Version number is correct and allows me to centralise on |
Late to the party but yes, I quickly realised this wasn't going to work I have dropped this since. I'm not sure what the best approach is to handle this |
@MaxLenormand What do you mean? It is a solved problem, use |
Here is what I put in import importlib.metadata
from pathlib import Path
def _package_version() -> str:
"""Find the version of the package."""
package_version = "unknown"
try:
# Try to get the version of the current package if
# it is running from a distribution.
package = Path(__file__).parent.name
metadata = importlib.metadata.metadata(package)
package_version = metadata["Version"]
except importlib.metadata.PackageNotFoundError:
# Fall back on getting it from a local pyproject.toml.
# This works in a development environment where the
# package has not been installed from a distribution.
import toml
pyproject_toml_file = Path(__file__).parent.parent / "pyproject.toml"
if pyproject_toml_file.exists() and pyproject_toml_file.is_file():
package_version = toml.load(pyproject_toml_file)["tool"]["poetry"][
"version"
]
# Indicate it might be locally modified or unreleased.
package_version = package_version + "+"
return package_version
version = _package_version() I suppose I could move the private function |
@vengroff Why? Why is this code necessary? Why is having a |
@sinoroc Thanks, you are right about the first half. I should use |
@vengroff Be mindful that this creates a run time cost (import time even!) on all production users only for some slight developer comfort. I do not have any use case in mind where it would make sense to compute the value of |
@sinoroc I personally need to have access to the version variable in various cases:
I don't think I'm alone and so I find having easy access to the version variable important. |
* add application conda env * add application dockerfile * update Makefile to build application * add application styling * remove flake8 config file * remove darglint config file * set version from importlib * python-poetry/poetry#273 (comment) * set version using importlib rather than expecting hard-coded #64 * update application dockerfile to install library * update application environment * gcs * simplify library install by installing leidenalg and astropy from conda * add make target for shell in running container * download app data from gcs
@juanmirocks All those are legitimate use cases and can be done with |
I forgot to follow up with what I did following the previous messages in this issue from a couple weeks ago. For the reasons @juanmirocks mentioned among others, it can be nice to have the version available as from typing import Any
import importlib.metadata
from pathlib import Path
__package_version = "unknown"
def __get_package_version() -> str:
"""Find the version of this package."""
global __package_version
if __package_version != "unknown":
# We already set it at some point in the past,
# so return that previous value without any
# extra work.
return __package_version
try:
# Try to get the version of the current package if
# it is running from a distribution.
__package_version = importlib.metadata.version("my_package_name")
except importlib.metadata.PackageNotFoundError:
# Fall back on getting it from a local pyproject.toml.
# This works in a development environment where the
# package has not been installed from a distribution.
import toml
pyproject_toml_file = Path(__file__).parent.parent / "pyproject.toml"
if pyproject_toml_file.exists() and pyproject_toml_file.is_file():
__package_version = toml.load(pyproject_toml_file)["tool"]["poetry"][
"version"
]
# Indicate it might be locally modified or unreleased.
__package_version = __package_version + "+"
return __package_version
def __getattr__(name: str) -> Any:
"""Get package attributes."""
if name in ("version", "__version__"):
return __get_package_version()
else:
raise AttributeError(f"No attribute {name} in module {__name__}.") |
@vengroff Yes, this seems like this would have less technical side effects and drawbacks. But this is a lot of code for -- in my opinion -- very little gain, and this gain is strictly limited to the development phase, which -- again in my opinion -- is definitely not what one should optimize for. |
From my experience, many packages are in the "development phase" FOREVER or for a VERY LONG TIME. They never get "distributed" and yet they keep running for a long time. For those scenarios, either we have some code similar as to what @vengroff suggested (which IMHO is not long; it's mostly comments), or otherwise developers need to keep remembering to install from time to time their dev packages ( I guess that second option is acceptable though feels strange to me at least. Nonetheless, if that's so far the "recommended" approach, it would be nice adding it to the poetry docs somewhere. Do you agree? I guess some people don't know such option exists (me included before) and that's why the confusion and the many comments in this issue. |
There should be a package for this.
пт, 5 янв. 2024 г., 00:33 Darren Erik Vengroff ***@***.***>:
… I forgot to follow up with what I did following the previous messages in
this issue from a couple weeks ago.
For the reasons @juanmirocks <https://github.com/juanmirocks> mentioned
among others, it can be nice to have the version available as
package.version or package.__version__. And if, as in the API case
mentioned, you want to easily tell whether you are running a dev or
released server, you can make the version of unpackaged code come from
pyproject.toml but look different, you have more work to do. If you don't
want to execute that code at import time, which was @sinoroc
<https://github.com/sinoroc>'s objection, you can hide it in a function
triggered by getting the attribute with package.__getattr__. And you can
cache it so the work only happens once per process. I put all those
together and ended up with this in my __init__.py:
from typing import Anyfrom .impl.exceptions import CensusApiExceptionimport importlib.metadatafrom pathlib import Path
__package_version = "unknown"
def __get_package_version() -> str:
"""Find the version of this package."""
global __package_version
if __package_version != "unknown":
# We already set it at some point in the past,
# so return that previous value without any
# extra work.
return __package_version
try:
# Try to get the version of the current package if
# it is running from a distribution.
__package_version = importlib.metadata.version("my_package_name")
except importlib.metadata.PackageNotFoundError:
# Fall back on getting it from a local pyproject.toml.
# This works in a development environment where the
# package has not been installed from a distribution.
import toml
pyproject_toml_file = Path(__file__).parent.parent / "pyproject.toml"
if pyproject_toml_file.exists() and pyproject_toml_file.is_file():
__package_version = toml.load(pyproject_toml_file)["tool"]["poetry"][
"version"
]
# Indicate it might be locally modified or unreleased.
__package_version = __package_version + "+"
return __package_version
def __getattr__(name: str) -> Any:
"""Get package attributes."""
if name in ("version", "__version__"):
return __get_package_version()
else:
raise AttributeError(f"No attribute {name} in module {__name__}.")
—
Reply to this email directly, view it on GitHub
<#273 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACGWBLR7IKDGAB7UDTUIT7DYM4N27AVCNFSM4FHXZN4KU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOBXG43TQOJZGY3Q>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
There is a package for this now. I factored a variation of the code above to a new package called Things get just a little more complex when you want to move this functionality out of a package's |
Note that it is not specific to Poetry. No build backend or dev workflow tool that I know of does it any better than Poetry, because that is the state of the art as far as I can tell. If packaging metadata changes (which includes the version) then the thing must be reinstalled (to take the new metadata into account), even if the thing is installed as "editable". I am not saying things can not be improved. Maybe someone will come up with a good solution to handle updating metadata transparently for editable installations.
Without thinking much about this... I wonder what is the point of bumping the version number in such a use case then? How does that work? You |
@sinoroc I see your points. I was referring rather to developers working on some tool, e.g. a REST API web server, which returns the version somewhere, expecting to see the version changed in From a user point of view, the version property in the |
So I just tested And yes, maybe it is worth a note somewhere that updating the installed metadata (via editing Maybe in the output of the |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Question
I have a basic python app, and inside this app there's a
--version
flag.I want to show the same version as I have in
pyproject.toml
, but without duplicating this information.What should I do here?
Thanks!
The text was updated successfully, but these errors were encountered: