Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 376 – 已安装 Python 发行版的数据库

作者:
Tarek Ziadé <tarek at ziade.org>
状态:
最终
类型:
标准轨迹
主题:
打包
创建:
2009-02-22
Python 版本:
2.7, 3.2
历史记录:
2009-06-22

目录

注意

本 PEP 是一个历史文档。最新的规范规范,核心元数据规范,在PyPA 规范页面上维护。

×

请参阅PyPA 规范更新流程,了解如何提出更改。

摘要

本 PEP 的目标是提供一个标准基础设施来管理系统上安装的项目发行版,这样所有安装或删除项目的工具都能够互操作。

为了实现此目标,本 PEP 提出了一个新的格式来描述系统上安装的发行版。它还描述了标准库的参考实现。

过去曾尝试创建安装数据库(参见PEP 262)。

PEP 345结合,当前提案取代了PEP 262

注意:实施计划没有按预期进行,因此对于本 PEP 而言,它应该被视为仅供参考。

基本原理

目前 Python 中的发行版安装方式存在两个问题

  • 安装方式太多,这使得互操作性变得困难。
  • 没有 API 可以获取已安装发行版的信息。

发行版如何安装

现在,当发行版在 Python 中安装时,每个元素都可以在不同的目录中安装。

例如,Distutils 将纯 Python 代码安装在 purelib 目录中,对于类 Unix 系统和 Mac OS X,该目录为 lib/python2.6/site-packages,对于 Windows,该目录为 Lib\site-packages,位于 Python 的安装目录下。

此外,Distutils install 命令的 install_egg_info 子命令会在 purelib 目录中为项目添加一个 .egg-info 文件。

例如,对于 docutils 发行版,它包含一个包、一个额外的模块和可执行脚本,三个元素安装在 site-packages

  • docutilsdocutils 包。
  • roman.pydocutils 使用的额外模块。
  • docutils-0.5-py2.6.egg-info:包含发行版元数据的文件,如PEP 314中所述。此文件对应于名为 PKG-INFO 的文件,由 sdist 命令构建。

一些可执行脚本,例如 rst2html.py,也会添加到 Python 安装的 bin 目录中。

另一个名为 setuptools [3] 的项目还有另外两种格式来安装发行版,称为 EggFormats [6]

  • 一个自包含的 .egg 目录,其中包含所有发行版文件和发行版元数据,这些元数据存储在一个名为 PKG-INFO 的文件中,该文件位于名为 EGG-INFO 的子目录中。 setuptools 在该目录中创建了其他文件,这些文件可以被视为补充元数据。
  • 一个 .egg-info 目录,安装在 site-packages 中,其中包含与 .egg 格式中的 EGG-INFO 相同的文件。

当您安装使用 setuptools.setup 函数(而不是 distutils.core.setup 函数)在其 setup.py 文件中设置的发行版时,会自动使用第一种格式。

setuptools 还会在 easy-install.pth 文件中添加对发行版的引用。

最后,setuptools 项目提供了一个名为 easy_install [4] 的可执行脚本,该脚本安装所有发行版,包括基于 distutils 的发行版,这些发行版位于自包含的 .egg 目录中。

如果您希望为您的发行版创建独立的 .egg-info 目录,例如第二种 setuptools 格式,您必须在使用基于 setuptools 的发行版或 easy_install 脚本时强制执行此操作。您可以通过使用 --single-version-externally-managed 选项 **或** --root 选项来强制执行此操作。这将使 setuptools 项目像 distutils 一样安装项目。

此选项由以下项目使用

  • the pip [5] installer
  • Fedora 打包者 [7]
  • Debian 打包者 [8]

卸载信息

Distutils 没有提供 uninstall 命令。如果您要卸载发行版,您必须成为超级用户,并删除安装的各种元素,然后检查 .pth 文件,如有必要则清除它们。

该过程因您用来安装发行版的工具以及发行版的 setup.py 使用的是 Distutils 还是 Setuptools 而有所不同。

在某些情况下,您可能无法确定您是否已删除所有内容,或者您是否在删除多个发行版共享的文件时破坏了其他发行版。

但是有一个共同的行为:当您安装发行版时,文件会被复制到您的系统中。并且可以跟踪这些文件,以便以后删除。

此外,Pip 项目最近获得了 uninstall 功能。它使用 install 命令的 record 选项记录所有已安装的文件。

本 PEP 的提案

为了解决这些问题,本 PEP 提出了以下几点更改

  • 一个新的 .dist-info 结构,使用目录,灵感来自 setuptoolsEggFormats 标准的一种格式。
  • pkgutil 中添加新的 API,以便能够查询已安装发行版的信息。
  • 在 Distutils 中添加卸载函数和卸载脚本。

每个已安装发行版一个 .dist-info 目录

本 PEP 提出了一个安装格式,该格式的灵感来自于 EggFormats 标准中的一个选项,该选项使用位于 site-packages 目录中的一个单独目录。

此单独目录的命名方式如下

name + '-' + version + '.dist-info'

.dist-info 目录可以包含以下文件

  • METADATA:包含元数据,如PEP 345PEP 314PEP 241 中所述。
  • RECORD:记录已安装文件的列表
  • INSTALLER:记录用于安装项目的工具的名称
  • REQUESTED:该文件的出现表示项目安装是显式请求的(即不是作为依赖项安装的)。

METADATA、RECORD 和 INSTALLER 文件是必需的,而 REQUESTED 可以缺失。

此提案不会影响 Python 本身,因为元数据文件除了 Distutils 之外,尚未在标准库中的任何地方使用。

它将影响 setuptoolspip 项目,但是,考虑到它们已经使用包含 PKG-INFO 文件的目录,因此更改不会产生深远的影响。

RECORD

在使用 install 命令安装源代码发行版时,会在安装时在 .dist-info 目录中添加一个 RECORD 文件。请注意,当安装使用 bdist 命令或基于 bdist 的命令创建的二进制发行版时,也会安装 RECORD 文件,因为这些命令使用 install 命令创建二进制发行版。

RECORD 文件保存已安装文件的列表。这些文件对应于 install 命令的 record 选项列出的文件,并且默认情况下会生成这些文件。这允许实现卸载功能,如本 PEP 稍后所述。该 install 命令还提供了一个选项,用于阻止写入 RECORD 文件,并且在创建系统包时应使用此选项。

第三方安装工具也不应该在没有提示或警告的情况下覆盖或删除 RECORD 文件中不存在的文件。

此 RECORD 文件的灵感来自 PEP 262 FILES。

RECORD 文件是一个 CSV 文件,由记录组成,每个已安装文件一行。使用 csv 模块读取文件,并具有以下选项:

  • 字段分隔符:,
  • 引用字符:"
  • 行终止符:os.linesep(所以 \r\n\n

当安装发行版时,可以将文件安装到以下位置:

  • **基本位置**:由 --install-lib 选项定义的路径,默认情况下为 site-packages 目录。
  • **安装前缀**:由 --prefix 选项定义的路径,默认情况下为 sys.prefix
  • 系统上的任何其他路径。

每个记录由三个元素组成:

  • 文件的**路径**
    • 相对于**基本位置**的“/”分隔路径,如果文件位于**基本位置**下。
    • 相对于**基本位置**的“/”分隔路径,如果文件位于**安装前缀**下,并且**基本位置**是**安装前缀**的子路径。
    • 使用本地平台分隔符的绝对路径。
  • 文件内容的哈希值。请注意,生成的 pycpyo 文件没有哈希值,因为它们是根据 py 文件自动生成的。因此,检查相应 py 文件的哈希值足以确定文件及其关联的 pycpyo 文件是否已更改。

    哈希值要么为空字符串,要么是在 hashlib.algorithms_guaranteed 中命名的哈希算法,后面跟着等号 =,后面跟着摘要的 urlsafe-base64-nopad 编码 (base64.urlsafe_b64encode(digest),末尾的 = 已删除)。

  • 文件的字节大小

使用 csv 模块生成此文件,因此字段分隔符为“,”。任何在字段中找到的“,” 字符都会被 csv 自动转义。

读取文件时,使用 U 选项,以便激活通用换行符支持(参见 PEP 278),避免在读取使用不同换行符终止符的平台上生成的任何文件时出现任何问题。

以下是一个 RECORD 文件(提取)的示例:

lib/python2.6/site-packages/docutils/__init__.py,md5=nWt-Dge1eug4iAgqLS_uWg,9544
lib/python2.6/site-packages/docutils/__init__.pyc,,
lib/python2.6/site-packages/docutils/core.py,md5=X90C_JLIcC78PL74iuhPnA,66188
lib/python2.6/site-packages/docutils/core.pyc,,
lib/python2.6/site-packages/roman.py,md5=7YhfNczihNjOY0FXlupwBg,234
lib/python2.6/site-packages/roman.pyc,,
/usr/local/bin/rst2html.py,md5=g22D3amDLJP-FhBzCi7EvA,234
/usr/local/bin/rst2html.pyc,,
python2.6/site-packages/docutils-0.5.dist-info/METADATA,md5=ovJyUNzXdArGfmVyb0onyA,195
lib/python2.6/site-packages/docutils-0.5.dist-info/RECORD,,

请注意,RECORD 文件不能包含自身哈希值,这里只是提到了它。

/etc/myapp 中安装 config.ini 文件的项目将按以下方式添加:

/etc/myapp/config.ini,md5=gLfd6IANquzGLhOkW4Mfgg,9544

对于 Windows 平台,驱动器号将添加到绝对路径中,因此复制到 c:MyApp 中的文件将是:

c:\etc\myapp\config.ini,md5=gLfd6IANquzGLhOkW4Mfgg,9544

INSTALLER

install 命令有一个名为 installer 的新选项。此选项是用于调用安装的工具的名称。这是一个规范化的 lowercase 字符串,与 [a-z0-9_\-\.] 匹配。

$ python setup.py install –installer=pkg-system

如果没有提供,则默认值为 distutils

当安装发行版时,INSTALLER 文件将使用此值在 .dist-info 目录中生成,以跟踪**谁**安装了发行版。该文件是一个单行文本文件。

REQUESTED

某些安装工具会自动检测未满足的依赖项并安装它们。在这些情况下,跟踪哪些发行版纯粹是作为依赖项安装的很有用,这样,如果其依赖发行版稍后被卸载,用户就会收到有关孤立依赖项的警报。

如果发行版是通过直接用户请求(通常情况)安装的,则 REQUESTED 文件将被添加到已安装发行版的 .dist-info 目录中。REQUESTED 文件可能是空的,或者可能包含一个以“#”字符开头的标记注释行。

如果安装工具自动安装发行版,作为另一个发行版的依赖项,则不应创建 REQUESTED 文件。

distutils 的 install 命令默认情况下会创建 REQUESTED 文件。它接受 --requested--no-requested 选项,以明确指定是否创建该文件。

如果一个发行版已经作为依赖项安装到系统中,而后来又通过名称安装,则 distutils 的 install 命令将在现有安装的 .dist-info 目录中创建 REQUESTED 文件。

实现细节

注意:本节是非规范性的。最终,此 PEP 由第三方库和工具实现,而不是由标准库实现。

pkgutil 中的新函数和类

要使用 .dist-info 目录内容,我们需要在标准库中添加一组 API。将这些 API 放置的最佳位置是 pkgutil

函数

pkgutil 模块中添加的新函数是:

  • distinfo_dirname(name, version) -> 目录名称
    name 被转换为标准发行版名称,方法是将任何非字母数字字符的连续序列替换为单个“ - ”。

    version 被转换为标准版本字符串。空格变成点,所有其他非字母数字字符(点除外)变成连字符,多个连字符的连续序列被压缩成一个连字符。

    然后将这两个属性都转换为其文件名转义形式,即任何“ - ”字符都被替换为“_”,但“dist-info”中的“ - ”和用于将名称与版本号分隔的“ - ”除外。

  • get_distributions() -> Distribution 实例的迭代器。

    提供一个迭代器,用于在 sys.path 中查找 .dist-info 目录,并为其中的每一个目录返回 Distribution 实例。

  • get_distribution(name) -> Distribution 或 None。
  • obsoletes_distribution(name, version=None) -> Distribution 实例的迭代器。

    迭代所有发行版,以查找哪些发行版过时name。如果提供了一个 version,它将用于过滤结果。

  • provides_distribution(name, version=None) -> Distribution 实例的迭代器。

    迭代所有发行版,以查找哪些发行版提供 name。如果提供了一个 version,它将用于过滤结果。扫描 sys.path 中的所有元素,并查找所有以 .dist-info 结尾的目录。返回一个 Distribution,对应于包含与 name 匹配的 METADATA 的 .dist-info 目录的 name 元数据。

    此函数仅返回第一个找到的结果,因为预计不会出现多个值。如果未找到目录,则返回 None。

  • get_file_users(path) -> Distribution 实例的迭代器。

    迭代所有发行版,以找出哪些发行版使用 pathpath 可以是本地绝对路径或相对的“/”分隔路径。

    本地绝对路径是绝对路径,其中“/”的出现已由 os.sep 给出的系统分隔符替换。

Distribution 类

创建一个名为 Distribution 的新类,其中包含传递给构造函数的 .dist-info 目录的路径。它在实例化时读取包含在 METADATA 中的元数据。

Distribution(path) -> 实例

为给定的 path 创建一个 Distribution 实例。

Distribution 提供以下属性:

  • name:发行版的名称。
  • metadata:一个 DistributionMetadata 实例,使用发行版的 METADATA 文件加载。
  • requested:一个布尔值,指示 REQUESTED 元数据文件是否存在(换句话说,发行版是否通过用户请求安装)。

以及以下方法:

  • get_installed_files(local=False) -> (path, hash, size) 的迭代器

    迭代 RECORD 条目,并为每行返回一个元组 (path, hash, size)。如果 localTrue,则路径将转换为本地绝对路径。否则,将返回 RECORD 中的原始值。

    本地绝对路径是绝对路径,其中“/”的出现已由 os.sep 给出的系统分隔符替换。

  • uses(path) -> 布尔值

    如果 RECORD 中列出了 path,则返回 Truepath 可以是本地绝对路径或相对的“/”分隔路径。

  • get_distinfo_file(path, binary=False) -> 文件对象
    返回位于 .dist-info 目录下的文件。

    返回一个 file 实例,指向 path 指示的文件。

    path 必须是相对于 .dist-info 目录的“/”分隔路径或绝对路径。

    如果 path 是一个绝对路径,并且没有以 .dist-info 目录路径开头,则会引发 DistutilsError

    如果 binaryTrue,则以只读二进制模式 (rb) 打开文件,否则以只读模式 (r) 打开文件。

  • get_distinfo_files(local=False) -> 路径的迭代器

    迭代 RECORD 条目,如果路径指向位于 .dist-info 目录或其子目录中的文件,则为每行返回路径。

    如果 localTrue,则每个路径将转换为本地绝对路径。否则,将返回 RECORD 中的原始值。

请注意,API 组织为五个类,它们与目录和 Zip 文件一起工作(因此它与包含在 Zip 文件中的文件一起工作,有关更多详细信息,请参阅 PEP 273)。这些类在原型实现的文档中进行了描述,供感兴趣的读者参考 [9].

示例

让我们在 docutils 示例中使用一些新的 API

>>> from pkgutil import get_distribution, get_file_users, distinfo_dirname
>>> dist = get_distribution('docutils')
>>> dist.name
'docutils'
>>> dist.metadata.version
'0.5'

>>> distinfo_dirname('docutils', '0.5')
'docutils-0.5.dist-info'

>>> distinfo_dirname('python-ldap', '2.5')
'python_ldap-2.5.dist-info'

>>> distinfo_dirname('python-ldap', '2.5 a---5')
'python_ldap-2.5.a_5.dist-info'

>>> for path, hash, size in dist.get_installed_files()::
...     print '%s %s %d' % (path, hash, size)
...
python2.6/site-packages/docutils/__init__.py,b690274f621402dda63bf11ba5373bf2,9544
python2.6/site-packages/docutils/core.py,9c4b84aff68aa55f2e9bf70481b94333,66188
python2.6/site-packages/roman.py,a4b84aff68aa55f2e9bf70481b943D3,234
/usr/local/bin/rst2html.py,a4b84aff68aa55f2e9bf70481b943D3,234
python2.6/site-packages/docutils-0.5.dist-info/METADATA,6fe57de576d749536082d8e205b77748,195
python2.6/site-packages/docutils-0.5.dist-info/RECORD

>>> dist.uses('docutils/core.py')
True

>>> dist.uses('/usr/local/bin/rst2html.py')
True

>>> dist.get_distinfo_file('METADATA')
<open file at ...>

>>> dist.requested
True

Distutils 中的新函数

Distutils 已经提供了一种非常基本的方式来安装发行版,即对发行版的 setup.py 脚本运行 install 命令。

Distutils2 将提供一个非常基本的 uninstall 函数,它在 distutils2.util 中添加,并以要卸载的发行版的名称作为其参数。 uninstall 使用前面描述的 API 并删除所有唯一文件,只要它们的哈希值没有改变。然后,它删除留下的空目录。

uninstall 返回已卸载文件的列表

>>> from distutils2.util import uninstall
>>> uninstall('docutils')
['/opt/local/lib/python2.6/site-packages/docutils/core.py',
 ...
 '/opt/local/lib/python2.6/site-packages/docutils/__init__.py']

如果未找到发行版,则会引发 DistutilsUninstallError

过滤

为了使其成为希望控制 uninstall 工作方式的第三方项目的参考 API,可以使用第二个可调用参数。它会为删除的每个文件调用。如果可调用函数返回 True,则会删除该文件。如果它返回 False,则保留该文件。

示例

>>> def _remove_and_log(path):
...     logging.info('Removing %s' % path)
...     return True
...
>>> uninstall('docutils', _remove_and_log)

>>> def _dry_run(path):
...     logging.info('Removing %s (dry run)' % path)
...     return False
...
>>> uninstall('docutils', _dry_run)

当然,第三方工具可以使用更低级的 pkgutil API 来实现自己的卸载功能。

安装程序标记

如本 PEP 中所述,install 命令在 .dist-info 目录中添加一个名为 INSTALLER 的文件,其中包含安装程序的名称。

为了避免删除由其他打包系统安装的发行版,uninstall 函数接受一个额外的参数 installer,其默认值为 distutils2

当调用时,uninstall 会控制 INSTALLER 文件是否与该参数匹配。如果不匹配,它会引发 DistutilsUninstallError

>>> uninstall('docutils')
Traceback (most recent call last):
...
DistutilsUninstallError: docutils was installed by 'cool-pkg-manager'

>>> uninstall('docutils', installer='cool-pkg-manager')

这允许第三方应用程序使用 uninstall 函数,并强烈建议没有其他程序删除它之前安装的发行版。当依赖 Distutils API 的第三方程序在安装时对系统执行额外步骤时,它必须在卸载时撤消这些步骤。这很有用。

添加卸载脚本

一个 uninstall 脚本已添加到 Distutils2 中,使用方法如下

$ python -m distutils2.uninstall projectname

请注意,该脚本不会控制删除发行版是否会破坏另一个发行版。尽管它确保它删除的所有文件都没有被其他发行版使用,但它通过使用 uninstall 函数来实现这一点。

另请注意,此卸载脚本不会关注 REQUESTED 元数据;该数据仅供外部工具使用,以提供更高级的依赖项管理。

向后兼容性和路线图

这些更改不会引入任何兼容性问题,因为它们将在

  • pkgutil 中的新函数中实现
  • distutils2

计划将此 PEP 中概述的功能包含在 Python 3.2 中的 pkgutil 和 Distutils2 中。

Distutils2 还将包含新 pgkutil 的移植版本,并且可以用于 2.4 及更高版本。

使用现有、非标准化格式安装的发行版没有新 API 所需的元数据,因此将被忽略。第三方工具当然可以继续支持旧格式,以除了新格式之外,以便简化过渡。

参考文献

致谢

Jim Fulton、Ian Bicking、Phillip Eby、Rafael Villar Burke 和 Pycon 和 Distutils-SIG 的许多人。


来源:https://github.com/python/peps/blob/main/peps/pep-0376.rst

最后修改时间:2023-09-09 17:39:29 GMT