likes
comments
collection
share

如何上传python包到pypi仓库

作者站长头像
站长
· 阅读数 8

概述

前段时间使用python写了一个大模型评测工具,但是想要将这个工具分享给其他人使用,就需要将python包上传到 pypi 仓库,查阅了相关资料,最终将该评测工具上传到了内部的私有仓库中,本文主要将自己如何上传python包到pypi仓库的学习和实践过程记录下来,希望对有同样需求的小伙伴提供一些帮忙。

python 包基础

python 包的导入路径

在Python中,使用import语句导入包时,Python会按照以下路径顺序来查找需要导入的包:

  1. 内置模块:Python首先会在内置模块中查找需要导入的包,这些内置模块是Python安装时自带的,可以直接使用。
  2. sys.path路径:如果在内置模块中没有找到需要导入的包,Python会继续在sys.path路径中查找。sys.path是一个Python列表,包含了模块搜索的路径。它包括以下几个元素:
    • 当前脚本所在的目录
    • PYTHONPATH环境变量所包含的路径
    • Python安装目录下的site-packages目录
  1. 本地目录:如果在sys.path路径中也没有找到需要导入的包,Python会在当前脚本所在的目录中查找。
  2. 标准库:如果在本地目录中还没有找到需要导入的包,Python会在标准库中查找。标准库是Python提供的一些常用模块,它们存放在Python安装目录下的Lib目录中。
  3. 第三方库:如果在标准库中也没有找到需要导入的包,Python会在第三方库site-packages目录中查找。第三方库是由其他开发者编写的,并且需要通过pip等工具进行安装。

如果在上述路径中都没有找到需要导入的包,Python会抛出ModuleNotFoundError异常。

执行 pip install package 的作用

在Python中,执行pip install package的作用是使用pip工具安装名为package的第三方库或包。

具体而言,当你需要在Python项目中使用某个第三方库时,可以通过执行pip install package来安装该库,package是你要安装的库的名称。

执行pip install package命令会从Python Package Index(PyPI)中下载并安装指定的库,PyPI是Python的官方库仓库,包含了大量的第三方库供开发者使用。如果 python 包在私有仓库中,则需要使用 pip install package --index [your repositoy] 来安装指定仓库的包。

安装过程中,pip会自动解析库的依赖关系,并下载安装所需的依赖库,一旦安装完成,你就可以在Python项目中使用该库的功能了。

需要注意的是,执行pip install package会将库安装到Python环境的site-packages目录中,而不是当前目录,因此在项目中使用import package时,Python会自动从site-packages目录中找到并导入对应的库。另外,你也可以使用pip install -r requirements.txt命令来安装项目中的依赖库,requirements.txt 文件中列出了项目所需的所有库及其版本号,pip会根据这个文件自动安装所有依赖库,这在共享项目或部署项目时非常有用,可以确保所有开发者使用相同的库版本。

当使用pip install package命令安装Python包时,pip会优先尝试找到并安装Wheel包(.whl)。Wheel是一种Python包的分发格式,它通常包含预编译的扩展模块,这使得安装过程更快,因为不需要在用户侧编译代码。

如果没有找到与目标环境(如Python版本、操作系统)相匹配的Wheel包,pip将回退到使用源代码分发包(sdist),即.tar.gz或.zip文件。安装sdist通常会慢一些,因为它可能涉及到编译步骤,尤其是当包包含C语言扩展时。

简而言之,安装优先级如下:

  1. Wheel包(.whl),如果可用且兼容。
  2. 源代码分发包(sdist,如.tar.gz或.zip),如果Wheel不可用。

你可以在pip install命令中使用--only-binary :all:--prefer-binary选项来告诉pip优先使用二进制包,或者使用--no-binary :all:来强制使用源码包。

有兴趣的可以了解一下另一个python包安装工具 pipx:在独立环境中安装和运行 Python 应用程序。

如何构建 python 包

在Python项目中,pyproject.toml文件是用来构建python系统配置的标准方式,它是在PEP 518中引入的,旨在取代setup.pyrequirements.txt等旧有的配置文件,使用pyproject.toml可以让项目的构建过程更加清晰和一致。

pyproject.toml的主要用途包括:

  1. 定义构建系统要求: 你可以指定构建项目所需的工具和版本。
  2. 依赖管理: 你可以列出项目的直接依赖,有些工具如poetry还支持依赖解析和锁定。
  3. 包元数据: 你可以定义包的名称、版本、作者等信息。
  4. 构建和打包配置: 能够指定打包过程中需要的脚本和文件。

一个基本的pyproject.toml示例如下:

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[tool.other_tool]
# 其他工具的配置信息

[tool.another_tool.sub_tool]
# 另一个工具的子工具配置信息

构建系统

[build-system]部分用于指定项目的构建系统。requires列表中包含构建项目所需的工具和它们的版本。build-backend指定了实现PEP 517构建接口的Python模块。

工具特定配置

[tool.*]部分用于特定工具的配置。例如,如果你使用poetry来管理依赖和打包,你的pyproject.toml可能包含以下部分:

[tool.poetry]
name = "awesome-package"
version = "0.1.0"
description = "An awesome package"
authors = ["Your Name <youremail@example.com>"]

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

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

这里,[tool.poetry]部分包含了包的元数据,[tool.poetry.dependencies]和[tool.poetry.dev-dependencies]分别列出了运行时依赖和开发时依赖。

使用pyproject.toml的优点

  • 统一性: 所有构建和打包的配置都在一个文件中。
  • 清晰性: 使用TOML格式,比setup.py的Python脚本更易读和编辑。
  • 现代化: 更适合现代Python打包工具,如pip, build, poetry等。
  • 灵活性: 支持多个工具的配置,易于扩展和集成。

注意事项

  • 并非所有打包和依赖管理工具都完全支持pyproject.toml。
  • 虽然pyproject.toml逐渐成为标准,但一些项目可能仍在使用setup.py等传统方法。

总的来说,pyproject.toml文件是现代Python项目的建议配置方法,它为项目构建和依赖管理提供了一个清晰、一致的方式。

以下是一些支持使用pyproject.toml文件来构建Python项目的工具:

  1. Poetry: 是一个依赖管理和打包工具,它使用pyproject.toml来管理项目的依赖、打包以及发布。
  2. Flit: 是一个简单的打包工具,专注于简单的情况和纯Python,它使用pyproject.toml作为其配置文件。
  3. PDM (Python Development Master) : 又一个依赖管理工具,类似于Poetry,它也使用pyproject.toml来管理项目依赖和打包。
  4. Hatch: 是一个现代化项目、包和虚拟环境管理工具,它使用pyproject.toml构建和管理项目。
  5. setuptools: 是Python的经典打包库,虽然它传统上使用setup.py,但最新版本支持使用pyproject.toml文件。
  6. pip: Python的包安装器。从版本10开始,pip可以使用pyproject.toml中指定的构建系统依赖来构建wheel和源码发行版(sdist)。
  7. build: 它是一个简单的、基于标准的、无需额外配置的Python包构建工具。它遵循PEP 517和PEP 518,使用pyproject.toml文件来构建wheel和源码发行版(sdist)。

这些工具中的每一个都有其使用pyproject.toml的特定方式,因此在使用之前应该参阅相应工具的文档来了解具体的使用细节。随着Python社区对这一新的标准配置文件的支持日益增长,我们可以预期将来会有更多的工具支持pyproject.toml

Hatch 使用介绍

hatch 是一个用于构建、发布和管理 Python 项目的工具,它遵循 PEP 517 和 PEP 621 的规范。使用 hatch 时,你可以在 pyproject.toml 文件中配置项目信息和构建参数。接下来将介绍一下 hatch 的基本使用,详细的使用建议查看官方文档

安装 hatch

使用下面的命令安装 hatch:

pip install hatch

使用 hatch

安装 hatch 后,在命令行执行 hatch 命令,输出使用说明如下:

% hatch
Usage: hatch [OPTIONS] COMMAND [ARGS]...

   _   _       _       _
  | | | |     | |     | |
  | |_| | __ _| |_ ___| |__
  |  _  |/ _` | __/ __| '_ \
  | | | | (_| | || (__| | | |
  _| |_/__,_|_____|_| |_|

Options:
  -e, --env TEXT                  The name of the environment to use [env var:
                                  `HATCH_ENV`]
  -p, --project TEXT              The name of the project to work on [env var:
                                  `HATCH_PROJECT`]
  --color / --no-color            Whether or not to display colored output
                                  (default is auto-detection) [env vars:
                                  `FORCE_COLOR`/`NO_COLOR`]
  --interactive / --no-interactive
                                  Whether or not to allow features like
                                  prompts and progress bars (default is auto-
                                  detection) [env var: `HATCH_INTERACTIVE`]
  -v, --verbose                   Increase verbosity (can be used additively)
                                  [env var: `HATCH_VERBOSE`]
  -q, --quiet                     Decrease verbosity (can be used additively)
                                  [env var: `HATCH_QUIET`]
  --data-dir TEXT                 The path to a custom directory used to
                                  persist data [env var: `HATCH_DATA_DIR`]
  --cache-dir TEXT                The path to a custom directory used to cache
                                  data [env var: `HATCH_CACHE_DIR`]
  --config TEXT                   The path to a custom config file to use [env
                                  var: `HATCH_CONFIG`]
  --version                       Show the version and exit.
  -h, --help                      Show this message and exit.

Commands:
  build    Build a project
  clean    Remove build artifacts
  config   Manage the config file
  dep      Manage environment dependencies
  env      Manage project environments
  new      Create or initialize a project
  project  View project information
  publish  Publish build artifacts
  run      Run commands within project environments
  shell    Enter a shell within a project's environment
  status   Show information about the current environment
  version  View or set a project's version

hatch 创建新项目

假设要创建一个名为 my project 的项目,可以运行下面的命令:

hatch new "my project"

这将在当前的工作目录中创建以下结构:

my-project
├── src
│   └── my_project
│       ├── __about__.py
│       └── __init__.py
├── tests
│   └── __init__.py
├── LICENSE.txt
├── README.md
└── pyproject.toml

hatch 帮我们生产了一个标准的 python 项目结构,以及 pyproject.toml 文件。

hatch 初始化现有项目

要初始化现有项目,需要在该项目的目录中运行以下命令:

hatch new --init

如果项目中有setup.py文件,该命令将自动迁移 setuptools 配置,否则将以交互方式引导你完成设置过程。

配置 hatch

以下是一个结合 hatchling 使用的 pyproject.toml 配置文件的基本示例(hatchling 是 hatch 的构建后端,不需要手动安装它):

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
# 项目名,也就是使用 pip install 安装的名称
name = "my-project"
# 使用动态版本,需要结合[tool.hatch.version]一起使用
# 如果是静态版本指定 version = "0.0.1" 
dynamic = ["version"]
# 项目的描述信息
description = '描述信息'
# 项目的 readme 文件,
readme = "README.md"
# 运行项目需要的 python 版本
requires-python = ">=3.7"
# 项目遵循的协议
license = "MIT"
keywords = []
authors = [
  { name = "your name", email = "your email" },
]
# 每个项目的维护人员都向 PyPI 提供了一个“ Trove classifiers”列表,用于对每个版本进行分类,描述它是为谁服务的,它可以在什么系统上运行。
# 然后,社区成员可以使用这些标准化的分类器根据他们想要的标准查找项目。
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3.7",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: Implementation :: CPython",
  "Programming Language :: Python :: Implementation :: PyPy",
]
# 指定运行项目所需要的依赖
dependencies = [

]

[project.urls]
Documentation = "https://github.com/unknown/my-project#readme"
Issues = "https://github.com/unknown/my-project/issues"
Source = "https://github.com/unknown/my-project"

# 动态版本控制,需要与dynamic = ["version"]一起使用
[tool.hatch.version]
path = "src/my_project/__about__.py"

# 对于不同的环境可以定义不同版本的依赖
[tool.hatch.envs.default]
dependencies = [
  "coverage[toml]>=6.5",
  "pytest",
]

[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
test-cov = "coverage run -m pytest {args:tests}"
cov-report = [
  "- coverage combine",
  "coverage report",
]
cov = [
  "test-cov",
  "cov-report",
]

在上面的示例中,有几个关键部分:

  • [build-system]:定义了构建系统的需求,指定 hatchling 作为构建后端。
  • [project]:包含了项目的元数据,如名称、版本、描述、作者、许可证、README、Python版本要求、分类器和依赖关系。这部分遵守 PEP 621 的规范。
  • [tool.hatch.version]:指定了版本号的来源,如从文件中读取。
  • [tool.hatch.envs.default]:定义了默认环境的依赖,如 pytest。

使用这个配置文件,你可以运行 hatch 相关命令来构建项目、创建虚拟环境、安装依赖等。

构建项目

使用 hatch build 命令构建项目的 sdist 和 wheel 包:

hatch build

这会在项目根目录下的 dist/ 文件夹中生成源码包(sdist)和二进制包(wheel)

在 hatch 中共有三个内置构建目标:

wheel:用来构建 wheel 二进制分发包

sdist:用来构建源码包

custom:用来自定义构建目标

这几个构建目标可以定义 tool.hatch.build.targets 中,对于每个构建目标,可以覆盖顶级 tool.hatch.build 中设置的任何默认值,定义如下:

[tool.hatch.build.targets.<TARGET_NAME>]

wheel 构建目标的使用示例如下:

[tool.hatch.build.targets.wheel]
packages = ["src/llmtuner"]

sdist 构建目标的使用示例如下:

[tool.hatch.build.targets.sdist]
include = [
  "pkg/*.py",
  "/tests",
]
exclude = [
  "*.json",
  "pkg/_compat.py",
]

pyproject.toml 文件中没有指定配置构建目标相关的选项时,有哪些文件会被打包呢?这就涉及到打包文件选择的问题,详细的内容建议查看官方文档,我这里做一个简单的说明。

wheel 构建目标默认文件选择

当没有在 pyproject.toml 中设置任何的文件选择选项时,wheel 构建目标默认情况下用项目的名称按照如下的顺序选择哪些文件被打包到 wheel 二进制包中:

  1. <NAME>/__init__.py
  2. src/<NAME>/__init__.py
  3. <NAME>.py
  4. <NAMESPACE>/<NAME>/__init__.py
  5. 否则将包括每个不以单词 test 开头的 Python 包和文件

sdist 构建目标默认文件选择

当没有在 pyproject.toml 中设置任何的文件选择选项时,将包括 VCS 不会忽略的所有文件。下列文件始终包括在内,不能排除:

  • /pyproject.toml
  • /hatch.toml
  • /hatch_build.py
  • /.gitignore or /.hgignore
  • Any defined readme file
  • All defined license-files

发布项目

项目构建完成后,您可以使用 hatch publish命令发布 源码包(sdist)和二进制包(wheel) 到 pypi 仓库。

hatch publish

hatch 使用实战

需求背景

学习了前面的基础知识,接下来将使用 hatch 完成一个实际需求。最近在使用 LLaMA-Factory 对大模型进行微调,但是 LLaMA-Factory 只提供了源码的安装方式,也就是说我们在使用的时候需要将代码 clone 下来,然后进行安装,命令如下:

git clone https://github.com/hiyouga/LLaMA-Factory.git
conda create -n llama_factory python=3.10
conda activate llama_factory
cd LLaMA-Factory
pip install -r requirements.txt

能否将 LLaMA-Factory 打包并上传到私有仓库,然后通过 pip install 的方式安装呢?答案是肯定的,因为学习了 hatch,所以实现这个需求还是非常简单的,接下来我将带你一步步实现该需求。

详细步骤

首先使用下面的命令 clone 代码:

git clone https://github.com/hiyouga/LLaMA-Factory.git

然后进入到项目根目录中:

cd LLaMA-Factory

因为这是一个现有项目,所以使用下面的命令初始化项目:

hatch new --init

因为项目中有 setup.py 文件,hatch new --init命令将自动迁移 setuptools 配置,并生成一个 pyproject.toml文件,文件的内容如下:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "llmtuner"
dynamic = ["version"]
description = "Easy-to-use LLM fine-tuning framework"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.8.0"
authors = [
    { name = "hiyouga", email = "hiyouga@buaa.edu.cn" },
]
keywords = [
    "BLOOM",
    "ChatGPT",
    "Falcon",
    "LLM",
    "LLaMA",
    "deep learning",
    "pytorch",
    "transformer",
]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "Intended Audience :: Education",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: Apache Software License",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
    "accelerate>=0.21.0",
    "datasets>=2.14.0",
    "fastapi",
    "gradio>=3.38.0,<4.0.0",
    "jieba",
    "matplotlib",
    "nltk",
    "peft>=0.6.0",
    "protobuf",
    "pydantic",
    "rouge-chinese",
    "scipy",
    "sentencepiece",
    "sse-starlette",
    "tiktoken",
    "torch>=1.13.1",
    "transformers>=4.31.0,<4.35.0",
    "trl>=0.7.4",
    "uvicorn",
    "hatch",
]

[project.urls]
Homepage = "https://github.com/hiyouga/LLaMA-Factory"

[tool.hatch.version]
path = "src/llmtuner/__init__.py"

默认情况下,使用 hatch build 命令构建python包时会生成 源码包(sdist)和二进制包(wheel) ,如下所示:

% hatch build
[sdist]
dist/llmtuner-0.3.3.tar.gz

[wheel]
dist/llmtuner-0.3.3-py3-none-any.whl

我们可以查看默认情况下构建文件的大小,使用下面的命令:

 % ls -lh dist                                                                                
total 263264
-rw-------  1 xiniao  staff   103K Nov 29 22:17 llmtuner-0.3.3-py3-none-any.whl
-rw-------  1 xiniao  staff   119M Nov 29 22:17 llmtuner-0.3.3.tar.gz

因为没有指定构建目标的文件选择,默认情况下,llmtuner-0.3.3.tar.gz包含VCS(.gitignore)不会忽略的所有文件,llmtuner-0.3.3-py3-none-any.whl包含 src/llmtuner 目录下的所有内容,因为项目的名称是 llmtuner 所以会将 src/llmtuner目录下的文件打包到 whl 二进制包中。

如果我们想要在打包时指定特定的文件或者目录,可以按照如下方式指定:

[tool.hatch.build.targets.wheel]
# packages 的作用是将 src/llmtuner 重命名为 llmtuner,也就是将 src 目录去除
# 那么我们在导入包使用的使用就可以直接通过 import llmtuner 的方式导入了
packages = ["src/llmtuner"]

[tool.hatch.build.targets.sdist] 
# 将指定的 data 目录排除,不会打包在 *.tar.gz 文件中
exclude = ["data"]

当我们使用上面的配置选择指定的目录或者文件后,再次使用 hatch build命令打包,查看文件大小:

% ls -lh dist
total 10248
-rw-------  1 xiniao  staff   103K Nov 29 22:18 llmtuner-0.3.3-py3-none-any.whl
-rw-------  1 xiniao  staff   4.9M Nov 29 22:18 llmtuner-0.3.3.tar.gz

项目打包完成后,接下来就需要将python包推送到远程仓库中,在默认情况下,使用 hatch publish 命令会将 python 包发布到官方的 pypi 仓库中,需要自己注册官方的账号,我这里打算将python包推到内部的私有仓库中,可以使用使用下面的命令指定仓库的地址、用户名和密码:

> hatch publish -h
Usage: hatch publish [OPTIONS] [ARTIFACTS]...

  Publish build artifacts.

Options:
  -r, --repo TEXT       The repository with which to publish artifacts [env
                        var: `HATCH_INDEX_REPO`]
  -u, --user TEXT       The user with which to authenticate [env var:
                        `HATCH_INDEX_USER`]
  -a, --auth TEXT       The credentials to use for authentication [env var:
                        `HATCH_INDEX_AUTH`]

为了避免输入的命令过长以及暴露用户名和密码,建设使用环境变量配置相关参数,在 ~/.bash_profile文件中添加如下环境变量:

export HATCH_INDEX_REPO="https://your_repository_url/artifact/repositories/simple-dev/"
export HATCH_INDEX_USER="username"
export HATCH_INDEX_AUTH="password"

然后使用如下命令使配置生效:

source ~/.bash_profile

当再次执行 hatch publish 命令时就会使用环境变量的配置信息,不需要手动输入了。 默认情况下会将 dist 目录下的 whl 包和源码包都推送到远程仓库,如果想要指定特定的包,可以使用如下命令:

hatch publish /path/to/artifacts foo-1.tar.gz

将只发布以 .whl .tar.gz 结尾的文件

如何构建命令行接口

很多时候发布的python包都有命令行接口,比如 hatch 包,我们使用命令 pip install hatch 安装成功后,就可以使用 hatch 命令了,我们应该如何实现呢?我这里还是用一个实际的需求来进行说明,因为前面我们已经将 python 项目打包发布成功了,现在我想要实现几个命令,命令的名字可以自己定义,这里定义如下几个名字:

llmtuner-chat:该命令用来在命令行中进行问答,调用的是 cli_demo.py 文件的 main方法,在使用的时候直接运行该命令即可,运行所需的参数传递方式不变。

llmtuner-train:该命令用来微调大模型,调用的是 train_bash.py 文件的 main方法,在使用的时候直接运行该命令即可,运行所需的参数传递方式不变。

pyproject.toml 文件中添加如下两个参数即可实现上述功能:

[project.scripts]
llmtuner-chat = "llmtuner.cli_demo:main"
llmtuner-train = "llmtuner.train_bash:main"

可以通过如下方式查看 llmtuner-chat命令的位置:

$whereis llmtuner-chat 
llmtuner-chat: /opt/conda/bin/llmtuner-chat

查看 llmtuner-chat 命令的本质就是将下面的python脚本放在了 /opt/conda/bin/目录下面,可以直接在命令行中执行,执行该命令就是执行下面的 python 脚本:

$cat /opt/conda/bin/llmtuner-chat

#!/opt/conda/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from llmtuner.cli_demo import main
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script.pyw|.exe)?$', '', sys.argv[0])
    sys.exit(main())

写在最后

最后对本文的内容做一个简单总结,本文写作的思路就是自己学习的思路,在学习的过程中遇到问题就去查阅相关资料并记录下来,最终形成了本文的主要内容,主要包括如下内容:

  • python 如何找到导入的包
  • 如何构建python包
  • hatch 的简单介绍
  • hatch 使用实战

这里非常有必要说明一下,文中的有一些问题的答案来自于 chatgpt3.5,然后经过自己的整理记录到本文中,大模型真的是太强大了,它能够非常精准的回答你的提问,当我们遇到问题的时候,如果可以一定要优先问大模型,就我目前的使用来说,可以解决90%以上的技术相关问题。

参考文档

toml.io/cn/v1.0.0

hatch.pypa.io/latest/

pypi.org

packaging.python.org/en/latest/t…