wake-up-neo.com

Wie kann ich die in setup.py (setuptools) definierte Version in meinem Paket erhalten?

Wie kann ich die in setup.py Definierte Version aus meinem Paket beziehen (für --version Oder für andere Zwecke)?

135
elmarco

Fragen Sie die Versionszeichenfolge der bereits installierten Distribution ab

Um die Version zur Laufzeit aus Ihrem Paket abzurufen (was Ihre Frage tatsächlich zu stellen scheint), können Sie Folgendes verwenden:

import pkg_resources  # part of setuptools
version = pkg_resources.require("MyProject")[0].version

Speichern Sie die Versionszeichenfolge zur Verwendung während der Installation

Wenn Sie den umgekehrten Weg einschlagen möchten (was anscheinend der Meinung anderer Antwortautoren ist, dass Sie gefragt haben), geben Sie die Versionszeichenfolge in eine separate Datei ein und lesen Sie den Inhalt dieser Datei in setup.py.

Sie können eine version.py in Ihrem Paket mit einer __version__ - Zeile erstellen und diese dann mit execfile('mypackage/version.py') aus setup.py lesen, sodass im Setup __version__ Festgelegt wird. py Namespace.

Wenn Sie einen viel einfacheren Weg suchen, der mit allen Python Versionen und sogar Nicht-Python-Sprachen funktioniert, die möglicherweise Zugriff auf die Versionszeichenfolge benötigen:

Speichern Sie die Versionszeichenfolge als einzigen Inhalt einer Nur-Text-Datei, z. VERSION, und lesen Sie diese Datei während setup.py.

version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()

Dieselbe VERSION -Datei funktioniert dann in jedem anderen Programm, auch in Nicht-Python-Programmen, genauso gut, und Sie müssen nur die Versionszeichenfolge an einer Stelle für alle Programme ändern.

Warnung vor Rennbedingungen während der Installation

Importieren Sie Ihr Paket übrigens NICHT aus der Datei setup.py, wie in einer anderen Antwort hier vorgeschlagen: Es scheint für Sie zu funktionieren (da die Abhängigkeiten Ihres Pakets bereits installiert sind), aber neue Benutzer Ihres Pakets werden verwüstet , da sie Ihr Paket nicht installieren können, ohne vorher die Abhängigkeiten manuell zu installieren.

226
PJ Eby

beispielstudie: mymodule

Stellen Sie sich diese Konfiguration vor:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

Stellen Sie sich dann ein übliches Szenario vor, in dem Sie Abhängigkeiten haben und setup.py sieht aus wie:

setup(...
    install_requires=['dep1','dep2', ...]
    ...)

Und ein Beispiel __init__.py:

from mymodule.myclasses import *
from mymodule.version import __version__

Und zum Beispiel myclasses.py:

# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2

problem Nr. 1: Importieren von mymodule während des Setups

Wenn dein setup.py importiert mymodule dann während des Setups erhalten Sie höchstwahrscheinlich ein ImportError. Dies ist ein sehr häufiger Fehler, wenn Ihr Paket Abhängigkeiten aufweist. Wenn Ihr Paket keine anderen Abhängigkeiten als die eingebauten hat, sind Sie möglicherweise sicher. Dies ist jedoch keine gute Praxis. Der Grund dafür ist, dass es nicht zukunftssicher ist; Sagen Sie morgen, Ihr Code muss eine andere Abhängigkeit verbrauchen.

problem Nr. 2: Wo ist mein __version__?

Wenn Sie __version__ im setup.py dann stimmt es möglicherweise nicht mit der Version überein, die Sie in Ihrem Modul ausliefern würden. Um konsistent zu sein, würden Sie es an einem Ort ablegen und an der gleichen Stelle lesen, wenn Sie es benötigen. Mit import erhalten Sie möglicherweise das Problem Nr. 1.

lösung: à la setuptools

Sie würden eine Kombination aus open, exec verwenden und ein Diktat für exec angeben, um Variablen hinzuzufügen:

# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path

main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
    exec(ver_file.read(), main_ns)

setup(...,
    version=main_ns['__version__'],
    ...)

Und in mymodule/version.py die Version ausstellen:

__version__ = 'some.semantic.version'

Auf diese Weise wird die Version mit dem Modul geliefert, und Sie haben keine Probleme beim Versuch, ein Modul mit fehlenden Abhängigkeiten zu importieren (das noch installiert werden muss).

30
dnozay

Die beste Technik besteht darin, __version__ in Ihren Produktcode ein und importieren Sie ihn von dort in die Datei setup.py. Dies gibt Ihnen einen Wert, den Sie in Ihrem laufenden Modul lesen können und der nur an einer Stelle definiert werden kann.

Die Werte in setup.py werden nicht installiert, und setup.py bleibt nach der Installation nicht erhalten.

Was ich (zum Beispiel) in coverage.py gemacht habe:

# coverage/__init__.py
__version__ = "3.2"


# setup.py
from coverage import __version__

setup(
    name = 'coverage',
    version = __version__,
    ...
    )

UPDATE (2017): coverage.py importiert sich nicht mehr selbst, um die Version zu erhalten. Durch das Importieren Ihres eigenen Codes kann er deinstalliert werden, da der Produktcode versucht, Abhängigkeiten zu importieren, die noch nicht installiert sind, da sie durch setup.py installiert werden.

14
Ned Batchelder

Ihre Frage ist ein wenig vage, aber ich denke, Sie fragen sich, wie Sie sie spezifizieren sollen.

Sie müssen __version__ Folgendermaßen definieren:

__version__ = '1.4.4'

Und dann können Sie bestätigen, dass setup.py die soeben angegebene Version kennt:

% ./setup.py --version
1.4.4
13
jathanism

Ich war mit diesen Antworten nicht zufrieden ... wollte keine Setuptools benötigen, noch ein ganz separates Modul für eine einzelne Variable erstellen, also habe ich mir diese ausgedacht.

Wenn Sie sicher sind, dass das Hauptmodul im pep8-Stil ist und dies auch bleiben wird:

version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            _, _, version = line.replace("'", '').split()
            break

Wenn Sie besonders vorsichtig sein und einen echten Parser verwenden möchten:

import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
    for line in f:
        if line.startswith('__version__'):
            version = ast.parse(line).body[0].value.s
            break

setup.py ist so etwas wie ein Wegwerfmodul, also kein Problem, wenn es ein bisschen hässlich ist.

7
Gringo Suave

Erstellen Sie eine Datei in Ihrem Quellbaum, z. in yourbasedir/yourpackage/_version.py. Lassen Sie diese Datei nur eine einzige Codezeile enthalten:

__version__ = "1.1.0-r4704"

Öffnen Sie dann in Ihrer Datei setup.py diese Datei und analysieren Sie die Versionsnummer wie folgt:

 verstr = "unknown" 
 try: 
 verstrline = open ('yourpackage/_version.py', "rt"). read () 
 except EnvironmentError: 
 pass # Okay, es gibt keine Versionsdatei. 
 else: 
 VSRE = r "^ __ version__ = ['\"] ([^'\"] *) [' "]" 
 mo = re.search (VSRE, verstrline, re.M) 
 if mo: 
 verstr = mo.group (1) 
 else : 
 RuntimeError auslösen ("Version in yourpackage/_version.py nicht gefunden") 

Zum Schluss importieren Sie in yourbasedir/yourpackage/__init__.py _Version wie folgt:

 __ version__ = "unknown" 
 try: 
 from _version import __version __ 
 except ImportError: 
 # Wir laufen in einem Baum, der nicht ' Sie haben keine _version.py, daher wissen wir nicht, wie unsere Version lautet. 
 Pass 

Ein Beispiel für Code, der dies tut, ist das von mir verwaltete "pyutil" -Paket. (Siehe PyPI oder Google Search - Stackoverflow verhindert, dass ich in dieser Antwort einen Hyperlink dazu aufnehme.)

@pjeby ist richtig, dass Sie Ihr Paket nicht aus seiner eigenen setup.py importieren sollten. Das funktioniert, wenn Sie einen neuen Python Interpreter erstellen und als erstes setup.py ausführen: python setup.py, Aber es gibt Fälle, in denen es nicht funktioniert. Das liegt daran, dass import youpackage Nicht bedeutet, das aktuelle Arbeitsverzeichnis für ein Verzeichnis mit dem Namen "yourpackage" zu lesen, sondern im aktuellen sys.modules Nach einem Schlüssel "yourpackage" zu suchen und dann verschiedene Aktionen auszuführen Dinge, wenn es nicht da ist, also funktioniert es immer, wenn Sie python setup.py tun, weil Sie einen frischen, leeren sys.modules haben, aber dies funktioniert im Allgemeinen nicht.

Was ist zum Beispiel, wenn py2exe Ihre Datei setup.py während des Packens einer Anwendung ausführt? Ich habe einen Fall wie diesen gesehen, in dem py2exe die falsche Versionsnummer für ein Paket angegeben hat, weil das Paket seine Versionsnummer von import myownthing In seiner setup.py erhalten hat, aber zuvor eine andere Version dieses Pakets wird während des py2exe-Laufs importiert. Was passiert auch, wenn setuptools, easy_install, distribute oder distutils2 versuchen, Ihr Paket als Teil eines Installationsprozesses für ein anderes Paket zu erstellen, das von Ihrem abhängt? Dann, ob Ihr Paket zum Zeitpunkt der Auswertung von setup.py importierbar ist, oder ob es bereits eine Version Ihres Pakets gibt, die während der Laufzeit des Python Interpreters importiert wurde, oder ob es importiert wird Ihr Paket erfordert, dass andere Pakete zuerst installiert werden, oder hat Nebenwirkungen, kann die Ergebnisse verändern. Ich hatte mehrere Probleme mit der Wiederverwendung von Python) -Paketen, die Probleme für Tools wie verursachten py2exe und setuptools, da deren setup.py das Paket selbst importiert, um die Versionsnummer zu ermitteln.

Übrigens, diese Technik funktioniert sehr gut mit Tools zum automatischen Erstellen der yourpackage/_version.py - Datei, zum Beispiel durch Lesen des Versionskontrollverlaufs und Schreiben einer Versionsnummer basierend auf dem neuesten Tag im Versionskontrollverlauf. Hier ist ein Tool, das dies für Darcs erledigt: http://tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst und hier ist ein Code-Snippet, das dasselbe tut git: http://github.com/warner/python-ecdsa/blob/0ed702a9d4057ecf33eea969b8cf280eaccd89a1/setup.py#L34

4
Zooko

Mit einer Struktur wie dieser:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

wobei version.py enthält:

__version__ = 'version_string'

Sie können dies in setup.py tun:

import sys

sys.path[0:0] = ['mymodule']

from version import __version__

Dies verursacht keine Probleme mit den Abhängigkeiten, die Sie in Ihrem Mymodul/__ init __. Py haben.

3
Jahid

Dies sollte auch mit regulären Ausdrücken und abhängig von den Metadatenfeldern funktionieren, um ein Format wie das folgende zu erhalten:

__field= 'value'

Verwenden Sie zu Beginn Ihrer setup.py-Datei Folgendes:

import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))

Danach können Sie die Metadaten in Ihrem Skript wie folgt verwenden:

print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
3
thp

Um zu vermeiden, dass eine Datei importiert wird (und somit ihr Code ausgeführt wird), könnte man sie analysieren und das version -Attribut aus dem Syntaxbaum wiederherstellen:

# 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

Dieser Code versucht, die Zuweisung __version__ Oder VERSION auf der obersten Ebene des Moduls zu finden. Return is string value. Die rechte Seite kann entweder eine Zeichenfolge oder ein Tupel sein.

2
Mikhail Edoshin

Es gibt tausend Möglichkeiten, eine Katze zu häuten - hier meine:

# 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
Marc Abramowitz

Aufräumen https://stackoverflow.com/a/124138 von @ gringo-suave:

from itertools import ifilter
from os import path
from ast import parse

with open(path.join('package_name', '__init__.py')) as f:
    __version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
                                     f))).body[0].value.s
2
A T

Wir wollten die Metainformationen zu unserem Paket pypackagery in __init__.py Einfügen, konnten dies aber nicht, da es, wie PJ Eby bereits betont hat, Abhängigkeiten von Drittanbietern aufweist (siehe seine Antwort und die Warnung bezüglich des Rennens) Bedingung).

Wir haben es gelöst, indem wir ein separates Modul pypackagery_meta.py Erstellt haben, das nur die Metainformationen enthält:

"""Define meta information about pypackagery package."""

__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
                   'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = '[email protected]'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'

importierte dann die Metainformationen in packagery/__init__.py:

# ...

from pypackagery_meta import __title__, __description__, __url__, \
    __version__, __author__, __author_email__, \
    __license__, __copyright__

# ...

und habe es schließlich in setup.py verwendet:

import pypackagery_meta

setup(
    name=pypackagery_meta.__title__,
    version=pypackagery_meta.__version__,
    description=pypackagery_meta.__description__,
    long_description=long_description,
    url=pypackagery_meta.__url__,
    author=pypackagery_meta.__author__,
    author_email=pypackagery_meta.__author_email__,
    # ...
    py_modules=['packagery', 'pypackagery_meta'],
 )

Sie müssen pypackagery_meta Mit dem Setup-Argument py_modules In Ihr Paket aufnehmen. Andernfalls können Sie es bei der Installation nicht importieren, da es der Paketdistribution fehlen würde.

2
marko.ristin

Nun ist dies grob und bedarf einiger Verbesserungen (es könnte sogar einen nicht abgedeckten Member-Aufruf in pkg_resources geben, den ich verpasst habe), aber ich verstehe einfach nicht, warum dies nicht funktioniert oder warum es bisher niemand vorgeschlagen hat (googeln hat) nicht aufgedreht) ... beachten Sie, dass dies Python 2.x ist und erfordert pkg_resources (seufz):

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
1

Erstellen Sie einfach und direkt eine Datei mit dem Namen source/package_name/version.py Mit folgendem Inhalt:

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

Anschließend importieren Sie in Ihrer Datei source/package_name/__init__.py Die Version, die andere Benutzer verwenden können:

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

Jetzt können Sie dies auf setup.py Setzen

#!/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()

Getestet mit Python 2.7, 3.3, 3.4, 3.5, 3.6 Und 3.7 Unter Linux, Windows und Mac OS. Ich habe mein Paket mit Integrations- und Komponententests für alle diese Plattformen verwendet. Die Ergebnisse von .travis.yml Und appveyor.yml Finden Sie hier:

  1. https://travis-ci.org/evandrocoan/debugtools/builds/5271108
  2. https://ci.appveyor.com/project/evandrocoan/pythondebugtools/builds/24245446

Eine alternative Version verwendet den Kontextmanager:

#!/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 ) )

Sie können auch das codecs -Modul verwenden, um Unicode-Fehler sowohl auf Python 2.7 Als auch auf 3.6 Zu behandeln.

#!/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 ) )

Wenn Sie ein Python -Modul zu 100% in C/C++ mit Python C-Erweiterungen schreiben, können Sie dasselbe tun, aber statt mit C/C++ Python.

Erstellen Sie in diesem Fall den folgenden 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'],
            )
        ],
    )

Welches liest die Version aus der Datei version.h:

const char* __version__ = "1.0.12";

Vergessen Sie jedoch nicht, den MANIFEST.in Zu erstellen, um die version.h - Datei einzuschließen:

include README.md
include LICENSE.txt

recursive-include source *.h

Und es ist in die Hauptanwendung integriert mit:

#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__ ) );

    ...
}

Verweise:

  1. Fehler beim Öffnen der Python-Datei
  2. Definiere ein Global in einem Python Modul aus einer C API
  3. Wie binde ich Paketdaten in setuptools/distribute ein?
  4. https://github.com/lark-parser/lark/blob/master/setup.py#L4
  5. Wie werden setuptools-Pakete und ext_modules mit demselben Namen verwendet?
  6. Ist es möglich, Unterverzeichnisse mit dist utils (setup.py) als Teil der Paketdaten einzuschließen?
0
user