mirror of
https://github.com/Telecominfraproject/oopt-gnpy-libyang.git
synced 2026-01-10 01:11:35 +00:00
135 lines
5.0 KiB
Markdown
135 lines
5.0 KiB
Markdown
# Opinionated Python bindings for the `libyang` library
|
|
|
|
[](https://pypi.org/project/oopt-gnpy-libyang/)
|
|
[](https://pypi.org/project/oopt-gnpy-libyang/)
|
|
[](https://github.com/Telecominfraproject/oopt-gnpy-libyang/actions/workflows/ci.yaml)
|
|
|
|
Python bindings and packaging of [`libyang`](https://github.com/CESNET/libyang).
|
|
We're focusing on parsing, validating and accessing YANG-modeled JSON data trees.
|
|
Essentially, just enough to get [`gnpy`](https://github.com/Telecominfraproject/oopt-gnpy) going.
|
|
Want more?
|
|
Patches welcome.
|
|
|
|
Compared to the [CFFI libyang bindings](https://github.com/CESNET/libyang-python), this wrapper takes care of low-level memory management.
|
|
This means no more `node.free()` and `ctx.destroy()`.
|
|
We also produce prebuilt binary [wheels](https://realpython.com/python-wheels/) to make installation very simple.
|
|
|
|
## Usage
|
|
|
|
### Loading YANG data
|
|
|
|
```python
|
|
import oopt_gnpy_libyang as ly
|
|
|
|
c = ly.Context('tests/yang', ly.ContextOptions.AllImplemented | ly.ContextOptions.NoYangLibrary)
|
|
for m in ('iana-if-type', 'ietf-interfaces', 'ietf-ip'):
|
|
c.load_module(m)
|
|
blob = '''{
|
|
"ietf-interfaces:interfaces": {
|
|
"interface": [
|
|
{
|
|
"name": "lo",
|
|
"type": "iana-if-type:softwareLoopback",
|
|
"ietf-ip:ipv4": {
|
|
"address": [
|
|
{
|
|
"ip": "127.0.0.1",
|
|
"prefix-length": 8
|
|
}
|
|
]
|
|
},
|
|
"ietf-ip:ipv6": {
|
|
"address": [
|
|
{
|
|
"ip": "::1",
|
|
"prefix-length": 128
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"name": "eth0",
|
|
"type": "iana-if-type:ethernetCsmacd"
|
|
}
|
|
]
|
|
}
|
|
}'''
|
|
|
|
data = c.parse_data_str(blob,
|
|
ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered,
|
|
ly.ValidationOptions.Present | ly.ValidationOptions.NoState)
|
|
```
|
|
### Working with data
|
|
|
|
Libyang works with forests (sets of trees), this is how to process all the data:
|
|
```python
|
|
for x in data.siblings():
|
|
print(f'a sibling: {x.path}')
|
|
for xx in x.childrenDfs():
|
|
print(f' {"term " if xx.is_term else "child"}: {xx.path}')
|
|
if xx.is_term:
|
|
print(f' {xx.as_term()} {" (default)" if xx.as_term().is_default_value else ""}')
|
|
```
|
|
Data can be accessed via their known paths, of course. Either as a full, multi-level XPath:
|
|
|
|
```python
|
|
data["interface[name='lo']/ietf-ip:ipv6/address[ip='::1']/prefix-length"].as_term().value == 128
|
|
```
|
|
Or individually, one item per index:
|
|
```python
|
|
data["interface[name='lo']"]["ietf-ip:ipv6"]["address[ip='::1']"]["prefix-length"].as_term().value
|
|
```
|
|
Everything is an XPath, so it's possible to take a shortcut and skip specifying keys for single-element lists:
|
|
```python
|
|
data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"].as_term().value == 128
|
|
```
|
|
The data are provided as native Python types:
|
|
```python
|
|
type(data["interface[name='lo']"]["ietf-ip:ipv6"]["address"]["prefix-length"]
|
|
.as_term().value) == int
|
|
```
|
|
### Validation errors
|
|
In libyang, if an operation fails, error details are available via `context.errors()`:
|
|
```python
|
|
import json
|
|
wrong = json.loads(blob)
|
|
wrong["ietf-interfaces:interfaces"]["interface"][0]\
|
|
["ietf-ip:ipv6"]["address"][0]["prefix-length"] = 666
|
|
try:
|
|
data = c.parse_data_str(json.dumps(wrong),
|
|
ly.DataFormat.JSON, ly.ParseOptions.Strict | ly.ParseOptions.Ordered,
|
|
ly.ValidationOptions.Present | ly.ValidationOptions.NoState)
|
|
assert False
|
|
except ly.Error:
|
|
for error in c.errors():
|
|
assert error.path == "Schema location \"/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address/prefix-length\", data location \"/ietf-ip:address[ip='::1']\", line number 1."
|
|
assert error.message == 'Value "666" is out of type uint8 min/max bounds.'
|
|
```
|
|
|
|
## Installing
|
|
|
|
We're producing wheels for many popular platforms.
|
|
The installation is as simple as:
|
|
```console-session
|
|
$ pip install oopt-gnpy-libyang
|
|
```
|
|
|
|
### Building from source
|
|
|
|
Since this library is a Python wrapper around a C++ wrapper around a C library, source-based builds are more complex.
|
|
They require:
|
|
|
|
- a C++20 compiler (e.g., GCC 10+, clang 10+, MSVC 17.2+)
|
|
- [`libyang`](https://github.com/CESNET/libyang) and its dependencies
|
|
- [`libyang-cpp`](https://github.com/CESNET/libyang-cpp/) and its dependencies
|
|
- [CMake](https://cmake.org/) 3.21+
|
|
|
|
Unlike the wheels already bundle all the required libraries, when building from source, `libyang`, `libyang-cpp` and all their dependencies will have to be installed first.
|
|
Also, in a from-source build these won't be bundled into the resulting package.
|
|
For an inspiration, consult our [GitHub packaging recipes](./.github/workflows/ci.yaml).
|
|
|
|
## License
|
|
|
|
Copyright © 2021-2022 Telecom Infra Project and GNPy contributors.
|
|
Licensed under the [3-clause BSD license](LICENSE).
|