You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Hatch构建Python3.11库时触发TOML解码错误及相关配置咨询

使用Hatch构建Python3.11库时触发TOML解码错误及相关配置咨询

问题现象:执行构建命令时触发TOML解码错误

执行命令 hatch build pyproject.tomlpython3 -m build pyproject.toml 时,出现以下TOML解码错误:

hatch build pyproject.toml
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/cli/__init__.py:221 in main                  │
│                                                                                                  │
│   218                                                                                            │
│   219 def main():  # no cov                                                                      │
│   220 │   try:                                                                                   │
│ ❱ 221 │   │   hatch(prog_name='hatch', windows_expand_args=False)                                │
│   222 │   except Exception:  # noqa: BLE001                                                      │
│   223 │   │   import sys                                                                         │
│   224                                                                                            │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/click/core.py:1157 in __call__                     │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/click/core.py:1078 in main                         │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/click/core.py:1688 in invoke                       │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/click/core.py:1434 in invoke                       │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/click/core.py:783 in invoke                        │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/click/decorators.py:45 in new_func                 │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/cli/build/__init__.py:55 in build            │
│                                                                                                  │
│    52 @click.pass_obj                                                                            │
│    53 def build(app: Application, location, targets, hooks_only, no_hooks, ext, clean, clean_h   │
│    54 │   """Build a project."""                                                                 │
│ ❱  55 │   app.ensure_environment_plugin_dependencies()                                           │
│    56 │                                                                                          │
│    57 │   from hatch.config.constants import AppEnvVars                                          │
│    58 │   from hatch.utils.fs import Path                                                        │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/cli/application.py:239 in                    │
│ ensure_environment_plugin_dependencies                                                           │
│                                                                                                  │
│   236 │                                                                                          │
│   237 │   def ensure_environment_plugin_dependencies(self) -> None:                              │
│   238 │   │   self.ensure_plugin_dependencies(                                                   │
│ ❱ 239 │   │   │   self.project.config.env_requires_complex, wait_message='Syncing environment    │
│   240 │   │   )                                                                                  │
│   241 │                                                                                          │
│   242 │   def ensure_plugin_dependencies(self, dependencies: list[Requirement], *, wait_messag   │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/project/core.py:46 in config                 │
│                                                                                                  │
│    43 │   │   if self._config is None:                                                           │
│    44 │   │   │   from hatch.project.config import ProjectConfig                                 │
│    45 │   │   │                                                                                  │
│ ❱  46 │   │   │   self._config = ProjectConfig(self.location, self.metadata.hatch.config, self   │
│    47 │   │                                                                                      │
│    48 │   │   return self._config                                                                │
│    49                                                                                            │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/project/core.py:127 in metadata              │
│                                                                                                  │
│   124 │   │   if self._metadata is None:                                                         │
│   125 │   │   │   from hatchling.metadata.core import ProjectMetadata                            │
│   126 │   │   │                                                                                  │
│ ❱ 127 │   │   │   self._metadata = ProjectMetadata(self.location, self.plugin_manager, self.ra   │
│   128 │   │                                                                                      │
│   129 │   │   return self._metadata                                                              │
│   130                                                                                            │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/project/core.py:140 in raw_config            │
│                                                                                                  │
│   137 │   │   │   else:                                                                          │
│   138 │   │   │   │   from hatch.utils.toml import load_toml_file                                │
│   139 │   │   │   │                                                                              │
│ ❱ 140 │   │   │   │   raw_config = load_toml_file(str(self._project_file_path))                  │
│   141 │   │   │   │   # Assume environment management only                                       │
│   142 │   │   │   │   if 'project' not in raw_config:                                            │
│   143 │   │   │   │   │   raw_config['project'] = {'name': self.location.name}                   │
│                                                                                                  │
│ /home/gort/Saber/lib/python3.11/site-packages/hatch/utils/toml.py:18 in load_toml_file           │
│                                                                                                  │
│   15                                                                                             │
│   16 def load_toml_file(path: str) -> dict[str, Any]:                                            │
│   17 │   with open(path, encoding='utf-8') as f:                                                 │
│ ❱ 18 │   │   return tomllib.loads(f.read())                                                      │
│   19                                                                                             │
│                                                                                                  │
│ /usr/lib/python3.11/tomllib/_parser.py:102 in loads                                              │
│                                                                                                  │
│    99 │   │   │   pos += 1                                                                       │
│   100 │   │   │   continue                                                                       │
│   101 │   │   if char in KEY_INITIAL_CHARS:                                                      │
│ ❱ 102 │   │   │   pos = key_value_rule(src, pos, out, header, parse_float)                       │
│   103 │   │   │   pos = skip_chars(src, pos, TOML_WS)                                            │
│   104 │   │   elif char == "[":                                                                  │
│   105 │   │   │   try:                                                                           │
│                                                                                                  │
│ /usr/lib/python3.11/tomllib/_parser.py:326 in key_value_rule                                     │
│                                                                                                  │
│   323 def key_value_rule(                                                                        │
│   324 │   src: str, pos: Pos, out: Output, header: Key, parse_float: ParseFloat                  │
│   325 ) -> Pos:                                                                                  │
│ ❱ 326 │   pos, key, value = parse_key_value_pair(src, pos, parse_float)                          │
│   327 │   key_parent, key_stem = key[:-1], key[-1]                                               │
│   328 │   abs_key_parent = header + key_parent                                                   │
│   329                                                                                            │
│                                                                                                  │
│ /usr/lib/python3.11/tomllib/_parser.py:369 in parse_key_value_pair                               │
│                                                                                                  │
│   366 │   │   raise suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")       │
│   367 │   pos += 1                                                                               │
│   368 │   pos = skip_chars(src, pos, TOML_WS)                                                    │
│ ❱ 369 │   pos, value = parse_value(src, pos, parse_float)                                        │
│   370 │   return pos, key, value                                                                 │
│   371                                                                                            │
│   372                                                                                            │
│                                                                                                  │
│ /usr/lib/python3.11/tomllib/_parser.py:616 in parse_value                                        │
│                                                                                                  │
│   613 │                                                                                          │
│   614 │   # Arrays                                                                               │
│   615 │   if char == "[":                                                                        │
│ ❱ 616 │   │   return parse_array(src, pos, parse_float)                                          │
│   617 │                                                                                          │
│   618 │   # Inline tables                                                                        │
│   619 │   if char == "{":                                                                        │
│                                                                                                  │
│ /usr/lib/python3.11/tomllib/_parser.py:428 in parse_array                                        │
│                                                                                                  │
│   425 │   │   if c == "]":                                                                       │
│   426 │   │   │   return pos + 1, array                                                          │
│   427 │   │   if c != ",":                                                                       │
│ ❱ 428 │   │   │   raise suffixed_err(src, pos, "Unclosed array")                                 │
│   429 │   │   pos += 1                                                                           │
│   430 │   │                                                                                      │
│   431 │   │   pos = skip_comments_and_array_ws(src, pos)                                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
TOMLDecodeError: Unclosed array (at line 25, column 31)

环境说明

  • 系统:基于Debian的单板计算机(SBC)
  • Python版本:3.11
  • 环境管理:使用venv虚拟环境,通过pip3python3 -m pip管理包
  • 构建工具:hatch/hatchling

项目的pyproject.toml内容

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "Saber"
version = "0.5.0"
dependencies = [
  "*pyserial",
#  "gidgethub[httpx]>4.0.0",
#  "django>2.1; os_name != 'nt'",
#  "django>2.0; os_name == 'nt'",
]
requires-python = ">=3.11"
authors = [
  {name = "User_One", email = "<User_One@example.com>"},
  {name = "User_Two", email = "<User_Two@example.com>"},
]
maintainers = [
  {name = "Seth", email = "seth@example.com"},
]
description = "Making UARTs work on new 64-bit Machines with Dimension Engineering!"
readme = "readme.rst"
license = {file = "MIT"}
keywords = ["beagleboard.org" "BeagleY-AI" "UART" "Dimension Engineering"]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python",
]

[project.optional-dependencies]
#gui = ["PyQt5"]
cli = [
  "tomli",
  "pep517",
  "setuptools-pep660",
]

问题分析与解决方案

1. 触发TOML解码错误的根本原因

错误提示显示 Unclosed array (at line 25, column 31),对应pyproject.toml中的keywords数组:

keywords = ["beagleboard.org" "BeagleY-AI" "UART" "Dimension Engineering"]

TOML规范中,数组的元素之间必须用逗号分隔,这里的元素直接空格连接,违反了TOML语法,导致解析器认为数组未闭合。

修复方法:在keywords数组的每个元素之间添加逗号:

keywords = ["beagleboard.org", "BeagleY-AI", "UART", "Dimension Engineering"]

2. 关于tomli/tomllib的解码/编码疑问解答

  • Python 3.11及以上版本已经内置了tomllib(符合TOML 1.0规范的解析器),无需额外安装tomli。你遇到的解析错误是语法问题,和解析库无关,修复语法后即可正常解析。
  • 如果需要手动处理TOML文件(比如解码错误的文件、编码TOML内容),可以使用tomllib(内置)或tomli-w(用于编码,因为tomllib只有解析能力):
    • 解码TOML内容:
      import tomllib
      with open("pyproject.toml", "rb") as f:
          data = tomllib.load(f)
      
    • 编码生成TOML内容:
      需先安装tomli-wpip install tomli-w,然后:
      import tomli_w
      data = {"project": {"name": "Saber", "version": "0.5.0"}}
      with open("pyproject.toml", "wb") as f:
          tomli_w.dump(data, f)
      

3. 关于PEP 517/PEP 660的补充说明

  • PEP 517是构建后端的标准,hatchling已经实现了该标准,所以你的[build-system]配置是正确的,无需额外依赖pep517包(除非你需要使用pep517命令行工具)。
  • PEP 660支持可编辑安装,setuptools-pep660是setuptools的兼容层,但hatch本身已经支持PEP 660的可编辑安装(通过hatch develop命令),所以如果使用hatch作为环境和构建管理工具,无需安装setuptools-pep660

4. 构建命令的小提示

hatch build命令不需要指定pyproject.toml文件,只要在项目根目录(pyproject.toml所在目录)执行即可,简化为:

hatch build

验证修复

修复pyproject.tomlkeywords数组后,再次在项目根目录执行hatch build,即可正常完成构建。

备注:内容来源于stack exchange,提问作者Mala Dies

火山引擎 最新活动