如何从`src /`目录下的python包访问项目根目录中的文本文件

时间:2017-10-23 00:47:40

标签: python versioning setuptools setup.py pkg-resources

我希望我的软件包的版本号能够存放在一个需要它的所有内容都能引用它的地方。

我在Single Sourcing the Package Version的Python指南中找到了一些建议,并决定尝试#4,将其存储在名为VERSION的项目根目录中的简单文本文件中。

这是我项目目录树的缩短版本(你可以看到the full project on 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

由于VERSIONsetup.py是兄弟姐妹,所以很容易阅读设置脚本中的版本文件,并随意使用它。

但是VERSIONsrc/fluidspaces/__main__.py不是兄弟姐妹,主模块不知道项目根目录的路径,所以我不能使用这种方法。

指南有这个提示:

  

警告:使用这种方法,您必须确保所有源代码和二进制分发中都包含VERSION文件(例如,在您的MANIFEST.in中添加包含VERSION)。

这似乎是合理的 - 而不是需要项目根路径的包模块,可以在构建时将版本文件复制到包中以便于访问 - 但我将该行添加到清单中,并且版本文件似乎仍然不显示在任何地方出现在构建中。

要构建,我正在从项目根目录和virtualenv中运行pip install -U .。以下是<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',
)

我发现这个问题Any python function to get “data_files” root directory?让我觉得pkg_resources库是我问题的答案,但我无法弄清楚如何在我的情况下使用它。 / p>

我遇到了麻烦,因为我发现的大多数示例都直接在项目根目录中包含python包,而不是在src/目录中隔离。我正在使用src/目录,因为有以下建议:

我发现的其他旋钮,并尝试扭曲一点package_datainclude_package_datadata_files kwargs setup()。不知道他们有多相关。似乎在这些事件与清单中声明的​​事物之间存在一些相互作用,但我不确定细节。

2 个答案:

答案 0 :(得分:1)

与Freenode上#python IRC频道中的某些人讨论此问题。我学到了:

  • pkg_resources可能我应该如何我应该做的事情,但它需要将版本文件放在包目录而不是项目根目录。
  • setup.py中,我可以从包目录中读取这样的版本文件,而无需导入包本身(由于某些原因而禁止使用),但是需要对从根到目录的路径进行硬编码包,我想避免。

最终我决定使用setuptools_scm包来从我的git标签中获取版本信息,而不是从我的repo中的文件中获取版本信息(其他人正在使用他们的包来做这些,并且他们的参数令人信服)。

因此,我很容易在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',
)

但我最终必须有一个硬编码路径,指明项目根目录应该与包代码有关,这是我以前一直避免的。我认为this issue on the setuptools_scm GitHub repo可能是为什么这是必要的。

<强>的src / fluidspaces / __主__吡啶

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

答案 1 :(得分:1)

对于仍在寻找答案的人们,以下是我尝试遵循Single Sourcing the Package Version指南的第4种版本。值得注意的是,当有其他更简单的解决方案时,为什么您会选择此解决方案。正如链接所指出的那样,当您拥有可能也希望轻松检查版本的外部工具(例如CI / CD工具)时,此方法很有用。

文件树

myproj
├── MANIFEST.in
├── myproj
│   ├── VERSION
│   └── __init__.py
└── setup.py

myproj / VERSION

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中,以便减少文件数量,并不确定cfg解决方案是否会消除VERSION文件中版本周围的潜在空白。