1. 题引

1.1 pipx 是什么?

   据官网介绍,pipx 是安装并运行 Python 终端用户应用(end-user applications)的工具。
   终端用户应用,其实可以理解为用 Python 编写的命令行工具,可以直接从命令行调用的那种。
   有点类似 Mac 下的 brew,类似 JavaScript 的 npx,和 pip 也很像,
只是专门用来安装命令行工具库。
   pipx 之所以存在,是因为 Python 和 PyPI 支持开发者发布带有”终端脚本入口“的代码,
   用户可以在命令行调用 Python 代码,使得这个 Python 包类似于一个独立的应用。

官网

1.2 pipx 的主要功能

pipx 支持如下功能:

将 Python 包安全地安装在隔离环境中,同时又可以全局暴露出命令行的调用入口。
这样可以避免依赖之间的冲突。
列举、更新和删除使用 pipx 安装的包
在临时环境中运行某个 Python 应用的最新版
pipx 需要 Python 3.6 及以上版本,同时必须已经安装好了 pip。

1.3 为什么要使用poetry?

因为想使用pyproject.toml,并通过pyproject.toml进行依赖包管理.
目前pip还不支持,所以poetry是首选

1.4 为什么要使用pyproject.toml?

首先pytest、black、isort等常用工具都支持pyproject.toml了,
可以实现一个文件完成全项目的配置。
其次pyproject.toml是PEP中的内容,是将来的方向。
事实上,已经有越来越多的开源下项目使用pyproject.toml,因此我们也有必要学习了解一下

扩展阅读:

pyproject.toml到底是什么东西?.

2 安装pipx

pipx会为安装的每一个包自动创建隔离环境,并自动设置环境变量。
安装的包能够被执行,非常使用安装那些命令行程序,
比如block、httpie、poetry。

首先在系统级python环境中安装pipx

pip install pipx

将pipx的虚拟环境加入到环境变量里面

pipx ensurepath

按照此命令打印的说明,可以完成shell操作:

pipx completions

验证安装成功

pipx list 

如果你尚未安装任何软件包,你将看到以下输出:

  nothing has been installed with pipx ?

2.1安装 Python 包

以下是 Pipx 入门的一个例子

要全局安装 Python 应用,例如 cowsay,请运行:

$ pipx install cowsay

此命令将自动创建虚拟环境,在其中安装包并包的可执行文件放在 $PATH 中。

示例输出:

installed package cowsay 2.0.3, Python 3.6.8
These binaries are now globally available
- cowsay
done! ✨ ? ✨

让我们测试新安装的 cowsay 程序:

$cowsay moon
  ____
| mooo |
  ====
    \
     \
       ^__^
       (oo)\_______
       (__)\       )\/\
           ||----w |
           ||     ||

在这里,它仅仅是一个例子。你可以安装/测试任何其他的 Python 包。

再次列出pipx 安装的Python 包

$ pipx list

示例输出:

venvs are in C:\Users\admin\.local\pipx\venvs
apps are exposed on your $PATH at C:\Users\emijnae\.local\bin
   package cowsay 4.0, Python 3.8.4
    - cowsay.exe

Pipx 的默认虚拟环境位置是 ~/.local/pipx。这可以用环境变量 PIPX_HOME 覆盖。

   pipx 二进制文件的默认位置是 ~/.local/bin。
   你可以使用 PIPX_BIN_DIR 环境变量覆盖它。
   如果要覆盖 PIPX_BIN_DIR,只需运行 userpath append $PIPX_BIN_DIR,
   确保它在你的路径中。

让我们继续看看如何使用 Pipx 安装 Python 应用。
使用 Pipx 在隔离环境中安装和运行 Python 应用

2.2升级包

要升级指定的安装包,只需执行以下操作:

  $ pipx upgrade cowsay

要一次性升级所有已安装的软件包,请使用:

  $ pipx upgrade-all

从临时虚拟环境运行应用

有时,你可能希望运行特定的 Python 程序,但并不实际安装它。

$ pipx run pycowsay moooo

在临时隔离虚拟环境中运行 Python 应用

此命令实际上并不安装指定程序,而是从临时虚拟环境运行它。你可以使用此命令快速测试 Python 应用。

你甚至可以直接运行 .py 文件。

$ pipx run https://gist.githubusercontent.com/cs01/fa721a17a326e551ede048c5088f9e0f/raw/6bdfbb6e9c1132b1c38fdd2f195d4a24c540c324/pipx-demo.py
pipx is working!

2.3 卸载软件包

可以使用以下命令卸载软件包:

$ pipx uninstall cowsay

要删除所有已安装的包:

$ pipx uninstall-all

2.4 获得帮助

要查看帮助部分,请运行:

$ pipx --help

就是这些了。如果你一直在寻找安全,方便和可靠的程序来安装和运行 Python 应用,Pipx 可能是一个不错的选择。

资源:Pipx 的 GitHub 仓库

3 安装 配置poetry

3.1 安装

可以使用python自带的pip工具来安装,但是这里我们使用pipx来安装

  pipx install poetry 
  installed package poetry 1.1.4, Python 3.9.0
  These apps are now globally available
    - poetry.exe
done!

安装成功后,使用pipx检查安装效果

pipx list
venvs are in C:\Users\san\.local\pipx\venvs
apps are exposed on your $PATH at C:\Users\san\.local\bin
   package poetry 1.1.4, Python 3.9.0
    - poetry.exe

检查一下是否安装成功

poetry
Poetry version 1.1.10

USAGE
  poetry [-h] [-q] [-v [<...>]] [-V] [--ansi] [--no-ansi] [-n] <command> [<arg1>] ... [<argN>]

ARGUMENTS
  <command>              The command to execute
  <arg>                  The arguments of the command

GLOBAL OPTIONS
  -h (--help)            Display this help message
  .......

自此,poetry就已经安装好了。

我们是通过pipx安装的poetry,日后也可以通过pipx 更新poetry:

pipx upgrade poetry

3.2配置设置

查询当前所有配置

$ poetry config list
cache-dir = "/home/$user/.cache/pypoetry"
experimental.new-installer = true
installer.parallel = true
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.path = "{cache-dir}/virtualenvs"  # /home/$user/.cache/pypoetry/virtualenvs

查询单个配置

poetry config virtualenvs.path 

添加或者更新配置

poetry config virtualenvs.in-project true 

删除配置

poetry config virtualenvs.path --unset 

查看可用软件包

根据 poetry.lock 列出所有可用的软件包,并不是根据 pyproject.toml 文件的 [tool.poetry.dependencies]

poetry show

查看某个包的详细信息

$ poetry show fastapi 
name         : fastapi
version      : 0.61.2
description  : FastAPI framework, high performance, easy to learn, fast to code, ready for production

dependencies
 - pydantic >=1.0.0,<2.0.0
 - starlette 0.13.6

options

--no-dev:不要列出开发依赖项

--tree:树的形式列出依赖项

--latest (-l):显示最新版本
--outdated (-o):显示最新版本,但仅适用于过时的软件包

4. 在项目中使用 poetry

4.1新建项目

new

通过创建适合大多数项目的目录结构来启动新的Python项目

poetry new my-package 

my-package 是路径,默认目录结构:

 my-package
    ├── pyproject.toml              # poetry 用于管理项目的 all-in-one 配置文档.
    ├── README.rst                  # 说明文档
       ├── my_package               # poetry 会帮你将包名调整为下划线格式.
    │   └── __init__.py             # 请在该目录下编写你的业务逻辑.
    └── tests                       # 测试模块.
        ├── __init__.py               
        └── test_my_package.py

它有如下参数:

	OPTIONS
	  --name           Set the resulting package name.自定义项目名称
      --src            Use the src layout for the project.  使用 src 目录

比如创建一个名字为my-folder的项目,并使用src目录:

poetry new my-folder --name my-package  --src

目录结构

my-folder
├── pyproject.toml
├── README.rst
├── src
│   └── my_package
│       └── __init__.py
└── tests
    ├── __init__.py
    └── test_my_package.py

4.2初始化一个项目

poetry init

如果想在已存在的 Python 项目使用 poetry,可以用 init 命令,poetry 会以交互方式创建 pyproject.toml 文件

在项目的根目录,执行poetry是, 并一路回车

poetry init
This command will guide you through creating your pyproject.toml config.

Package name [apipractice]:
Version [0.1.0]:
Description []: 
Author []:
License []:
Compatible Python versions [^3.9]:

Would you like to define your main dependencies interactively? (yes/no) [yes]
You can specify a package in the following forms:
  - A single name (requests)
  - A name and a constraint (requests@^2.23.0)
  - A git url (git+https://github.com/python-poetry/poetry.git)
  - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
  - A file path (../my-package/my-package.whl)
  - A directory (../my-package/)
  - A url (https://example.com/packages/my-package-0.1.0.tar.gz)

Search for package to add (or leave blank to continue):

Would you like to define your development dependencies interactively? (yes/no) [yes]
Search for package to add (or leave blank to continue):

Generated file
[tool.poetry]
name = "apipractice"
version = "0.1.0"
description = ""
authors = ["$user <email@github.com>"] 

[tool.poetry.dependencies]
python = "^3.9"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Do you confirm generation? (yes/no) [yes]

在Do you confirm generation? (yes/no) [yes] 后按下回车之后,将会在当前目录生成pyproject.toml,内容如下


[tool.poetry]
name = "apipractice"    # 项目名称,默认会填写当前目录的名称
version = "0.1.0"       # 默认版本
description = ""        # 对本项目简单的描述
authors = ["$user <email@github.com>"]  

[tool.poetry.dependencies]
python = "^3.9"       # 版本依赖包括python版本和第三方库

[tool.poetry.dev-dependencies]  # 开发模式下依赖的第三方库

[build-system]  # 默认构建项目时使用的内容,一般不用改
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

我是一只回车没有自己输入东西的
options

--name:包的名称
--description:包的描述
--author:包的作者
--python:兼容的 Python 版本
--dependency:需要具有版本约束的包,格式 foo:1.0.0
--dev-dependency:开发需求

生成 pyproject 已有的字段 和 options 是可以对齐的

4.3 安装依赖

从当前项目读取 pyproject.toml 文件,解析依赖项 [tool.poetry.dependencies] 并安装它们

poetry install

如果当前目录中有 poetry.lock 文件,它将使用其中的确切版本,而不是解析它们, 这确保使用库的每个人都将获得相同版本的依赖项
如果没有 poetry.lock 文件,poetry 将在依赖项解析后创建一个

重点

默认会安装 [tool.poetry.dependencies] 和 [tool.poetry.dev-dependencies] 下所有强制安装的(不带 optional)依赖项
它有如下参数

--no-dev    	不安装开发依赖项 [tool.poetry.dev-dependencies] 

poetry install --no-dev

--remove-untracked	移除 poetry.lock 文件中不再存在的旧依赖项

poetry install --remove-untracked

-E|--extras	指定安装的包

poetry install --extras

--no-root	不要安装根目录包

poetry install  	--no-root	

同时,记得上面提到的这个参数

poetry config virtualenvs.in-project true

如果没有设置,可以配置一下,但是最好的方法是在项目目录下创建一个文件poetry.toml,写下如下内容:

[virtualenvs]
in-project = true

这样能够保证,在使用poetry安装依赖文件时自动创建虚拟环境。
然后使用的时候执行下面命令激活虚拟环境

source .venv/bin/activate

4.4 其他命令

update

获取所有依赖项的最新版本并更新 poetry.lock 文件

poetry update

指定依赖项进行更新

poetry update requests toml

options

--dry-run :输出操作,但不执行操作
--no-dev : 不安装开发依赖项
--lock:不执行安装,仅更新 poetry.lock 文件

add

将所需要的包添加到 pyproject.toml 的 [tool.poetry.dependencies] 下面,并安装他们
未指定版本的话,则 poetry 会自动选择合适的版本

poetry add requests pendulum

注意

默认不会将包添加到 [tool.poetry.dev-dependencies] 下,若需要得用 --dev 参数
指定版本

poetry add pendulum@^2.0.5
poetry add "pendulum>=2.0.5"

获取最新的版本

poetry add pendulum@latest

添加 github 依赖项

poetry add git+https://github.com/sdispater/pendulum.git

添加 github 依赖项,指定分支

poetry add git+https://github.com/sdispater/pendulum.git#develop
poetry add git+https://github.com/sdispater/pendulum.git#2.0.5

通过本地目录、文件进行安装

poetry add ./my-package/
poetry add ../my-package/dist/my-package-0.1.0.tar.gz
poetry add ../my-package/dist/my_package-0.1.0.whl

以可编辑模式安装依赖项

在 pyproject.toml 文件指定,意味着本地目录中的更改会直接反映在环境中

[tool.poetry.dependencies]
my-package = {path = "../my/path", develop = true}

options

--dev (-D):将包添加为开发依赖项
--path:指定依赖项的路径
--optional:作为可选依赖项添加
--dry-run:输出操作,不执行任何操作
--lock:不执行安装,仅更新 poetry.lock 文件

remove

从已安装包列表删除指定包

poetry remove pendulum
options

    --dev(-D):从开发依赖项中删除包
    --dry-run:输出操作,不执行任何操作

show

根据 poetry.lock 列出所有可用的软件包,并不是根据 pyproject.toml 文件的 [tool.poetry.dependencies]

poetry show

查看某个包的详细信息

poetry show fastapi  

options

--no-dev:不要列出开发依赖项

--tree:树的形式列出依赖项

--latest (-l):显示最新版本
--outdated (-o):显示最新版本,但仅适用于过时的软件包

run

在项目的 virtualenv 中执行指定的命令

poetry run python -V

还可以执行 pyproject.toml 中定义的脚本

[tool.poetry.scripts]
my_script = "my_module:main"

执行 shell

poetry run my_script

显式激活当前虚拟环境,会自动调用虚拟环境下的激活命令

如果不存在虚拟环境,会自动创建一个

check

验证 pyproject.toml 文件的结构,并在出现任何错误时返回详细报告

poetry check

search

在远程库上搜索包

poetry search requests

lock

将所有依赖项锁定为最新的可用兼容版本

poetry lock

version

显示项目的当前版本

是 pyproject.toml 文件的 version 哦

export

将锁文件导出为其他格式

poetry export -f requirements.txt --output requirements.txt

options

--format (-f):要导出的格式(默认值:requirements.txt)目前,仅支持requirements.txt
--output (-o):输出文件的名称,如果省略,则打印到标准输出
--dev(-D):从开发依赖项中删除包
--extras (-E):要包含的额外依赖项集
--without-hashes:从导出的文件中排除散列
--with-credentials:包括用于额外索引的凭据

env

管理虚拟环境,具体教程看:javascript:void(0)
cache

build

生成源文件

poetry build

publish

将使用 build 命令生成的包发布到远程存储库

poetry publish

options

--repository (-r):要将包注册到的存储库(默认值:pypi)应与config命令设置的存储库名称匹配
--username (-u):访问存储库的用户名
--password (-p):访问存储库的密码
--dry-run:执行除上传包以外的所有操作

5 使用过程中遇到的问题

5.1 依赖源的问题

添加依赖的命令

poetry add <pakge_name>

但是有些不好用

poetry add fastapi[all]
Using version ^0.63.0 for fastapi

Updating dependencies
Resolving dependencies...

  ConnectionError

  HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Max retries exceeded with url: /packages/f4/2b/078a9771ae4b67e36b0c2a973df845260833a4eb088b81c84b738509b4c4/aiofiles-0.5.0-py3-none-any.whl (Caused by NewConnectionError('
<urllib3.connection.HTTPSConnection object at 0x0000021BE6355250>: Failed to establish a new connection: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。'))

  at c:\users\admin\.local\pipx\venvs\poetry\lib\site-packages\requests\adapters.py:516 in send
      512│             if isinstance(e.reason, _SSLError):
      513│                 # This branch is for urllib3 v1.22 and later.
      514│                 raise SSLError(e, request=request)
      515│
    → 516│             raise ConnectionError(e, request=request)
      517│
      518│         except ClosedPoolError as e:
      519│             raise ConnectionError(e, request=request)
      520│

从错误提示来看,从其他源下载文件,所以出现了网络错误。

这是poetry已经被吐槽很多次的问题了。。。

解决办法:

pyproject.toml 文件中添加清华镜像或者阿里云镜像:

[[tool.poetry.source]]
name = "aliyun"
url = "https://mirrors.aliyun.com/pypi/simple"
# name = "tsinghua"
# url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/"

但是这不能够完全保证,依然会遇到一些问题
[[tool.poetry.source]] 仅对部分环节有效, 在某些步骤 (如 resolve dependencies...) poetry 仍然使用的是 pypi url,
而我们国内访问不了或者连接超时, 就引起了报错.

解决方法:

加一个 default 参数:

[[tool.poetry.source]]
name = "tsinghua"
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/"
default = true  # 添加一个 default 参数, 使 poetry 的默认回调行为都调用到这个清华镜像上.

参考:

https://github.com/python-poetry/poetry/issues/559
https://python-poetry.org/docs/repositories/#disabling-the-pypi-repository

5.2 添加开发依赖

默认情况下,poetry所添加的依赖是在运行这个项目时所需要的一些包,但是在我们开发的时候,其实还需要一些工具进行配合。

比如说测试框架pytest,如果单纯是为了启动这个项目的话,其实是不需要进行安装的。

所以把这些在运行时不需要安装,在开发时所需要安装的依赖我们称之为开发依赖。

安装开发依赖的方式很简单,就是加一个-D参数

poetry add -D pytest
Using version ^6.2.2 for pytest

Updating dependencies
Resolving dependencies...

Writing lock file

Package operations: 9 installs, 0 updates, 0 removals

  • Installing pyparsing (2.4.7)
  • Installing atomicwrites (1.4.0)
  • Installing attrs (20.3.0)
  • Installing iniconfig (1.1.1)
  • Installing packaging (20.9)
  • Installing py (1.10.0)
  • Installing pluggy (0.13.1)
  • Installing toml (0.10.2)
  • Installing pytest (6.2.2)

然后我们运行pytest执行测试。

有两种方式来验证:

第一种:先进入虚拟环境,然后执行pytest

poetry shell
pytest

第二种:让pytest在虚拟环境中执行

poetry run pytest
   
=============== 29 passed in 1.13s ===============

5.3 什么时候使用 ‘poetry.lock’, 什么时候不使用?

多数人以及要发布模块到 pypi 的人不需要 poetry.lock; 通过 Version Control 进行协作的开发团队以及对他机部署环境要求非常严格的人则需要放入 poetry.lock.

poetry.lock 的意义在于, 它会彻底 “锁死” 项目依赖的版本,

比如我们在 pyproject.toml 中要求的依赖版本是 requests = "^2.24.0",

那么哪怕 requests 的作者后面发布了 2.24.1, 使用 poetry.lock 安装时仍会选择安装 2.24.0 版本.

因此, poetry.lock 帮助我们彻底杜绝因依赖的版本的微小变化而产生的任何不确定性.

补充说明:

如需使用 poetry.lock, 请将它伴随项目一起打包发布, 在执行 poetry install 时它会优先查找 lock 文件.

你可以通过 poetry update 来更新所有依赖到最新版本, 同时 poetry.lock 也会锁定到新的依赖版本.

当pyproject.toml 中所列的依赖与 poetry.lock 不匹配时, poetry install 会中断并提示该错误.

5.4 为项目添加执行入口

我们可以将项目启动的命令,或者启动入口写为脚本,然后加入到配置文件中,然后文件执行 poetry install 操作时,就会将项目编译为可执行文件了

[tool.poetry.scripts]
  package-transformer = "newapi.test:main"

这里注意格式, “ package-transformer”是编译生成的可执行目标文件,方便以后运行使用
"newapi.test:main" 是程序执行的如可文件和函数名

6 例子和总结

现在新的项目已经使用poetry和pyproject.toml来进行管理了。

poetry new  newtest --name newapi
[tool.poetry]
name = "newapi"
version = "0.1.0"
description = ""
authors = ["Dummy"]

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

# 程序执行入口
[tool.poetry.scripts]
  test-package = "newapi.main:main"

# 添加清华源,防止构建依赖时因为网络问题不能构建成功
[[tool.poetry.source]]
name = "tsinghua"
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/"
default = true  # 添加一个 default 参数, 使 poetry 的默认回调行为都调用到

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

main文件:

import time
import os

def test():
   print('hello word')
   print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()))

def main(**kwargs):
   """Entry point """
   test()
   print(os.getcwd())
   time.sleep(10)
   print('test end after 10s')

if __name__ == "__main__":
    main()

现在项目结构如下:‘

 newtest
    ├── pyproject.toml              # poetry 用于管理项目的 all-in-one 配置文档.
    ├── README.rst                  # 说明文档
       ├── newapi               # poetry 会帮你将包名调整为下划线格式.
    │    ├──__init__.py             # 请在该目录下编写你的业务逻辑.
    │   └── main.py              
    └── tests                       # 测试模块.
        ├── __init__.py               
        └── test_my_package.py

够构建环境`

poetry install

激活虚拟环境

source .venv/Script/activate

根据构建的程序入口运行程序

test-package

回顾发现,poetry在开始做了几件件事:

初始化pyproject.toml
构建主程序入口
修改配置文件,增加程序入口和修改镜像源
创建虚拟环境
构建下载相关依赖
根据配置信息生成可执行入口

之后,主要负责:

管理依赖