关于 toml-python
面向 Python 开发者的 TOML 配置文件操作技能,介绍使用 tomlkit 读写、验证和更新文件,并保留注释与格式,也说明只读场景下何时选用 tomllib。
主要功能
- 01使用 tomlkit 读写 TOML
- 02保留注释与格式
- 03创建和修改 TOML 文档
- 04处理解析与验证错误
- 05比较 tomlkit 和 tomllib
适用场景
- 01更新 pyproject.toml 并保留注释
- 02构建 Python 配置文件管理功能
- 03验证 TOML 并排查语法错误
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jamie-bitflight/ claude_skills --skill toml-python安装后在 Claude Code 中输入/toml-python确认已加载。
详细介绍
TOML Python 集成
使用 tomlkit 库处理 TOML 配置文件,在读取、修改和写入过程中保留注释和格式。
何时使用此技能
在以下情况下使用此技能:
- 读取或写入 TOML 配置文件(config.toml、pyproject.toml)
- 修改现有 TOML 文件,同时保留注释和格式
- 将 TOML 解析为 Python 数据结构
- 以编程方式创建 TOML 文档
- 处理 TOML 语法错误和验证
- 为 Python 应用实现配置文件管理
- 使用 XDG 基础目录规范确定配置文件位置
核心能力
库选择:tomlkit 与 tomllib
在以下情况下使用 tomlkit:
- 修改现有配置文件(保留注释和格式)
- 构建会写入配置的应用
- 需要使用同一个库进行读写
- 需要兼容 Python 3.8+
在以下情况下使用 tomllib(标准库):
- 仅支持 Python 3.11+
- 只需只读访问(无需写入功能)
- 优先选择最少依赖
对于配置文件管理,推荐使用 tomlkit。
安装
# Using uv (recommended)
uv add tomlkit
# Using pip
pip install tomlkit
要求: Python >=3.8,tomlkit >=0.12.0
tomlkit API 参考
读取 TOML
import tomlkit
# From string
doc = tomlkit.parse(toml_string)
doc = tomlkit.loads(toml_string) # Alias for parse()
# From file object
with open("config.toml", "r") as f:
doc = tomlkit.load(f)
# Using TOMLFile class (convenient)
from tomlkit import TOMLFile
toml_file = TOMLFile("config.toml")
doc = toml_file.read()
返回: TOMLDocument 对象(类似 dict,会保留格式)
写入 TOML
import tomlkit
# To string
toml_string = tomlkit.dumps(data)
# To file object
with open("config.toml", "w") as f:
tomlkit.dump(data, f)
# Using TOMLFile class
from tomlkit import TOMLFile
toml_file = TOMLFile("config.toml")
toml_file.write(doc)
创建 TOML 文档
from tomlkit import document, table, comment, nl, array, inline_table
# Create document
doc = document()
doc.add(comment("Configuration file"))
doc.add(nl())
doc.add("title", "My Config")
# Create table
db_config = table()
db_config["host"] = "localhost"
db_config["port"] = 5432
doc["database"] = db_config
# Create inline table
point = inline_table()
point.update({"x": 1, "y": 2})
doc["point"] = point
# Create array
numbers = array()
numbers.extend([1, 2, 3])
doc["numbers"] = numbers
操作文档
# Dict-like access
doc["section"]["key"] = "value"
value = doc["section"]["key"]
# Get with default
value = doc.get("key", "default")
# Check existence
if "key" in doc:
pass
# Iterate
for key, value in doc.items():
print(key, value)
# Remove key
doc.pop("key")
doc.remove("key")
# Convert to pure Python dict
pure_dict = doc.unwrap()
# Get as TOML string
toml_str = doc.as_string()
值创建辅助函数
from tomlkit import (
item, # Auto-detect type
string, # String with options
integer, # Integer
float_, # Float
boolean, # Boolean
datetime, # Datetime
date, # Date
time, # Time
)
# Auto-detect type
doc["key"] = item(42)
doc["key"] = item([1, 2, 3])
doc["key"] = item({"nested": "table"})
# Explicit string types
doc["basic"] = string("text")
doc["literal"] = string("text", literal=True) # Single quotes
doc["multiline"] = string("line1\nline2", multiline=True)
错误处理
异常类型
from tomlkit.exceptions import (
TOMLKitError, # Base exception
ParseError, # Syntax errors (has .line and .col)
NonExistentKey, # Missing key access
KeyAlreadyPresent, # Duplicate key
ConvertError, # Type conversion failure
)
# Handle parse errors
try:
doc = tomlkit.parse(toml_string)
except ParseError as e:
print(f"Parse error at line {e.line}, column {e.col}: {e}")
# Handle missing keys
try:
value = doc["nonexistent"]
except (KeyError, NonExistentKey):
value = "default"
# Handle file not found
try:
with open("config.toml", "r") as f:
doc = tomlkit.load(f)
except FileNotFoundError:
# Create default config
doc = create_default_config()
常见模式
模式 1:加载或创建配置
import tomlkit
from pathlib import Path
def load_or_create_config(path: Path) -> tomlkit.TOMLDocument:
"""Load existing config or create default if missing."""
if path.exists():
with open(path, "r") as f:
return tomlkit.load(f)
# Create default
doc = tomlkit.document()
doc.add(tomlkit.comment("Default configuration"))
doc.add(tomlkit.nl())
doc["app"] = tomlkit.table()
doc["app"]["name"] = "myapp"
doc["app"]["version"] = "1.0.0"
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
tomlkit.dump(doc, f)
return doc
模式 2:更新单个值(保留注释)
import tomlkit
def update_config_value(path: str, section: str, key: str, value):
"""Update single value while preserving all comments."""
with open(path, "r") as f:
doc = tomlkit.load(f)
if section not in doc:
doc[section] = tomlkit.table()
doc[section][key] = value
with open(path, "w") as f:
tomlkit.dump(doc, f)
# Usage
update_config_value("config.toml", "database", "port", 5433)
模式 3:原子更新
import tomlkit
from pathlib import Path
import tempfile
import shutil
def atomic_config_update(path: Path, updates: dict):
"""Update config atomically to prevent corruption."""
with open(path, "r") as f:
doc = tomlkit.load(f)
# Apply updates
for section, values in updates.items():
if section not in doc:
doc[section] = tomlkit.table()
for key, value in values.items():
doc[section][key] = value
# Write to temp file, then atomic move
temp_fd, temp_path = tempfile.mkstemp(suffix=".toml")
try:
with open(temp_fd, "w") as f:
tomlkit.dump(doc, f)
shutil.move(temp_path, path)
except Exception:
Path(temp_path).unlink(missing_ok=True)
raise
模式 4:配置验证
import tomlkit
from tomlkit.exceptions import ParseError
def validate_config(path: str) -> tuple[bool, str]:
"""Validate config structure. Returns (is_valid, error_message)."""
try:
with open(path, "r") as f:
doc = tomlkit.load(f)
except FileNotFoundError:
return False, "Config file not found"
except ParseError as e:
return False, f"Invalid TOML at line {e.line}, col {e.col}"
required_sections = ["app", "database"]
missing = [s for s in required_sections if s not in doc]
if missing:
return False, f"Missing sections: {', '.join(missing)}"
if "name" not in doc.get("app", {}):
return False, "Missing required key: app.name"
return True, ""
XDG 基础目录集成
若要使用符合 XDG 规范的配置文件位置,请启用 xdg-base-directory 技能:
Skill(skill: "xdg-base-directory:xdg-base-directory")
标准配置路径模式:
from pathlib import Path
def get_config_path(app_name: str) -> Path:
"""Get XDG-compliant config path."""
config_dir = Path.home() / ".config" / app_name
return config_dir / "config.toml"
# Usage
config_path = get_config_path("myapp")
# Returns: ~/.config/myapp/config.toml
TOML 语法速查
基本类型
# Strings
string = "Hello, World!"
multiline = """
Multiple
lines
"""
literal = 'C:\path\no\escaping'
# Numbers
integer = 42
float = 3.14
scientific = 1e10
# Boolean
flag = true
# Date/Time
datetime = 2024-01-15T10:30:00Z
date = 2024-01-15
time = 10:30:00
表和数组
# Standard table
[database]
host = "localhost"
port = 5432
# Nested table
[database.pool]
max_connections = 100
# Inline table
point = { x = 1, y = 2 }
# Array
numbers = [1, 2, 3]
# Array of tables
[[products]]
name = "Widget"
price = 9.99
[[products]]
name = "Gadget"
price = 19.99
类型映射
| TOML 类型 | Python 类型 |
|---|---|
| 字符串 | str |
| 整数 | int |
| 浮点数 | float |
| 布尔值 | bool |
| 带偏移量的日期时间 | datetime.datetime |
| 本地日期时间 | datetime.datetime |
| 本地日期 | datetime.date |
| 本地时间 | datetime.time |
| 数组 | list |
| 表 | dict |
tomlkit 的主要特性
注释保留
import tomlkit
original = """
# Configuration file
[database]
# Database host
host = "localhost"
# Database port
port = 5432
"""
doc = tomlkit.parse(original)
doc["database"]["port"] = 5433
result = tomlkit.dumps(doc)
# Comments are preserved in result
原因: 配置文件中由用户添加的注释应在应用更新后继续保留。
格式保留
tomlkit 会保留:
- 原始缩进
- 空白字符模式
- 键的顺序
- 注释位置
- 引号样式偏好
原因: 配置更改时,版本控制中的差异最小。
表创建辅助函数
from tomlkit import document, table
doc = document()
# Regular table
config = table()
config["key"] = "value"
doc["config"] = config
# Super table (parent of nested tables)
parent = table(is_super_table=True)
child = table()
child["x"] = 1
parent.append("child", child)
doc.append("parent", parent)
print(doc.as_string())
# [parent.child]
# x = 1
常见问题
问题:注释丢失
# ❌ Wrong: Using unwrap() loses formatting
doc = tomlkit.load(f)
pure_dict = doc.unwrap()
# Modifications to pure_dict lose all comments
# ✓ Correct: Modify doc directly
doc = tomlkit.load(f)
doc["section"]["key"] = "value"
# Comments preserved
问题:类型不匹配
# ❌ Wrong: Assuming types
value = doc["port"] # Might be string or int
# ✓ Correct: Validate types
port = doc["port"]
if not isinstance(port, int):
raise ValueError(f"Expected int for port, got {type(port)}")
问题:缺少键
# ❌ Wrong: Direct access without checking
value = doc["section"]["key"] # KeyError if missing
# ✓ Correct: Use .get() with defaults
value = doc.get("section", {}).get("key", "default")
配置文件示例
# ~/.config/myapp/config.toml
# Application configuration
[app]
# Application name
name = "myapp"
# Application version
version = "1.0.0"
# Debug mode
debug = false
[database]
# Database connection settings
host = "localhost"
port = 5432
name = "myapp_db"
pool_size = 10
[logging]
# Logging configuration
level = "INFO"
file = "/var/log/myapp/app.log"
max_size_mb = 100
[features]
# Feature flags
enable_api = true
enable_web = true
enable_workers = false
数据类集成模式
from dataclasses import dataclass
import tomlkit
from pathlib import Path
@dataclass
class AppConfig:
name: str
version: str
debug: bool = False
@dataclass
class DatabaseConfig:
host: str
port: int
name: str
pool_size: int = 10
@dataclass
class Config:
app: AppConfig
database: DatabaseConfig
def load_config(path: Path) -> Config:
"""Load TOML config into dataclasses."""
with open(path, "r") as f:
data = tomlkit.load(f)
return Config(app=AppConfig(**data.get("app", {})), database=DatabaseConfig(**data.get("database", {})))
def save_config(config: Config, path: Path):
"""Save dataclasses to TOML, preserving existing comments."""
if path.exists():
with open(path, "r") as f:
doc = tomlkit.load(f)
else:
doc = tomlkit.document()
# Update from dataclasses
if "app" not in doc:
doc["app"] = tomlkit.table()
doc["app"]["name"] = config.app.name
doc["app"]["version"] = config.app.version
doc["app"]["debug"] = config.app.debug
if "database" not in doc:
doc["database"] = tomlkit.table()
doc["database"]["host"] = config.database.host
doc["database"]["port"] = config.database.port
doc["database"]["name"] = config.database.name
doc["database"]["pool_size"] = config.database.pool_size
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
tomlkit.dump(doc, f)
参考资料
官方文档
- tomlkit 文档 - 完整的 API 参考
- tomlkit PyPI - 软件包信息
- tomlkit GitHub - 源代码
- TOML 规范 - TOML v1.0.0 规范
- Python tomllib - 标准库替代方案(只读)
相关技能
xdg-base-directory- 用于符合 XDG 规范的配置文件位置standards-for-python-development- 用于通用 Python 规则uv- 用于依赖管理
工具
tomlkit- 保留注释的 TOML 库(读写)tomllib- 标准库 TOML 解析器(只读,Python 3.11+)tomli_w- 兼容标准库的 TOML 写入器
评论
支持 Markdown