前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >使用 Python 开发 CLI 工具并打包发布到 PyPI

使用 Python 开发 CLI 工具并打包发布到 PyPI

作者头像
yiyun
发布2023-02-06 15:43:27
发布2023-02-06 15:43:27
3.2K00
代码可运行
举报
文章被收录于专栏:yiyun 的专栏yiyun 的专栏
运行总次数:0
代码可运行

引言

使用 Python 开发 CLI 工具并打包发布到 PyPI

  • 打包 Python 并发布到 PyPi
  • 使用 Python 开发 CLI 工具

打包并发布

方式1: setup.py

参考:

TODO:

方式2: 使用 poetry

参考:

安装 poetry

Windows 10

Windows (Powershell)

代码语言:javascript
代码运行次数:0
复制
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -

PS: 其实现在 PowerShell 也支持 curl , 其实好像就是 WebRequest 实现的

By default, Poetry is installed into a platform and user-specific directory:

  • ~/Library/Application Support/pypoetry on MacOS.
  • ~/.local/share/pypoetry on Linux/Unix.
  • %APPDATA%\pypoetry on Windows.

If you wish to change this, you may define the $POETRY_HOME environment variable:

这里我不想安装在默认路径

Windows (Powershell)

代码语言:javascript
代码运行次数:0
复制
$env:APPDATA
代码语言:javascript
代码运行次数:0
复制
$env:POETRY_HOME=“D:\Program Files\pypoetry”

现在可以安装了

代码语言:javascript
代码运行次数:0
复制
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -

注意最后有个 -

安装失败:

参考: - Poetry教程一(Poetry安装与卸载)_成都 - 阿木木的博客-CSDN博客_poetry安装

代码语言:javascript
代码运行次数:0
复制
🦄  (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
Retrieving Poetry metadata

# Welcome to Poetry!

This will download and install the latest version of Poetry,
a dependency and package manager for Python.

It will add the `poetry` command to Poetry's bin directory, located at:

D:\Program Files\pypoetry\bin

You can uninstall at any time by executing this script with the --uninstall option,
and these changes will be reverted.

Installing Poetry (1.3.1)
Installing Poetry (1.3.1): Creating environment
Installing Poetry (1.3.1): Installing Poetry
Installing Poetry (1.3.1): An error occurred. Removing partial environment.
Poetry installation failed.
See F:\Repos\notebook\poetry-installer-error-5nxluwjh.log for error logs.
代码语言:javascript
代码运行次数:0
复制
🦄  cat poetry-installer-error-5nxluwjh.log
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple/poetry/

WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple/poetry/

WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple/poetry/

WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple/poetry/

WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', OSError(0, 'Error'))': /simple/poetry/

ERROR: Could not find a version that satisfies the requirement poetry==1.3.1 (from versions: none)

ERROR: No matching distribution found for poetry==1.3.1

WARNING: There was an error checking the latest version of pip.


Traceback:

  File "<stdin>", line 919, in main
  File "<stdin>", line 550, in run
  File "<stdin>", line 572, in install
  File "<stdin>", line 675, in install_poetry
  File "<stdin>", line 367, in pip
  File "<stdin>", line 364, in python
  File "<stdin>", line 357, in run

指定使用 代理, 还是失败了

代码语言:javascript
代码运行次数:0
复制
(Invoke-WebRequest -Proxy http://127.0.0.1:10808 -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
代码语言:javascript
代码运行次数:0
复制
🦄  (Invoke-WebRequest -Proxy http://127.0.0.1:10808 -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -
Invoke-WebRequest: The response ended prematurely.

尝试另外一种方式 curl

代码语言:javascript
代码运行次数:0
复制
curl -sSL https://install.python-poetry.org | python -

还是相同错误 失败

代码语言:javascript
代码运行次数:0
复制
curl -sSL https://install.python-poetry.org | python3 - --git https://github.com/python-poetry/poetry.git@master

从 GitHub 安装失败

代码语言:javascript
代码运行次数:0
复制
(Invoke-WebRequest -Proxy http://127.0.0.1:10808 -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -

还是失败

离线安装 poetry

参考:

  1. 下载: https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py
  2. 下载: https://github.com/python-poetry/poetry/releases
  1. 将下载的压缩包存到与 install-poetry.py 文件 同级的文件夹下,不要解压
  1. 在此目录下运行安装
代码语言:javascript
代码运行次数:0
复制
python install-poetry.py --file poetry-1.3.1.tar.gz
代码语言:javascript
代码运行次数:0
复制
python install-poetry.py --path poetry-1.3.1.tar.gz

安装失败, 详细查看,还是 代理连接出错, 好奇怪的报错, pip.ini 代理等配置与代理工具均已关闭

尝试下设置 pip 国内镜像源, 发现上次电脑重装后, 还没有重新配置

安装过程极慢, 但总算是安装上了

Add Poetry to your PATH

The installer creates a poetry wrapper in a well-known, platform-specific directory:

  • $HOME/.local/bin on Unix.
  • %APPDATA%\Python\Scripts on Windows.
  • $POETRY_HOME/bin if $POETRY_HOME is set.

If this directory is not present in your $PATH, you can add it in order to invoke Poetry as poetry.

Alternatively, the full path to the poetry binary can always be used:

  • ~/Library/Application Support/pypoetry/venv/bin/poetry on MacOS.
  • ~/.local/share/pypoetry/venv/bin/poetry on Linux/Unix.
  • %APPDATA%\pypoetry\venv\Scripts\poetry on Windows.
  • $POETRY_HOME/venv/bin/poetry if $POETRY_HOME is set.

如下图

代码语言:javascript
代码运行次数:0
复制
poetry --version
代码语言:javascript
代码运行次数:0
复制
poetry self update

基础用法

代码语言:javascript
代码运行次数:0
复制
poetry new --src my-package
代码语言:javascript
代码运行次数:0
复制
my-package
├── pyproject.toml
├── README.md
├── src
│   └── my_package
│       └── __init__.py
└── tests
    └── __init__.py

打包并发布

参考:

代码语言:javascript
代码运行次数:0
复制
poetry build
代码语言:javascript
代码运行次数:0
复制
poetry publish
代码语言:javascript
代码运行次数:0
复制
poetry config http-basic.pypi <username> <password>

发布成功

开发 CLI 工具

使用 Typer

参考:

代码语言:javascript
代码运行次数:0
复制
poetry add "typer[all]"
代码语言:javascript
代码运行次数:0
复制
# src/my_package/main.py

import typer


app = typer.Typer()


@app.callback()
def callback():
    """
    Awesome Portal Gun
    """


@app.command()
def shoot():
    """
    Shoot the portal gun
    """
    typer.echo("Shooting portal gun")


@app.command()
def load():
    """
    Load the portal gun
    """
    typer.echo("Loading portal gun")

Add a "script"

pyproject.toml

代码语言:javascript
代码运行次数:0
复制
[tool.poetry.scripts]
my-package = "my_package.main:app"

my-package: 是 CLI 程序的名, 用于在 terminal 中呼叫

测试

代码语言:javascript
代码运行次数:0
复制
poetry install

my-package

poetry build

# 从本地文件包安装, 注意替换文件路径
pip install --user /home/rock/code/rick-portal-gun/dist/rick_portal_gun-0.1.0-py3-none-any.whl

poetry install 安装后, 新开 Terimal 还是不能使用, 尝试 build 再 pip install

代码语言:javascript
代码运行次数:0
复制
pip install --user dist/imaging-0.0.1-py3-none-any.whl

可在 main.py 最后添加 __main__ 用于启动测试

main.py

代码语言:javascript
代码运行次数:0
复制
if __name__ == "__main__":
    app()

Q&A

Q: imaging 识别不到

代码语言:javascript
代码运行次数:0
复制
[tool.poetry.scripts]
imaging = "imaging.main:app"

注意: 不是 src.imaging.main:app , 因为前面 packages 都已经 include

目测还必须将以下路径添加到环境变量 PATH 中,就如上面的图中 Warning 一样

代码语言:javascript
代码运行次数:0
复制
C:\Users\yiyun\AppData\Roaming\Python\Python38\Scripts

发现若 Scripts 文件夹 已存在 imaging.exe 则反复安装并不会更新, 需要先卸载, 再安装即可成功

代码语言:javascript
代码运行次数:0
复制
pip uninstall dist/pyimaging-0.0.1-py3-none-any.whl

pip install --user dist/pyimaging-0.0.1-py3-none-any.whl

PS: 很神奇, 目测有除包名外区分方法, 居然旧的包名(imaging)也一并卸载了

或者:

代码语言:javascript
代码运行次数:0
复制
pip uninstall pyimaging

Q: UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 100: illegal multibyte sequence

参考:

代码语言:javascript
代码运行次数:0
复制
pip install --user dist/pyimaging-0.0.1-py3-none-any.whl

ERROR: Exception:
Traceback (most recent call last):
  File "D:\anaconda3\lib\site-packages\pip\_internal\cli\base_command.py", line 188, in _main
    status = self.run(options, args)
  File "D:\anaconda3\lib\site-packages\pip\_internal\cli\req_command.py", line 185, in wrapper
    return func(self, options, args)
  File "D:\anaconda3\lib\site-packages\pip\_internal\commands\install.py", line 398, in run
    installed = install_given_reqs(
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\__init__.py", line 67, in install_given_reqs
    requirement.install(
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_install.py", line 804, in install
    install_wheel(
  File "D:\anaconda3\lib\site-packages\pip\_internal\operations\install\wheel.py", line 622, in install_wheel
    install_unpacked_wheel(
  File "D:\anaconda3\lib\site-packages\pip\_internal\operations\install\wheel.py", line 596, in install_unpacked_wheel
    rows = get_csv_rows_for_installed(
  File "D:\anaconda3\lib\site-packages\pip\_internal\operations\install\wheel.py", line 247, in get_csv_rows_for_installed
    for row in old_csv_rows:
UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 100: illegal multibyte sequence

修改下方路径文件

代码语言:javascript
代码运行次数:0
复制
D:\anaconda3\Lib\site-packages\pip\_internal\operations\install\wheel.py

找到 get_csv_rows_for_installed, 发现此方法没有文件读取操作, 不是此方法, 搜索 open( , 为所有打开文件操作加上 下方 encoding 要求

代码语言:javascript
代码运行次数:0
复制
, encoding="utf-8"
代码语言:javascript
代码运行次数:0
复制
🦄  pip uninstall pyimaging
Found existing installation: pyimaging 0.2.0
ERROR: Exception:
Traceback (most recent call last):
  File "D:\anaconda3\lib\site-packages\pip\_internal\cli\base_command.py", line 188, in _main
    status = self.run(options, args)
  File "D:\anaconda3\lib\site-packages\pip\_internal\commands\uninstall.py", line 85, in run
    uninstall_pathset = req.uninstall(
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_install.py", line 675, in uninstall
    uninstalled_pathset = UninstallPathSet.from_dist(dist)
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_uninstall.py", line 535, in from_dist
    for path in uninstallation_paths(dist):
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_uninstall.py", line 67, in unique
    for item in fn(*args, **kw):
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_uninstall.py", line 85, in uninstallation_paths
    r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD')))
  File "D:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1432, in get_metadata_lines
    return yield_lines(self.get_metadata(name))
  File "D:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1424, in get_metadata
    return value.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc7 in position 908: invalid continuation byte in RECORD file at path: c:\users\yiyun\appdata\roaming\python\python38\site-packages\pyimaging-0.2.0.dist-info\RECORD

此错误也是相同解决方法, encoding 问题 注意: 下方不要添加

代码语言:javascript
代码运行次数:0
复制
# ValueError: binary mode doesn't take an encoding argument
with open(path, 'rb') as stream:

Q: Could not install packages due to an EnvironmentError: [Errno 2] No such file or directory

参考:

代码语言:javascript
代码运行次数:0
复制
🦄  pip install --user dist/pyimaging-0.0.1-py3-none-any.whl
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple/, https://pypi.tuna.tsinghua.edu.cn/simple/, http://pypi.douban.com/simple/, http://pypi.mirrors.ustc.edu.cn/simple/
Requirement already satisfied: pyimaging==0.0.1 from file:///F:/Repos/imaging/dist/pyimaging-0.0.1-py3-none-any.whl in c:\users\yiyun\appdata\roaming\python\python38\site-packages (0.0.1)
WARNING: No metadata found in c:\users\yiyun\appdata\roaming\python\python38\site-packages
ERROR: Could not install packages due to an EnvironmentError: [Errno 2] No such file or directory: 'c:\\users\\yiyun\\appdata\\roaming\\python\\python38\\site-packages\\pyimaging-0.0.1.dist-info\\METADATA'

Q: FileNotFoundError: [Errno 2] No such file or directory: 'c:\\users\\yiyun\\appdata\\roaming\\python\\python38\\site-packages\\pyimaging-0.0.1.dist-info\\RECORD'

代码语言:javascript
代码运行次数:0
复制
🦄  pip uninstall pyimaging
Found existing installation: pyimaging 0.0.1
ERROR: Exception:
Traceback (most recent call last):
  File "D:\anaconda3\lib\site-packages\pip\_internal\cli\base_command.py", line 188, in _main
    status = self.run(options, args)
  File "D:\anaconda3\lib\site-packages\pip\_internal\commands\uninstall.py", line 85, in run
    uninstall_pathset = req.uninstall(
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_install.py", line 675, in uninstall
    uninstalled_pathset = UninstallPathSet.from_dist(dist)
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_uninstall.py", line 535, in from_dist
    for path in uninstallation_paths(dist):
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_uninstall.py", line 67, in unique
    for item in fn(*args, **kw):
  File "D:\anaconda3\lib\site-packages\pip\_internal\req\req_uninstall.py", line 85, in uninstallation_paths
    r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD')))
  File "D:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1432, in get_metadata_lines
    return yield_lines(self.get_metadata(name))
  File "D:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1420, in get_metadata
    value = self._get(path)
  File "D:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1616, in _get
    with open(path, 'rb') as stream:
FileNotFoundError: [Errno 2] No such file or directory: 'c:\\users\\yiyun\\appdata\\roaming\\python\\python38\\site-packages\\pyimaging-0.0.1.dist-info\\RECORD'

经过测试, 直接删除 下方路径文件夹即可再次成功安装

代码语言:javascript
代码运行次数:0
复制
C:\Users\yiyun\AppData\Roaming\Python\Python38\site-packages\pyimaging-0.0.1.dist-info

发现我反复安装后, 终于有了这两个在 install (METADATA) 与 uninstall(RECORD) 时会寻找的两个文件

代码语言:javascript
代码运行次数:0
复制
C:\Users\yiyun\AppData\Roaming\Python\Python38\site-packages\pyimaging-0.0.1.dist-info

补充

目前 GitHub Package 不支持 Python 包

目前 GitHub Package 不支持 Python 包

Python 为图片加水印

参考:

代码语言:javascript
代码运行次数:0
复制
import os
from PIL import Image, ImageDraw, ImageFont

def add_watermark(image_path, watermark_text):
  image = Image.open(image_path)
  font = ImageFont.truetype("arial.ttf", 32)
  fill_color = (255, 255, 255)
  width, height = image.size
  draw = ImageDraw.Draw(image)
  text_width, text_height = draw.textsize(watermark_text, font)
  x = (width - text_width) / 2
  y = (height - text_height) / 2
  draw.text((x, y), watermark_text, font=font, fill=fill_color)
  image.save(image_path)

def watermark_images_in_folder(folder_path, watermark_text):
  for root, dirs, files in os.walk(folder_path):
    for file in files:
      if file.endswith(('.jpg', '.jpeg', '.png')):
        image_path = os.path.join(root, file)
        add_watermark(image_path, watermark_text)

watermark_images_in_folder(".", "Powered by Python")

使用 .NET 为图片加水印

参考:

poetry 添加私有仓库源 (eg: 国内 PyPi 镜像源)

参考:

添加国内清华镜像源 - pypi | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

代码语言:javascript
代码运行次数:0
复制
poetry source add tsinghua https://pypi.tuna.tsinghua.edu.cn/simple

对应配置文件 (pyproject.toml) 会自动添加上以下部分

代码语言:javascript
代码运行次数:0
复制
[[tool.poetry.source]]
name = "tsinghua"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = false
secondary = false

PS:

代码语言:javascript
代码运行次数:0
复制
# 添加 foo 源 为 次要(secondary) 源
poetry source add --secondary foo https://pypi.example.org/simple/
# 指定从 foo 源下载 private-package
poetry add --source foo private-package

参考

感谢帮助!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 打包并发布
    • 方式1: setup.py
    • 方式2: 使用 poetry
      • 安装 poetry
      • 离线安装 poetry
      • 基础用法
      • 打包并发布
  • 开发 CLI 工具
    • 使用 Typer
      • Add a "script"
      • 测试
  • Q&A
    • Q: imaging 识别不到
    • Q: UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 100: illegal multibyte sequence
    • Q: Could not install packages due to an EnvironmentError: [Errno 2] No such file or directory
    • Q: FileNotFoundError: [Errno 2] No such file or directory: 'c:\\users\\yiyun\\appdata\\roaming\\python\\python38\\site-packages\\pyimaging-0.0.1.dist-info\\RECORD'
  • 补充
    • 目前 GitHub Package 不支持 Python 包
    • Python 为图片加水印
    • 使用 .NET 为图片加水印
    • poetry 添加私有仓库源 (eg: 国内 PyPi 镜像源)
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档