Skip to content

Exceptions

Every error YAMLRocks raises derives from a single base, YAMLRocksError, which carries a human-readable message plus the source location (file, line, column) whenever it is known. Below the base sit two categories, each of which also subclasses a familiar builtin so existing handlers keep working:

  • YAMLRocksDecodeError (also a ValueError) for anything that goes wrong while reading YAML, with a fine-grained subtree for parsing, schema validation, includes, secrets, and environment variables.
  • YAMLRocksEncodeError (also a TypeError) for anything that goes wrong while writing it.
YAMLRocksError .message, .file, .line, .column
├── YAMLRocksDecodeError (also ValueError)
│ ├── YAMLRocksParseError malformed YAML syntax
│ ├── YAMLRocksDuplicateKeyError duplicate key (OPT_DUPLICATE_KEYS_ERROR)
│ ├── YAMLRocksComplexKeyError collection used as a key (OPT_REJECT_COMPLEX_KEYS)
│ ├── YAMLRocksSchemaError schema validation failed (.schema_path)
│ ├── YAMLRocksIncludeError !include family (.include_stack)
│ │ ├── YAMLRocksIncludeNotFoundError
│ │ ├── YAMLRocksCircularIncludeError
│ │ ├── YAMLRocksIncludeDepthError
│ │ └── YAMLRocksIncludeConfinementError
│ ├── YAMLRocksSecretError
│ │ └── YAMLRocksSecretNotFoundError
│ └── YAMLRocksEnvVarError
└── YAMLRocksEncodeError (also TypeError)
└── YAMLRocksUnserializableError

Catch at whatever level fits: a specific class (YAMLRocksIncludeNotFoundError), a category (YAMLRocksDecodeError), the base (YAMLRocksError), or the builtin (ValueError). The classes live in yamlrocks.exceptions and are also re-exported on the top-level yamlrocks package.

The location is available as attributes, not just in the message text. line and column are 1-based; file is the source path, or None for in-memory input:

import yamlrocks
try:
yamlrocks.loads(b'key: "unterminated')
except yamlrocks.YAMLRocksParseError as exc:
print(exc.line, exc.column) # 1 6
print(exc.file) # None (loaded from bytes)
print(str(exc)) # unterminated double-quoted scalar at line 1, column 6
raise

load (and load_all) fill in file with the path they read from, so an error points at the file on disk. Because YAMLRocksParseError is a YAMLRocksDecodeError is a ValueError, any of those except clauses catches it.

YAMLRocksDecodeError is the category base for every read-side failure. Its subclasses let you react to a specific cause:

ExceptionRaised when
YAMLRocksParseErrorthe input is not well-formed YAML
YAMLRocksDuplicateKeyErrora duplicate key is found under OPT_DUPLICATE_KEYS_ERROR
YAMLRocksComplexKeyErrora collection (sequence or mapping) is used as a mapping key under OPT_REJECT_COMPLEX_KEYS
YAMLRocksSchemaErrorschema validation fails; .schema_path is the JSON path of the offending node
YAMLRocksIncludeNotFoundErroran !include target does not exist
YAMLRocksCircularIncludeErroran !include chain forms a cycle
YAMLRocksIncludeDepthErroran !include chain is too deep
YAMLRocksIncludeConfinementErroran !include resolves outside include_dir
YAMLRocksSecretNotFoundErrora !secret name is not in any secrets.yaml
YAMLRocksEnvVarErroran !env_var is undefined and has no default

Include errors also carry include_stack, the chain of (file, line) pairs that led to the failure:

import os
import tempfile
import yamlrocks
config = tempfile.mkdtemp()
with open(os.path.join(config, "main.yaml"), "w") as handle:
handle.write("data: !include missing.yaml\n")
try:
yamlrocks.load(os.path.join(config, "main.yaml"), option=yamlrocks.OPT_INCLUDES)
except yamlrocks.YAMLRocksIncludeNotFoundError as exc:
print(exc.file is not None) # True
print(isinstance(exc.include_stack, list)) # True

YAMLRocksEncodeError (a TypeError) is raised by dumps/dump when a value has no YAML representation. The concrete class is YAMLRocksUnserializableError:

import yamlrocks
try:
yamlrocks.dumps({"value": object()})
except yamlrocks.YAMLRocksEncodeError as exc:
print(exc)
# type object is not YAML serializable
raise

The usual fix is a default callable that maps each unsupported value to something YAML can represent:

import yamlrocks
yamlrocks.dumps({"value": object()}, default=str)
# b'value: <object object at 0x...>\n'

The PyYAML shim exposes yamlrocks.compat.YAMLError, aliased to the base YAMLRocksError, so code written against PyYAML that does except yaml.YAMLError keeps catching both read and write errors after you switch the import:

import yamlrocks
from yamlrocks import compat
print(compat.YAMLError is yamlrocks.YAMLRocksError) # True