Как получить доступ к текстовому файлу в корне проекта из пакета python в каталоге `src/`
Я хочу, чтобы номер версии моего пакета жил в одном месте, где все, что ему нужно, может ссылаться на него.
Я нашел несколько предложений в этом руководстве по Python для одиночной поставки версии пакета и решил попробовать #4, сохранив его в простом текстовом файле в корне моего проекта с именем VERSION
,
Вот сокращенная версия дерева каталогов моего проекта (вы можете увидеть полный проект на GitHub):
.
├── MANIFEST.in
├── README.md
├── setup.py
├── VERSION
├── src/
│ └── fluidspaces/
│ ├── __init__.py
│ ├── __main__.py
│ ├── i3_commands.py
│ ├── rofi_commands.py
│ ├── workspace.py
│ └── workspaces.py
└── tests/
├── test_workspace.py
└── test_workspaces.py
поскольку VERSION
а также setup.py
это братья и сестры, очень легко прочитать файл версии в скрипте установки и делать с ним все, что я захочу.
Но VERSION
а также src/fluidspaces/__main__.py
не являются братьями и сестрами, и основной модуль не знает путь к корню проекта, поэтому я не могу использовать этот подход.
Гид имел это напоминание:
Предупреждение. При таком подходе вы должны убедиться, что файл VERSION включен во все ваши исходные и двоичные дистрибутивы (например, добавьте include VERSION в ваш файл MANIFEST.in).
Это казалось разумным - вместо модулей пакета, нуждающихся в корневом пути проекта, файл версии мог быть скопирован в пакет во время сборки для легкого доступа - но я добавил эту строку в манифест, и файл версии по-прежнему не отображается в строю где угодно.
Чтобы построить, я бегу pip install -U .
из корня проекта и внутри virtualenv. Вот папки, которые создаются в <virtualenv>/lib/python3.6/site-packages
в следствии:
fluidspaces/
├── i3_commands.py
├── __init__.py
├── __main__.py
├── __pycache__/ # contents snipped
├── rofi_commands.py
├── workspace.py
└── workspaces.py
fluidspaces-0.1.0-py3.6.egg-info/
├── dependency_links.txt
├── entry_points.txt
├── installed-files.txt
├── PKG-INFO
├── SOURCES.txt
└── top_level.txt
Больше моих файлов конфигурации:
MANIFEST.in:
include README.md
include VERSION
graft src
prune tests
setup.py:
#!/usr/bin/env python3
from setuptools import setup, find_packages
def readme():
'''Get long description from readme file'''
with open('README.md') as f:
return f.read()
def version():
'''Get version from version file'''
with open('VERSION') as f:
return f.read().strip()
setup(
name='fluidspaces',
version=version(),
description='Navigate i3wm named containers',
long_description=readme(),
author='Peter Henry',
author_email='me@peterhenry.net',
url='https://github.com/mosbasik/fluidspaces',
license='MIT',
classifiers=[
'Development Status :: 3 - Alpha',
'Programming Language :: Python :: 3.6',
],
packages=find_packages('src'),
include_package_data=True,
package_dir={'': 'src'},
package_data={'': ['VERSION']},
setup_requires=[
'pytest-runner',
],
tests_require=[
'pytest',
],
entry_points={
'console_scripts': [
'fluidspaces = fluidspaces.__main__:main',
],
},
python_requires='~=3.6',
)
Я нашел этот вопрос. Есть ли какая-либо функция Python для получения корневого каталога "data_files"? это заставляет меня думать pkg_resources
Библиотека - это ответ на мои проблемы, но я не смог понять, как использовать ее в моей ситуации.
У меня были проблемы, потому что большинство примеров, которые я обнаружил, содержат пакеты Python непосредственно в корне проекта, а не изолированы в src/
каталог. Я использую src/
каталог из-за таких рекомендаций:
- PyTest: передовой опыт: тесты вне кода приложения
- Ionel Cristian Mărieș - Упаковка библиотеки Python
- Hynek Schlawack - Тестирование и упаковка
Другие ручки, которые я нашел и попробовал немного крутить, это package_data
, include_package_data
, а также data_files
Kwargs для setup()
, Не знаю, насколько они хороши. Похоже, что между вещами, объявленными с ними, и вещами, объявленными в манифесте, есть некоторое взаимодействие, но я не уверен в деталях.
1 ответ
Обсудили эту проблему с некоторыми людьми на IRC-канале #python на Freenode. Я научился:
pkg_resources
Возможно, я должен был сделать то, о чем просил, но для этого потребовалось бы поместить файл версии в каталог пакета вместо корня проекта.- В
setup.py
Я мог бы прочитать такой файл версии из каталога пакета, не импортируя сам пакет (нет-нет по нескольким причинам), но это потребовало бы жесткого кодирования пути от корня до пакета, которого я хотел избежать.
В конце концов я решил использовать setuptools_scm
пакет для получения информации о версии из моих тегов git, а не из файла в моем репозитории (кто-то другой делал это с помощью своего пакета, и его аргументы были убедительными).
В результате я получил свой номер версии в setup.py
очень легко:
setup.py:
from setuptools import setup, find_packages
def readme():
'''Get long description from readme file'''
with open('README.md') as f:
return f.read()
setup(
name='fluidspaces',
use_scm_version=True, # use this instead of version
description='Navigate i3wm named containers',
long_description=readme(),
author='Peter Henry',
author_email='me@peterhenry.net',
url='https://github.com/mosbasik/fluidspaces',
license='MIT',
classifiers=[
'Development Status :: 3 - Alpha',
'Programming Language :: Python :: 3.6',
],
packages=find_packages('src'),
package_dir={'': 'src'},
setup_requires=[
'pytest-runner',
'setuptools_scm', # require package for setup
],
tests_require=[
'pytest',
],
entry_points={
'console_scripts': [
'fluidspaces = fluidspaces.__main__:main',
],
},
python_requires='~=3.6',
)
но в итоге мне пришлось иметь жестко заданный путь, указывающий, каким должен быть корень проекта по отношению к коду пакета, а это своего рода то, чего я раньше избегал. Я думаю, что эта проблема в репозитории GitHub setuptools_scm может быть, почему это необходимо.
src / fluidspaces / __ main__.py:
import argparse
from setuptools_scm import get_version # import this function
def main(args=None):
# set up command line argument parsing
parser = argparse.ArgumentParser()
parser.add_argument('-V', '--version',
action='version',
version=get_version(root='../..', relative_to=__file__)) # and call it here
Для тех, кто все еще ищет ответ на этот вопрос, ниже я попытаюсь следовать четвертому варианту руководства по выбору версии пакета из одного источника. Стоит отметить, ПОЧЕМУ вы можете выбрать это решение, когда есть другие более простые. Как указано в ссылке, этот подход полезен, когда у вас есть внешние инструменты, которые также могут захотеть легко проверить версию (например, инструменты CI/CD).
Файловое дерево
myproj
├── MANIFEST.in
├── myproj
│ ├── VERSION
│ └── __init__.py
└── setup.py
myproj/ ВЕРСИЯ
1.4.2
MANIFEST.in
include myproj/VERSION
setup.py
with open('myproj/VERSION') as version_file:
version = version_file.read().strip()
setup(
...
version=version,
...
include_package_data=True, # needed for the VERSION file
...
)
myproj/ __ init__.py
import pkgutil
__name__ = 'myproj'
__version__ = pkgutil.get_data(__name__, 'VERSION').decode()
Стоит отметить, что настройка конфигурации в setup.cfg - хорошая и чистая альтернатива включению всего в функцию установки setup.py. Вместо того, чтобы читать версию в setup.py, а затем включать ее в функцию, вы можете сделать следующее:
setup.cfg
[metadata]
name = my_package
version = attr: myproj.VERSION
В полном примере я решил оставить все в setup.py для упрощения использования одним файлом меньше и неуверенности в том, удаляются ли потенциальные пробелы вокруг версии в файле VERSION решением cfg.