PyangBind is distributed through PyPI, it can be installed by simply running:
$ pip install pyangbind
pyangbind module installs both the Pyang plugin (
pyangbind.plugin.*), as well as a set of library modules (
pyangbind.lib.*) that are used to provide the Python representation of YANG types.
To generate your first set of classes, you will need a YANG module, and its dependencies. A number of simple modules can be found in the
tests directory (e.g.,
To generate a set of Python classes, Pyang needs to be provided a pointer to where PyangBind’s plugin is installed. This location can be found by running:
$ export PYBINDPLUGIN=`/usr/bin/env python -c \ 'import pyangbind; import os; print "%s/plugin" % os.path.dirname(pyangbind.__file__)'` $ echo $PYBINDPLUGIN
Once this path is known, it can be provided to the
--plugin-dir argument to Pyang. In the simplest form the command used is:
$ pyang --plugindir $PYBINDPLUGIN -f pybind -o binding.py tests/base-test.yang
$PYBINDPLUGINis the location that was exported from the above command.
binding.pyis the desired output file.
doc/base-test.yangis the path to the YANG module that bindings are to be generated for.
There are a number of other options for PyangBind, which are discussed further in the
PyangBind generates a (set of) Python modules. The top level module is named after the YANG module - with the name made Python safe. In general this appends underscores to reserved names, and replaces hyphens with underscores - such that
openconfig_local_routing as a module name.
Primarily, we need to generate a set of classes for the model(s) that we are interested in. The OpenConfig local routing module will be used as an example for this walkthrough.
The bindings can be simply generated by running the
doc/example/oc-local-routing/generate_bindings.sh script. This script simply uses
curl to retrieve the modules, and subsequently, builds the bindings to a
binding.py file as expressed above.
The simplest program using a PyangBind set of classes will look like:
At this point, the
oclr object can be used to manipulate the YANG data tree that is expressed by the module.
A subset of
openconfig-local-routing looks like the following tree:
module: openconfig-local-routing +--rw local-routes ... +--rw static-routes | +--rw static* [prefix] | +--rw prefix -> ../config/prefix | +--rw config | | +--rw prefix? inet:ip-prefix | | +--rw next-hop* union
To add an entry to the
static list the
add method of the
static object is used:
static list is addressed exactly as per the path that it has within the YANG module - such that it is a member of the
static-routes container (whose name has been made Python-safe), which itself is a member of the
add method returns a reference to the newly created list object - such that we can use the
rt object to change characteristics of the newly created list entry. For example, a tag can be set on the route:
The tag value can then be accessed directly via the
rt object, or via the original
oclr object (which both refer to the same object in memory):
In addition, PyangBind classes which represent
list objects have a special
get() method. This dumps a dictionary representation of the object for printing or debug purposes (see the sections on serialisation for outputting data instances for interaction with other devices). For example:
filter keyword allows only the elements within the class that have changed (are not empty or their default) to be output - rather than all possible elements.
next-hop element in this model is a
leaf-list this acts like a Python list, albeit one that validates the contents that are put into it. For example, the
append method can be used to add entries to the list, which can be iterated through using a standard Python loop:
Where (type or value) restrictions exist. PyangBind generated classes will result in a Python
ValueError being raised. For example, if we attempt to set the
set_tag leaf to an invalid value:
Clearly, populated PyangBind classes are not useful in and of themselves - the common use case is that they are sent to a external system (e.g., a router, or other NMS component). To achieve this the class hierarchy needs to be serialised into a format that can be sent to the remote entity. There are currently multiple ways to do this:
default) format used by PyangBind. Some network equipment vendors utilise this serialisation format.
Any PyangBind class can be serialised into any of the supported formats. Using the static route example above, the entire
local-routing module can be serialised into OC-JSON using the following code:
This outputs the following JSON structured text:
Note here that the
static list is represented as a JSON object (such that if this JSON is loaded elsewhere, a prefix can be referenced using
It is also possible to serialise a subset of the data, e.g., only one
container within the class hierarchy. This is done as follows (into IETF-JSON):
# Dump the static routes instance as JSON in IETF format print(pybindJSON.dumps(oclr.local_routes, mode="ietf"))
And the corresponding output:
Here, note that the list is represented as a JSON array, as per the IETF specification; and that only the
static-routes children of the object have been serialised.
PyangBind also supports taking data instances from a remote system (or locally saved documents) and loading them into either a new, or existing set of classes. This is useful for when a remote system sends a data instance in response to a query - and the programmer wishes to injest this response such that further logic can be performed based on it.
Instances can be deserialised from any of the supported serialisation formats (see above) into the classes.
To de-serialise into a new object, the
load method of the serialise module can be used:
1new_oclr = pybindJSON.load(os.path.join("json", "oc-lr.json"), binding, "openconfig_local_routing")
This creates a new instance of the
openconfig_local_routing class that is within the
binding module, and loads the data from
json/oc-lr.json into it. The
new_oclr object can then be manipulated as per any other class:
1# Manipulate the data loaded 2print("Current tag: %d" % new_oclr.local_routes.static_routes.static[u"192.0.2.1/32"].config.set_tag) 3# Outputs: 'Current tag: 42' 4 5new_oclr.local_routes.static_routes.static[u"192.0.2.1/32"].config.set_tag += 1 6print("New tag: %d" % new_oclr.local_routes.static_routes.static[u"192.0.2.1/32"].config.set_tag) 7# Outputs: 'Current tag: 43'
Equally, a JSON instance can be loaded into an existing set of classes - this is done by directly calling the relevant deserialisation class – in this case
load_ietf_json method is handed a JSON object - and no longer requires the arguments for the module and class name (hence they are both set to
None), rather the optional
obj= argument is used to specify the object that corresponds to the JSON that is being loaded.
Following this load, the classes can be iterated through - showing both the original loaded route (
192.0.2.1/32) and the one in the IETF JSON encoded file (
192.0.2.2/32) exist in the data instance:
1# Iterate through the classes - both the 192.0.2.1/32 prefix and 192.0.2.2/32 2# prefix are now in the objects 3for prefix, route in new_oclr.local_routes.static_routes.static.iteritems(): 4 print("Prefix: %s, tag: %d" % (prefix, route.config.set_tag)) 5 6# Output: 7# Prefix: 192.0.2.2/32, tag: 256 8# Prefix: 192.0.2.1/32, tag: 43
This worked example can be found in the
docs/example/oc-local-routing directory on GitHub
Copyright 2015, Rob Shakir (firstname.lastname@example.org) This project has been supported by: * Jive Communications, Inc. * BT plc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.