首页 > 解决方案 > 从包中获取 Python 包分发版本

问题描述

您可以使用以下方式获取 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包含 package 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/*

如何以编程方式访问此映射?

标签: pythonpython-3.xpython-packaging

解决方案


如果您正在寻找一种既适用于您的开发(未安装或仅在本地调用)版本,又适用于已安装版本的解决方案,请尝试此解决方案。

进口:

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"最后通过更改为:替换@Gricey 的答案中的函数:

return parse_package_name_from_setup_py(get_first_setup_py(path.dirname(__file__)))

取自我的回答https://stackoverflow.com/a/60352386


推荐阅读