YAML Demystified: A Comprehensive Guide to YAML and Python Integration

A comprehensive guide to understanding YAML syntax, features, and how to effectively work with YAML files in Python applications.

YAML Demystified: A Comprehensive Guide to YAML and Python Integration

Table of Contents

YAML Demystified: A Comprehensive Guide to YAML and Python Integration

Introduction

In the world of configuration files and data serialization, YAML (YAML Ain’t Markup Language) has emerged as a popular format, valued for its human-readability and expressiveness. Whether you’re configuring CI/CD pipelines, Kubernetes resources, or application settings, YAML’s straightforward syntax makes it an excellent choice for both developers and operations teams.

This guide will dive deep into YAML—its syntax, advantages, best practices, and how to work with it effectively in Python applications. By the end, you’ll have a thorough understanding of this versatile format and be equipped to use it in your projects.

What is YAML?

YAML is a human-friendly data serialization standard designed for all programming languages. Originally, YAML was said to mean “Yet Another Markup Language,” but now stands for “YAML Ain’t Markup Language” to emphasize that it’s for data, not documents.

Created in 2001 by Clark Evans, Ingy döt Net, and Oren Ben-Kiki, YAML was designed to be more readable and less verbose than XML and more expressive than JSON. It uses whitespace indentation to denote structure, making it naturally readable by humans.

Key Features of YAML

  1. Human-readable: Clean, minimal syntax with meaningful whitespace
  2. Rich data structures: Supports mappings (dictionaries), sequences (arrays), and scalars (strings, numbers)
  3. Portable: Works across programming languages
  4. Supports complex data types: Includes timestamps, binary data, and more
  5. Comments support: Unlike JSON, YAML allows comments
  6. Document separation: Multiple documents can exist in a single file
  7. References and anchors: Allows for reusing content within a document

When to Use YAML

YAML is particularly well-suited for:

  • Configuration files: Application settings, environment configurations
  • Infrastructure as Code (IaC): Defining cloud resources, CI/CD pipelines
  • Data exchange: When human readability is important
  • Kubernetes: Resource definitions and Helm charts
  • Documentation: Content requiring structured data with good readability

YAML Syntax Fundamentals

Let’s explore the basic building blocks of YAML:

Key-Value Pairs (Mappings)

The simplest YAML structure is a key-value pair:

name: John Doe
age: 30
occupation: Developer

Lists/Sequences

Lists are represented with hyphens:

fruits:
  - Apple
  - Banana
  - Cherry
languages:
  - Python
  - JavaScript
  - Go

Nested Structures

YAML allows for deeply nested structures:

person:
  name: Jane Smith
  contact:
    email: [email protected]
    phone: 555-1234
  skills:
    - Python
    - Docker
    - Kubernetes

Multi-line Text

YAML offers several ways to handle multi-line text:

Literal Block Style (|)

Preserves line breaks:

description: |
  This is a multi-line description.
  Line breaks will be preserved.
  Indentation is removed.  

Folded Block Style (>)

Converts line breaks to spaces:

description: >
  This is a multi-line description.
  Line breaks will be converted to spaces.
  Multiple lines become a single paragraph.  

Comments

Comments in YAML start with a hash symbol (#):

# This is a comment
name: John # This is an inline comment

Document Separators

Multiple YAML documents can exist in a single file, separated by ---:

---
# First document
name: Document 1
---
# Second document
name: Document 2

Anchors and References

YAML supports content reuse with anchors (&) and references (*):

defaults: &defaults
  timeout: 30
  retries: 3

development:
  <<: *defaults # Include all default settings
  environment: development
  debug: true

production:
  <<: *defaults # Include all default settings
  environment: production
  debug: false

Data Types

YAML automatically infers data types:

# String
name: John Doe

# Integer
age: 30

# Float
score: 98.7

# Boolean (various formats)
active: true
inactive: false
enabled: yes
disabled: no

# Null (various formats)
value: null
empty: ~
nothing:

# Date and Time
date: 2023-12-25
datetime: 2023-12-25T10:30:00Z

YAML vs Other Formats

How does YAML compare to other popular data formats?

YAML vs JSON

FeatureYAMLJSON
SyntaxIndentation-basedBraces and brackets
CommentsSupportedNot supported
Data typesRich (dates, binary, etc.)Limited (string, number, boolean, null, array, object)
Multiline stringsMultiple formatsRequires escaping
ReferencesSupportedNot supported
Human readabilityHighModerate
Parsing complexityHigherLower

YAML vs XML

FeatureYAMLXML
VerbosityLowHigh
NamespacesNot nativeSupported
Schema validationLimitedExtensive (XSD)
AttributesNot a separate conceptNative concept
Learning curveLowerHigher
ProcessingSimplerMore complex

YAML vs INI

FeatureYAMLINI
Hierarchical dataDeeply supportedLimited (sections only)
StandardizationWell-defined standardVariations between implementations
Data typesRichTypically strings only
ComplexityCan be complexSimple

Working with YAML in Python

Python has excellent YAML support through the PyYAML library. Let’s explore how to use it:

Prerequisites

To work with YAML in Python, you’ll need:

  1. Python installed (3.6+ recommended)
  2. PyYAML package:
    pip install pyyaml
    

Basic YAML Parsing

Here’s how to read a YAML file in Python:

import yaml

# Reading a YAML file
with open("config.yaml", "r") as file:
    try:
        # Convert YAML to Python object
        data = yaml.safe_load(file)
        print(data)
    except yaml.YAMLError as e:
        print(f"Error parsing YAML: {e}")

Writing YAML Files

Creating YAML from Python objects:

import yaml

# Python dictionary
data = {
    'name': 'Project Alpha',
    'version': 1.0,
    'settings': {
        'debug': True,
        'timeout': 30
    },
    'environments': ['development', 'staging', 'production']
}

# Writing to a YAML file
with open("output.yaml", "w") as file:
    yaml.dump(data, file, default_flow_style=False)

Working with Multiple Documents

Handling YAML files with multiple documents:

import yaml

# Reading multiple documents
docs = []
with open("multi_doc.yaml", "r") as file:
    for doc in yaml.safe_load_all(file):
        docs.append(doc)

# Now docs is a list of Python objects, one for each YAML document
for i, doc in enumerate(docs):
    print(f"Document {i+1}:")
    print(doc)

Advanced PyYAML Usage

Custom Tag Handling

PyYAML allows custom data type handling with tags:

import yaml
from datetime import datetime

# Define a custom constructor for a tag
def timestamp_constructor(loader, node):
    value = loader.construct_scalar(node)
    return datetime.strptime(value, '%Y-%m-%d %H:%M:%S')

# Register the constructor
yaml.add_constructor('!timestamp', timestamp_constructor)

# Parse YAML with custom tag
data = yaml.safe_load('''
date: !timestamp 2023-01-15 12:30:00
''')

print(data['date'])  # Output: 2023-01-15 12:30:00 as a datetime object

Error Handling

Robust error handling for YAML parsing:

import yaml

def parse_yaml_file(file_path):
    try:
        with open(file_path, 'r') as file:
            try:
                return yaml.safe_load(file)
            except yaml.MarkedYAMLError as e:
                # This exception provides line and column info
                print(f"YAML syntax error: {e}")
                if hasattr(e, 'problem_mark'):
                    mark = e.problem_mark
                    print(f"Error position: line {mark.line+1}, column {mark.column+1}")
            except yaml.YAMLError as e:
                print(f"General YAML error: {e}")
    except FileNotFoundError:
        print(f"File not found: {file_path}")
    except PermissionError:
        print(f"Permission denied when accessing file: {file_path}")
    except Exception as e:
        print(f"Unexpected error: {e}")

    return None

Controlling Output Format

PyYAML offers several options for formatting output:

import yaml

data = {
    'name': 'Project X',
    'config': {
        'debug': True,
        'logging': 'verbose'
    },
    'versions': [1, 2, 3]
}

# Default output
with open("output1.yaml", "w") as f:
    yaml.dump(data, f)

# Pretty output with block style
with open("output2.yaml", "w") as f:
    yaml.dump(data, f, default_flow_style=False)

# Control indentation (4 spaces)
with open("output3.yaml", "w") as f:
    yaml.dump(data, f, default_flow_style=False, indent=4)

# Sort keys alphabetically
with open("output4.yaml", "w") as f:
    yaml.dump(data, f, default_flow_style=False, sort_keys=True)

Real-World YAML Examples

Let’s explore some common real-world uses of YAML:

Docker Compose

version: "3"
services:
  web:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - app

  app:
    build: ./app
    environment:
      - DB_HOST=db
      - DB_USER=root
      - DB_PASSWORD=example
    depends_on:
      - db

  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=example
      - MYSQL_DATABASE=myapp

volumes:
  db_data:

GitHub Actions Workflow

name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.10"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi          

      - name: Run tests
        run: |
          pytest          

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.14.2
          ports:
            - containerPort: 80
          resources:
            limits:
              cpu: "0.5"
              memory: "512Mi"
            requests:
              cpu: "0.2"
              memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
  type: LoadBalancer

Application Configuration

app:
  name: MyApplication
  version: 1.2.3

database:
  host: localhost
  port: 5432
  name: myapp_db
  user: ${DB_USER} # Environment variable
  password: ${DB_PASSWORD}

logging:
  level: info
  file: /var/log/myapp.log
  max_size: 100MB
  backups: 5

features:
  dark_mode: true
  beta_features: false

YAML Best Practices

Follow these best practices to make your YAML files more maintainable and less error-prone:

1. Use Consistent Indentation

Stick to a consistent indentation style—typically 2 spaces:

# Good
parent:
  child1: value
  child2: value

# Avoid
parent:
    child1: value
  child2: value

2. Leverage Comments

Use comments to explain complex sections:

# Database configuration for production environment
database:
  host: db.example.com # Main database server
  port: 5432

3. Validate Your YAML

Always validate YAML files before deployment. Use tools like:

  • yamllint for linting
  • Online YAML validators
  • IDE extensions for real-time validation

4. Use Quotes When Needed

Quotes are necessary for strings containing special characters:

# Without quotes (will cause errors)
message: Hello: World

# With quotes (correct)
message: "Hello: World"

5. Be Careful with Type Conversions

YAML automatically converts types, which can sometimes be problematic:

# These are interpreted as booleans, not strings
enabled: yes
disabled: no

# Use quotes for string values
enabled: "yes"
disabled: "no"

6. Use Anchors and References for DRY Code

Don’t repeat yourself—use anchors to reuse configuration:

defaults: &defaults
  timeout: 30
  retry: 3

development:
  <<: *defaults
  environment: development

production:
  <<: *defaults
  environment: production
  timeout: 60 # Override specific values

7. Use Environment Variables

For sensitive data or environment-specific values:

database:
  password: ${DB_PASSWORD}
  host: ${DB_HOST:-localhost} # With default value

8. Document Separators

Use document separators when bundling multiple resources:

---
# First resource
kind: Service
# ...
---
# Second resource
kind: Deployment
# ...

9. Keep Files Manageable

Split large YAML files into smaller, focused files:

config/
  ├── database.yaml
  ├── logging.yaml
  └── security.yaml

10. Avoid Complex Nesting

Deeply nested structures become hard to read—consider flattening:

# Too nested
app:
  database:
    connection:
      settings:
        timeout: 30

# Better
app_database_connection_timeout: 30

Common YAML Pitfalls and How to Avoid Them

YAML’s simplicity can be deceptive. Here are common pitfalls and solutions:

1. Indentation Errors

Problem: Inconsistent indentation breaks the structure.

Solution: Use an editor with YAML support for visual indentation guides.

2. Tab vs. Spaces

Problem: Mixing tabs and spaces causes unpredictable behavior.

Solution: Configure your editor to convert tabs to spaces automatically.

3. Unquoted Special Characters

Problem: Special characters like :, {, }, [, ], ,, &, *, #, ?, |, -, <, >, =, !, %, @ can cause parsing errors.

Solution: Quote strings containing special characters:

# Wrong
name: John: Doe

# Correct
name: "John: Doe"

4. Boolean Interpretation

Problem: Words like yes, no, true, false, on, off are automatically converted to booleans.

Solution: Quote these values if you want them as strings:

# Boolean
enabled: yes

# String
status: "yes"

5. Number Interpretation

Problem: YAML tries to convert numeric-looking strings to numbers.

Solution: Quote numeric strings:

# Number
version: 1.0

# String
id: "1234567890"
phone: "555-123-4567"

6. Multi-line String Handling

Problem: Incorrect use of multi-line string indicators.

Solution: Use the appropriate indicator for your needs:

# Preserve line breaks with |
description: |
  First line
  Second line  

# Fold line breaks to spaces with >
description: >
  First line
  Second line  

7. Duplicate Keys

Problem: YAML parsers handle duplicate keys differently; some use the last value, others throw errors.

Solution: Ensure keys are unique:

# Problematic - duplicate key
settings:
  timeout: 30
  timeout: 60

# Correct
settings:
  connection_timeout: 30
  response_timeout: 60

8. YAML Flow Style Confusion

Problem: Mixing flow style and block style can be confusing.

Solution: Stick to one style consistently:

# Block style (recommended for readability)
items:
  - name: item1
    value: 10
  - name: item2
    value: 20

# Flow style
items: [{name: item1, value: 10}, {name: item2, value: 20}]

Security Considerations

YAML parsing can introduce security risks if not handled carefully:

1. YAML Deserialization Vulnerabilities

Problem: Some YAML parsers can execute arbitrary code during deserialization.

Solution: Always use safe_load() instead of load():

# Insecure
data = yaml.load(file)  # Can execute arbitrary code!

# Secure
data = yaml.safe_load(file)  # Safe deserialization

2. Environment Variable Exposure

Problem: Environment variables in YAML might expose sensitive information.

Solution: Use external secrets management:

# Avoid having secrets directly in files
database:
  password: ${DB_PASSWORD} # Get from environment

3. File Permissions

Problem: YAML files might contain sensitive data with improper permissions.

Solution: Restrict file permissions:

chmod 600 secrets.yaml  # Only owner can read/write

4. Comment Leakage

Problem: Comments might contain sensitive information.

Solution: Audit comments before sharing YAML files:

# DON'T: password is admin123
password: ${PASSWORD}

# DO: reference environment variable
password: ${PASSWORD}

Advanced YAML Topics

Let’s explore some advanced YAML features for power users:

Custom Tags

YAML allows custom tags for special data types:

# Custom date type
date: !date 2023-12-25

# Custom binary data
certificate: !binary |
  R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=

Complex Mapping Keys

Keys can be more than simple strings:

? - complex
  - key
: value
# Equivalent to {"complex key": "value"}

Explicit Type Conversion

Force specific types with tags:

# Force string type for numbers
version: !!str 2.0

# Force string type for boolean-like value
enabled: !!str yes

Merge Keys

Combine mappings efficiently:

defaults: &defaults
  timeout: 30
  retries: 3

development:
  <<: *defaults
  environment: development

production:
  <<: *defaults
  environment: production
  timeout: 60 # Override specific value

Nested Anchors

Anchors can be used with nested structures:

base: &base
  name: BaseConfig
  logging: &logging
    level: info
    format: json

dev:
  <<: *base
  environment: development
  logging:
    <<: *logging
    level: debug # Override specific nested value

Here are some useful tools for working with YAML:

Editors and IDE Support

  • Visual Studio Code: YAML extension with schema validation
  • JetBrains IDEs: Built-in YAML support
  • Vim: YAML syntax highlighting plugins
  • Online YAML editors: Tools like https://www.yamllint.com/

Validation Tools

  • yamllint: Command-line YAML syntax checker
  • yq: Command-line YAML processor (like jq for JSON)
  • kube-score: Kubernetes YAML validator
  • SchemaStore: JSON Schema definitions for YAML validation

Libraries in Different Languages

  • Python: PyYAML, ruamel.yaml
  • JavaScript: js-yaml
  • Ruby: psych
  • Java: SnakeYAML
  • Go: gopkg.in/yaml.v3
  • C#: YamlDotNet

Demo Project for YAML in Python

Let’s build a simple Python project that demonstrates working with YAML files:

Project Structure

yaml-demo/
├── config/
│   ├── default.yaml
│   ├── development.yaml
│   └── production.yaml
├── data/
│   ├── users.yaml
│   └── products.yaml
├── sample_data.yaml
├── config_manager.py
├── yaml_parser.py
└── main.py

Example Files

default.yaml

app:
  name: YAML Demo App
  version: 1.0.0

database:
  host: localhost
  port: 5432

logging:
  level: info
  file: app.log

development.yaml

environment: development

database:
  name: dev_db
  user: dev_user

logging:
  level: debug

production.yaml

environment: production

database:
  name: prod_db
  user: prod_user

logging:
  level: warning

users.yaml

---
- id: 1
  username: john_doe
  email: [email protected]
  roles:
    - user
    - admin
---
- id: 2
  username: jane_smith
  email: [email protected]
  roles:
    - user

Python Implementation

yaml_parser.py

import yaml
from pathlib import Path
from typing import Dict, List, Any, Optional, Union


class YamlParser:
    @staticmethod
    def read_yaml(file_path: Union[str, Path]) -> Optional[Dict]:
        """
        Safely read a YAML file and return its contents as a Python dictionary.

        Args:
            file_path: Path to the YAML file

        Returns:
            Dictionary containing the YAML data, or None if an error occurs
        """
        try:
            with open(file_path, 'r') as file:
                return yaml.safe_load(file)
        except yaml.YAMLError as e:
            print(f"Error parsing YAML file {file_path}: {e}")
            return None
        except FileNotFoundError:
            print(f"File not found: {file_path}")
            return None
        except Exception as e:
            print(f"Unexpected error reading {file_path}: {e}")
            return None

    @staticmethod
    def read_all_yaml_documents(file_path: Union[str, Path]) -> List[Any]:
        """
        Read all YAML documents from a multi-document YAML file.

        Args:
            file_path: Path to the YAML file

        Returns:
            List of Python objects, one for each YAML document
        """
        try:
            with open(file_path, 'r') as file:
                return list(yaml.safe_load_all(file))
        except Exception as e:
            print(f"Error reading multi-doc YAML {file_path}: {e}")
            return []

    @staticmethod
    def write_yaml(data: Any, file_path: Union[str, Path],
                   sort_keys: bool = False) -> bool:
        """
        Write Python data to a YAML file.

        Args:
            data: Python object to serialize to YAML
            file_path: Path where the YAML file should be written
            sort_keys: Whether to sort dictionary keys alphabetically

        Returns:
            True if successful, False otherwise
        """
        try:
            with open(file_path, 'w') as file:
                yaml.dump(data, file, default_flow_style=False,
                         sort_keys=sort_keys, indent=2)
            return True
        except Exception as e:
            print(f"Error writing YAML to {file_path}: {e}")
            return False

    @staticmethod
    def write_all_yaml_documents(documents: List[Any],
                                file_path: Union[str, Path]) -> bool:
        """
        Write multiple Python objects as a multi-document YAML file.

        Args:
            documents: List of Python objects to serialize
            file_path: Path where the YAML file should be written

        Returns:
            True if successful, False otherwise
        """
        try:
            with open(file_path, 'w') as file:
                yaml.dump_all(documents, file, default_flow_style=False,
                             explicit_start=True, indent=2)
            return True
        except Exception as e:
            print(f"Error writing multi-doc YAML to {file_path}: {e}")
            return False

config_manager.py

from pathlib import Path
from typing import Dict, Any, Optional
from yaml_parser import YamlParser


class ConfigManager:
    def __init__(self, config_dir: str = 'config'):
        """
        Initialize the config manager.

        Args:
            config_dir: Directory containing configuration YAML files
        """
        self.config_dir = Path(config_dir)
        self.config = {}
        self.load_default_config()

    def load_default_config(self) -> None:
        """Load the default configuration."""
        default_config = YamlParser.read_yaml(self.config_dir / 'default.yaml')
        if default_config:
            self.config = default_config
        else:
            print("Warning: Failed to load default configuration")

    def load_environment_config(self, environment: str) -> bool:
        """
        Load environment-specific configuration and merge with default.

        Args:
            environment: Environment name (e.g., 'development', 'production')

        Returns:
            True if successful, False otherwise
        """
        env_config_path = self.config_dir / f'{environment}.yaml'
        env_config = YamlParser.read_yaml(env_config_path)

        if not env_config:
            print(f"Error: Could not load config for environment: {environment}")
            return False

        # Merge configuration (simple recursive merge)
        self._recursive_merge(self.config, env_config)
        return True

    def _recursive_merge(self, base: Dict, override: Dict) -> None:
        """
        Recursively merge override dict into base dict.

        Args:
            base: Base dictionary to merge into
            override: Dictionary with values to override
        """
        for key, value in override.items():
            if (key in base and isinstance(base[key], dict) and
                isinstance(value, dict)):
                self._recursive_merge(base[key], value)
            else:
                base[key] = value

    def get_config(self) -> Dict[str, Any]:
        """
        Get the current configuration.

        Returns:
            The full configuration dictionary
        """
        return self.config

    def get_value(self, key_path: str, default: Any = None) -> Any:
        """
        Get a configuration value using dot notation.

        Args:
            key_path: Path to the config value (e.g., 'database.host')
            default: Default value to return if key doesn't exist

        Returns:
            Configuration value or default
        """
        keys = key_path.split('.')
        result = self.config

        for key in keys:
            if isinstance(result, dict) and key in result:
                result = result[key]
            else:
                return default

        return result

main.py

from yaml_parser import YamlParser
from config_manager import ConfigManager
from pathlib import Path


def demo_yaml_parser():
    """Demonstrate basic YAML parsing functionality."""
    print("\n=== YAML Parser Demo ===")

    # Read a simple YAML file
    sample_data = YamlParser.read_yaml('sample_data.yaml')
    print("\nSample data:")
    print(sample_data)

    # Read multi-document YAML
    users = YamlParser.read_all_yaml_documents('data/users.yaml')
    print("\nUsers from multi-document YAML:")
    for i, user in enumerate(users):
        print(f"User {i+1}: {user}")

    # Create and write a new YAML file
    new_data = {
        'services': {
            'web': {
                'image': 'nginx',
                'ports': [80, 443]
            },
            'database': {
                'image': 'postgres',
                'environment': {
                    'POSTGRES_USER': 'user',
                    'POSTGRES_PASSWORD': 'password'
                }
            }
        },
        'volumes': ['data', 'logs']
    }

    success = YamlParser.write_yaml(new_data, 'output.yaml')
    if success:
        print("\nSuccessfully wrote output.yaml")

    # Create multi-document YAML
    documents = [
        {'name': 'Document 1', 'type': 'test'},
        {'name': 'Document 2', 'type': 'example'}
    ]

    success = YamlParser.write_all_yaml_documents(documents, 'multi_output.yaml')
    if success:
        print("Successfully wrote multi_output.yaml")


def demo_config_manager():
    """Demonstrate configuration management with YAML."""
    print("\n=== Config Manager Demo ===")

    # Initialize with default config
    config_manager = ConfigManager()
    print("\nDefault configuration:")
    print(config_manager.get_config())

    # Load environment-specific config
    if config_manager.load_environment_config('development'):
        print("\nDevelopment configuration:")
        print(config_manager.get_config())

    # Get specific configuration values
    db_host = config_manager.get_value('database.host')
    db_name = config_manager.get_value('database.name')
    log_level = config_manager.get_value('logging.level')

    print(f"\nDatabase connection: {db_host}/{db_name}")
    print(f"Logging level: {log_level}")

    # Get a value with a default
    timeout = config_manager.get_value('server.timeout', 30)
    print(f"Server timeout: {timeout}")


if __name__ == "__main__":
    # Ensure our data directories exist
    Path("data").mkdir(exist_ok=True)
    Path("config").mkdir(exist_ok=True)

    # Create sample data if it doesn't exist
    if not Path("sample_data.yaml").exists():
        sample = {
            'name': 'YAML Sample',
            'items': ['item1', 'item2', 'item3'],
            'metadata': {
                'created': '2023-01-01',
                'version': 1.0,
                'active': True
            }
        }
        YamlParser.write_yaml(sample, 'sample_data.yaml')

    # Run demos
    demo_yaml_parser()
    demo_config_manager()

    print("\nDemo completed successfully!")

Conclusion

YAML’s combination of readability, expressiveness, and flexibility makes it an excellent choice for configuration files, data serialization, and many other use cases. By understanding YAML’s syntax, features, and best practices, you can leverage its full potential in your projects.

As with any technology, YAML has its strengths and weaknesses. It excels at human-readable configuration but can become unwieldy for very complex data structures. By following the best practices outlined in this guide and using appropriate tools, you can avoid common pitfalls and create maintainable YAML files.

Whether you’re configuring a CI/CD pipeline, defining Kubernetes resources, or storing application settings, YAML offers a clean, standardized approach that works across programming languages and platforms.

Additional Resources

To deepen your understanding of YAML, check out these resources:

Official Documentation

Tools

Books and Tutorials

Table of Contents