Standardowy sposób osadzenia wersji w pakiecie Pythona?

Czy istnieje standardowy sposób kojarzenia ciągu wersji z pakietem Pythona w taki sposób, że mógłbym wykonać następujące czynności?

import foo
print foo.version

Wyobrażam sobie, że istnieje jakiś sposób na odzyskanie tych danych bez dodatkowego twardego kodowania, ponieważ ciągi minor / major są już określone w setup.py. Alternatywnym rozwiązaniem, które znalazłem było mieć {[2] } w moim foo/__init__.py, a następnie mieć __version__.py wygenerowane przez setup.py.

Author: Tshepang, 2009-01-19

14 answers

Nie jest bezpośrednio odpowiedzią na twoje pytanie, ale powinieneś rozważyć nazwanie go __version__, a nie version.

To prawie quasi-standard. Wiele modułów w bibliotece standardowej używa __version__, A jest to również używane w partiach zewnętrznych modułów, więc jest to quasi-standard.

Zazwyczaj __version__ jest ciągiem znaków, ale czasami jest to również float lub krotka.

Edit: jak wspomniał S. Lott (Dziękuję!), PEP 8 mówi wprost:

Wersja Księgowość

Jeśli musisz mieć Subversion, CVS lub RCS crud w pliku źródłowym, zrób to w następujący sposób.

    __version__ = "$Revision: 63990 $"
    # $Source$

Linie te powinny być dołączone po docstringu modułu, przed każdy inny kod, oddzielony pustą linią powyżej i poniżej.

Należy również upewnić się, że Numer wersji jest zgodny z formatem opisanym w PEP 440 (PEP 386 poprzednia wersja tego standardu).

 99
Author: oefe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-03 14:07:18

Używam pojedynczego pliku _version.py jako "raz cannonical place" do przechowywania informacji o wersji:

  1. Zapewnia atrybut __version__.

  2. Zapewnia standardową wersję metadanych. Dlatego zostanie ona wykryta przez pkg_resources lub inne narzędzia Przetwarzające metadane pakietu (EGG-INFO i/lub PKG-INFO, PEP 0345).

  3. Nie importuje pakietu (lub czegokolwiek innego) podczas budowania pakietu, co może powodować problemy w niektórych sytuacjach. (Patrz komentarze poniżej o tym, jakie problemy może to spowodować.)

  4. Jest tylko jedno miejsce, w którym Numer wersji jest zapisywany, więc jest tylko jedno miejsce, aby go zmienić, gdy Numer wersji się zmieni, i jest mniejsza szansa na niespójne wersje.

Oto Jak to działa: "jednym kanonicznym miejscem" do przechowywania numeru wersji jest plik. py o nazwie "_version.py", który znajduje się w Twoim pakiecie Pythona, na przykład w myniftyapp/_version.py. Ten plik jest modułem Pythona, ale twój setup.py nie importuje! (Który pokonałby 3.) Zamiast Twojego setup.py wie, że zawartość tego pliku jest bardzo prosta, coś w stylu:

__version__ = "3.6.5"

I tak twoje setup.py otwiera plik i przetwarza go kodem:

import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
    verstr = mo.group(1)
else:
    raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))

Then your setup.py przekazuje ten łańcuch jako wartość argumentu "version" do setup(), spełniając tym samym funkcję 2.

Aby spełnić funkcję 1, możesz mieć swój pakiet (w czasie uruchamiania, Nie w czasie konfiguracji!) Importuj plik _version z myniftyapp/__init__.py tak:

from _version import __version__
Oto przykład tej techniki, którą używam od lat.

Kod w tym przykładzie jest nieco bardziej skomplikowany, ale uproszczony przykład, który napisałem w tym komentarzu, powinien być kompletną implementacją.

Oto przykładowy kod importowania wersji .

Jeśli widzisz coś złego w tym podejściu, proszę dać mi znać.

 91
Author: Zooko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-13 18:13:12

Przepisane 2017-05

Po ponad dziesięciu latach pisania kodu Pythona i zarządzania różnymi pakietami doszedłem do wniosku, że DIY może nie jest najlepszym podejściem.

Zacząłem używać pbr pakietu do radzenia sobie z wersjonowaniem w moich pakietach. Jeśli używasz git jako SCM, będzie to pasować do twojego przepływu pracy jak magia, oszczędzając tygodnie pracy(będziesz zaskoczony, jak skomplikowany może być problem).

Od dziś pbr jest # 11 najczęściej używanych pakiet Pythona i osiągnięcie tego poziomu nie zawierało żadnych brudnych sztuczek: było tylko jedno: naprawienie wspólnego problemu z opakowaniem w bardzo prosty sposób.

pbr może zrobić więcej obciążeń związanych z utrzymaniem pakietu, nie ogranicza się do wersjonowania, ale nie zmusza do przyjęcia wszystkich jego korzyści.

Więc aby dać ci wyobrażenie jak wygląda przyjęcie pbr w jednym commicie, zajrzyj swiching packaging to pbr

Prawdopodobnie zauważyłbyś, że wersja nie jest przechowywane w repozytorium. PBR wykrywa go z gałęzi i tagów Git.

Nie musisz się martwić o to, co się stanie, gdy nie masz repozytorium git, ponieważ pbr "kompiluje" i buforuje wersję podczas pakowania lub instalowania aplikacji, więc nie ma zależności od Git.

Stare rozwiązanie

Oto najlepsze rozwiązanie, jakie widziałem do tej pory i wyjaśnia również dlaczego:

Wewnątrz yourpackage/version.py:

# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'

Wewnątrz yourpackage/__init__.py:

from .version import __version__

Wewnątrz setup.py:

exec(open('yourpackage/version.py').read())
setup(
    ...
    version=__version__,
    ...

Jeśli znasz inne podejście, które wydaje się być lepsze daj mi znać.

 80
Author: sorin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-05 19:25:57

Zgodnie z odroczonym PEP 396 (numery wersji modułów) , jest na to proponowany sposób. Opisuje, z uzasadnieniem, (co prawda opcjonalny) standard dla modułów do naśladowania. Oto fragment:

3) jeśli moduł (lub pakiet) zawiera numer wersji, wersja powinna być dostępna w atrybucie __version__.

4) Dla modułów, które żyją wewnątrz pakietu przestrzeni nazw, moduł powinien zawierać atrybut __version__. Sam pakiet przestrzeni nazw powinien Nie zawiera własnego atrybutu __version__.

5) Wartość atrybutu __version__ powinna być łańcuchem znaków.

 24
Author: Oddthinking,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-20 21:45:36

Chociaż jest to prawdopodobnie za późno, istnieje nieco prostsza alternatywa dla poprzedniej odpowiedzi:

__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)

(i byłoby dość proste przekonwertowanie automatycznie zwiększających się części numerów wersji na ciąg znaków za pomocą str().)

Oczywiście, z tego co widziałem, ludzie mają tendencję do używania czegoś podobnego do wspomnianej wcześniej wersji podczas używania __version_info__, i jako takie przechowują ją jako krotkę ints; jednak nie do końca widzę w tym sens, ponieważ wątpię, aby były sytuacje, w których możesz wykonywać operacje matematyczne, takie jak dodawanie i odejmowanie na częściach numerów wersji w dowolnym celu poza ciekawością lub automatyczną inkrementacją (a nawet wtedy, int() i str() mogą być używane dość łatwo). (Z drugiej strony, istnieje możliwość, że czyjś kod oczekuje krotki liczbowej, a nie krotki ciągów, a tym samym nie.)

Jest to oczywiście mój własny pogląd i chętnie chciałbym, aby inni wnieśli wkład w używanie krotki numerycznej.


Jako shezi przypomniała mi, (leksykalne) porównania ciągów liczb niekoniecznie mają taki sam wynik jak bezpośrednie porównania numeryczne; aby to zapewnić, wymagane byłyby czołowe zera. W końcu, przechowywanie __version_info__ (lub jak by się to nazywało) jako krotki wartości całkowitych pozwoliłoby na bardziej efektywne porównywanie wersji.

 21
Author: JAB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-27 21:54:36

Używam pliku JSON w katalogu pakietu. To pasuje do wymagań Zooko.

Wewnątrz pkg_dir/pkg_info.json:

{"version": "0.1.0"}

Wewnątrz setup.py:

from distutils.core import setup
import json

with open('pkg_dir/pkg_info.json') as fp:
    _info = json.load(fp)

setup(
    version=_info['version'],
    ...
    )

Wewnątrz pkg_dir/__init__.py:

import json
from os.path import dirname

with open(dirname(__file__) + '/pkg_info.json') as fp:
    _info = json.load(fp)

__version__ = _info['version']

Umieszczam również inne informacje w pkg_info.json, jak autor. I lubię używać JSON, ponieważ mogę zautomatyzować zarządzanie metadanymi.

 9
Author: Andy Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-25 21:23:11

Wydaje się, że nie istnieje standardowy sposób osadzania ciągu wersji w pakiecie Pythona. Większość pakietów, które widziałem, używa jakiegoś wariantu Twojego rozwiązania, np.]}

  1. Osadzenie wersji w setup.py i wygenerowanie setup.py modułu (np. version.py) zawierającego tylko informacje o wersji, które są importowane przez pakiet, lub

  2. Odwrotnie: umieść informacje o wersji w swoim pakiecie i zaimportuj , że , aby ustawić wersję w setup.py

 7
Author: dF.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-19 19:01:40

Warto również zauważyć, że podobnie jak {[1] } jest półproduktem. w Pythonie tak jest __version_info__, który jest krotką, w prostych przypadkach można po prostu zrobić coś w stylu:

__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])

...i możesz pobrać __version__ string z pliku, czy cokolwiek innego.

 6
Author: James Antill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-14 06:27:09

Wiele z tych rozwiązań tutaj ignoruje git znaczniki wersji, co nadal oznacza, że musisz śledzić wersję w wielu miejscach (źle). Podszedłem do tego z następującymi celami:

  • wywołaj wszystkie odwołania do wersji Pythona z tagu w repo git
  • Automatyzacja git tag/push i setup.py upload kroki z pojedynczym poleceniem, które nie pobiera żadnych danych wejściowych.

Jak to działa:

  1. Z polecenia make release, Ostatnia oznaczona wersja w repo git zostanie znaleziona i / align = "left" / Znacznik jest przesunięty z powrotem do origin.

  2. Makefile przechowuje wersję w src/_version.py, gdzie będzie ona odczytywana przez setup.py, a także dołączona do wydania. Nie sprawdzaj _version.py do kontroli źródła!

  3. setup.py polecenie odczytuje ciąg nowej wersji z package.__version__.

Szczegóły:

Makefile

# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver      = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))

.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
    echo '__version__ = "$(call git_describe_ver)"' > $@

.PHONY: release
release: test lint mypy
    git tag -a $(call next_patch_ver)
    $(MAKE) ${MODULE}/_version.py
    python setup.py check sdist upload # (legacy "upload" method)
    # twine upload dist/*  (preferred method)
    git push origin master --tags

Cel release zawsze zwiększa trzecią cyfrę wersji, ale możesz użyć next_minor_ver lub next_major_ver, aby zwiększyć pozostałe cyfry. Polecenia te opierają się na skrypcie versionbump.py, który jest sprawdzany w katalogu głównym repo

Versionbump.py

"""An auto-increment tool for version strings."""

import sys
import unittest

import click
from click.testing import CliRunner  # type: ignore

__version__ = '0.1'

MIN_DIGITS = 2
MAX_DIGITS = 3


@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
    """Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
    optional 'v' prefix is allowed and will be included in the output if found."""
    prefix = version[0] if version[0].isalpha() else ''
    digits = version.lower().lstrip('v').split('.')

    if len(digits) > MAX_DIGITS:
        click.secho('ERROR: Too many digits', fg='red', err=True)
        sys.exit(1)

    digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS]  # Extend total digits to max.
    digits[bump_idx] = str(int(digits[bump_idx]) + 1)  # Increment the desired digit.

    # Zero rightmost digits after bump position.
    for i in range(bump_idx + 1, MAX_DIGITS):
        digits[i] = '0'
    digits = digits[:max(MIN_DIGITS, bump_idx + 1)]  # Trim rightmost digits.
    click.echo(prefix + '.'.join(digits), nl=False)


if __name__ == '__main__':
    cli()  # pylint: disable=no-value-for-parameter

To robi ciężkie podnoszenie, jak przetworzyć i zwiększyć numer wersji z git.

__init__.py

Plik my_module/_version.py jest importowany do my_module/__init__.py. Umieść tutaj dowolną statyczną konfigurację instalacji, którą chcesz rozpowszechnić wraz z Twoim modułem.

from ._version import __version__
__author__ = ''
__email__ = ''

Setup.py

Ostatnim krokiem jest odczytanie informacji o wersji z my_module moduł.

from setuptools import setup, find_packages

pkg_vars  = {}

with open("{MODULE}/_version.py") as fp:
    exec(fp.read(), pkg_vars)

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

Oczywiście, aby to wszystko zadziałało, musisz mieć co najmniej jeden znacznik wersji w repo, aby rozpocząć.

git tag -a v0.0.1
 6
Author: cmcginty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-16 06:20:30

Widziałem też inny styl:

>>> django.VERSION
(1, 1, 0, 'final', 0)
 4
Author: L1ker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-11 17:32:47

Arrow radzi sobie z tym w ciekawy sposób.

W arrow/__init__.py:

__version__ = '0.8.0'
VERSION = __version__

W setup.py:

def grep(attrname):
    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
    strval, = re.findall(pattern, file_text)
    return strval

setup(
    name='arrow',
    version=grep('__version__'),
    # [...]
)
 2
Author: Anto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 12:59:55

Jeśli to coś warte, jeśli używasz NumPy distutils, numpy.distutils.misc_util.Configuration mA make_svn_version_py() metoda osadzająca numer rewizji wewnątrz package.__svn_version__ W Zmiennej version.

 0
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-19 18:42:23
  1. Użyj pliku version.py tylko z __version__ = <VERSION> param w pliku. W pliku setup.py Importuj param __version__ i umieść jego wartość w pliku setup.py w następujący sposób: version=__version__
  2. innym sposobem jest użycie pliku setup.py z version=<CURRENT_VERSION> - CURRENT_VERSION jest zakodowany na twardo.

Ponieważ nie chcemy ręcznie zmieniać wersji w pliku za każdym razem, gdy tworzymy nowy znacznik (gotowy do wydania nowej wersji pakietu), możemy użyć poniższego..

Gorąco polecam bumpversion pakiet. Używam go od lat, aby podbić wersję.

Zacznij od dodania version=<VERSION> do pliku setup.py, jeśli go jeszcze nie masz.

Powinieneś używać takiego krótkiego skryptu za każdym razem, gdy podbijasz wersję:

bumpversion (patch|minor|major) - choose only one option
git push
git push --tags

Następnie dodaj jeden plik na repo o nazwie: .bumpversion.cfg:

[bumpversion]
current_version = <CURRENT_TAG>
commit = True
tag = True
tag_name = {new_version}
[bumpversion:file:<RELATIVE_PATH_TO_SETUP_FILE>]

Uwaga:

  • możesz użyć parametru __version__ pod plikiem version.py tak jak sugerowano w innych postach i zaktualizować plik bumpversion jak to: [bumpversion:file:<RELATIVE_PATH_TO_VERSION_FILE>]
  • ty musisz git commit albo git reset Wszystko w Twoim repo, inaczej dostaniesz brudny błąd repo.
  • upewnij się, że Twoje środowisko wirtualne zawiera pakiet bumpversion, bez niego nie będzie działać.
 -1
Author: Oran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-07 23:54:57

Jeśli używasz CVS (lub RCS) i chcesz szybkiego rozwiązania, możesz użyć:

__version__ = "$Revision: 1.1 $"[11:-2]
__version_info__ = tuple([int(s) for s in __version__.split(".")])

(oczywiście numer wersji zostanie zastąpiony przez CVS.)

To daje wersję przyjazną do wydruku oraz informacje o wersji, których możesz użyć, aby sprawdzić, czy importowany moduł ma co najmniej oczekiwaną wersję:

import my_module
assert my_module.__version_info__ >= (1, 1)
 -2
Author: Martin Ibert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-02 10:11:23