[英]Getting Python package distribution version from within a package
您可以使用以下命令獲取 python 發行版的版本
import pkg_resources
pkg_resources.get_distribution("distro").version
如果您知道分發名稱,這很好,但是我需要在運行時動態找出我的分發名稱。
# 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"
這適用於應用程序包名稱與分發名稱相同的情況(例如requests
)。 然而,一旦它們不匹配(例如my-app
包含包my_app
),這就會失敗。
所以我需要的是發行版和它們的包之間的映射,我確定它一定存在於某個地方,因為 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/*
如何以編程方式訪問此映射?
如果您正在尋找既適用於您的開發(未安裝,或僅在本地調用)版本和已安裝版本的解決方案,請嘗試此解決方案。
進口:
import ast
import csv
import inspect
from os import listdir, path
import pkg_resources
實用功能:
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)
現在使用 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')
最后通過將return "development"
更改為:
return parse_package_name_from_setup_py(get_first_setup_py(path.dirname(__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']
更新(2021 年 2 月):
由於importlib_metadata
新添加的packages_distributions()
函數,看起來這會變得更容易:
經過幾個小時的探索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"
這將遍歷所有已安裝的包,並且對於其中的每一個都遍歷其文件列表並嘗試將其與當前文件匹配,這將包與分發相匹配。 這不是很理想,我仍然願意接受更好的答案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.