pytest 配置类型严格化前哨理解pyproject.toml中 TOML 数组触发PytestRemovedIn10Warning的弃用警告【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest本文围绕 pytest 的一个弃用公告展开向string类型的 ini 选项传入非字符串值时pytest 会发出pytest.PytestRemovedIn10Warning警告并在 pytest 10 中升级为TypeError。你将理解这一警告的确切触发路径——它来自pyproject.toml的[tool.pytest.ini_options]表中 TOML 数组被保留为 Python 列表的行为——并掌握如何定位受影响的配置、给出修复方案以及如何在 pytest 10 到来之前让项目保持零警告。一、这次弃用到底说了什么对应公告位于 changelog/14808.deprecation.rst原文只有一句话信息密度却很高Passing a non-string value to astring-typed ini option now emits apytest.PytestRemovedIn10Warning. This affects[tool.pytest.ini_options]inpyproject.toml, where TOML arrays are kept as lists bymake_scalarinstead of being converted to strings. In pytest 10 this will raise aTypeError, matching the behavior of the corresponding TOML config path.拆解为三个要点行为变更插件通过parser.addini()声明的 ini 选项如果类型为string这也是不显式指定类型时的隐式默认而实际拿到的值不是str现在会发出PytestRemovedIn10Warning弃用警告触发场景主要命中pyproject.toml的[tool.pytest.ini_options]表——这里的值以 TOML 语法书写TOML 数组如opt [a, b]不会被转成字符串而是保持为 Pythonlist从而绕过字符串化的保护演进计划在 pytest 10 中这一行为将从警告但仍返回原值升级为直接抛出TypeError与原生 TOML 配置路径[tool.pytest]、pytest.toml的严格类型校验行为对齐。二、触发机制make_scalar为什么漏掉了列表要理解为什么普通.ini文件不会触发、而 TOML 配置会触发需要看 ini 模式配置值的转换逻辑。在 src/_pytest/config/findpaths.py 中_config_from_tool_pytest()负责解析pyproject.toml# src/_pytest/config/findpaths.py[tool.pytest.ini_options] 的 INI 模式解析 def make_scalar(v: object) - str | list[str]: return v if isinstance(v, list) else str(v) return { k: ConfigValue(make_scalar(v), originfile, modeini) for k, v in ini_config.items() }关键就在make_scalarINI 配置系统.ini/.cfg/.toml的ini_options表假设值只有两种形态——str或list[str]。TOML 文件虽然支持更丰富的数据类型整数、布尔、日期等但为了兼容 INI 语义标量值统一被str(v)字符串化唯独列表被原样保留因为ini_options中本来就可以写 TOML 数组来表示多值选项。于是出现了一个类型缝隙配置写法[tool.pytest.ini_options]经make_scalar后传给typestring选项时myname hellostr(hello)→hello正常无警告myname 42str(42)→42正常已被字符串化myname [value1, value2][value1, value2]list 原样保留触发PytestRemovedIn10Warning这也解释了公告中where TOML arrays are kept as lists bymake_scalarinstead of being converted to strings这句话的确切含义。作为对照同一文件中对[pytest]表与[tool.pytest]的 TOML 模式解析不做任何转换modetoml保留原生 TOML 类型# src/_pytest/config/findpaths.pyTOML 模式保留原生类型 return { k: ConfigValue(v, originfile, modetoml) for k, v in config[pytest].items() }三、警告发出点_getini_ini中的string分支警告的发出位置在 src/_pytest/config/init.py 的Config._getini_ini()方法中——该方法处理所有 INI 模式含ini_options读出的值并按注册的选项类型做强制转换# src/_pytest/config/__init__.py 的 _getini_ini() 中节选 elif type string: if not isinstance(value, str): warnings.warn( _pytest.deprecated.INI_STRING_TYPE_NON_STR_VALUE, stacklevel2, ) return value elif type int: if not isinstance(value, str): raise TypeError( fExpected an int string for option {name} of type integer, but got: {value!r} ) from None return int(value) elif type float: if not isinstance(value, str): raise TypeError( fExpected a float string for option {name} of type float, but got: {value!r} ) from None return float(value)注意两个关键对比int/float类型已经是硬校验值不是str时直接raise TypeErrorstring类型当前只是软校验发出警告后仍然return value把非字符串值原样交给调用方。这正是弃用期的典型形态——先暴露问题、保持兼容再在新大版本中翻转为异常。警告文本本身集中定义在 src/_pytest/deprecated.py 的INI_STRING_TYPE_NON_STR_VALUE常量中全文如下INI_STRING_TYPE_NON_STR_VALUE PytestRemovedIn10Warning( Passing a value that is not a string to a string-typed ini option is deprecated.\n In a future version this will raise a TypeError, matching the behavior of the corresponding TOML config path.\n If your plugin intentionally accepts non-string values, declare an explicit type (e.g. typeargs) instead of relying on the implicit string default. )它同时给出了官方修复指引如果你的插件确实需要接受非字符串值比如列表应当显式声明类型如typeargs而不是依赖隐式的string默认。PytestRemovedIn10Warning类定义于 src/_pytest/warning_types.py继承自PytestDeprecationWarning是 pytest 9 周期中所有10 将被移除行为共用的警告类别。四、复现示例一个最小触发场景pytest 官方测试套件中 testing/test_config.py 的test_addini_string_non_str_deprecated用例精确复现了这个行为第 1156–1178 行conftest.py插件声明一个 string 类型的 ini 选项def pytest_addoption(parser): parser.addini(myname, , typestring)pyproject.toml用 TOML 数组传入列表值[tool.pytest.ini_options] myname [value1, value2]测试断言config pytester.parseconfig() with pytest.warns( pytest.PytestRemovedIn10Warning, matchPassing a value that is not a string to a string-typed ini option, ): result config.getini(myname) assert result [value1, value2]注意最后的assert result [value1, value2]——弃用期间值仍会原样返回所以这类问题在弃用窗口内往往看起来能跑只有开了严格警告策略-W error、CI 中把警告当错误的项目才会暴露。这正是这类弃用警告最有价值的地方它让配置错误从静默吞掉变成可观测。对照同一测试文件前面的用例可以看到int/float选项在同样场景下的行为差异向typefloat选项传入[foo]时getini()会直接以UsageError内部包装TypeError失败错误信息为Expected a float string for option ini_param。五、pytest 10 会发生什么把警告翻转为错误从源码结构看pytest 10 中警告升级为错误的开关已经埋在配置层。src/_pytest/config/init.py 的Config._catch_configured_warnings()中有一段被注释的过滤器# To be enabled in pytest 10.0.0. # warnings.filterwarnings(error, categorypytest.PytestRemovedIn10Warning)其上方注释明确写着To be enabled in pytest 10.0.0。可以推断pytest 10 发布时该行将被启用PytestRemovedIn10Warning将默认按 error 处理叠加_getini_ini()中string分支的TypeError改动任何仍依赖string 选项吃进列表值的配置或插件都会在收集/配置阶段直接失败而不是收到一条可以忽略的警告。如果你想在 pytest 10 之前提前演练这一行为可以自行用-W把该警告提升为错误例如python -m pytest -W error::pytest.PytestRemovedIn10Warning仓库自身测试中就有这种用法见 testing/deprecated_test.py 中多处runpytest(-Werror::pytest.PytestRemovedIn10Warning)是验证项目是否已清理干净的实用手段。六、修复建议按角色分类1. 普通项目用户配置方如果你在项目pyproject.toml的[tool.pytest.ini_options]中给一个期望字符串的选项写了 TOML 数组典型症状就是本警告。修复方式按选项语义二选一选项本就应取单值改为标量字符串如addopts -ra而不是addopts [-ra]选项语义是多个值这类选项通常已声明为typeargs如addopts或typelinelist如python_files本身不会触发本警告若你自定义的选项需要列表应改用第 2 节的方式在插件侧声明类型。2. 插件作者pytest_addoption方警告文本已经给出官方指引不要依赖隐式 string 默认按真实语义显式声明类型def pytest_addoption(parser): # 需要字符串列表显式声明 args而不是 typestring parser.addini(my_list_opt, space or newline separated list, typeargs) # 确实要字符串保持 string并确保文档要求用户传标量 parser.addini(my_str_opt, a single string, typestring)_getini_ini()对args/linelist类型的处理同一方法内见 src/_pytest/config/init.py天然兼容list[str]输入args对str做shlex.split、对 list 原样返回linelist同理。所以列表值 string 类型这个组合本身就是一个类型声明错误。3. 追求类型严格的团队原生 TOML 配置路径[tool.pytest.ini_options]属于 INI 兼容模式值必须字符串化/列表化。pytest 同时提供保留原生 TOML 类型的配置路径——pyproject.toml的[tool.pytest]表或独立的pytest.toml/.pytest.toml文件见 src/_pytest/config/findpaths.py 中_config_from_pytest_table()与_load_pytest_toml()。TOML 模式下由Config._getini_toml()做严格类型校验类型不符直接TypeError例如要求 list 的选项收到标量会报config option ... expects a list for type paths, got ...。迁移到该路径后类型错误会在配置阶段立即暴露不存在弃用窗口。需注意同一个文件中[tool.pytest]TOML 模式与[tool.pytest.ini_options]INI 模式不能混用_config_from_tool_pytest()会直接抛出UsageError提示二选一。4. 临时抑制不推荐作为长期方案如果警告来自第三方插件且暂无新版可在filterwarnings中定向忽略并跟踪[tool.pytest.ini_options] filterwarnings [ ignore:Passing a value that is not a string to a string-typed ini option:pytest.PytestRemovedIn10Warning, ]这只是给迁移争取时间——pytest 10 中同类问题会变成TypeError届时抑制警告也无济于事。七、排查与验证清单结合本仓库的实现给出一份可操作的自查流程全局搜警告文本在 CI 日志或本地运行输出中搜索Passing a value that is not a string to a string-typed ini option确认是否命中定位配置来源警告触发点在config.getini(name)调用时检查pyproject.toml中[tool.pytest.ini_options]下所有值为 TOML 数组的键对照插件声明的类型凡typestring或未声明类型者即嫌疑对象严格化复验用python -m pytest -W error::pytest.PytestRemovedIn10Warning跑一遍完整测试确保无残留PytestRemovedIn10Warning插件侧核查若是自研插件审查pytest_addoption中每个addini()调用的type参数把实际语义是列表的选项改为typeargs/typelinelist参考官方用例testing/test_config.py 中test_addini_string_non_str_deprecated提供了触发与断言的标准写法可作为回归测试模板。八、附如何读懂这条 changelog 文件本仓库的弃用公告遵循 towncrier 风格的命名规范存放于 changelog/ 目录格式约定见 changelog/README.rst文件名issue号.类型.rst其中类型包括bugfix、feature、improvement、deprecation、breaking等。本文的源头 changelog/14808.deprecation.rst 中的deprecation后缀表明这是一条弃用预告而非故障修复——它不会改变 pytest 9.x 的兼容性承诺值仍会原样返回只是把原本静默的类型不匹配暴露成可观测的警告为 pytest 10 的类型严格化预留迁移期。阅读此类文件时建议固定沿公告 →src/_pytest/deprecated.py中的警告常量 →src/_pytest/config/__init__.py中的触发点 →testing/中的对应用例这条链路核对实现本文第二节至第六节即按该链路展开。【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考