我已经不再使用
setup.py
文件来构建我的包/库,而是完全使用 pyproject.toml
。总的来说,我更喜欢它,但似乎放置在 pyproject.toml
中的版本不会以任何方式通过构建传播。因此,我无法弄清楚如何将包版本(或 pyproject.toml 中提供的任何其他元数据)注入到我的包中。
谷歌搜索让我找到了这个帖子,其中有一些建议。它们看起来都像是黑客,但我尝试了这个,因为它看起来最好:
from pip._vendor import tomli ## I need to be backwards compatible to Python 3.8
with open("pyproject.toml", "rb") as proj_file:
_METADATA = tomli.load(proj_file)
DESCRIPTION = _METADATA["project"]["description"]
NAME = _METADATA["project"]["name"]
VERSION = _METADATA["project"]["version"]
它在测试时工作正常,但我的测试不够稳健:一旦我尝试将其安装在新位置/机器上,它就会失败,因为
pyproject.toml
文件不是软件包安装的一部分。 (我应该意识到这一点。)
那么,向我构建的包提供元数据(例如包版本)的正确/最佳方式是什么?我需要满足以下要求:
pyproject.toml
中提供一次信息。 (我知道如果我需要重复一个值,在某些时候就会出现不匹配。)mypackage.VERSION
的操作。pyproject.toml
和诗歌/PDM。 (我实际上使用 PDM,但我知道 Poetry 更受欢迎。重点是我不想要
setup.py
或
setup.cfg
hack。我想纯粹使用新方法。)
pyproject.toml
向构建的包提供元数据(包括包版本),您可以使用
importlib.metadata
模块,该模块自版本 3.8 起成为 Python 标准库的一部分。该模块允许您访问已安装软件包的元数据,包括您自己的软件包,而无需直接读取
pyproject.toml
文件。以下是实现此目标的方法:
importlib_metadata
。如果您使用的是 Python 3.8 或更高版本,它应该已经可用。
pyproject.toml
中,确保您拥有必要的元数据信息,包括项目名称和版本。例如:
[project]
name = "mypackage"
version = "1.0.0"
description = "My package description"
importlib.metadata
模块访问元数据:
import importlib.metadata as metadata
def get_package_version(package_name):
return metadata.version(package_name)
def get_package_description(package_name):
return metadata.metadata(package_name)["Summary"]
# Example usage
package_name = "mypackage"
package_version = get_package_version(package_name)
package_description = get_package_description(package_name)
print(package_version)
print(package_description)
请注意,importlib.metadata
会读取已安装软件包的元数据,因此您需要在访问元数据之前确保您的软件包已安装(例如,使用 Poetry、PDM 或其他软件包管理器)。安装后,最终用户将可以使用元数据,并且假设您在包的主模块中定义了相关变量,他们可以使用
mypackage.VERSION
或
mypackage.DESCRIPTION
从交互式 Python 会话中访问元数据。此方法满足您的要求:
pyproject.toml
提供一次信息。
mypackage.VERSION
访问它。
pyproject.toml
兼容,不需要
setup.py
或
setup.cfg
hack。
源树
中的
pyproject.toml
文件通常无法从已安装的软件包中读取。但是,包元数据可在包安装旁边的 .dist-info 子目录中找到。
必要时使用 stdlib 从包元数据中读取版本。版本字符串保证存在于已安装的软件包中,因为它是元数据规范中的“必填字段”。访问元数据并非易事的所有 Python 版本现已停产。
您可以使用 __package__
属性,这样您就不必在源代码中硬编码包名称:
# can be accessed anywhere within the package
import importlib.metadata
the_version_str = importlib.metadata.version(__package__)
这满足第 1 点和第 3 点。对于第 2 点:
我希望最终用户可以使用这些信息,以便安装该包的人可以从她的交互式 Python 会话中执行类似 mypackage.VERSION 的操作。
类似的食谱有效:
# in your_package/__init__.py
import importlib.metadata
VERSION = importlib.metadata.version(__package__)
但是,我建议
不提供版本属性。这样做有几个缺点,原因我已经在
here描述过。如果您以前提供过版本属性,并且出于向后兼容性考虑而需要保留它,您可以考虑使用后备模块来维护支持__getattr__
,当通过通常的方式找不到属性时将调用该模块:
def __getattr__(name):
if name == "VERSION":
# consider adding a deprecation warning here advising user to call
# importlib.metadata.version directly
return importlib.metadata.version(__package__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")