tool 2021-11-05 Python

Python 项目打包发布

  • 本文介绍如何对一个 Python 项目在本地打包,发布在 PyPI 上
  • 大致流程:
    • project => package => distribution package => archives => PyPI => pip

创建一个简单的 project

创建一个 project

  1. 创建 project 目录
$ mkdir packaging_tutorial
  1. 在 project 目录中创建用于存放 project 源代码的 src 目录
$ cd packaging_tutorial
$ mkdir src
  1. src 目录中创建 Python 源代码包 example_package 目录
$ cd src
$ mkdir example_package
  1. example_package 目录中创建 Python 源代码模块文件 __init__.pyexample.py
$ cd example_package
$ touch __init__.py
$ touch example.py
  1. example.py 源代码模块中添加一个简单的函数
$ vim example.py
1 def add_one(number):
2    return number + 1


-- INSERT --

生成的 project 结构

packaging_tutorial/
└── src/
    └── example_package/
        ├── __init__.py
        └── example.py

创建 package 文件

最终生成的 project 结构如下:

packaging_tutorial/         # project
├── LICENSE                 # 许可证
├── pyproject.toml          # TODO
├── README.md               # 项目介绍 
├── setup.cfg               # metadata
├── src/                    # 源代码
│   └── example_package/
│       ├── __init__.py
│       └── example.py
└── tests/                  # 单元测试

创建用于单元测试的 tests 目录

tests 目录用于存放单元测试脚本,关于 Python 单元测试另外介绍

  • packaging_tutorial 目录下创建 tests 目录
$ pwd
~/packaging_tutorial/src/example_package

$ cd ../..
$ mkdir tests

创建 pyproject.toml 文件

pyproject.toml 用于…

  • packaging_tutorial 目录下创建 ·pyproject.toml 文件
$ pwd
~/packaging_tutorial

$ touch pyproject.toml
$ vim pyproject.toml

-- INSERT --
  • 文件内容如下
[build-system]
requires = [
    "setuptools>=42",
    "wheel"
]
build-backend = "setuptools.build_meta"

配置 metadata

metadata 用于…

  • 方法一:在 packaging_tutorial 目录下创建静态 metadata setup.cfg。本文以静态 metadata 举例
$ pwd
~/packaging_tutorial

$ touch setup.cfg
$ vim setup.cfg

-- INSERT --
  • 文件内容如下
[metadata]
name = example-pkg-YOUR-USERNAME-HERE
version = 0.0.1
author = Example Author
author_email = author@example.com
description = A small example package
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/pypa/sampleproject
project_urls =
    Bug Tracker = https://github.com/pypa/sampleproject/issues
classifiers =
    Programming Language :: Python :: 3
    License :: OSI Approved :: MIT License
    Operating System :: OS Independent

[options]
package_dir =
    = src
packages = find:
python_requires = >=3.6

[options.packages.find]
where = src
  • 方法二:在 packaging_tutorial 目录下创建动态 metadata setup.py
$ pwd
~/packaging_tutorial

$ touch setup.py
$ vim setup.py

-- INSERT --
  • 文件内容如下
import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="example-pkg-YOUR-USERNAME-HERE",
    version="0.0.1",
    author="Example Author",
    author_email="author@example.com",
    description="A small example package",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/pypa/sampleproject",
    project_urls={
        "Bug Tracker": "https://github.com/pypa/sampleproject/issues",
    },
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    package_dir={"": "src"},
    packages=setuptools.find_packages(where="src"),
    python_requires=">=3.6",
)

创建 README.md 文件

  • 每个 project 都应该有一个 README.md 文件,README.md 文件用于对整个项目进行一个简单的功能介绍。 包括 project 安装、功能、使用
  • 关于如何写一个好的 README.md 介绍,可以参考这里
  • packaging_tutorial 目录下创建 README.md 文件
$ pwd
~/packaging_tutorial

$ touch README.md
$ vim README.md

-- INSERT --
  • 文件内容如下
# Example Package

This is a simple example package. You can use
[Github-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
to write your content.

创建 LICENSE 文件

  • packaging_tutorial 目录下创建 LICENSE 文件
$ pwd
~/packaging_tutorial

$ touch LICENSE
$ vim LICENSE

-- INSERT --
  • 文件内容如下
Copyright (c) 2018 The Python Packaging Authority

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

创建其他文件

生成分发档案

包(package) 生成 分发包(distribution packages)。这些 archives 被上传到 Python Package Index(PyPI) 上, 并且通过 pip 安装

  • 生成分发档案
$ pwd
~/packaging_tutorial

$ python3 -m pip install --upgrade build

# 在 pyproject.toml 文件所在目录运行以下命令
$ python3 -m build                       # 静态构建
$ python3 -m setup.py sdist bdist_wheel  # 动态构建
  • 以上命令生在 dist 目录中生成两个文件

    • tar.gz:source archive
    • .whl:build distribution
packaging_tutorial/
    dist/
      example_pkg_YOUR_USERNAME_HERE-0.0.1-py3-none-any.whl
      example_pkg_YOUR_USERNAME_HERE-0.0.1.tar.gz
    ...
  • 最终的project packaging_tutorial 结构如下图

packaging_tutorial

上传分发档案

在 TestPyPI 上注册账户,并生成一个 API token

将生成的 API token 配置在本地

原理:

$ vim ~/.pypirc

[testpypi]
username = __token__
password = ***

-- INSERT --

上传分发档案

原理:

$ pwd
~/packaging_tutorial

$ python3 -m pip install --upgrade twine
$ python3 -m twine upload --repository testpypi dist/*

安装新上传的 package

  • 安装 package
$ python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps example-pkg-YOUR-USERNAME-HERE
  • 测试安装的 package example_package
$ python3

>>> from example_package import example
>>> example.add_one(2)
3

下一步–PyPI

  • 上面的第 45 步,都是在 TestPyPI 上为了进行测试完成的, 如果一个包要正式发布在 PyPI 上,需要将第 45 步切换到 PyPI 上进行
  • PyPI地址: https://pypi.org/manage/projects/
  1. 重新选择一个好的 package name

  2. 在 PyPI 上注册账户,并生成一个 API token

  3. 将生成的 API token 配置在本地

$ vim ~/.pypirc

[pypi]
username = __token__
password = ***

-- INSERT --
  1. 上传分发档案
$ pwd
~/package_dir

$ python3 -m pip install --upgrade twine
$ python3 -m twine upload dist/*
  1. 安装 package
python3 -m pip install [your-package]
  1. 测试 package
$ python3

>>> from your_package import function
>>> ...

最后 Git 和 GitHub

Clone

  1. 在 GitHub 上新建一个 packaging_tutorial 空仓库,并 Clone 到本地
$ cd project_path
$ git clone git@github.com:wangzhefeng/packaging_example.git
  1. 从第 1 步开始做

在命令行新建一个 GitHub 仓库

  1. 在 GitHub 上新建一个 packaging_tutorial 空仓库
  2. 将本地 project 进行 Git 初始化,并 Push 到 GitHub 仓库 packaging_tutorial
$ echo "# packaging_example" >> README.md

$ touch .gitignore

$ git init
$ git add .
$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin git@github.com:wangzhefeng/packaging_example.git
$ git push -u origin main

将本地仓库直接推送到一个 GitHub 仓库

  1. 在 GitHub 上新建一个 packaging_tutorial 空仓库
  2. 将本地 project 直接 Push 到 GitHub 仓库 packaging_tutorial
$ git remote add origin git@github.com:wangzhefeng/packaging_example.git
$ git branch -M main
$ git push -u origin main

从其他仓库导入代码

  • 暂时没用过,不知道是啥

参考