"
This article is part of in the series

python codes on laptop screen - Python YAML

Python's extensive standard library includes modules that meet most of an average developer's coding needs. Not to mention, there are hundreds of external modules that make a developer's life easy. 

However, Python still has one drawback. It does not support the YAML data format, known for its easy configuration and serialization features, despite its similarities to Python. 

In this comprehensive guide, we will walk you through working with YAML in Python with third-party libraries, specifically PyYAML.

Understanding YAML's Syntax

As mentioned earlier, YAML shares similarities with Python, and when you look at YAML, you will realize that its block indentation is the same as that of Python.

Interestingly, several other languages and data formats heavily inspire YAML. It doesn't involve any special characters or tags and relies on the leading whitespace in each line to define the block's scope. 

family_tree:

  father:
    son:
      name: James
    daughter:
      name: Emily

 

In this document, we have a family tree with "family_tree" as the root element. 

The immediate child of the root element is the "father," who has two children: a son named James and a daughter named Emily. Each individual's name is defined using the "name" attribute at the lowest level in the tree.

It's interesting to note that YAML doesn't allow using tabs for indentation. If you accidentally put a tab space in your YAML, it will throw a syntax error. 

This is in line with PEP-8, a 2001 document for Python aiming to improve the readability and consistency of the code.

If you prefer, you can use the inline-block syntax that YAML takes from JSON. Here's how you could write the same YAML as above in this syntax:

family_tree:

  father:
    son: {name: James}
    daughter: {'name': "Emily"}

 

As you can see, YAML doesn't have a problem with you mixing the inline and indented blocks in the same document. 

You might have also noticed that an attribute and its value are enclosed in single and double quotes. Doing this is not necessary, but choosing to do it enables an interpolation method of YAML's special character sequences. 

You can escape these sequences with a backslash (\), similar to how it's done in Python. 

The only time you must use quotes in YAML is when declaring a string that could be interpreted as a data type. The best example of this is True – it will be treated as a Python Boolean unless it is enclosed in quotes.

Data Structures in YAML

Like most computer languages, YAML has data structures. But there are only three of them, and they are inspired by Perl, which used to be a popular scripting language. The structures are scalars, arrays, and hashes.

Scalars are numeral, string, or Boolean values. In YAML, arrays are sequences of scalars. Finally, hashes are associative arrays comprised of key-value pairs, which is why they're sometimes called maps or dictionaries. 

Defining a scalar in YAML is similar to defining a literal in Python. Here's how you can define different data types in YAML:

Data Type YAML
Null null, ~
Boolean true, false (Before YAML 1.2: yes, no, on, off)
Integer 0x10, 0b10, 10, 0o10 (Before YAML 1.2: 010)
Floating-Point 12.5e-9, .nan, .inf, 5.21
String This is a string
Date and Time 2023-11-01, 14:59; 2023-11-01, 14:59:59

 

YAML allows you to write its reserved words in uppercase, lowercase, and title case. It can parse it into the relevant data type regardless of the casing of the words. But bear in mind that YAML will treat any other case variant of the words as plain text. 

This means writing null, NULL, and Null will mean the same thing. However, if letters are capitalized randomly (like in nUll), YAML will treat it as text. It's worth mentioning that leaving a value blank has the same effect as writing null or mentioning its alternative, the ~ symbol.

While it's great that YAML offers implicit typing, its developers realized how this can cause serious issues in edge cases. This is why built-in literals like yes and no were removed from YAML in version 1.2. 

Now that you're familiar with scalars, it's time to learn about sequences. YAML works with sequences just like they're JSON arrays or Python lists. To define a sequence, you must enclose its values in square brackets and separate them by commas. 

Interestingly, you can also define sequences in the inline-block mode. It involves adding a dash at the beginning of every item. Here's what all this looks like:

desserts: [cake, ice cream, pie]

snacks:

  - popcorn

  - pretzels

  - chips

 

When working with sequences in YAML, don't hesitate to add indentation if it makes it easier for you to read. The extra whitespace won't cause any parsing problems.

The third type of data structure in YAML is the hash. Hashes are like objects in JavaScript or dicts in Python. As you'd expect, they comprise keys, which are also called property names or attributes. 

The keys are followed by a colon (:) and then the corresponding values. You've seen an instance of YAML hashes in this post already, but here's one that's more fleshed out:

employee:

  name: Alice

  jobTitle: Manager

  department: Sales

  salary: 60000

  contact:

    email: [email protected]

    phone: +1-123-456-7890

  projects:

    - name: Project A

      status: In Progress

    - name: Project B

      status: Completed

 

In this code, all the important details of an employee have been defined. This employee is named Alice and holds the position of a Manager in the Sales department. Her annual salary is set at $60,000. 

Additionally, her contact information is specified, which includes an email address ([email protected]) and a phone number (+1-123-456-7890).

Furthermore, you can see that this employee is associated with a list of projects they are involved in. There are two projects listed. 

The first project, named "Project A," is currently marked as "In Progress," while the second project, named "Project B," is marked as "Completed."

Did you notice how some attribute names have whitespace characters? YAML is flexible and allows you to do this. The whitespace characters can even span multiple lines!

It's more impressive that YAML doesn't restrict you to using strings only. You can create a hash that holds any data type as a key, which is something you cannot do with JSON.

So far, we've only covered the basics of YAML. But it has a lot more features packed into it. We'll explore some of them in the next section.

YAML's Advanced Features

YAML offers several advanced features, including tags, merged attributes, anchors, aliases, multi-doc streams, and flow and block styles. You'll learn how to use some of these in this section.

Type Systems in YAML

One of the features that makes YAML stand out is how it integrates with the type systems of modern programming languages. YAML's alternatives, XML and JSON, cannot work with data types at the level YAML does.

For example, YAML can serialize data types that are built into Python, including date and time:

YAML Python
2023-11-01 08:30:45 datetime.datetime(2023, 11, 1, 8, 30, 45)
2023-11-01  datetime.date(2023, 11, 1)
08:30:45  30645
30:45  1845

 

As you can see, YAML is familiar with date and time formats. It can also work with different time zones. 

The above table showcases how the dates and timestamps are deserialized for use by Python. For instance, the 08:30:45 time value gets deserialized into the number of seconds that have passed since midnight.

Using YAML seems like a convenient way to convert timestamps into the number of seconds. However, this guide to using YAML with Python using PyYAML is based on YAML 1.1, which is an older version.

Parsing such literals in YAML 1.1 can result in errors and unexpected outcomes. This is because YAML 1.1 parses a number with a leading zero as a string rather than a datetime object.

If you want to rule out the ambiguity for YAML, you will need to cast values to specific data types. You can do this with YAML tags, which start with two exclamation points. 

Though you can alternatively use language-independent tags, your parser may or may not support them. It's better to write !! followed by the data type you want to store, like so:

employee:

  name: Alice
  jobTitle: Manager
  department: Sales
  salary: 60000
  contact:
    email: [email protected]
    phone: +1-123-456-7890
  projects:
    - name: Project A
      status: In Progress
    - name: Project B
      status: Completed

As you can see, the !!str tag makes YAML treat the values as regular strings. The question marks in the middle represent a mapping key. 

Adding question marks in code isn't necessary, but it helps define a compound key from a different collection of data or a key using reserved words.

What's interesting is that you can add images and other resources using the !!binary tag. The tag embeds Base64-encoded binary files and turns them into instances of bytes for Python to process.

The YAML example above translates to the Python dictionary below:

{
    'vehicle': 'Car',
    'make': 'Toyota',
    'model': 'Camry',
    'year': 2023,
    'color': 'Blue',
    'coordinates': {
        'latitude': 37.7749,
        'longitude': -122.4194
    },
    'features': {'Air Conditioning', 'GPS Navigation', 'Bluetooth Connectivity'},
    'owner': {
        'name': 'Sarah Smith',
        'age': 35,
        'city': 'New York'
    },

    'rating': 4.7,
    'is_available': True
}

 

Can you see how the parser has turned the attribute into specific data types?

Anchors and Aliases

This interesting feature allows you to define an element once so you can refer to it several times in the document. This feature can come in handy in many situations, for instance, when reusing one address to create an invoice. 

You can do this by declaring an anchor with the & symbol. You can dereference it using the * symbols later in the program. Let's see anchors in action:

fruits:
  - &apple
    name: Apple
    color: Red
  - &banana
    name: Banana
    color: Yellow

fruit_basket:
  - *apple
  - *banana
  - &orange
    name: Orange
    color: Orange

selected_fruit: *banana

 

In this example, we define a list of fruits under the "fruits" key. Two fruits, Apple and Banana, are given as items in the list. We use anchor labels (&apple and &banana) to mark these items, allowing us to reuse their properties later.

Next, we create a "fruit_basket" list. We use the * notation to refer to the previously defined fruits. The first two items in the "fruit_basket" are aliases for the Apple and Banana fruits, while the third item, an Orange, is defined along with its properties.

Finally, we create "selected_fruit" and set it to the alias *banana. This means that "selected_fruit" references the properties of the Banana fruit.

YAML also supplies the flexibility of inheriting and overriding attributes with the merge (<<) feature. Here's how this works:

base_properties: &base
  shape: Circle
  color: Red

custom_properties: &custom
  size: Large

merged_object:
  << : *base
  << : *custom
  color: Blue
  weight: 2 kg

 

Here, we have two sets of properties defined as anchors, namely base_properties and custom_properties.

The merged_object is created by merging the attributes from both base_properties and custom_properties. This results in the shape and color properties being inherited from base_properties and the size property from custom_properties.

In the merged_object, we override the color property by setting it to "Blue" and introduce a new property, weight, with a value of "2 kg."

If you're interested in folding lines but want to maintain the indentation determined by the paragraph's first line, you can use the > indicator like this:

folded_text: >
  This is an example of folding lines with
  the greater than sign (>) indicator in YAML.
  It preserves the first-line indentation.
  It allows you to represent multi-line text
  while keeping the initial indentation determined
  by the first line of the paragraph.

 

This is a convenient way to represent multi-line text with consistent formatting in YAML. Its output in Python is:

{'folded_text': 'This is an example of folding lines with the greater than sign (>) indicator in YAML. It preserves the first-line indentation.\nIt allows you to represent multi-line text while keeping the initial indentation determined by the first line in the paragraph.\n'}

 

Flow and Block Styles

The two styles of scalars in YAML provide you with different levels of control over multi-line strings and newline handling. 

A flow scalar typically starts on the same line as its attributes and can span several lines, like so:

details: This is an example of flow style
  that spans multiple lines.
  It provides a concise way to represent
  complex data structures in YAML.

 

The whitespace in the beginning and end of these scalars are always folded into a single space. This turns paragraphs into lines, similar to how HTML works. 

On the other hand, block scalars can alter how the indentation, newlines, and trailing newlines work. For instance, putting a "|" after the name of an attribute will preserve the new line. This comes in handy when embedding shell scripts:

code_block: |
  def calculate_sum(a, b):
      """
      This function calculates the sum of two numbers.
      """
      return a + b
  if __name__ == "__main__":
      result = calculate_sum(5, 7)
      print("The result is:", result)

 

This YAML document defines a property called "code_block." The document holds a Python script, but without the pipe indicator, the YAML parser would have treated the script as nested elements.

It's worth noting that you can store several YAML documents in one file by separating the files with a triple dash (---). Alternatively, you can separate the files with three dots (…).

Setting Up PyYAML On Your Machine

As mentioned earlier, YAML doesn't work with Python straight out of the box. As Python doesn't officially support the format, you must take some extra steps to get YAML to work with Python. 

Specifically, you need to set up a third-party library that can deserialize YAML into Python objects and serialize the objects into YAML.

Additionally, installing the following command-line tools using pip can help you debug your code:

  • shyaml: A command-line YAML processor.
  • yq: Another YAML processor that works on CLI. Based on jq, it is suitable for filtering data.
  • yamllint: A linter for YAML. It checks for syntactical issues and more.

Instead of these Python-only tools, you could choose to install yq, a popular Go implementation, to work with YAML and Python. Bear in mind that this tool has a slightly different command-line interface. 

If you're not interested in downloading anything on your machine, feel free to use one of the popular online tools like YAML Parser. There are several similar tools available online, and most are free to use!

Serializing YAML as JSON

Every Python programmer reads about how flexible the language is, but the proof is in the pudding. Though Python doesn't support YAML directly, you can get it working using the json module that is built into Python. 

If you're unfamiliar with JSON, it is actually a subset of YAML. This means you can dump your Python data in the JSON format, and YAML parsers will accept it as an input. 

Let's see this in action by creating a simple Python script that prints out a JSON dictionary:

# print_data.py
import json
data = {
    "name": "Alice",
    "age": 30,
    "city": "New York",
    "interests": ["Reading", "Traveling"],
    "is_student": True,
}

print(json.dumps(data, indent=4))

 

At the end of the code, we're calling json.dumps() to dump a string. 

Now, run the script and feed its output to a command-line YAML parser (e.g., yq or shyaml) through a Unix pipeline (|):

$ python print_data.py | yq -y .
name: Alice
age: 30
city: New York
interests:
  - Reading
  - Traveling
is_student: true

$ python print_data.py | shyaml get-value

name: Alice
age: 30
city: New York
interests:
  - Reading
  - Traveling
is_student: true

 

As you can see, both the parsers accepted the data and formatted it in YAML – without any hiccups. 

But notice how we've requested that the yq parser do the transcoding with the -y option since yq is only a thin wrapper built on top of JSON's jq. This also means you will need to install jq on your machine before you can install yq. 

Of course, we've also put a trailing dot after the -y flag as a filtering expression. You may also notice a small difference between yq and shyaml in the resulting indentation. 

Aren't these parsers great? It would feel like cheating to use these and make working with YAML so easy, but the problem with these parsers is they only work one way. 

You cannot use them to read a YAML document back to Python. 

The good news? There is another way to do it.

Installing the PyYAML Library

As far as YAML libraries go, the PyYAML library has remained the go-to choice for Python programmers ever since its release. To this day, it's one of the top packages downloaded on PyPI. 

Its interface shares similarities to the built-in JSON module and is recommended on the official YAML website, fortifying its reputation. As you'd expect, it's actively maintained, and installing it on your virtual environment is as simple as running:

(venv) $ python -m pip install pyyaml

One of the best things about this library is that it doesn't require dependencies since it was written in Python. 

However, it's commonplace for distribution bundles to come with a compiled C binding for the LibYAML library. The binding makes PyYAML a lot faster and a must-have for any serious programmer.

You can double-check that your installation comes with a C binding by launching Python's interpreter and running the following:

>>> import yaml
>>> yaml.__with_libyaml__

 

If it returns "true," your installation of PyYAML is sure to run as fast as possible – with one extra step. You will need to instruct the library to take advantage of the shared C library by running the following code:

>>> try:
...     from yaml import CSafeLoader as SafeLoader
... except ImportError:
...     from yaml import SafeLoader
>>> SafeLoader
<class 'yaml.cyaml.CSafeLoader'>

 

Without explicit instruction, your PyYAML installation will run on pure Python. As you can see, we import one of the loader classes with the prefix "C," which denotes using the C library. 

If this fails, you must import a corresponding class that has been implemented in Python. While it will work, it'll make the code look verbose. Plus, you won't be able to use the shortcut functions you can otherwise use in Python. 

Conclusion

Though PyYAML is popular, it comes with some unique drawbacks. The main drawback is that it's not optimized for using the features introduced in YAML 1.2. 

If you want to use features such as safe literals and JSON compliance, it's better for you to use the ruamel.yaml library, which is a fork of an older version of PyYAML. The library can also pull off round-trip parsing, allowing you to preserve the original formatting of the YAML document and also the comments. 

In contrast, if you're worried about type safety or need to validate your YAML documents against schemas, the StrictYAML library is right for you. It disables the high-risk features of YAML to give you a smooth experience. However, it works slower than other libraries. 

Now that you've understood the basics of YAML and how to make it work with Python, you're ready to start working with Python libraries like PyYAML that support it.Â