mrkeyoor.com_
Sat 19 Sept 12:04 UTC
PyPIUtilsupdated 18 Sept 2026

pyyaml review

PyYAML 6.0.3 parses YAML streams into Python scalars, lists, dictionaries, and tagged objects, and writes Python values back to YAML. SafeLoader limits construction to basic YAML tags, while FullLoader and UnsafeLoader accept wider tag sets and belong only on input you trust. The distribution has pure Python loader classes plus LibYAML-backed C variants. Version 6.0.3 adds Python 3.14 support and marks free-threading support experimental. Our Python 3.12 installation included a compiled extension, and import _yaml passed. The package does not ship py.typed, so strict type checking needs external stubs or local wrappers.

Verdict

PyYAML 6.0.3 installed in 0.2 seconds as one 3 MB package with 0 dependencies and 0 pip-audit findings, and its compiled _yaml extension imported in 0.25 seconds on our box. Use it to consume YAML with safe loaders; choose ruamel.yaml for source-preserving edits, add duplicate-key validation for strict configuration, and avoid PyYAML when YAML 1.2 scalar behavior is mandatory.

We installed it

Lab card: what happened when we installed pyyamlScreenshot of pyyaml documentation
Install✓ · 0.2s1 package on disk · 3 MB
Importimport _yaml in 0.25s · compiled extensions · requires Python >=3.8
Known vulns0(pip-audit)

Answers from our run

Does pyyaml install cleanly?

Yes. In a fresh container with an empty cache, pip install pyyaml finished in 0.2s, leaving 1 package and 3 MB on disk. pip-audit reported no known vulnerabilities.

What does pyyaml need to run?

Python >=3.8, and a platform wheel with compiled extensions. In our run import _yaml succeeded in 0.25s.

pyyaml or ruamel.yaml: which should you use?

ruamel.yaml: Use it when an editor must retain comments, quoting, and other source details across a YAML round trip. PyYAML 6.0.3 installed in 0.2 seconds as one 3 MB package with 0 dependencies and 0 pip-audit findings, and its compiled _yaml extension imported in 0.25 seconds on our box.

When should you not use pyyaml?

The application edits YAML files that humans maintain and must retain comments, quote choices, anchors, and layout. PyYAML rebuilds output from Python values; ruamel.yaml is built for round trips.

API stability4/5safe_load(), safe_load_all(), safe_dump(), loader classes, constructors, representers, nodes, events, and tokens have long-lived calling patterns. Current load() requires an explicit Loader, so examples written before that safety change are no longer valid. Version 6.0.3 adds interpreter compatibility without changing the everyday data API. Its experimental free-threading label means Python 3.14 concurrency behavior is not yet a settled promise.
Docs2/5The README gives the safe-load rule, LibYAML selection, source-build switches, test targets, support channels, and license. The linked wiki covers tags, resolvers, constructors, representers, tokens, events, and dump options, but its layout and examples are dated. YAML 1.1 booleans, duplicate keys, comment loss, C-loader selection, and missing typing metadata are not presented together as a production checklist.
Maintenance3/5GitHub showed an unarchived repository pushed on June 17, 2026, with 2,940 stars and 361 open issues and pull requests. Version 6.0.3 shipped in September 2025 for Python 3.14 and experimental free-threading support, and PyPI has current wheels for several interpreters and platforms. Packaging work is active, while YAML 1.2 support and duplicate-key rejection remain open after years of discussion.
Ecosystem5/5PyPI Stats counted 273,925,363 downloads in its recent weekly window. PyPI publishes CPython wheels across macOS, Windows, manylinux, and musllinux targets, including Python 3.14 and free-threaded builds. The yaml module name and safe_load() convention are common throughout Python infrastructure software. That familiarity comes with old yaml.load() snippets online and no bundled py.typed marker for static analyzers.

Discussed on

  1. hnBugs in the YAML specification102 points
  2. hnTips that may save you from the hell of PyYAML5 points
  3. hnCython and pyyaml is breaking many builds3 points

Use it if

  • A Python service must consume YAML emitted by CI, deployment, infrastructure, or user configuration tools.
  • You need ordinary parsing and emitting, and preserving comments, quotes, or the original layout is unnecessary.
  • A deployed wheel includes LibYAML and the application can select CSafeLoader or CSafeDumper for its hot path.
  • Custom tags, constructors, representers, tokens, or parse events are needed under application control.
Skip it if

Setup reality

We installed PyYAML 6.0.3 without a cache in an unprivileged Python 3.12 Bookworm container. The install finished in 0.2 seconds and left one package using 3 MB. It declared 0 direct dependencies and requires Python 3.8 or newer. The installed files contained compiled .so extensions, the license was MIT, and no py.typed marker was present. pip-audit found 0 known vulnerabilities. import _yaml succeeded in 0.25 seconds.

No credentials or configuration files are involved. Loader choice is the setup decision that matters. yaml.safe_load() uses the pure Python SafeLoader and constructs only basic tags. When the compiled module is available, select yaml.CSafeLoader explicitly; safe_load() does not switch to it automatically. Keep yaml.SafeLoader as a fallback if a target may install from source without LibYAML bindings. PyYAML 6.0.3 exposes the same split for SafeDumper and CSafeDumper.

Parsing does not preserve a document for editing. Comments and quote style are gone after construction, and safe_dump() writes a new representation. It sorts mapping keys by default unless sort_keys=False is passed. Plain yes, no, on, and off values resolve as booleans under the current YAML 1.1 resolver. Ordinary loaders also accept duplicate keys and keep the later value. Add schema validation and a duplicate-key loader when configuration mistakes must stop startup.

Use safe_load_all() for a stream containing more than one document. Each yielded item is still constructed in memory, so enforce upload-size limits before parsing untrusted files. Anchors can produce shared references and recursive data, which downstream code may not expect. Version 6.0.3 calls free-threading support experimental on Python 3.14. The package has no bundled typing marker, so verify any third-party stubs against the loaders and dump options your code actually uses.

Patterns

Read one untrusted YAML document load-safe-document

from pathlib import Path
import yaml

text = Path('config.yaml').read_text(encoding='utf-8')
config = yaml.safe_load(text)
if config is None:
    config = {}

safe_load() restricts construction to basic YAML tags. An empty document returns None rather than an empty dictionary.

Use LibYAML with a safe fallback load-with-c-extension

import yaml

try:
    SafeLoader = yaml.CSafeLoader
except AttributeError:
    SafeLoader = yaml.SafeLoader

config = yaml.load(text, Loader=SafeLoader)

safe_load() always selects SafeLoader. Choose CSafeLoader explicitly when _yaml is installed, and keep a fallback for source-only targets.

Write Unicode YAML without sorting keys dump-readable-yaml

import yaml

text = yaml.safe_dump(
    config,
    allow_unicode=True,
    sort_keys=False,
    default_flow_style=False,
)

safe_dump() sorts mapping keys by default. sort_keys=False retains Python insertion order, but comments and original quotes are not restored.

Iterate over a multi-document stream load-multiple-documents

import yaml

with open('resources.yaml', encoding='utf-8') as stream:
    for document in yaml.safe_load_all(stream):
        if document is not None:
            deploy(document)

safe_load() expects one document. safe_load_all() advances through documents separated by --- and yields each constructed value.

Emit an explicit multi-document stream dump-multiple-documents

import yaml

yaml.safe_dump_all(
    documents,
    stream=output,
    explicit_start=True,
    sort_keys=False,
)

explicit_start=True writes --- before every document, which makes stream boundaries visible to other YAML tools.

Fail when a mapping repeats a key reject-duplicate-keys

import yaml
from yaml.constructor import ConstructorError
from yaml.resolver import BaseResolver

class UniqueKeyLoader(yaml.SafeLoader):
    pass

def construct_unique_mapping(loader, node, deep=False):
    loader.flatten_mapping(node)
    mapping = {}
    for key_node, value_node in node.value:
        key = loader.construct_object(key_node, deep=deep)
        if key in mapping:
            raise ConstructorError(
                'while constructing a mapping', node.start_mark,
                f'duplicate key: {key!r}', key_node.start_mark,
            )
        mapping[key] = loader.construct_object(value_node, deep=deep)
    return mapping

UniqueKeyLoader.add_constructor(
    BaseResolver.DEFAULT_MAPPING_TAG,
    construct_unique_mapping,
)
config = yaml.load(text, Loader=UniqueKeyLoader)

PyYAML 6.0.3 normally keeps the later duplicate value. This SafeLoader subclass turns that silent overwrite into ConstructorError.

Parse one application-specific tag add-safe-constructor

import dataclasses
import yaml

@dataclasses.dataclass(frozen=True)
class Endpoint:
    url: str

class AppLoader(yaml.SafeLoader):
    pass

def endpoint_constructor(loader, node):
    return Endpoint(loader.construct_scalar(node))

AppLoader.add_constructor('!endpoint', endpoint_constructor)
value = yaml.load('api: !endpoint https://api.example.com', Loader=AppLoader)

Register the tag on a SafeLoader subclass so the global loader is not changed for unrelated YAML input.

Emit an application-specific tag add-safe-representer

class AppDumper(yaml.SafeDumper):
    pass

def endpoint_representer(dumper, value):
    return dumper.represent_scalar('!endpoint', value.url)

AppDumper.add_representer(Endpoint, endpoint_representer)
text = yaml.dump({'api': Endpoint('https://api.example.com')}, Dumper=AppDumper)

A matching constructor is required when reading !endpoint back. SafeLoader does not accept an unknown custom tag by itself.

Inspect the representation tree without constructing objects inspect-node-tags

import yaml

node = yaml.compose(text, Loader=yaml.SafeLoader)
if node is not None:
    print(node.tag, node.start_mark.line + 1)

compose() returns ScalarNode, SequenceNode, or MappingNode objects with tags and source marks. It does not return normal Python values.

Report a useful YAML location catch-parse-error

import yaml

try:
    config = yaml.safe_load(text)
except yaml.YAMLError as error:
    mark = getattr(error, 'problem_mark', None)
    if mark is None:
        raise
    raise ValueError(
        f'YAML error at line {mark.line + 1}, column {mark.column + 1}'
    ) from error

Parser errors may carry a zero-based problem_mark. Some YAMLError subclasses do not, so check before reading line and column.

Keep YAML 1.1 words as strings quote-ambiguous-scalars

feature_state: 'on'
confirmation: 'yes'
country_code: 'NO'

PyYAML 6.0.3 resolves plain on, yes, and NO as booleans. Quotes force these values to remain strings.

Check the data shape after parsing validate-loaded-shape

import yaml
from pydantic import BaseModel, ConfigDict

class ServiceConfig(BaseModel):
    model_config = ConfigDict(extra='forbid')
    host: str
    port: int

raw = yaml.safe_load(text)
config = ServiceConfig.model_validate(raw)

safe_load() controls which tags can construct values; it does not enforce required keys, field types, or unknown-key policy.

Alternatives

PackageRegistryPick it when
ruamel.yamlPyPIUse it when an editor must retain comments, quoting, and other source details across a YAML round trip.
strictyamlPyPIUse it when configuration should obey an explicit schema and avoid much of YAML's implicit typing and tag behavior.
tomliPyPIUse it on Python before 3.11 when a read-only TOML parser covers a configuration format you control.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.