Python API Overview
Learn about the NSO Python API and its usage.
The NSO Python library contains a variety of APIs for different purposes. In this section, we introduce these and explain their usage. The NSO Python module deliverables are found in two variants, the low-level APIs and the high-level APIs.
The low-level APIs are a direct mapping of the NSO C APIs, CDB, and MAAPI. These will follow the evolution of the C APIs. See man confd_lib_lib
for further information.
The high-level APIs are an abstraction layer on top of the low-level APIs to make them easier to use and to improve code readability and development rate for common use cases. E.g. services and action callbacks and common scripting towards NSO.
Python API Overview
MAAPI (Management Agent API) Northbound interface that is transactional and user session-based. Using this interface, both configuration and operational data can be read. Configuration and operational data can be written and committed as one transaction. The API is complete in the way that it is possible to write a new northbound agent using only this interface. It is also possible to attach to ongoing transactions to read uncommitted changes and/or modify data in these transactions. | |
Python low-level CDB API The Southbound interface provides access to the CDB configuration database. Using this interface, configuration data can be read. In addition, operational data that is stored in CDB can be read and written. This interface has a subscription mechanism to subscribe to changes. A subscription is specified on a path that points to an element in a YANG model or an instance in the instance tree. Any change under this point will trigger the subscription. CDB also has functions to iterate through the configuration changes when a subscription has been triggered. | |
Python low-level DP API Southbound interface that enables callbacks, hooks, and transforms. This API makes it possible to provide the service callbacks that handle service-to-device mapping logic. Other usual cases are external data providers for operational data or action callback implementations. There are also transaction and validation callbacks, etc. Hooks are callbacks that are fired when certain data is written and the hook is expected to do additional modifications of data. Transforms are callbacks that are used when complete mediation between two different models is necessary. | |
Python high-level API: API that resides on top of the MAAPI, CDB, and DP APIs. It provides schema model navigation and instance data handling (read/write). Uses a MAAPI context as data access and incorporates its functionality. It is used in service implementations, action handlers, and Python scripting. |
Python scripting
Scripting in Python is a very easy and powerful way of accessing NSO. This document has several examples of scripts showing various ways of accessing data and requesting actions in NSO.
The examples are directly executable with the Python interpreter after sourcing the ncsrc
file in the NSO installation directory. This sets up the PYTHONPATH
environment variable, which enables access to the NSO Python modules.
Edit a file and execute it directly on the command line like this:
High-level MAAPI API
The Python high-level MAAPI API provides an easy-to-use interface for accessing NSO. Its main targets are to encapsulate the sockets, transaction handles, data type conversions, and the possibility of using the Python with
statement for proper resource cleanup.
The simplest way to access NSO is to use the single_transaction
helper. It creates a MAAPI context and a transaction in one step.
This example shows its usage, connecting as user admin
and python
in the AAA context:
The example code here shows how to start a transaction but does not properly handle the case of concurrency conflicts when writing data. See Handling Conflicts for details.
A common use case is to create a MAAPI context and reuse it for several transactions. This reduces the latency and increases the transaction throughput, especially for backend applications. For scripting the lifetime is shorter and there is no need to keep the MAAPI contexts alive.
This example shows how to keep a MAAPI connection alive between transactions:
Maagic API
Maagic is a module provided as part of the NSO Python APIs. It reduces the complexity of programming towards NSO, is used on top of the MAAPI high-level API, and addresses areas that require more programming. First, it helps in navigating the model, using standard Python object dot notation, giving very clear and easily read code. The context handlers remove the need to close sockets, user sessions, and transactions and the problems when they are forgotten and kept open. Finally, it removes the need to know the data types of the leafs, helping you to focus on the data to be set.
When using Maagic, you still do the same procedure of starting a transaction.
To use the Maagic functionality, you get access to a Maagic object either pointing to the root of the CDB:
In this case, it is a ncs.maagic.Node
object with a ncs.maapi.Transaction
backend.
From here, you can navigate in the model. In the table, you can see examples of how to navigate.
The table below lists Maagic object navigation.
Action | Returns |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
You can also get a Maagic object from a keypath:
Namespaces
Maagic handles namespaces by a prefix to the names of the elements. This is optional but recommended to avoid future side effects.
The syntax is to prefix the names with the namespace name followed by two underscores, e.g., ns_name__ name
.
Examples of how to use namespaces:
In cases where there is a name collision, the namespace prefix is required to access an entity from a module, except for the module that was first loaded. A namespace is always required for root entities when there is a collision. The module load order is found in the NCS log file: logs/ncs.log
.
Reading Data
Reading data using Maagic is straightforward. You will just specify the leaf you are interested in and the data is retrieved. The data is returned in the nearest available Python data type.
For non-existing leafs, None
is returned.
Writing Data
Writing data using Maagic is straightforward. You will just specify the leaf you are interested in and assign a value. Any data type can sent as input, as the str
function is called, converting it to a string. The format depends on the data type. If the type validation fails, an Error
exception is thrown.
Deleting Data
Data is deleted the Python way of using the del
function:
Some entities have a delete method, this is explained under the corresponding type.
Object Deletion
The delete mechanism in Maagic is implemented using the __delattr__
method on the Node
class. This means that executing the del function on a local or global variable will only delete the object from the Python local or global namespaces. E.g., del obj
.
Containers
Containers are addressed using standard Python dot notation: root.container1.container2
.
Presence Containers
A presence container is created using the create
method:
Existence is checked with the exists
or bool
functions:
A presence container is deleted with the del
or delete
functions:
Choices
The case of a choice is checked by addressing the name of the choice in the model:
Changing a choice is done by setting a value in any of the other cases:
Lists and List Elements
List elements are created using the create method on the List
class:
The objects ce5
and o
above are of type ListElement
which is actually an ordinary container
object with a different name.
Existence is checked with the exists
or bool
functions List
class:
A list element is deleted with the Python del
function:
To delete the whole list, use the Python del
function or delete()
on the list.
Unions
Unions are not handled in any specific way - you just read or write to the leaf and the data is validated according to the model.
Enumeration
Enumerations are returned as an Enum
object, giving access to both the integer and string values.
Writing values to enumerations accepts both the string and integer values.
Leafref
Leafrefs are read as regular leafs and the returned data type corresponds to the referred leaf.
Leafrefs are set as the leaf they refer to. The data type is validated as it is set. The reference is validated when the transaction is committed.
Identityref
Identityrefs are read and written as string values. Writing an identityref without a prefix is possible, but doing so is error-prone and may stop working if another model is added which also has an identity with the same name. The recommendation is to always use a prefix when writing identityrefs. Reading an identityref will always return a prefixed string value.
Instance Identifier
Instance identifiers are read as xpath formatted string values.
Instance identifiers are set as xpath formatted strings. The string is validated as it is set. The reference is validated when the transaction is committed.
Leaf-list
A leaf-list is represented by a LeafList
object. This object behaves very much like a Python list. You may iterate it, check for the existence of a specific element using in
, or remove specific items using the del
operator. See examples below.
From NSO version 4.5 and onwards, a Yang leaf-list is represented differently than before. Reading a leaf-list using Maagic used to result in an ordinary Python list (or None if the leaf-list was non-existent). Now, reading a leaf-list will give back a LeafList
object whether it exists or not. The LeafList
object may be iterated like a Python list and you may check for existence using the exists()
method or the bool()
operator. A Maagic leaf-list node may be assigned using a Python list, just like before, and you may convert it to a Python list using the as_list()
method or by doing list(my_leaf_list_node)
.
Binary
Binary values are read and written as byte strings.
Bits
Reading a bits
leaf will give a Bits object back (or None if the bits
leaf is non-existent). To get some useful information out of the Bits object, you can either use the bytearray()
method to get a Python byte array object in return or the Python str()
operator to get a space-separated string containing the bit names.
There are four ways of setting a bits
leaf: One is to set it using a string with space-separated bit names, the other one is to set it using a byte array, the third by using a Python binary string, and as a last option is it may be set using a Bits object. Note that updating a Bits object does not change anything in the database - for that to happen, you need to assign it to the Maagic node.
Empty Leaf
An empty leaf is created using the create
method. If the type empty leaf is part of a union, the leaf must be set to the C_EMPTY
value instead.
If the type empty leaf is part of a union, then you read the leaf to see if empty
is the current value. Otherwise, existence is checked with the exists
or bool
functions:
An empty leaf is deleted with the del
or delete
functions:
Maagic Examples
Action Requests
Requesting an action may not require an ongoing transaction and this example shows how to use Maapi as a transactionless back-end for Maagic.
This example shows how to request an action that requires an ongoing transaction. It is also valid to request an action that does not require an ongoing transaction.
Providing parameters to an action with Maagic is very easy: You request an input object, with get_input
from the Maagic action object, and set the desired (or required) parameters as defined in the model specification.
If you have a leaf-list, you need to prepare the input parameters
A common use case is to script the creation of devices. With the Python APIs, this is easily done without the need to generate set commands and execute them in the CLI.
PlanComponent
This class is a helper to support service progress reporting using plan-data
as part of a Reactive FASTMAP nano service. More info about plan-data
is found in Nano Services for Staged Provisioning.
The interface of the PlanComponent
is identical to the corresponding Java class and supports the setup of plans and setting the transition states.
See pydoc3 ncs.application.PlanComponent
for further information about the Python class.
The pattern is to add an overall plan (self) for the service and separate plans for each component that builds the service.
When appending a new state to a plan the initial state is set to ncs:not-reached
. At the completion of a plan the state is set to ncs:ready
. In this case when the service is completely setup:
Python Packages
Action Handler
The Python high-level API provides an easy way to implement an action handler for your modeled actions. The easiest way to create a handler is to use the ncs-make-package
command. It creates some ready-to-use skeleton code.
The generated package skeleton:
This example action handler takes a number as input, doubles it, and returns the result.
When debugging Python packages refer to Debugging of Python Packages.
Test the action by doing a request from the NSO CLI:
The input and output parameters are the most commonly used parameters of the action callback method. They provide the access objects to the data provided to the action request and the returning result.
They are maagic.Node
objects, which provide easy access to the modeled parameters.
The table below lists the action handler callback parameters:
Parameter | Type | Description |
---|---|---|
|
| The action object. |
|
| User information of the requester. |
|
| The tailf:action name. |
|
| The keypath of the action. |
|
| An object containing the parameters of the input section of the action yang model. |
|
| The object where to put the output parameters as defined in the output section of the action yang model. |
Service Handler
The Python high-level API provides an easy way to implement a service handler for your modeled services. The easiest way to create a handler is to use the ncs-make-package
command. It creates some skeleton code.
The generated package skeleton:
This example has some code added for the service logic, including a service template.
When debugging Python packages, refer to Debugging of Python Packages.
Add some service logic to the cb_create
:
Add a template to packages/pyservice/templates/service.template.xml
:
The table below lists the service handler callback parameters:
Parameter | Type | Description |
---|---|---|
|
| The service object. |
|
| Transaction context. |
|
| An object pointing to the root with the current transaction context, using shared operations ( |
|
| An object pointing to the service with the current transaction context, using shared operations ( |
| list(tuple(str, str)) | The opaque object for the service configuration used to store hidden state information between invocations. It is updated by returning a modified list. |
Validation Point Handler
The Python high-level API provides an easy way to implement a validation point handler. The easiest way to create a handler is to use the ncs-make-package
command. It creates ready-to-use skeleton code.
The generated package skeleton:
This example validation point handler accepts all values except invalid
.
When debugging Python packages refer to Debugging of Python Packages.
Test the validation by setting the value to invalid and validating the transaction from the NSO CLI:
The table below lists the validation point handler callback parameters:
Parameter | Type | Description |
---|---|---|
|
| The validation point object. |
|
| Transaction context. |
|
| The keypath of the node being validated. |
|
| Current value of the node being validated. |
|
| The validation point that triggered the validation. |
Low-level APIs
The Python low-level APIs are a direct mapping of the C-APIs. A C call has a corresponding Python function entry. From a programmer's point of view, it wraps the C data structures into Python objects and handles the related memory management when requested by the Python garbage collector. Any errors are reported as error.Error
.
The low-level APIs will not be described in detail in this document, but you will find a few examples showing their usage in the coming sections.
See pydoc3 _ncs
and man confd_lib_lib
for further information.
Low-level MAAPI API
This API is a direct mapping of the NSO MAAPI C API. See pydoc3 _ncs.maapi
and man confd_lib_maapi
for further information.
Note that additional care must be taken when using this API in service code, as it also exposes functions that do not perform reference counting (see Reference Counting Overlapping Configuration).
In the service code, you should use the shared_*
set of functions, such as:
And, avoid the non-shared variants:
The following example is a script to read and de-crypt a password using the Python low-level MAAPI API.
This example is a script to do a check-sync
action request using the low-level MAAPI API.
Low-level CDB API
This API is a direct mapping of the NSO CDB C API. See pydoc3 _ncs.cdb
and man confd_lib_cdb
for further information.
Setting of operational data has historically been done using one of the CDB APIs (Python, Java, C). This example shows how to set a value and trigger subscribers for operational data using the Python low-level API. API.
Advanced Topics
Schema Loading - Internals
When schemas are loaded, either upon direct request or automatically by methods and classes in the maapi
module, they are statically cached inside the Python VM. This fact presents a problem if one wants to connect to several different NSO nodes with diverging schemas from the same Python VM.
Take for example the following program that connects to two different NSO nodes (with diverging schemas) and shows their ned-id's.
Running this program may produce output like this:
The output shows identities in string format for the active NEDs on the different nodes. Note that for lsa-2
, the last three lines do not show the name of the identity but instead the representation of a _ncs.Value
. The reason for this is that lsa-2
has different schemas which do not include these identities. Schemas for this Python VM were loaded and cached during the first call to ncs.maapi.single_read_trans()
so no schema loading occurred during the second call.
The way to make the program above work as expected is to force the reloading of schemas by passing an optional argument to single_read_trans()
like so:
Running the program with this change may produce something like this:
Now, this was just an example of what may happen when wrong schemas are loaded. Implications may be more severe though, especially if maagic nodes are kept between reloads. In such cases, accessing an "invalid" maagic object may in the best case result in undefined behavior making the program not work, but might even crash the program. So care needs to be taken to not reload schemas in a Python VM if there are dependencies to other parts in the same VM that need previous schemas.
Functions and methods that accept the load_schemas
argument:
ncs.maapi.Maapi() constructor
ncs.maapi.single_read_trans()
ncs.maapi.single_write_trans()
Last updated