Adding Custom Genie Parsers

While we encourage everyone to contribute new parsers to the genie community (see documentation here), there will be cases where you also need to add parsers to your project “on the fly”, for example to make use of a new or fixed parser right away, without waiting for the next Genie release.

Genie provides a method to link in a custom parser module which will be searched for when looking for parsers. Parsers contained in this module will also be used first, which allows you to override shipping Genie parsers, which is often used to fix parser bugs.

Please follow the below guide when creating custom parsers.

1. Create a Skeleton Python Module

Follow the below directory structure when creating a python module. In the below example, we create a module local_parsers and add an iosxe parser:

# file tree
#
setup.py            # optional, but recommended for packaging
local_parsers/      # module directory
├── __init__.py
└── iosxe           # os-specific sub-directory
    └── __init__.py

The top-level __init__.py should be empty (it is required to declare a python module), and the iosxe/__init__.py must contain the following statements:

from genie import abstract
abstract.declare_token(os='iosxe')      # adjust to the enclosign directory/os name

With this skeleton you can now place parser files into the os-specific directories.

Note: We recommend adding a setup.py to the toplevel directory to make the module installable, and it will also allow you to use an entrypoint to point Genie to your parser module (see also further below).

A simple setup.py could look like this:

from setuptools import setup

setup(
    name="local_parsers",
    version="0.1",
    license="",
    packages=['local_parsers'],
    install_requires=[
        "genie",
    ],
    entry_points={
        "genie.libs.parser": [
            "local_parsers = local_parsers",
        ],
    },
)

2. Create a Parser

Please refer to https://pubhub.devnetcloud.com/media/pyats-development-guide/docs/writeparser/writeparser.html for instructions on how to write a genie parser. You can find plenty of examples in the Genie parser repo at https://github.com/CiscoTestAutomation/genieparser/tree/main/src/genie/libs/parser.

Place the parser into a file (the filename used has no significance) within the os-specific directory. You can place multiple parsers into a single python file, or use different ones to organize them according to your requirement.

Please find a simple example at the end of this document.

3. Point Genie to your Parser Module

There are two approaches to make Genie aware of your parser module and use it as part of your script or radkit-client session:

  • Use an Entrypoint in your Parser package

    If you have followed the above guide and created a setup.py for your parser module and installed the package using pip, you can use an entrypoint to point Genie to your parser module:

    # part of local_parsers's setup.py
    
    setup(
        ...
        entry_points = {
            'genie.libs.parser': [
                'local_parsers = local_parsers',
            ],
        },
    )
    
  • Use the PYATS_LIBS_EXTERNAL_PARSER environment variable

    You can also populate the environment variable PYATS_LIBS_EXTERNAL_PARSER with the name of your module i.e.

    export PYATS_LIBS_EXTERNAL_PARSER=local_parsers
    

    If you did not follow our recommendation of making the module installable, you might need to adjust your PYTHONPATH environment variable to point to the directory in which you created the skeleton directory structure.

Simple Parser Example

Please find here a simple parser parsing the fictitious command show foobar, returning the output

myrouter# show foobar
foo: some text
bar: other stuff
myrouter#

which would be parsed into such a dictionary structure:

{
    "foobar": {
        "foo": "some text",
        "bar": "other stuff"
    }
}

While the implementation of the actual parsing loop in the parser is up to you, we highly recommend following the common approach used within Genie parsers to maintain consistency, and to make it easier for you to contribute the parser back to the Genie community.

# IOSXE parsers for the following show commands:
#    * show foobar
#    * show foobar <WORD>

import re

from genie.metaparser import MetaParser
from genie.metaparser.util.schemaengine import Optional


class ShowFoobarSchema(MetaParser):
    """
    Schema for  show foobar
                show foobar <WORD>
    """

    schema = {
        Optional('foobar'): {
            'foo': str,
            'bar': str,
        },
    }


class ShowFoobar(ShowFoobarSchema):
    """
    Parser for  show foobar
                show foobar <WORD>
    """

    cli_command = ['show foobar', 'show foobar {arg}']

    def cli(self, arg='', cmd=None, output=None):
        if output is None:
            if not cmd:
                cmd = self.cli_command[0]
                if arg:
                    cmd = self.cli_command[1].format(arg=arg)
            out = self.device.execute(cmd)
        else:
            out = output

        # foo:  1.1.1.1
        p1 = re.compile(r'^foo:\s+(?P<foo>\S+)')
        # bar:  1.1.1.1
        p2 = re.compile(r'^bar:\s+(?P<bar>\S+)')
        # initial variables
        ret_dict = {}

        for line in out.splitlines():
            line = line.strip()

            m = p1.match(line)
            if m:
                ret_dict.setdefault('foobar', {})['foo'] = m.groupdict()['foo']
                continue

            m = p2.match(line)
            if m:
                ret_dict.setdefault('foobar', {})['bar'] = m.groupdict()['bar']
                continue

        return ret_dict