Filtering the parser

YANG models can grow very large and sometimes we may be interested in only a part of the tree. For those cases Yangify supports filtering the model when parsing.

As usual, let’s see by example. We are going to be using the same parser and data we used in our previous tutorial. Let’s tart by loading the parser, data model and the configuration.

[1]:
import json

import tutorial_parser

with open("data/ios/config.txt", "r") as f:
    config = f.read()

from yangson.datamodel import DataModel
dm = DataModel.from_file("yang/yang-library-data.json", ["yang/yang-modules/ietf", "yang/yang-modules/openconfig"])

Now we are going to create our root parser:

[2]:
from yangify import parser
from yangify.parser.text_tree import parse_indented_config

class IOSParser(parser.RootParser):
    class Yangify(parser.ParserData):
        def init(self) -> None:
            self.root_native = parse_indented_config(self.native.splitlines())
            self.native = self.root_native

    interfaces = tutorial_parser.Interfaces
    vlans = tutorial_parser.Vlans

Now let’s see what we get by default:

[3]:
p = IOSParser(dm, native=config)
result = p.process()
print(json.dumps(result.raw_value(), indent=4))
{
    "openconfig-interfaces:interfaces": {
        "interface": [
            {
                "name": "FastEthernet1",
                "config": {
                    "name": "FastEthernet1",
                    "type": "iana-if-type:ethernetCsmacd",
                    "description": "This is Fa1",
                    "enabled": false
                },
                "subinterfaces": {
                    "subinterface": [
                        {
                            "index": 1,
                            "config": {
                                "index": 1,
                                "description": "This is Fa1.1"
                            }
                        },
                        {
                            "index": 2,
                            "config": {
                                "index": 2,
                                "description": "This is Fa1.2"
                            }
                        }
                    ]
                }
            },
            {
                "name": "FastEthernet3",
                "config": {
                    "name": "FastEthernet3",
                    "type": "iana-if-type:ethernetCsmacd",
                    "description": "This is Fa3",
                    "enabled": true
                }
            },
            {
                "name": "FastEthernet4",
                "config": {
                    "name": "FastEthernet4",
                    "type": "iana-if-type:ethernetCsmacd",
                    "enabled": false
                }
            }
        ]
    },
    "openconfig-vlan:vlans": {
        "vlan": [
            {
                "vlan-id": 10,
                "config": {
                    "vlan-id": 10,
                    "name": "prod",
                    "status": "ACTIVE"
                }
            },
            {
                "vlan-id": 20,
                "config": {
                    "vlan-id": 20,
                    "name": "dev",
                    "status": "SUSPENDED"
                }
            }
        ]
    }
}

We got a lot of stuff! Let’s imagine we only want the vlans:

[4]:
p = IOSParser(
    dm,
    native=config,
    include=["/openconfig-vlan:vlans"]
)
result = p.process()
print(json.dumps(result.raw_value(), indent=4))
{
    "openconfig-vlan:vlans": {
        "vlan": [
            {
                "vlan-id": 10,
                "config": {
                    "vlan-id": 10,
                    "name": "prod",
                    "status": "ACTIVE"
                }
            },
            {
                "vlan-id": 20,
                "config": {
                    "vlan-id": 20,
                    "name": "dev",
                    "status": "SUSPENDED"
                }
            }
        ]
    }
}

By just passing include=["/openconfig-vlan:vlans"] we told the parser that we only wanted to include in our parsing that specific path.

The path can be a bit more specific. Imagine you only want your vlan names:

[5]:
p = IOSParser(
    dm,
    native=config,
    include=["/openconfig-vlan:vlans/vlan/config/name"]
)
result = p.process(validate=False)
print(json.dumps(result.raw_value(), indent=4))
{
    "openconfig-vlan:vlans": {
        "vlan": [
            {
                "vlan-id": 10,
                "config": {
                    "name": "prod"
                }
            },
            {
                "vlan-id": 20,
                "config": {
                    "name": "dev"
                }
            }
        ]
    }
}

Now we managed to filter even more the model. However, not that we passed to the process function a new argument, validate=False. Why? Turns out that /openconfig-vlan:vlans/vlan/vlan-id is a reference to /openconfig-vlan:vlans/vlan/config/vlan-id. Because the latter it’s not in our data we need to disable the automated validation of the instance.

You can have multiple paths as well:

[6]:
p = IOSParser(
    dm,
    native=config,
    include=[
        "/openconfig-vlan:vlans/vlan/config/name",
        "/openconfig-vlan:vlans/vlan/config/status",
    ]
)
result = p.process(validate=False)
print(json.dumps(result.raw_value(), indent=4))
{
    "openconfig-vlan:vlans": {
        "vlan": [
            {
                "vlan-id": 10,
                "config": {
                    "name": "prod",
                    "status": "ACTIVE"
                }
            },
            {
                "vlan-id": 20,
                "config": {
                    "name": "dev",
                    "status": "SUSPENDED"
                }
            }
        ]
    }
}

Now, let’s imagine you want to get all of your interfaces but you don’t want subinterfaces:

[7]:
p = IOSParser(
    dm,
    native=config,
    include=["/openconfig-interfaces:interfaces"],
    exclude=["/openconfig-interfaces:interfaces/interface/subinterfaces"]
)
result = p.process(validate=False)
print(json.dumps(result.raw_value(), indent=4))
{
    "openconfig-interfaces:interfaces": {
        "interface": [
            {
                "name": "FastEthernet1",
                "config": {
                    "name": "FastEthernet1",
                    "type": "iana-if-type:ethernetCsmacd",
                    "description": "This is Fa1",
                    "enabled": false
                }
            },
            {
                "name": "FastEthernet3",
                "config": {
                    "name": "FastEthernet3",
                    "type": "iana-if-type:ethernetCsmacd",
                    "description": "This is Fa3",
                    "enabled": true
                }
            },
            {
                "name": "FastEthernet4",
                "config": {
                    "name": "FastEthernet4",
                    "type": "iana-if-type:ethernetCsmacd",
                    "enabled": false
                }
            }
        ]
    }
}

We first used include to tell which paths we wanted and then exclude to remove more specific paths.