Как я могу получить версию, определенную в setup.py (setuptools) в моем пакете? - PullRequest
134 голосов
/ 13 января 2010

Как я могу получить версию, определенную в setup.py, из моего пакета (для --version или других целей)?

Ответы [ 14 ]

2 голосов
/ 14 февраля 2014

Есть тысяча способов убрать кошку - вот мой:

# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
    import os
    import re

    here = os.path.dirname(os.path.abspath(__file__))
    f = open(os.path.join(here, filename))
    version_file = f.read()
    f.close()
    version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
                              version_file, re.M)
    if version_match:
        return version_match.group(1)
    raise RuntimeError("Unable to find version string.")
2 голосов
/ 11 мая 2010

Чтобы избежать импорта файла (и, следовательно, выполнения его кода), можно проанализировать его и восстановить атрибут version из синтаксического дерева:

# assuming 'path' holds the path to the file

import ast

with open(path, 'rU') as file:
    t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
    for node in (n for n in t.body if isinstance(n, ast.Assign)):
        if len(node.targets) == 1:
            name = node.targets[0]
            if isinstance(name, ast.Name) and \
                    name.id in ('__version__', '__version_info__', 'VERSION'):
                v = node.value
                if isinstance(v, ast.Str):
                    version = v.s
                    break
                if isinstance(v, ast.Tuple):
                    r = []
                    for e in v.elts:
                        if isinstance(e, ast.Str):
                            r.append(e.s)
                        elif isinstance(e, ast.Num):
                            r.append(str(e.n))
                    version = '.'.join(r)
                    break

Этот код пытается найти присваивание __version__ или VERSION на верхнем уровне возврата модуля - строковое значение. Правая сторона может быть либо строкой, либо кортежем.

1 голос
/ 27 июня 2017

Теперь это брутто и нуждается в некоторой доработке (возможно, в pkg_resources я пропустил непокрытый вызов члена, но я просто не понимаю, почему это не работает, и почему никто не предложил его на сегодняшний день Поиск в Google не включил это) ... обратите внимание, что это Python 2.x, и для него потребуется pkg_resources (sigh):

import pkg_resources

version_string = None
try:
    if pkg_resources.working_set is not None:
        disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
        # (I like adding ", None" to gets)
        if disto_obj is not None:
            version_string = disto_obj.version
except Exception:
    # Do something
    pass
0 голосов
/ 02 мая 2019

Просто и понятно, создайте файл с именем source/package_name/version.py со следующим содержимым:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"

Затем в вашем файле source/package_name/__init__.py вы импортируете версию для использования другими людьми:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__

Теперь вы можете поставить это на setup.py

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys

try:
    filepath = 'source/package_name/version.py'
    version_file = open( filepath )
    __version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )

except Exception as error:
    __version__ = "0.0.1"
    sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )

finally:
    version_file.close()

Протестировано с Python 2.7, 3.3, 3.4, 3.5, 3.6 и 3.7 в Linux, Windows и Mac OS. Я использовал пакет, в котором есть Интеграция и Модульные тесты для всех этих платформ. Вы можете увидеть результаты .travis.yml и appveyor.yml здесь:

  1. https://travis -ci.org / evandrocoan / debugtools / сборки / 527110800
  2. https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446

Альтернативная версия использует менеджер контекста:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys

try:
    filepath = 'source/package_name/version.py'

    with open( filepath ) as file:
        __version__ ,= re.findall( '__version__ = "(.*)"', file.read() )

except Exception as error:
    __version__ = "0.0.1"
    sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )

Вы также можете использовать модуль codecs для обработки ошибок Unicode как на Python 2.7, так и 3.6

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs

try:
    filepath = 'source/package_name/version.py'

    with codecs.open( filepath, 'r', errors='ignore' ) as file:
        __version__ ,= re.findall( '__version__ = "(.*)"', file.read() )

except Exception as error:
    __version__ = "0.0.1"
    sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )

Если вы пишете модуль Python на 100% на C / C ++ с использованием расширений Python C, вы можете сделать то же самое, но с использованием C / C ++ вместо Python.

В этом случае создайте следующее setup.py:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension

try:
    filepath = 'source/version.h'

    with codecs.open( filepath, 'r', errors='ignore' ) as file:
        __version__ ,= re.findall( '__version__ = "(.*)"', file.read() )

except Exception as error:
    __version__ = "0.0.1"
    sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )

setup(
        name = 'package_name',
        version = __version__,

        package_data = {
                '': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
            },

        ext_modules = [
            Extension(
                name = 'package_name',
                sources = [
                    'source/file.cpp',
                ],
                include_dirs = ['source'],
            )
        ],
    )

Который читает версию из файла version.h:

const char* __version__ = "1.0.12";

Но не забудьте создать MANIFEST.in для включения файла version.h:

include README.md
include LICENSE.txt

recursive-include source *.h

И он интегрирован в основное приложение с:

#include <Python.h>
#include "version.h"

// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
    PyObject* thismodule;
    ...

    // https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
    PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );

    ...
}

Ссылки

  1. Ошибка открытия файла Python
  2. Определение глобала в модуле Python из C API
  3. Как включить данные пакета в setuptools /ести?
  4. https://github.com/lark-parser/lark/blob/master/setup.py#L4
  5. Как использовать пакеты setuptools и ext_modules с одинаковым именем?
  6. Можно ли включить подкаталоги, используя dist utils (setup.py), как часть данных пакета?
...