PY-SIL Tutorial
Tutorial Introduction
Welcome to this comprehensive tutorial on creating a PY-SIL code with GET2 callbacks. In this guide, we will walk through the process of building a simple PY-SIL subsystem that efficiently retrieves data from a YAML file containing addresses and seamlessly returns it to the server.
A PY-SIL GET2 Callback function is usually mapped to a single data node. The server will issue “get” and “getnext” requests via this API, and the callback code will return value nodes for the data it is managing. These data nodes are not maintained in the server Data Tree. They are not cached in the server.
Create a New Folder for the Project
Start by creating a new Python project and setting up the necessary file structure. Create a new folder for your project by using the mkdir (make directory) command. Replace project-folder-name with your desired folder name and desired location. $HOME here is just an example location where the PY-SIL will be located:
mkdir $HOME/pysil-tutorial
Navigate into your new project folder by typing:
cd $HOME/pysil-tutorial
Virtual environment (optional)
You can optionally use a tool like virtualenv to create a virtual environment for your project.
> # Create a virtual environment
> python -m venv venv
>
> # Activate the virtual environment
> source venv/bin/activate
Project Setup
Tutorial will use following YANG module 'address_get_2'. Create it in your project folder with content like provided below.
module address_get_2 {
namespace "urn:address_get_2";
prefix "address_get_2";
revision "2024-01-29";
container addresses {
config false;
list address {
key "last-name first-name";
leaf last-name {
type string;
description
"Last name of the person who is associated with this address";
}
leaf first-name {
type string;
description
"First name of the person who is associated with this address";
}
leaf street {
type string;
description "street address";
}
leaf city {
type string;
description "City address";
}
leaf zipcode {
type string { length "5 | 10"; }
description "zipcode";
}
list phone {
key phone-type;
leaf phone-type {
type enumeration {
enum cell;
enum home;
enum work;
}
}
leaf phone-number {
mandatory true;
type string;
}
} // list phone
} // list address
} // container addresses
} // module address
Project Dependencies
To continue you should have PY-SIL Development Environment installed.
PY-SIL Code Generation Example
To generate PY-SIL code stub use following command:
pyang -f py-sil address_get_2.yang
By default it will generate following files in your project folder.
Following file structure will be generated
address_get_2/
|-- u_address_get_2.py
|-- y_address_get_2.py
For the full code generation options see PY-SIL Code Generation
Implement Callbacks
Next step we should implement actual code in the auto-generated callback functions in the u_address_get_2.py
get_address - callback function
Here is example of final get_address function that contains actual code with detailed comments below.
def get_address(self, silcb: SILCbGet):
"""
Get database object callback
Path: list /address_get_2:addresses/address
Parameters
----------
silcb: SILCbGet
Control block
Returns
-------
STATUS_T
status of the callback.
Additional
----------
silcb.local_keys: dict with local keys
silcb.local_keys["last-name"]: str
Local key leaf 'last-name' in list 'address' Path: /address_get_2:addresses/address/last-name
silcb.local_keys["first-name"]: str
Local key leaf 'first-name' in list 'address' Path: /address_get_2:addresses/address/first-name
silcb.local_keys_present: list of present and valid local keys
silcb.local_keys_fixed: list of local keys fixed in a getnext request
"""
self.log(
f"Enter 'get_address' {self.name}@{self.revision} ancestor_keys={silcb.ancestor_keys} local_keys={silcb.local_keys}"
)
# Step 1: Load data from YAML file
test_data = load_test_data()
# Step 2: apply filters by first and last name if needed
if "last-name" in silcb.local_keys_fixed:
test_data = [
r for r in test_data if r.get("last-name") == silcb.local_keys["last-name"]
]
if "first-name" in silcb.local_keys_fixed:
test_data = [
r
for r in test_data
if r.get("first-name") == silcb.local_keys["first-name"]
]
test_data_len = len(test_data)
# print("FILTERED DATA: %s" % test_data_len)
# Step 3: Set i index to 0 if mode is GETCB_GET_VALUE next code in function will return current row
i = 0
# Step 4: If the callback mode is GETCB_GET_VALUE, no additional action is taken, as the next code will return the current row.
if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
pass
# Step 5: Find current row if callback mode is GETCB_GETNEXT_VALUE
if silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
# find current row first
while test_data_len > i:
# print(test_data[i].get("last-name"))
# print(test_data[i].get("first-name"))
if (
test_data[i].get("last-name") == silcb.local_keys["last-name"]
and test_data[i].get("first-name") == silcb.local_keys["first-name"]
):
i += 1
break
i += 1
# Step 6: If the row doesn't exist, exit with an error
if test_data_len <= i:
return STATUS_T.ERR_NCX_NO_INSTANCE
curr = test_data[i]
# Step 7: Create an python object
return_data = {
"last-name": curr.get("last-name"),
"first-name": curr.get("first-name"),
"street": curr.get("street"),
"city": curr.get("city"),
"zipcode": curr.get("zipcode"),
}
# Step 8: Save return data and mark if more data exists
res = silcb.add_return_data(data=return_data, has_more=(test_data_len > i + 1))
# Step 9: Return status
return res
Step 1 Load Data from YAML File: Loads test data from a YAML file using the load_test_data function.
Step 2 Filter Data by Last and First Name: Applies filters to the test data based on the last and first names if they are fixed.
Step 3 Set i index to 0 if mode is GETCB_GET_VALUE next code in function will return current row
Step 4 If the callback mode is GETCB_GET_VALUE, no additional action is taken, as the next code will return the current row.
Step 5 Find current row if callback mode is GETCB_GETNEXT_VALUE
Step 6 If the row doesn't exist, exit with an error
Step 7 Create an python object
Step 8 Save return data and mark if more data exists
Step 9 Return status
get_phone - callback function Here is example of final get_phone function that contains actual code with detailed comments.
def get_phone(self, silcb: SILCbGet):
"""
Get database object callback
Path: list /address_get_2:addresses/address/phone
Parameters
----------
silcb: SILCbGet
Control block
Returns
-------
STATUS_T
status of the callback.
Additional
----------
silcb.ancestor_keys: dict with ancestor keys
silcb.ancestor_keys["address__last-name"]: str
Ancestor key leaf 'last-name' in list 'address' Path: /address_get_2:addresses/address/last-name
silcb.ancestor_keys["address__first-name"]: str
Ancestor key leaf 'first-name' in list 'address' Path: /address_get_2:addresses/address/first-name
silcb.local_keys: dict with local keys
silcb.local_keys["phone-type"]: str
Local key leaf 'phone-type' in list 'phone' Path: /address_get_2:addresses/address/phone/phone-type
silcb.local_keys_present: list of present and valid local keys
silcb.local_keys_fixed: list of local keys fixed in a getnext request
"""
self.log(
f"Enter 'get_phone' {self.name}@{self.revision} ancestor_keys={silcb.ancestor_keys} local_keys={silcb.local_keys}"
)
# load data from YAML file
test_data = load_test_data()
# find specific address
address = None
for r in test_data:
if (
r.get("last-name") == silcb.ancestor_keys["last-name"]
and r.get("first-name") == silcb.ancestor_keys["first-name"]
):
address = r
break
if not address:
return STATUS_T.ERR_NCX_NO_INSTANCE
# now test_data is data from specific address
test_data = address.get("phone", [])
# apply filters by phone-type
if "phone-type" in silcb.local_keys_fixed:
test_data = [
r
for r in test_data
if r.get("phone-type") == silcb.local_keys["phone-type"]
]
test_data_len = len(test_data)
print("FILTERED DATA: %s" % test_data_len)
# by default - first row
i = 0
if silcb.cbmode == GETCB_MODE_T.GETCB_GET_VALUE:
pass
if silcb.cbmode == GETCB_MODE_T.GETCB_GETNEXT_VALUE:
# find currecnt row first
while test_data_len > i:
print(test_data[i].get("phone-type"))
if test_data[i].get("phone-type") == silcb.local_keys["phone-type"]:
i += 1
break
i += 1
# if the row doesn't exists - exit with error
if test_data_len <= i:
return STATUS_T.ERR_NCX_NO_INSTANCE
curr = test_data[i]
# return data
phone = {
"phone-type": curr.get("phone-type"),
"phone-number": curr.get("phone-number"),
}
silcb.add_return_data(data=phone, has_more=(test_data_len > i + 1))
return STATUS_T.NO_ERR
load_test_data Here is example of load_test_data() function. It should be inserted into u_address_get_2.py file and it's not auto-generated. This function reads the content of the 'tutorial_addressbook.yaml' file, which is expected to be located in the same directory as this script. It uses the 'yaml' library to parse the YAML content and returns it in the form of a Python dictionary.
Note
This is an example file, and it is optional step. The callbacks can use hard wired values for testing purposes.
import yaml
def load_test_data():
from pathlib import Path
with open("%s/tutorial_addressbook.yaml" % Path(__file__).parent.resolve(), "r") as f:
data = yaml.safe_load(f)
return data
tutorial_addressbook.yaml
YAML file with actual data for retrieval. Need to be located in the same folder as your project:
- first-name: John
last-name: Doe
street: 123 Main St
city: Anytown
zipcode: 12345
phone:
- phone-type: cell
phone-number: 555-1234
- phone-type: home
phone-number: 535-5644
- phone-type: work
phone-number: 555-5678
- first-name: Jane
last-name: Smith
street: 456 Oak Ave
city: Somewhere
zipcode: 67890
phone:
- phone-type: cell
phone-number: 555-4321
- phone-type: home
phone-number: 555-8765
- first-name: Michael
last-name: Johnson
street: 789 Pine Ln
city: Cityville
zipcode: 45678
phone:
- phone-type: cell
phone-number: 555-1111
- phone-type: home
phone-number: 555-2222
- phone-type: work
phone-number: 555-3333
- first-name: Emily
last-name: Williams
street: 101 Elm St
city: Townsville
zipcode: 98765
phone:
- phone-type: cell
phone-number: 555-3333
- phone-type: work
phone-number: 555-4444
- first-name: Robert
last-name: Brown
street: 202 Maple Blvd
city: Villagetown
zipcode: 54321
phone:
- phone-type: cell
phone-number: 555-5555
- phone-type: work
phone-number: 555-6666
- first-name: Bob
last-name: Smith
street: 456 Oak Ave
city: Somewhere
zipcode: 67890
- first-name: Keks
last-name: Smith
street: 456 Oak Ave
city: Somewhere
zipcode: 67890
phone:
- phone-type: cell
phone-number: 555-4321
- phone-type: work
phone-number: 555-8765
- phone-type: home
phone-number: 555-9999
- first-name: Sarah
last-name: Johnson
street: 321 Pine Ln
city: Cityville
zipcode: 45678
phone:
- phone-type: cell
phone-number: 555-7777
- phone-type: work
phone-number: 555-8888
- first-name: Alex
last-name: White
street: 505 Cedar St
city: Villagetown
zipcode: 54321
phone:
- phone-type: work
phone-number: 555-0000
- phone-type: home
phone-number: 555-1111
Final project folder structure
pysil-tutorial/
|-- address_get_2/
| |-- u_address_get_2.py
| |-- y_address_get_2.py
| |-- tutorial_addressbook.yaml
|-- address_get_2.yang
Test environment
Follow next steps to create a test environment for the PY-SIL code. Use dedicated terminal windows for each process.
Server startup
> netconfd-pro --log-level=debug4 --module=address_get_2
--access-control=off --modpath=$HOME/pysil-tutorial
This command launches netconfd-pro with the following configuration:
--log-level=debug4: Sets the logging level to debug4, providing detailed debug information. Adjust the log level based on your debugging needs.
--module=address_get_2: Specifies the YANG module named address_get_2 for NETCONF operations.
--access-control=off: Disables access control, which means that there are no restrictions on who can access the NETCONF server. Intended solely for illustrative purposes.
--modpath=[path to your YANG modules folder]: Specifies folder where address_get_2.yang is located.
Python SIL-SA app startup
> py-sil-app.py --log-level=debug3 --pymodpath=$HOME/pysil-tutorial
--pymodpath=[path to your PY-SIL project folder]: Specifies folder where your PY-SIL libraries are located. In this tutorial that would be pysil-tutorial/ directory. Note, the path is not address_get_2/ but the parent directory pysil-tutorial/ where all the PY-SIL libraries are located.
Note
If the --pymodpath CLI parameter is not provided, the default search path for PY-SIL code is $HOME/pysil directory. If it is not found in this default location the application will attempt to locate PY-SIL in the current directory.
yangcli-pro startup
> yangcli-pro --server=localhost --user=[your user] --password=[your user password]
Launch yangcli-pro to connect to a NETCONF server. This command initiates a connection to a NETCONF server with the specified parameters:
--server=localhost: Specifies that the NETCONF server is located on the local machine. Adjust the localhost part if the server is on a different host.
--user=[your user]: Replace [your user] with the actual username you want to use for authentication.
--password=[your user password]: Replace [your user password] with the actual password corresponding to the specified username.
Make sure to replace the placeholders with your actual credentials.
Example Usage
You could try following commands using yangcli-pro:
Get all data
> sget /addresses
Server should reply with the following:
rpc-reply {
data {
addresses {
address Doe John {
last-name Doe
first-name John
street '123 Main St'
city Anytown
phone cell {
phone-type cell
phone-number 555-1234
}
}
address Smith Jane {
last-name Smith
first-name Jane
street '456 Oak Ave'
city Somewhere
phone cell {
phone-type cell
phone-number 555-4321
}
}
address Johnson Michael {
last-name Johnson
first-name Michael
street '789 Pine Ln'
city Cityville
phone cell {
phone-type cell
phone-number 555-1111
}
}
address Williams Emily {
last-name Williams
first-name Emily
street '101 Elm St'
city Townsville
phone cell {
phone-type cell
phone-number 555-3333
}
}
address Brown Robert {
last-name Brown
first-name Robert
street '202 Maple Blvd'
city Villagetown
phone cell {
phone-type cell
phone-number 555-5555
}
}
address Smith Bob {
last-name Smith
first-name Bob
street '456 Oak Ave'
city Somewhere
}
address Smith Keks {
last-name Smith
first-name Keks
street '456 Oak Ave'
city Somewhere
phone cell {
phone-type cell
phone-number 555-4321
}
}
address Johnson Sarah {
last-name Johnson
first-name Sarah
street '321 Pine Ln'
city Cityville
phone cell {
phone-type cell
phone-number 555-7777
}
}
address White Alex {
last-name White
first-name Alex
street '505 Cedar St'
city Villagetown
phone work {
phone-type work
phone-number 555-0000
}
phone home {
phone-type home
phone-number 555-1111
}
}
}
}
}
Filter by key
> sget /addresses/address[last-name="Smith"]
Server should reply with the following:
rpc-reply {
data {
addresses {
address Smith Jane {
last-name Smith
first-name Jane
street '456 Oak Ave'
city Somewhere
phone cell {
phone-type cell
phone-number 555-4321
}
}
address Smith Bob {
last-name Smith
first-name Bob
street '456 Oak Ave'
city Somewhere
}
address Smith Keks {
last-name Smith
first-name Keks
street '456 Oak Ave'
city Somewhere
phone cell {
phone-type cell
phone-number 555-4321
}
}
}
}
}
Get exact entry
> sget /addresses/address[last-name="Smith"][first-name="Keks"]
Server should reply with exact entry selected by two keys:
rpc-reply {
data {
addresses {
address Smith Keks {
last-name Smith
first-name Keks
street '456 Oak Ave'
city Somewhere
phone cell {
phone-type cell
phone-number 555-4321
}
}
}
}
}