Opinionated Python bindings for the libyang library

python, python-3, yang
pip install oopt-gnpy-libyang==0.0.3


Opinionated Python bindings for the libyang library

Install via pip Python versions GitHub Workflow Status

Python bindings and packaging of libyang. We're focusing on parsing, validating and accessing YANG-modeled JSON data trees. Essentially, just enough to get gnpy going. Want more? Patches welcome.

Compared to the CFFI libyang bindings, this wrapper takes care of low-level memory management. This means no more node.free() and ctx.destroy(). We also produce prebuilt binary wheels to make installation very simple.


Loading YANG data

import oopt_gnpy_libyang as ly

c = ly.Context('tests/yang', ly.ContextOptions.AllImplemented | ly.ContextOptions.NoYangLibrary)
for m in ('iana-if-type', 'ietf-interfaces', 'ietf-ip'):
blob = '''{
  "ietf-interfaces:interfaces": {
    "interface": [
        "name": "lo",
        "type": "iana-if-type:softwareLoopback",
        "ietf-ip:ipv4": {
          "address": [
              "ip": "",
              "prefix-length": 8
        "ietf-ip:ipv6": {
          "address": [
              "ip": "::1",
              "prefix-length": 128
        "name": "eth0",
        "type": "iana-if-type:ethernetCsmacd"

data = c.parse_data_str(blob,
    ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered,
    ly.ValidationOptions.Present | ly.ValidationOptions.NoState)

Working with data

Libyang works with forests (sets of trees), this is how to process all the data:

for x in data.siblings():
    print(f'a sibling: {x.path}')
    for xx in x.childrenDfs():
        print(f' {"term " if xx.is_term else "child"}: {xx.path}')
        if xx.is_term:
            print(f'  {xx.as_term()} {" (default)" if xx.as_term().is_default_value else ""}')

Data can be accessed via their known paths, of course. Either as a full, multi-level XPath:

data["interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length"].as_term().value == 128

Or individually, one item per index:


Everything is an XPath, so it's possible to take a shortcut and skip specifying keys for single-element lists:

data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value == 128

The data are provided as native Python types:

    .as_term().value) == int

Validation errors

In libyang, if an operation fails, error details are available via context.errors():

import json
wrong = json.loads(blob)
    ["ietf-ip:ipv6"]["address"][0]["prefix-length"] = 666
    data = c.parse_data_str(json.dumps(wrong),
        ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered,
        ly.ValidationOptions.Present | ly.ValidationOptions.NoState)
    assert False
except ly.Error:
    for error in c.errors():
        assert error.path == "Schema location \"/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address/prefix-length\", data location \"/ietf-ip:address[ip='::1']\", line number 1."
        assert error.message == 'Value "666" is out of type uint8 min/max bounds.'


We're producing wheels for many popular platforms. The installation is as simple as:

$ pip install oopt-gnpy-libyang

Building from source

Since this library is a Python wrapper around a C++ wrapper around a C library, source-based builds are more complex. They require:

  • a C++20 compiler (e.g., GCC 10+, clang 10+, MSVC 17.2+)
  • libyang and its dependencies
  • libyang-cpp and its dependencies
  • CMake 3.21+

Unlike the wheels already bundle all the required libraries, when building from source, libyang, libyang-cpp and all their dependencies will have to be installed first. Also, in a from-source build these won't be bundled into the resulting package. For an inspiration, consult our GitHub packaging recipes.


Copyright © 2021-2022 Telecom Infra Project and GNPy contributors. Licensed under the 3-clause BSD license.