Получение версии дистрибутива пакета 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, содержащий пакет 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/*

Как программно получить доступ к этому отображению?


person Gricey    schedule 07.05.2019    source источник


Ответы (3)


Если вы ищете решение, которое работает как из вашей разработки — не установленной или вызванной локально — версии, так и из установленной версии, попробуйте это решение.

Импорт:

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')

Наконец, замените функцию в ответе @Gricey, изменив return "development" на:

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

Взято из моего ответа https://stackoverflow.com/a/60352386

person A T    schedule 23.02.2020

Я считаю, что название проекта должно быть жестко закодировано, если это возможно. Если нет, то некоторая функция, подобная следующей, может помочь выяснить метаданные для установленного дистрибутива, содержащего текущий файл (__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 г.):

Похоже, это может стать проще благодаря недавно добавленной функции packages_distributions() в importlib_metadata:

person sinoroc    schedule 23.02.2020

После пары часов изучения pkg_resources и чтения источник удаления пипса У меня работает следующее:

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"

Это перебирает все установленные пакеты, и для каждого из них перебирает свой список файлов и пытается сопоставить его с текущим файлом, это сопоставляет пакет с дистрибутивом. Это не совсем идеально, и я все еще открыт для лучших ответов.

person Gricey    schedule 08.05.2019
comment
Спасибо, я расширил вашу версию, чтобы она также поддерживала версии дерева разработки stackoverflow.com/a/60352386 - person A T; 23.02.2020