[译] 如何将开源 Python 软件包发布到 PyPI

本教程有一个由 Real Python 团队创建的相关视频课程。与书面教程一起观看,以加深您的理解:如何将自己的 Python 包发布到 PyPI 。

Python因附带电池而闻名。标准库中提供了复杂的功能。您可以找到用于处理 套接字,解析 CSVJSONXML文件以及处理 文件文件路径的模块

尽管Python附带的软件包很棒,但标准库之外还有许多出色的项目可用。这些通常是在Python打包索引(PyPI)(历史上被称为Cheese Shop)上托管的。在PyPI,您可以找到从Hello World到高级深度学习库的所有内容

在本教程中,您将介绍如何 将自己的包上传到PyPI。尽管发布项目比以前要容易,但仍涉及一些步骤。

您将学习如何:

  • 准备要发布的Python包
  • 考虑版本控制
  • 将您的包上传到PyPI

在整个教程中,我们将使用一个简单的示例项目:一个reader可用于阅读Real Python教程的软件包。第一部分介绍此项目。

可以在 PyPI 上和导入时为包使用不同的名称。但是,如果您使用相同或非常相似的名称,则对您的用户而言将更加容易。

配置程序包

为了将您的包上传到PyPI,您需要提供有关它的一些基本信息。该信息通常以 setup.py 文件的形式提供。有一些举措试图简化这种信息收集。目前,这 setup.py 是提供有关包装信息的唯一完全受支持的方法。

一个小的Python包

本节将描述一个小的 Python 包,我们将使用它作为示例来发布到 PyPI。如果您已经有要发布的软件包,请随意浏览本节,然后在下一节开头再次加入。

我们将使用该软件包,该软件包 reader 是一个可用于下载和阅读Real Python文章的应用程序。如果要继续学习,可以从我们的GitHub存储库中获取完整的源代码。

注意:如下所示和说明的源代码是Real Python供稿阅读器的简化版本,但功能齐全。与在PyPIGitHub上发布的软件包相比,此版本缺少一些错误处理和其他选项。

首先,查看的目录结构reader。该软件包完全位于一个目录中,该目录也名为reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
reader/

├── reader/
│ ├── config.txt
│ ├── feed.py
│ ├── __init__.py
│ ├── __main__.py
│ └── viewer.py

├── tests/
│ ├── test_feed.py
│ └── test_viewer.py

├── MANIFEST.in
├── README.md
└── setup.py

软件包的源代码和reader配置文件位于子目录中。在单独的子目录中有一些测试。这些测试不在此处讨论,但是您可以在GitHub存储库中找到它们。要了解有关测试的更多信息,请参见Anthony Shaw的精彩教程《 Python测试入门》

如果您使用自己的软件包,则可以使用其他结构或在软件​​包目录中包含其他文件。我们的Python应用程序布局参考讨论了几个不同的选项。本指南中的说明将独立于您使用的布局工作。

在本节的其余部分,您将看到该 reader 程序包如何工作。在接下来的部分,你会得到的特殊文件,其中包括仔细一看setup.pyREADME.mdMANIFEST.in,这需要发布你的包。

使用Real Python Reader

reader 是一个非常基本的 Web提要阅读器,可以从 Real Python提要中下载最新的Real Python文章。

这是一个使用阅读器获取最新文章列表的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ python -m reader
The latest tutorials from Real Python (https://realpython.com/)
0 How to Publish an Open-Source Python Package to PyPI
1 Python "while" Loops (Indefinite Iteration)
2 Writing Comments in Python (Guide)
3 Setting Up Python for Machine Learning on Windows
4 Python Community Interview With Michael Kennedy
5 Practical Text Classification With Python and Keras
6 Getting Started With Testing in Python
7 Python, Boto3, and AWS S3: Demystified
8 Python's range() Function (Guide)
9 Python Community Interview With Mike Grouchy
10 How to Round Numbers in Python
11 Building and Documenting Python REST APIs With Flask and Connexion – Part 2
12 Splitting, Concatenating, and Joining Strings in Python
13 Image Segmentation Using Color Spaces in OpenCV + Python
14 Python Community Interview With Mahdi Yusuf
15 Absolute vs Relative Imports in Python
16 Top 10 Must-Watch PyCon Talks
17 Logging in Python
18 The Best Python Books
19 Conditional Statements in Python

请注意,每篇文章都有编号。要阅读一篇特定的文章,请使用相同的命令,但也要包括文章的编号。例如,要阅读如何将开源Python程序包发布到PyPI,您可以添加0以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python -m reader 0
# How to Publish an Open-Source Python Package to PyPI

Python is famous for coming with batteries included. Sophisticated
capabilities are available in the standard library. You can find modules
for working with sockets, parsing CSV, JSON, and XML files, and
working with files and file paths.

However great the packages included with Python are, there are many
fantastic projects available outside the standard library. These are
most often hosted at the Python Packaging Index (PyPI), historically
known as the Cheese Shop. At PyPI, you can find everything from Hello
World to advanced deep learning libraries.

[... The full text of the article ...]

这将使用 Markdown文本格式将完整的文章打印到控制台。

注意: python -m 用于运行库模块或软件包而不是脚本。如果运行程序包,__main__.py 则将执行文件的内容。有关更多信息,请参见 调用包的不同方式

通过更改文章编号,您可以阅读任何可用的文章。

快速查看代码

reader 对于本教程而言,如何工作的细节并不重要。但是,如果您对实现有兴趣,可以展开以下部分。该软件包包含五个文件:

config.txt

config.txt是一个配置文件,用于指定Real Python教程的feed的URL 。这是一个文本文件,可由configparser标准库读取:

1
2
3
4
# config.txt

[feed]
url = https://realpython.com/atom.xml

通常,这样的配置文件包含分成部分的键/值对。该特定文件仅包含一个部分(feed)和一个键(url)。

注意:对于这个简单的程序包,配置文件可能是多余的。我们将其包含在此处用于演示目的。

__main__.py

我们将看的第一个源代码文件是__main__.py。双下划线表示该文件在Python中具有特殊含义。确实,当使用上述方法将程序包作为脚本运行时-m,Python会执行__main__.py文件的内容。

换句话说,它__main__.py充当程序的切入点并负责主流程,并根据需要调用其他部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# __main__.py

from configparser import ConfigParser
from importlib import resources # Python 3.7+
import sys

from reader import feed
from reader import viewer

def main():
"""Read the Real Python article feed"""
# Read URL of the Real Python feed from config file
cfg = ConfigParser()
cfg.read_string(resources.read_text("reader", "config.txt"))
url = cfg.get("feed", "url")

# If an article ID is given, show the article
if len(sys.argv) > 1:
article = feed.get_article(url, sys.argv[1])
viewer.show(article)

# If no ID is given, show a list of all articles
else:
site = feed.get_site(url)
titles = feed.get_titles(url)
viewer.show_list(site, titles)

if __name__ == "__main__":
main()

注意,main()在最后一行调用了该命令。如果我们不调用main(),那么我们的程序将什么也不做。如您先前所见,该程序可以列出所有文章或打印一篇特定文章。这是由if-else内部处理的main()。

要从配置文件读取提要的URL,我们使用configparser和importlib.resources。后者用于从包中导入非代码(或资源)文件,而不必担心完整的文件路径。在将程序包发布到PyPI时特别有用,因为资源文件可能最终存储在二进制存档中。

importlib.resources成为Python 3.7中标准库的一部分。如果您使用的是旧版本的Python,则可以importlib_resources改用。这是与Python 2.7和3.4及更高版本兼容的反向端口。importlib_resources可以从PyPI安装:

1
$ pip install importlib_resources

有关更多信息,请参见Barry Warzaw在PyCon 2018上的演讲。

__init__.py

下一个文件是__init__.py。同样,文件名中的双下划线告诉我们这是一个特殊文件。init.py表示包的根。它通常应该保持非常简单,但这是放置包常量,文档等的好地方:

1
2
3
4
# __init__.py

# Version of the realpython-reader package
__version__ = "1.0.0"

特殊变量__version__是Python中的约定,用于将版本号添加到包中。它是在PEP 396中引入的。稍后我们将详细讨论版本控制。

中定义的__init__.py变量在包名称空间中可用作变量:

1
2
3
>>> import reader
>>> reader.__version__
'1.0.0'

您还应该__version__在自己的包中定义变量。

feed.py

查看__main__.py,您将看到导入了两个模块feed和viewer,并用于从Feed中读取内容并显示结果。这些模块完成大部分实际工作。

首先考虑一下feed.py。该文件包含用于从Web订阅源读取和解析结果的功能。幸运的是,已经有很棒的库可以做到这一点。feed.py取决于PyPI上已有的两个模块:feedparser和html2text。

feed.py包含几个功能。我们将一次讨论一次。

为了避免从Web提要中读取过多信息,我们首先创建一个函数,以在第一次读取提要时记住该提要:

feed.py

import feedparser
import html2text

_CACHED_FEEDS = dict()

def _feed(url):
"""Only read a feed once, by caching its contents"""
if url not in _CACHED_FEEDS:
_CACHED_FEEDS[url] = feedparser.parse(url)
return _CACHED_FEEDS[url]
feedparser.parse()从网络读取提要并以类似于字典的结构返回它。为了避免多次下载该供稿,将其存储在其中_CACHED_FEEDS并在以后调用时重复使用_feed()。两者_CACHED_FEEDS和_feed()均以下划线作为前缀,以表明它们是不打算直接使用的支持对象。

通过查看.feed元数据,我们可以获得有关提要的一些基本信息。以下功能挑选标题并链接到包含提要的网站:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_site(url):
"""Get name and link to web site of the feed"""
info = _feed(url).feed
return f"{info.title} ({info.link})"
除了.title和.link,属性一样.subtitle,.updated和.id是也可以。

供稿中可用的文章可以在.entries列表内找到。可以通过列表理解找到文章标题:

def get_titles(url):
"""List titles in feed"""
articles = _feed(url).entries
return [a.title for a in articles]
.entries列出了Feed中按时间顺序排序的文章,因此最新的文章是.entries[0]。

为了获取一篇文章的内容,我们将其在.entries列表中的索引用作文章ID:

def get_article(url, article_id):
"""Get article from feed with the given ID"""
articles = _feed(url).entries
article = articles[int(article_id)]
html = article.content[0].value
text = html2text.html2text(html)
return f"# {article.title}\n\n{text}"

从.entries列表中选择正确的文章后,我们在第28行找到该文章的文本为HTML。接下来,html2text做了一个体面的工作,将HTML转换为可读性更高的文本。由于HTML不包含文章标题,因此标题会在返回之前添加。

viewer.py

最后一个模块是viewer.py。目前,它包含两个非常简单的功能。实际上,我们可以print()直接使用in main.py而不是调用viewer函数。但是,将功能拆分开可以使以后更轻松地用更高级的功能替换它。也许我们可以在更高版本中添加GUI界面?

viewer.py 包含两个功能:

1
2
3
4
5
6
7
8
9
10
11
# viewer.py

def show(article):
"""Show one article"""
print(article)

def show_list(site, titles):
"""Show list of articles"""
print(f"The latest tutorials from {site}")
for article_id, title in enumerate(titles):
print(f"{article_id:>3} {title}")

show()只需将一篇文章打印到控制台,同时show_list()打印标题列表。后者还会创建在选择阅读一篇特定文章时使用的文章ID。

调用包裹的不同方式

当您的项目变得越来越复杂时,挑战之一就是与用户交流如何使用您的项目。由于该软件包包含四个不同的源代码文件,因此用户如何知道要调用哪个文件来运行 reader

python 解释程序有一个 -m 选项,允许你指定一个模块名称而不是文件名。例如,如果您有一个名为的脚本 hello.py,则以下两个命令是等效的:

1
2
3
4
5
$ python hello.py
Hi there!

$ python -m hello
Hi there!

后者的优点之一是它还允许您调用Python内置的模块。一个例子是调用antigravity

1
2
$ python -m antigravity
Created new window in existing browser session.

使用的另一个优点 -m 是它既适用于包又适用于模块。如前所述,您可以通过以下方式调用 reader 软件包 -m

1
2
$ python -m reader
[...]

由于 reader 是软件包,因此名称仅指目录。Python如何确定该目录中要运行的代码?它查找名为的文件 __main__.py 。如果存在这样的文件,则将其执行。如果 __main__.py 不存在,则会显示一条错误消息:

1
2
$ python -m math
python: No code object available for math

在此示例中,您看到 math 标准库尚未定义 __main__.py 文件。

如果要创建应该执行的程序包,则应包括一个 __main__.py 文件。稍后,您将看到如何还可以为程序包创建入口点,其行为类似于常规程序。

准备打包发布

现在,您已经有了要发布的软件包,或者您已经复制了我们的软件包。将包上传到PyPI之前,需要执行哪些步骤?

命名包裹

第一步(可能也是最困难的一步)是为您的包装命名。PyPI上的所有软件包都必须具有唯一的名称。在PyPI上已经有超过150,000个软件包,很可能已经采用了您喜欢的名称。

您可能需要集思广益,并做一些研究才能找到完美的名字。使用PyPI搜索来检查名称是否已被使用。您想出的名称将在PyPI上可见。

为了使该reader包更易于在PyPI上找到,我们给它起了更具描述性的名称,并命名为realpython-reader。使用以下名称来安装软件包pip

1
$ pip install realpython-reader

即使我们使用realpython-readerPyPI名称,reader但在导入时仍会调用该包:

1
2
3
4
5
6
>>> import reader
>>> help(reader)

>>> from reader import feed
>>> feed.get_titles()
['How to Publish an Open-Source Python Package to PyPI', ...]

如您所见,您可以在PyPI上和导入时为包使用不同的名称。但是,如果您使用相同或非常相似的名称,则对您的用户而言将更加容易。

配置程序包

为了将您的包上传到PyPI,您需要提供有关它的一些基本信息。该信息通常以 setup.py 文件的形式提供。有一些举措试图简化这种信息收集。目前,这 setup.py 是提供有关包装信息的唯一完全受支持的方法。

setup.py 文件应放在包的顶部文件夹中。一个相当最小 setup.pyreader 看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pathlib
from setuptools import setup

# The directory containing this file
HERE = pathlib.Path(__file__).parent

# The text of the README file
README = (HERE / "README.md").read_text()

# This call to setup() does all the work
setup(
name="realpython-reader",
version="1.0.0",
description="Read the latest Real Python tutorials",
long_description=README,
long_description_content_type="text/markdown",
url="https://github.com/realpython/reader",
author="Real Python",
author_email="office@realpython.com",
license="MIT",
classifiers=[
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
],
packages=["reader"],
include_package_data=True,
install_requires=["feedparser", "html2text"],
entry_points={
"console_scripts": [
"realpython=reader.__main__:main",
]
},
)

我们将仅涵盖 setuptools 此处提供的一些选项。该文档很好地完成了所有细节。

调用中100%必需的参数 setup() 如下:

  • name将在PyPI上显示的软件包名称
  • version您软件包的当前版本
  • packages包含您的源代码的软件包和子软件包

稍后我们将详细讨论版本。该packages参数获取软件包列表。在我们的示例中,只有一个包:reader

您还需要指定任何子包。在更复杂的项目中,可能会列出许多软件包。为了简化此工作,setuptools包括include find_packages()可以很好地发现所有子包。您可以按以下方式find_packages()reader项目中使用:

1
2
3
4
5
6
7
from setuptools import find_packages, setup

setup(
...
packages=find_packages(exclude=("tests",)),
...
)

虽然只是nameversionpackages需要,你的包变得更容易找到PyPI上如果添加一些更多的信息。看一下realpython-readerPyPI上的页面,并将信息与setup.py上面的内容进行比较。所有信息都来自setup.pyREADME.md

最后两个参数setup()值得特别提及:

  • install_requires用于列出您的软件包对第三方库的任何依赖关系。在reader依赖于feedparserhtml2text,因此他们应该在这里列出。

  • entry_points用于创建调用程序包中的函数的脚本。在我们的示例中,我们创建了一个在文件内realpython调用的新脚本 reader/__main__.py

有关典型安装文件的另一个示例,请参阅setup.pyGitHub上的 Kenneth Reitz的存储库

记录包裹

在向外界发布您的软件包之前,您应该添加一些文档。根据您的软件包,文档可以小到一个简单的README文件,也可以大到包括教程,示例库和API参考的完整网页。

至少,您应该README在项目中包含一个文件。一个好的人README应该快速描述您的项目,并告诉您的用户如何安装和使用您的软件包。通常,您希望将包括在内README作为long_description参数setup()。这将README在PyPI上显示您。

传统上,PyPI使用reStructuredText来包装文档。然而,由于2018三月降价已经也被支持

在PyPI之外,Markdown比reStructuredText更受支持。如果您不需要reStructuredText的任何特殊功能,最好将其保留README在Markdown中。请注意,您应该使用setup()参数long_description_content_type告诉您使用哪种格式的PyPI。有效值是text/markdowntext/x-rsttext/plain

对于较大的项目,您可能想提供比合理地容纳在单个文件中更多的文档。在这种情况下,您可以使用GitHubRead Docs之类的网站,并使用url参数链接到文档。在setup.py上面的示例中,url用于链接到readerGitHub存储库

打包版本

您的软件包需要有一个版本,而PyPI仅允许您为软件包上传一个特定版本。换句话说,如果要在PyPI上更新软件包,则需要先增加版本号。这是一件好事,因为它保证了可重复性:具有相同版本软件包的两个系统应具有相同的行为。

许多不同的方案,可用于您的版本号。对于Python项目,PEP 440提供了一些建议。但是,为了灵活起见,PEP很复杂。对于简单的项目,请坚持使用简单的版本控制方案。

语义版本控制是一个很好的默认方案。版本号由三个数字部分给出,例如0.1.2。这些组件称为MAJOR,MINOR和PATCH,并且有关于何时增加每个组件的简单规则:

  • 当您更改不兼容的API时,请增加MAJOR版本。
  • 当您以向后兼容的方式添加功能时,请增加MINOR版本。
  • 进行向后兼容的错误修复时,请增加PATCH版本。(来源

您可能需要在项目内的其他文件中指定版本。在reader项目中,我们在setup.py和中都指定了版本reader/__init__.py。为了确保版本号保持一致,可以使用一个名为Bumpversion的工具。

您可以从PyPI安装Bumpversion:

1
$ pip install bumpversion

要增加MINOR版本reader,您可以执行以下操作:

1
$ bumpversion --current-version 1.0.0 minor setup.py reader/__init__.py

这将更改从版本号1.0.01.1.0两个setup.pyreader/__init__.py。为了简化命令,您还可以在配置文件中提供大多数信息。有关详细信息,请参见Bumpversion文档

将文件添加到包中

有时,您的程序包中会包含不是源代码文件的文件。示例包括数据文件,二进制文件,文档以及(如我们在本项目中一样)配置文件。

要告知setup()要包含此类文件,请使用清单文件。对于许多项目,您无需担心清单,因为它setup()创建了一个清单,其中包括所有代码文件以及README文件。

如果需要更改清单,请创建一个清单模板,该模板必须命名为MANIFEST.in。该文件为要包含和排除的内容指定了规则:

1
include reader/*.txt

此示例将包括.txt目录中的所有文件,该文件reader实际上是配置文件。请参阅文档以获取可用规则列表。

除了创建之外MANIFEST.in,还需要告诉setup()复制这些非代码文件。这是通过将include_package_data参数设置为来完成的True

1
2
3
4
5
setup(
...
include_package_data=True,
...
)

include_package_data参数控制在安装软件包时是否复制非代码文件。

发布到PyPI

您的包裹终于准备就绪,可以与您的计算机外部世界见面!在本节中,您将看到如何实际将软件包上传到PyPI。

如果您尚未在PyPI上拥有帐户,那么现在该创建一个帐户了:在PyPI上注册您的帐户。在使用它时,还应该在TestPyPI上注册一个帐户。TestPyPI非常有用,因为如果您搞砸了,可以尝试发布软件包的所有步骤,而不会造成任何后果。

要将软件包上传到PyPI,您将使用一个名为Twine的工具。您可以照常使用Pip安装Twine:

1
$ pip install twine

使用Twine非常简单,您将很快看到如何使用它来检查和发布您的软件包。

建立你的包裹

PyPI上的软件包不作为纯源代码分发。而是将它们包装到分发包中。分发程序包最常见的格式是源档案和Python wheel

源归档文件由您的源代码和包装在一个tar文件中的所有支持文件组成。同样,轮子实际上是包含您的代码的zip存档。与源归档文件相反,该转轮包括任何可以使用的扩展名。

要为您的包裹创建一个源档案和一个转轮,您可以运行以下命令:

1
$ python setup.py sdist bdist_wheel

这将在一个新创建的dist目录中创建两个文件,一个源档案和一个转轮:

1
2
3
4
5
reader/

└── dist/
├── realpython_reader-1.0.0-py3-none-any.whl
└── realpython-reader-1.0.0.tar.gz

注意:在Windows上,.zip默认情况下,源归档文件将是一个文件。您可以使用--format命令行选项选择源归档的格式。

您可能想知道如何setup.py知道如何处理sdistand bdist_wheel参数。如果你回头看看如何setup.py被实现的,有没有提及sdistbdist_wheel或任何其他命令行参数。

相反,所有命令行参数都在上游distutils标准库中实现。您可以通过添加--help-commands选项列出所有可用的参数:

1
2
3
4
5
6
$ python setup.py --help-commands
Standard commands:
build build everything needed to install
build_py "build" pure Python modules (copy to build directory)
build_ext build C/C++ and Cython extensions (compile/link to build directory)
< ... many more commands ...>

有关一个特定命令的信息,您可以执行类似的操作python setup.py sdist --help

测试您的包裹

首先,您应检查新建的分发程序包是否包含所需的文件。在Linux和macOS上,您应该能够tar如下列出源档案的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ tar tzf realpython-reader-1.0.0.tar.gz
realpython-reader-1.0.0/
realpython-reader-1.0.0/setup.cfg
realpython-reader-1.0.0/README.md
realpython-reader-1.0.0/reader/
realpython-reader-1.0.0/reader/feed.py
realpython-reader-1.0.0/reader/__init__.py
realpython-reader-1.0.0/reader/viewer.py
realpython-reader-1.0.0/reader/__main__.py
realpython-reader-1.0.0/reader/config.txt
realpython-reader-1.0.0/PKG-INFO
realpython-reader-1.0.0/setup.py
realpython-reader-1.0.0/MANIFEST.in
realpython-reader-1.0.0/realpython_reader.egg-info/
realpython-reader-1.0.0/realpython_reader.egg-info/SOURCES.txt
realpython-reader-1.0.0/realpython_reader.egg-info/requires.txt
realpython-reader-1.0.0/realpython_reader.egg-info/dependency_links.txt
realpython-reader-1.0.0/realpython_reader.egg-info/PKG-INFO
realpython-reader-1.0.0/realpython_reader.egg-info/entry_points.txt
realpython-reader-1.0.0/realpython_reader.egg-info/top_level.txt

在Windows上,您可以使用7-zip之类的实用程序来查看相应zip文件的内部。

您应该看到列出了所有源代码,以及已经创建的一些新文件,其中包含您在中提供的信息setup.py。特别是,请确保包括所有子包和支持文件。

您也可以通过解压缩轮子来查看轮子内部,就好像它是一个zip文件一样。但是,如果您的源归档文件包含您期望的文件,则转轮也应该很好。

较新版本的Twine(1.12.0及更高版本)还可以检查您的包装说明可在PyPI上正确呈现。您可以twine check在中创建的文件上运行dist

1
2
3
$ twine check dist/*
Checking distribution dist/realpython_reader-1.0.0-py3-none-any.whl: Passed
Checking distribution dist/realpython-reader-1.0.0.tar.gz: Passed

虽然它不能解决您可能遇到的所有问题,但是例如,它将使您知道使用的内容类型是否错误。

上载您的包裹

现在,您可以将包实际上传到PyPI了。为此,您将再次使用“ Twine”工具,告诉它上载已构建的分发程序包。首先,您应该上传到TestPyPI以确保一切正常。

1
$ twine upload --repository-url https://test.pypi.org/legacy/ dist/*

Twine会询问您您的用户名和密码。

注意:如果您以reader软件包为例按照本教程进行操作,则先前的命令可能会失败,并显示一条消息,提示您不允许上载到realpython-reader项目。

您可以将namein 更改为setup.py唯一的内容,例如test-your-username。然后再次构建项目,并将新建的文件上传到TestPyPI。

如果上传成功,您可以快速转到TestPyPI,向下滚动并查看在新版本中自豪地显示的项目!单击您的包裹,并确保一切正常。

如果您一直在使用该reader软件包,那么教程将在此处结束!尽管您可以随意使用TestPyPI,但不应将虚拟包上传到PyPI进行测试。

但是,如果您要发布自己的软件包,那么此刻终于来临!完成所有准备工作后,最后一步很短:

1
$ twine upload dist/*

根据要求提供您的用户名和密码。而已!

前往PyPI并查找您的包裹。您可以通过搜索,查看“ 您的项目”页面或直接转到项目的URL:pypi.org/project/your-package-name/来找到它。

恭喜你!您的软件包已在PyPI上发布!

pip install 您的包裹

花一点时间沐浴在PyPI网页的蓝色光芒中,(当然)向您的朋友吹牛。

然后再次打开一个终端。还有一个更大的回报!

将您的软件包上传到PyPI后,您还可以通过以下方式进行安装pip

1
$ pip install your-package-name

替换your-package-name为您为软件包选择的名称。例如,要安装reader软件包,您可以执行以下操作:

1
$ pip install realpython-reader

看到自己安装的代码pip是一种美妙的感觉!

其他有用的工具

在总结之前,还有一些其他的工具对于创建和发布Python软件包非常有用。

虚拟环境

在本指南中,我们没有讨论虚拟环境。当使用不同的项目时,虚拟环境非常有用,每个项目都有各自不同的要求和依赖性。

有关更多信息,请参见以下指南:

特别是,在最小的虚拟环境中测试软件包很有用,以确保在setup.py文件中包含所有必需的依赖项。

切碎机

开始使用项目的一种好方法是使用Cookiecutter。它通过基于模板询问几个问题来设置您的项目。可以使用许多不同的模板

首先,请确保您的系统上安装了Cookiecutter。您可以从PyPI安装它:

1
$ pip install cookiecutter

作为示例,我们将使用pypackage-minimal模板。要使用模板,请给Cookiecutter指向模板的链接:

1
2
3
4
5
6
7
8
9
10
$ cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal
author_name [Louis Taylor]: Real Python
author_email [louis@kragniz.eu]: office@realpython.com
package_name [cookiecutter_pypackage_minimal]: realpython-reader
package_version [0.1.0]:
package_description [...]: Read Real Python tutorials
package_url [...]: https://github.com/realpython/reader
readme_pypi_badge [True]:
readme_travis_badge [True]: False
readme_travis_url [...]:

在回答了一系列问题之后,Cookiecutter设置您的项目。在此示例中,模板创建了以下文件和目录:

1
2
3
4
5
6
7
8
9
10
11
12
realpython-reader/

├── realpython-reader/
│ └── __init__.py

├── tests/
│ ├── __init__.py
│ └── test_sample.py

├── README.rst
├── setup.py
└── tox.ini

Cookiecutter的文档内容广泛,包括一长串可用的cookiecutter,以及有关如何创建自己的模板的教程。

掠过

Python打包的历史非常混乱。一种普遍的批评是,使用可执行文件之类setup.py的配置信息并不理想。

PEP 518定义了一个替代方案:使用一个名为的文件pyproject.toml代替。该TOML格式是一个简单的配置文件格式:

[…] it is human-usable (unlike JSON), it is flexible enough (unlike configparser), stems from a standard (also unlike configparser), and it is not overly complex (unlike YAML). (Source)

虽然PEP 518已经使用了几年,但 pyproject.toml 标准工具尚未完全支持该配置文件。

但是,有一些基于的新工具可以发布到PyPI pyproject.toml。这样的工具就是Flit,这是一个很棒的小项目,可轻松发布简单的Python包。Flit不支持高级软件包,例如创建C扩展的软件包。

您可以pip install flit,然后按如下所示开始使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ flit init
Module name [reader]:
Author []: Real Python
Author email []: office@realpython.com
Home page []: https://github.com/realpython/reader
Choose a license (see http://choosealicense.com/ for more info)
1\. MIT - simple and permissive
2\. Apache - explicitly grants patent rights
3\. GPL - ensures that code based on this is shared with the same terms
4\. Skip - choose a license later
Enter 1-4 [1]:

Written pyproject.toml; edit that file to add optional extra info.

flit init命令将pyproject.toml根据您对几个问题的答案创建文件。使用该文件之前,您可能需要稍微对其进行编辑。对于该reader项目,pyproject.tomlFlit文件最终看起来如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[build-system]
requires = ["flit"]
build-backend = "flit.buildapi"

[tool.flit.metadata]
module = "reader"
dist-name = "realpython-reader"
description-file = "README.md"
author = "Real Python"
author-email = "office@realpython.com"
home-page = "https://github.com/realpython/reader"
classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
]
requires-python = ">=3.7"
requires = ["feedparser", "html2text"]

[tool.flit.scripts]
realpython = "reader.__main__:main"

您应该从我们的原始物品中识别出大多数物品setup.py。但是要注意的一件事是,version并且description缺少。这不是一个错误。Flit实际上通过使用__version____init__.py文件中定义的docstring 自己找出了这些。Flit的文档解释了有关pyproject.toml文件的所有内容。

Flit可以构建您的包,甚至将其发布到PyPI。要构建您的软件包,只需执行以下操作:

1
$ flit build

就像python setup.py sdist bdist_wheel之前一样,这将创建一个源存档和一个转盘。要将软件包上传到PyPI,您可以像以前一样使用Twine。但是,您也可以直接使用Flit:

1
$ flit publish

如果需要 publish,该命令将构建您的软件包,然后将文件上传到PyPI,并在必要时提示您输入用户名和密码。

要了解Flit的实际应用,请查看EuroSciPy 2017 的2分钟闪电演讲。Flit 文档是获取更多信息的重要资源。Brett Cannon的打包PyPI的Python代码教程包括有关Flit的部分。

诗歌(poetry)

诗歌(poetry)是可以用来构建和上传软件包的另一种工具。它与Flit非常相似,尤其是对于我们在这里要看的东西。

使用诗歌之前,需要先安装它。也有可能pip install poetry。但是,作者建议您使用自定义安装脚本来避免潜在的依赖冲突。请参阅文档以获取安装说明。

安装了Poetry之后,您可以通过以下init命令开始使用它:

1
2
3
4
5
6
7
8
$ poetry init

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

Package name [code]: realpython-reader
Version [0.1.0]: 1.0.0
Description []: Read the latest Real Python tutorials
...

这将pyproject.toml根据您对包裹问题的回答创建一个文件。不幸的是,《pyproject.toml弗里特》和《诗歌》中的实际规范有所不同。对于诗歌,pyproject.toml文件最终看起来如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[tool.poetry]
name = "realpython-reader"
version = "1.0.0"
description = "Read the latest Real Python tutorials"
readme = "README.md"
homepage = "https://github.com/realpython/reader"
authors = ["Real Python <office@realpython.com>"]
license = "MIT"
packages = [{include = "reader"}]
include = ["reader/*.txt"]

[tool.poetry.dependencies]
python = ">=3.7"
feedparser = ">=5.2"
html2text = ">=2018.1"

[tool.poetry.scripts]
realpython = "reader.__main__:main"

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

同样,您应该从的早期讨论中认识到所有这些项目setup.py。需要注意的一件事是,Poetry会根据许可证和您指定的Python版本自动添加分类器。诗歌还要求您明确说明依赖项的版本。实际上,依赖性管理是诗歌的长处之一。

就像Flit一样,Poetry可以构建软件包并将其上传到PyPI。该build命令创建一个源档案和一个轮子:

1
$ poetry build

这将在dist子目录中创建两个常用文件,您可以像以前一样使用Twine上载这些文件。您还可以使用“诗歌”发布到PyPI:

1
$ poetry publish

这会将您的包上传到PyPI。除了构建和发布外,Poetry还可以在此过程的早期帮助您。类似于Cookiecutter,Poetry可以帮助您使用该new命令启动新项目。它还支持使用虚拟环境。有关所有详细信息,请参见诗歌的文档

除了略有不同的配置文件外,Flit和Poetry的工作原理非常相似。诗歌的范围更广,因为它还旨在帮助进行依属关系管理,而弗利特(Flit)的历史已经更长了。安德鲁·平克汉姆(Andrew Pinkham)的文章《Python的新包装格局》涵盖了Flit和Poetry。诗歌是Python Bytes播客第100集的特别主题之一。

结论

您现在知道了如何准备项目并将其上传到PyPI,以便其他人可以安装和使用它。尽管您需要完成一些步骤,但是在PyPI上看到自己的软件包是很有意义的。让其他人发现您的项目很有用!

在本教程中,您已经看到发布自己的程序包所必需的步骤:

  • 为您的包裹找一个好名字
  • 使用以下方式配置您的软件包 setup.py
  • 建立你的包裹
  • 将您的包上传到PyPI

此外,您还看到了一些用于发布程序包的新工具,这些工具使用新的pyproject.toml配置文件来简化过程。

如果您仍有疑问,请随时在下面的评论部分中提出。另外,Python打包机构拥有比我们此处介绍的更多的信息。

原文:How to Publish an Open-Source Python Package to PyPI
作者:About Geir Arne Hjelle