[英]Getting Python package distribution version from within a package
You can get the version of a python distribution using您可以使用以下命令获取 python 发行版的版本
import pkg_resources
pkg_resources.get_distribution("distro").version
This is great if you know the distribution name, however I need to dynamically figure out my distribution name at runtime.如果您知道分发名称,这很好,但是我需要在运行时动态找出我的分发名称。
# Common framework base app class, extended by each app
class App(object):
def get_app_version(self) -> str:
package_name = self.__class__.__module__.split('.')[0]
try:
return pkg_resources.get_distribution(package_name).version
except Exception:
return "development"
This works for cases where the app's package name is the same as the distribution name (eg requests
).这适用于应用程序包名称与分发名称相同的情况(例如
requests
)。 However this fails once they don't match (eg my-app
containing package my_app
).然而,一旦它们不匹配(例如
my-app
包含包my_app
),这就会失败。
So what I need is a mapping between distributions and their packages, which I'm sure must exist somewhere since pip seems to know what to delete when you call uninstall:所以我需要的是发行版和它们的包之间的映射,我确定它一定存在于某个地方,因为 pip 似乎知道在调用卸载时要删除的内容:
$ pip uninstall requests
Uninstalling requests-2.21.0:
Would remove:
/home/user/.virtualenvs/app/lib/python3.6/site-packages/requests-2.21.0.dist-info/*
/home/user/.virtualenvs/app/lib/python3.6/site-packages/requests/*
How do I programatically access this mapping?如何以编程方式访问此映射?
If you're looking for a solution that works both from your development—not installed, or just locally called—version, and an installed version, then try this solution.如果您正在寻找既适用于您的开发(未安装,或仅在本地调用)版本和已安装版本的解决方案,请尝试此解决方案。
Imports:进口:
import ast
import csv
import inspect
from os import listdir, path
import pkg_resources
Utility function:实用功能:
def get_first_setup_py(cur_dir):
if 'setup.py' in listdir(cur_dir):
return path.join(cur_dir, 'setup.py')
prev_dir = cur_dir
cur_dir = path.realpath(path.dirname(cur_dir))
if prev_dir == cur_dir:
raise StopIteration()
return get_first_setup_py(cur_dir)
Now using Python's ast
library:现在使用 Python 的
ast
库:
def parse_package_name_from_setup_py(setup_py_file_name):
with open(setup_py_file_name, 'rt') as f:
parsed_setup_py = ast.parse(f.read(), 'setup.py')
# Assumes you have an `if __name__ == '__main__':`, and that it's at the end:
main_body = next(sym for sym in parsed_setup_py.body[::-1]
if isinstance(sym, ast.If)).body
setup_call = next(sym.value
for sym in main_body[::-1]
if isinstance(sym, ast.Expr) and
isinstance(sym.value, ast.Call) and
sym.value.func.id in frozenset(('setup',
'distutils.core.setup',
'setuptools.setup')))
package_version = next(keyword
for keyword in setup_call.keywords
if keyword.arg == 'version'
and isinstance(keyword.value, ast.Name))
# Return the raw string if it is one
if isinstance(package_version.value, ast.Str):
return package_version.s
# Otherwise it's a variable at the top of the `if __name__ == '__main__'` block
elif isinstance(package_version.value, ast.Name):
return next(sym.value.s
for sym in main_body
if isinstance(sym, ast.Assign)
and isinstance(sym.value, ast.Str)
and any(target.id == package_version.value.id
for target in sym.targets)
)
else:
raise NotImplemented('Package version extraction only built for raw strings and '
'variables in the same function that setup() is called')
Finally replace the function in @Gricey's answer by changing return "development"
to:最后通过将
return "development"
更改为:
return parse_package_name_from_setup_py(get_first_setup_py(path.dirname(__file__)))
Taken from my answer https://stackoverflow.com/a/60352386摘自我的回答https://stackoverflow.com/a/60352386
I believe the project's name should be hard-coded if possible.我相信如果可能的话,项目的名称应该是硬编码的。 If not then some function like the following could help figuring out the metadata for the installed distribution containing the current file (
__file__
):如果不是,那么像下面这样的函数可以帮助找出包含当前文件(
__file__
)的已安装发行版的元数据:
import pathlib
import importlib_metadata
def get_project_distribution():
for dist in importlib_metadata.distributions():
try:
relative = pathlib.Path(__file__).relative_to(dist.locate_file(''))
except ValueError:
pass
else:
if relative in dist.files:
return dist
return None
project_distribution = get_project_distribution()
if project_distribution:
project_name = project_distribution.metadata['Name']
version = project_distribution.metadata['Version']
Update (February 2021):更新(2021 年 2 月):
Looks like this could become easier thanks to the newly added packages_distributions()
function in importlib_metadata
:由于
importlib_metadata
新添加的packages_distributions()
函数,看起来这会变得更容易:
After a couple of hours of exploring pkg_resources
and reading the source for pip's uninstall I've got the following working:经过几个小时的探索
pkg_resources
并阅读pip 卸载的源代码后,我得到了以下工作:
import inspect
import pkg_resources
import csv
class App(object):
def get_app_version(self) -> str:
# Iterate through all installed packages and try to find one that has the app's file in it
app_def_path = inspect.getfile(self.__class__)
for dist in pkg_resources.working_set:
try:
filenames = [
os.path.normpath(os.path.join(dist.location, r[0]))
for r in csv.reader(dist.get_metadata_lines("RECORD"))
]
if app_def_path in filenames:
return dist.version
except FileNotFoundError:
# Not pip installed or something
pass
return "development"
This iterates through all installed packages and for each of those iterates through its list of files and tries to match that to the current file, this matches the package to the distribution.这将遍历所有已安装的包,并且对于其中的每一个都遍历其文件列表并尝试将其与当前文件匹配,这将包与分发相匹配。 It's not really ideal, and I'm still open to better answers.
这不是很理想,我仍然愿意接受更好的答案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.