为 CLI 脚本构建 python 包

Posted

技术标签:

【中文标题】为 CLI 脚本构建 python 包【英文标题】:Build python package for CLI script 【发布时间】:2021-01-21 07:08:27 【问题描述】:

我正在尝试构建我的第一个 python 包并将其发布到 PyPi。我的目标是通过 CLI 提供某个功能,以便最终用户可以通过 pip 安装它,例如pip3 install my-package,并直接从终端使用它,例如my-package -fa first_argument -sa second_argument

假设我的包名为fp-test-package。我制作了我的项目文件夹结构的模型,您可以在此处找到 https://github.com/fabiopipitone/fp-test-package。

在模型中,我还插入了实际项目 (tqdm) 中所需的包之一作为要求,并保持完全相同的约定(外部文件夹和 github repo 上的连字符,内部包的下划线,帮助者的相同位置和导入以及实用程序等)。这样,如果它适用于模型,它必须适用于真实项目。

这是项目的目录结构

fp-test-package
├── fp_test_package
│   ├── __init__.py
│   ├── fp_test_package.py
│   ├── helpers
│   │   ├── arguments_checkers.py
│   │   ├── csv_handlers.py
│   │   └── utility_functions.py
│   └── utils
│       ├── __init__.py
│       ├── CustomLogger.py
│       └── TqdmLoggingHandler.py
├── LICENSE
├── README.rst
├── requirements.txt
├── setup.py
├── docs
└── tests

这里是 setup.py

from setuptools import setup, find_packages
with open("README.rst", "r") as fh:
  long_description = fh.read()

setup( 
  name ='fp-test-package', 
  version ='0.0.1', 
  description='Simple test building a CLI tool package',
  long_description=long_description,
  long_description_content_type='text/x-rst', 
  license ='GPLv2', 
  packages = find_packages(), 
  entry_points = 
    'console_scripts': [ 
      'fp_test_package = fp_test_package.py:main'
    ] 
  , 
  classifiers =( 
    "Programming Language :: Python :: 3", 
    "License :: OSI Approved :: GNU General Public License v2", 
    "Operating System :: Linux", 
  ), 
  keywords ='test packaging fabiopipitone', 
  install_requires = ['tqdm>=4.49.0'], 
  zip_safe = False
) 

我尝试了几个关于如何构建和发布 CLI python 包的操作指南,然后我尝试使用 sudo python3 setup.py installfp-test-package 目录(与 setup.py 相同的级别)中。它似乎安装了包(我可以在pip3 freeze 中找到条目fp-test-package==0.0.1)但是如果我在终端中尝试fp-test-package 它会返回fp-test-package: command not found,如果我尝试fp_test_package 它会返回:

Traceback (most recent call last):
  File "/usr/local/bin/fp_test_package", line 11, in <module>
    load_entry_point('fp-test-package==0.0.1', 'console_scripts', 'fp_test_package')()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 490, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2854, in load_entry_point
    return ep.load()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2445, in load
    return self.resolve()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2451, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
ModuleNotFoundError: No module named 'fp_test_package.py'

然后我选择了sudo pip3 uninstall fp_test_package,它成功卸载了包。我删除了之前创建的builddistfp_test_package.egg-info 目录并尝试使用pip3 install -e .(再次从fp-test-package 内部)。它创建了fp_test_package.egg-info 目录,但同样在返回的控制台中运行fp_test_package

Traceback (most recent call last):
  File "/usr/local/bin/fp_test_package", line 11, in <module>
    load_entry_point('fp-test-package==0.0.1', 'console_scripts', 'fp_test_package')()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 490, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2854, in load_entry_point
    return ep.load()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2445, in load
    return self.resolve()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2451, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
ModuleNotFoundError: No module named 'fp_test_package.py'

现在,我错过了什么?我想简单地打包并安装它,这样当用户输入fp-test-packagefp_test_package 时,它会返回有关需要--export_path 参数的错误(意味着脚本已正确启动),就像我从控制台调用它时一样:

$ python3 fp_test_package/fp_test_package.py
                                
usage: fp_test_package.py [-h] -ep EXPORT_PATH [-sa SECOND_ARGUMENT] [-ta THIRD_ARGUMENT]
fp_test_package.py: error: the following arguments are required: -ep/--export_path

我该怎么做?

编辑: 我注意到@Dustin 然后指出了有关 setup.py 的内容。事实上,我的一位同事 PRed 更改了 console_scripts 部分的虚拟仓库。 现在,在我同事的机器上,一切似乎都按预期工作。在我的身上,在拉取 repo 并重新运行 python3 setup.py install 后,当从控制台调用 fp_test_package 时,它返回以下内容:

Traceback (most recent call last):
  File "/usr/local/bin/fp_test_package", line 11, in <module>
    load_entry_point('fp-test-package==0.0.1', 'console_scripts', 'fp_test_package')()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 490, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2854, in load_entry_point
    return ep.load()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2445, in load
    return self.resolve()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2451, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/usr/local/bin/fp_test_package.py", line 4, in <module>
    __import__('pkg_resources').run_script('fp-test-package==0.0.1', 'fp_test_package.py')
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 667, in run_script
    self.require(requires)[0].run_script(script_name, ns)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 1452, in run_script
    raise ResolutionError(
pkg_resources.ResolutionError: Script 'scripts/fp_test_package.py' not found in metadata at '/usr/local/lib/python3.8/dist-packages/fp_test_package-0.0.1-py3.8.egg/EGG-INFO'

有什么想法吗?

【问题讨论】:

【参考方案1】:

好的,我最终在我的具体案例中发现了问题。使用python3 setup.py install 安装时导致该神秘错误的原因是什么

pkg_resources.ResolutionError: Script 'scripts/fp_test_package.py' not found in metadata at '/usr/local/lib/python3.8/dist-packages/fp_test_package-0.0.1-py3.8.egg/EGG-INFO'

或使用pip3 install . 安装时出现的等效错误

pkg_resources.ResolutionError: Script 'scripts/fp_test_package.py' not found in metadata at '/home/fabio/Desktop/test_python_package/fp-test-package/fp_test_package-0.0.1-py3.8.egg/EGG-INFO'

$PATH 变量中是否缺少正确启动脚本所需的路径。

总结一下,我所做的是:

根据 Dustin 关于console_scripts 的建议编辑setup.py 文件 从fp-test-package 文件夹中构建和安装运行pip3 install .(或pip3 install -e .,如果您想要可编辑版本)的包。 检查使用which fp_test_package(在我的情况下为/home/fabio/.local/bin/fp_test_package)创建脚本的二进制文件的位置 将 path 添加到 $PATH 环境变量(在我的 .bashrc.zshrc 中,因为我使用 zsh - 我添加了一行 export PATH="$HOME/.local/bin:$PATH"

现在一切都按预期进行。 我不会删除correct version of the repo,所以如果有人想使用工作骨架来启动项目,可以在那里找到。

【讨论】:

【参考方案2】:

您的setup.py 有:

  entry_points = 
    'console_scripts': [ 
      'fp_test_package = fp_test_package.py:main'
    ] 
  , 

这相当于以下导入:

from fp_test_package.py import main as fp_test_package

问题是你没有fp_test_package.py 模块,你有一个fp_test_package 模块(带有__init__.py 文件的目录)包含一个fp_test_package 子模块(模块名称不包含扩展名)。

假设您的 fp_test_package.py 文件定义了一个 main 函数,您可以将其更改为:

  entry_points = 
    'console_scripts': [ 
      'fp_test_package = fp_test_package.fp_test_package:main'
    ] 
  , 

或者你可以将你的main 函数移动到fp_test_package/__init__.py,所以它可以是:

  entry_points = 
    'console_scripts': [ 
      'fp_test_package = fp_test_package:main'
    ] 
  , 

【讨论】:

我注意到您指出的内容,我的一位同事 PRed git repo。合并后,您可以注意到 setup.py 的更改与您发布的相同解决方案。有趣的是,现在一切都可以在我同事的机器上运行,但是在我拉出新版本并使用python3 setup.py install 重新运行设置后,当尝试从控制台使用带有fp_test_package 的包时,它返回:```。 .. pkg_resources.ResolutionError:在'/usr/local/lib/python3.8/dist-packages/fp_test_package-0.0.1-py3.8.egg/EGG-INFO的元数据中找不到脚本'scripts/fp_test_package.py' ' ``` 您需要完全卸载软件包并重新安装,最好使用pip install . 完全卸载它是什么意思?我使用sudo pip3 uninstall fp_test_package 然后sudo rm -rf fp_test_package.egg-info dist build 删除创建的目录。然后我尝试了sudo python3 setup.py installpip3 install -e . (首先一个,然后删除所有内容并尝试另一个)并且都返回相同的错误。我什至尝试过使用venv。同样的错误。安装后,运行pip3 list,只有我的包行(fp-test-package 0.0.1)有Location列值(在我同事的机器上是空的)。这意味着什么? 卸载后/usr/local/lib/python3.8/dist-packages/fp_test_package-0.0.1-py3.8.egg还存在吗? 它没有。在pip3 uninstall fp_test_package 和所有rm -rf 之后,如果我从/ 以sudo 用户身份运行find . -name fp_test*,我只会得到./home/fabio/Desktop/test_python_package/fp-test-package/fp_test_package./home/fabio/Desktop/test_python_package/fp-test-package/fp_test_package/fp_test_package.py,那么对fp_test* 的唯一引用就是那些@987654353 @ 文件夹。现在,如果我运行pip3 install -e .pip3 listfp_test_package 行的位置列显示/home/fabio/Desktop/test_python_package/fp-test-package

以上是关于为 CLI 脚本构建 python 包的主要内容,如果未能解决你的问题,请参考以下文章

带有 CLI 和配置脚本的 python 项目结构

将 python3 包添加到 Buildroot

python- sys.argv CLI 参数的帮助程序列表

Systemd 服务不会将 python 脚本写入文件,python 脚本在从 cli 执行时名义上运行

3DsMax 从 CLI 使用参数运行 python 脚本

为 PERL 脚本构建 CLI 参数的 Shell 脚本