SaneYAML
SaneYAML (version 0.6.1) is a lightweight wrapper around PyYAML designed to provide a safer and more predictable experience when reading and writing configuration files. It focuses on preserving dictionary order and preventing common PyYAML footguns such as unwanted implicit type conversions for strings like 'yes', 'no', dates, or numbers. Its release cadence is sporadic, focusing on stability and essential bug fixes.
Common errors
-
saneyaml.parser.ParserError: while parsing a block mapping
cause This error, or a generic `YAMLError`, indicates malformed YAML input. Common causes include incorrect indentation, missing colons, or invalid syntax.fixReview the YAML file or string for syntax errors. YAML is highly sensitive to indentation. Use an online YAML validator or a linter in your IDE to pinpoint issues. -
AttributeError: 'module' object has no attribute 'CLoader'
cause This issue originates from the underlying `PyYAML` library and means it failed to load its C-based extensions, typically because the `libyaml` development headers are missing on your system.fixInstall `libyaml` development headers on your operating system (e.g., `sudo apt-get install libyaml-dev` on Debian/Ubuntu, `brew install libyaml` on macOS, or `choco install libyaml` on Windows). Then, reinstall `PyYAML` using `pip install --force-reinstall PyYAML` to ensure it compiles with the C extensions. -
ValueError: invalid literal for bool(): 'yes'
cause You are attempting to convert a string loaded by `saneyaml` (e.g., 'yes', 'no') directly to a boolean or another type, but `saneyaml` intentionally loads these as strings to prevent implicit conversions.fixCompare the string value explicitly (e.g., `if loaded_data['key'] == 'yes':`). For other types like dates, use appropriate parsing functions (e.g., `datetime.fromisoformat(loaded_data['date_key'])`).
Warnings
- gotcha SaneYAML deliberately disables implicit type conversions for common string values like 'yes', 'no', 'on', 'off', dates, and some numbers. This means `yes` will be loaded as the string 'yes', not the boolean `True`. If you expect PyYAML's default 'smart' parsing, your code might receive `str` instead of `bool` or `datetime` objects.
- gotcha While `saneyaml` preserves insertion order for mappings (dictionaries), if your application relies on PyYAML's advanced features like custom constructors/representers, anchors, or tags, `saneyaml` might not expose them directly or might override their behavior for safety. It prioritizes a simpler, more controlled YAML experience.
- breaking Prior to Python 3.7, standard `dict` objects did not guarantee insertion order. `saneyaml` handles this internally to preserve order during dump/load. However, if mixing `saneyaml` with older Python versions and other YAML libraries, unexpected order behavior might occur due to underlying dictionary implementations.
Install
-
pip install saneyaml
Imports
- load
from saneyaml import load
- dump
from saneyaml import dump
Quickstart
from saneyaml import load, dump
import io
# Example YAML data
config_data = {
'name': 'My App',
'version': '1.0.0',
'settings': {
'debug': 'No', # saneyaml keeps this as string, not bool
'log_level': 'INFO',
'features': ['alpha', 'beta']
},
'last_updated': '2023-10-27' # saneyaml keeps this as string, not datetime
}
# Dump to a YAML string (preserving order and types)
output_stream = io.StringIO()
dump(config_data, output_stream)
yaml_string = output_stream.getvalue()
print("\n--- Dumped YAML ---\n" + yaml_string)
# Load from a YAML string (preventing implicit conversions)
loaded_data = load(yaml_string)
print("\n--- Loaded Data ---")
print(f"Debug setting type: {type(loaded_data['settings']['debug'])} (value: {loaded_data['settings']['debug']})")
print(f"Last updated type: {type(loaded_data['last_updated'])} (value: {loaded_data['last_updated']})")
print(f"Loaded data equality: {loaded_data == config_data}")
# Demonstrate order preservation (requires Python 3.7+ for dicts)
# For older Pythons, saneyaml handles order internally.
original_keys = list(config_data.keys())
loaded_keys = list(loaded_data.keys())
print(f"Original top-level keys order: {original_keys}")
print(f"Loaded top-level keys order: {loaded_keys}")