如何针对多个版本的Python测试轮子?

问题描述 投票:0回答:1

问题描述

我正在编写一个Python库,并计划将sdist(.tar.gz)和wheel上传到PyPI。 构建文档说正在运行

python -m build

我从源树创建了 sdist ,并从 sdist 创建了 wheel ,这很好,因为我在这里“免费”测试了 sdist 。现在我想使用多个 python 版本对轮子运行测试(pytest)。最简单的方法是什么?

我一直在使用 tox,我看到有一个选项 将包设置为“wheel”

[testenv]
description = run the tests with pytest
package = wheel
wheel_build_env = .pkg

但这并没有说明轮子是如何生产的;我不确定是不是 a) 直接从源代码树创建轮子

b) 从 sdist 创建wheel,它是从源树创建的,其方式与

相同
python -m build

c) 从 sdist 创建wheel,它是从源树创建的,其方式
不同于
python -m build

即使答案是c),tox测试的轮子也不会是上传的轮子,所以它没有测试正确的东西。

我很可能应该以某种方式将轮子作为 tox / test runner 的参数。 问题

我想从 sdist 创建一个从源树创建的轮子,并且我想使用多个 python 版本对轮子运行单元测试。这是一个纯 python 项目,因此每个版本的包只有一个轮子。针对我要上传到 PyPI 的

same

轮运行测试的惯用方法是什么?我可以使用tox吗?

python python-packaging tox
1个回答
0
投票
External Package builder

的 Tox 4.12.2 文档说明可以定义 external 包选项。外部

package
选项意味着你设置 [testenv] ... package = external

除此之外,还必须创建一个名为 
[.pkg_external]

的部分(如果您编辑了具有别名

<package_env>_external
package_env
,则为 isolated_build_env)。在本节中,我们应该定义
至少
package_glob
,它告诉毒物在哪里安装轮子。如果您还想
创建
轮子,您可以在commands
[.pkg_external]
选项中进行操作。
简单的方法(多次构建)

工作配置示例(tox 4.12.2):

[testenv:.pkg_external] deps = build==1.1.1 commands = python -c 'import shutil; shutil.rmtree("{toxinidir}/dist", ignore_errors=True)' python -m build -o {toxinidir}/dist package_glob = {toxinidir}{/}dist{/}wakepy-*-py3-none-any.whl

优点:实施起来非常简单
  • 缺点:这种方法的缺点是您将为每个没有
  • python -m build
  • 的环境触发构建 (
    skip_install=True
    )。这有一个未解决的问题:
    tox #2729
  • 只造轮子一次

也可以使用 tox

hooks

让 tox 4.14.2 只构建一次轮子。从tox执行顺序(在附录中)可以看出,可用于此目的的一个钩子是“.pkg_external”(“requires”或“deps”)的tox_on_install。我用它来放置一个虚拟文件(

/dist/.TOX-ASKS-REBUILD
),这意味着应该完成构建。如果
.TOX-ASKS-REBUILD
存在,则运行构建脚本时,将删除
/dist
文件夹及其所有内容,并创建包含 .tar.gz 和 .whl 文件的新
/dist
文件夹。

优点:
  • 运行 tox 的速度更快,因为 sdist 和 Wheel 仅根据需要构建多次。
    • 如果使用带有单个环境的 tox 也将构建,例如
    • tox -e py311
    • (如果不是
      skip_install=True
      
      
    缺点:
  • 更多参与
    • 无法在并行模式下工作。为此,可能需要有单独的构建命令,该命令每次在 tox 之前运行(除非并行化插件支持通用的预命令)。
  • 希望这个解决方案在某个时候变得不必要(当#2729得到解决时)

挂钩

位于
    tests/tox_utils/tox_hooks.py
  • 
    
  • from __future__ import annotations import typing from pathlib import Path from typing import Any from tox.plugin import impl if typing.TYPE_CHECKING: from tox.tox_env.api import ToxEnv dist_dir = Path(__file__).resolve().parent.parent.parent / "dist" tox_asks_rebuild = dist_dir / ".TOX-ASKS-REBUILD" @impl def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str): if (tox_env.name != ".pkg_external") or (of_type != "requires"): return # This signals to the build script that the package should be built. tox_asks_rebuild.parent.mkdir(parents=True, exist_ok=True) tox_asks_rebuild.touch()
pyproject.toml

钩子必须在某处注册。我为此使用 pyproject.toml :

[project.entry-points.tox] mypkg_tox_hooks = "tests.tox_utils.tox_hooks"

build_mypkg.py

位于
    /tests/tox_utils/build_mypkg.py
  • 
    
  • import shutil import subprocess from pathlib import Path dist_dir = Path(__file__).resolve().parent.parent.parent / "dist" def build(): if not (dist_dir / ".TOX-ASKS-REBUILD").exists(): print("Build already done. skipping.") return print(f"Building sdist and wheel into {dist_dir}") # Cleanup. Remove all older builds; the /dist folder and its contents. # Note that tox would crash if there were two files with .whl extension. # This also resets the TOX-ASKS-REBUILD so we build only once. shutil.rmtree(dist_dir, ignore_errors=True) out = subprocess.run( f"python -m build -o {dist_dir}", capture_output=True, shell=True ) if out.stderr: raise RuntimeError(out.stderr.decode("utf-8")) print(out.stdout.decode("utf-8")) if __name__ == "__main__": build()
tox.ini

[testenv] ; The following makes the packaging use the external builder defined in ; [testenv:.pkg_external] instead of using tox to create sdist/wheel. ; https://tox.wiki/en/latest/config.html#external-package-builder package = external [testenv:.pkg_external] ; This is a special environment which is used to build the sdist and wheel ; After running this environment, the *.whl and *.tar.gz are available in the ; dist/ folder. deps = ; The build package from PyPA. See: https://build.pypa.io/en/stable/ build==1.1.1 commands = python tests/tox_utils/build_mypkg.py ; This determines which files tox may use to install mypkg in the test ; environments. This should match with the file created during running the ; command defined in this section. package_glob = {toxinidir}{/}dist{/}mypkg-*-py3-none-any.whl

附录

tox执行顺序

可以使用附录 (

tox_print_hooks.py

) 中定义的虚拟挂钩文件和

系统概述
中有关执行顺序的项目符号列表来对 tox 中的执行顺序进行逆向工程。请注意,我已经设置了 package = external,这对输出有一些影响。这是 tox 的作用:
1) CONFIGURATION
tox_register_tox_env
tox_add_core_config
tox_add_env_config (N+2 times[1])

2) ENVIRONMENT (for each environment)

tox_on_install (envname, deps)
envname: install_deps (if not cached)
    
If not all(skip_install) AND first time: [2]
  tox_on_install (.pkg_external, requires)
  .pkg_external: install_requires (if not cached)
  tox_on_install (.pkg_external, deps)
  .pkg_external: install_deps (if not cached)
      
If not skip_install:
  .pkg_external: commands  
  tox_on_install (envname, package) 
  envname: install_package [3]
    
tox_before_run_commands (envname)
envname: commands
tox_after_run_commands (envname)
  
tox_env_teardown (envname)


[1]

N = tox 配置文件中的环境数量。 “2”来自 .pkg_external 和 .pkg_external_sdist_meta [2]
“第一次”的意思是:第一次参与此毒害呼叫。仅当至少有一个选定的环境没有 skip_install=True 时才会执行此操作。

[3]
这将从wheel 安装软件包。如果在 [testenv] 中使用 package = external,它将从
package_glob
[testenv:.pkg_external]
定义的位置获取轮子


虚拟挂钩文件

tox_print_hooks.py

from typing import Any

from tox.config.sets import ConfigSet, EnvConfigSet
from tox.execute.api import Outcome
from tox.plugin import impl
from tox.session.state import State
from tox.tox_env.api import ToxEnv
from tox.tox_env.register import ToxEnvRegister


@impl
def tox_register_tox_env(register: ToxEnvRegister) -> None:
    print("tox_register_tox_env", register)


@impl
def tox_add_core_config(core_conf: ConfigSet, state: State) -> None:
    print("tox_add_core_config", core_conf, state)


@impl
def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None:
    print("tox_add_env_config", env_conf, state)


@impl
def tox_on_install(tox_env: ToxEnv, arguments: Any, section: str, of_type: str):
    print("tox_on_install", tox_env, arguments, section, of_type)


@impl
def tox_before_run_commands(tox_env: ToxEnv):
    print("tox_before_run_commands", tox_env)


@impl
def tox_after_run_commands(tox_env: ToxEnv, exit_code: int, outcomes: list[Outcome]):
    print("tox_after_run_commands", tox_env, exit_code, outcomes)


@impl
def tox_env_teardown(tox_env: ToxEnv):
    print("tox_env_teardown", tox_env)

© www.soinside.com 2019 - 2024. All rights reserved.