Showing preview only (613K chars total). Download the full file or copy to clipboard to get everything.
Repository: robshakir/pyangbind
Branch: master
Commit: d203f2b6dfb1
Files: 229
Total size: 556.0 KB
Directory structure:
gitextract_6v8090ee/
├── .coveragerc
├── .editorconfig
├── .github/
│ └── workflows/
│ ├── pypi.yml
│ └── python-test.yml
├── .gitignore
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── LICENSE
├── MANIFEST.in
├── README.md
├── README.rst
├── docs/
│ ├── README.md
│ ├── errors.md
│ ├── example/
│ │ ├── .gitignore
│ │ ├── oc-network-instance/
│ │ │ ├── generate_bindings.sh
│ │ │ ├── json/
│ │ │ │ ├── oc-ni.json
│ │ │ │ └── oc-ni_ietf.json
│ │ │ └── static_route_example.py
│ │ ├── simple-rpc/
│ │ │ ├── .gitignore
│ │ │ ├── generate_bindings.sh
│ │ │ ├── json/
│ │ │ │ └── rpc-output.json
│ │ │ ├── simple-rpc.py
│ │ │ └── simple_rpc.yang
│ │ └── simple-serialise/
│ │ ├── .gitignore
│ │ ├── generate_bindings.sh
│ │ ├── json/
│ │ │ ├── simple-instance-additional.json
│ │ │ ├── simple-instance-ietf.json
│ │ │ └── simple-instance.json
│ │ ├── simple-serialise.py
│ │ └── simple_serialise.yang
│ ├── extmethods.md
│ ├── generic_methods.md
│ ├── rpc.md
│ ├── serialisation.md
│ ├── usage.md
│ ├── xpathhelper.md
│ └── yang.md
├── pyangbind/
│ ├── __init__.py
│ ├── helpers/
│ │ ├── __init__.py
│ │ ├── identity.py
│ │ └── misc.py
│ ├── lib/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── pybindJSON.py
│ │ ├── serialise.py
│ │ ├── xpathhelper.py
│ │ └── yangtypes.py
│ └── plugin/
│ ├── __init__.py
│ └── pybind.py
├── pyproject.toml
├── requirements.DEVELOPER.txt
├── requirements.txt
├── scripts/
│ └── release.sh
├── setup.cfg
├── tests/
│ ├── __init__.py
│ ├── base-test.yang
│ ├── base.py
│ ├── binary/
│ │ ├── __init__.py
│ │ ├── binary.yang
│ │ └── run.py
│ ├── bits/
│ │ ├── __init__.py
│ │ ├── bits.yang
│ │ └── run.py
│ ├── boolean-empty/
│ │ ├── __init__.py
│ │ ├── boolean-empty.yang
│ │ └── run.py
│ ├── choice/
│ │ ├── __init__.py
│ │ ├── choice.yang
│ │ └── run.py
│ ├── config-false/
│ │ ├── __init__.py
│ │ ├── config-false.yang
│ │ └── run.py
│ ├── decimal64/
│ │ ├── __init__.py
│ │ ├── decimal.yang
│ │ └── run.py
│ ├── enumeration/
│ │ ├── __init__.py
│ │ ├── enumeration.yang
│ │ └── run.py
│ ├── extensions/
│ │ ├── __init__.py
│ │ ├── extdef-irr.yang
│ │ ├── extdef-two.yang
│ │ ├── extdef.yang
│ │ ├── extensions.yang
│ │ └── run.py
│ ├── extmethods/
│ │ ├── __init__.py
│ │ ├── extmethods.yang
│ │ └── run.py
│ ├── identityref/
│ │ ├── __init__.py
│ │ ├── identityref.yang
│ │ ├── remote-two.yang
│ │ ├── remote.yang
│ │ └── run.py
│ ├── include-import/
│ │ ├── __init__.py
│ │ ├── include-import.yang
│ │ ├── remote.yang
│ │ ├── run.py
│ │ └── subm.yang
│ ├── int/
│ │ ├── __init__.py
│ │ ├── int.yang
│ │ └── run.py
│ ├── integration/
│ │ ├── __init__.py
│ │ └── openconfig-interfaces/
│ │ ├── __init__.py
│ │ └── run.py
│ ├── leaf-list/
│ │ ├── __init__.py
│ │ ├── leaflist.yang
│ │ └── run.py
│ ├── list/
│ │ ├── __init__.py
│ │ ├── list.yang
│ │ └── run.py
│ ├── misc/
│ │ ├── __init__.py
│ │ ├── misc.yang
│ │ └── run.py
│ ├── nested-containers/
│ │ ├── __init__.py
│ │ ├── nested.yang
│ │ └── run.py
│ ├── notification/
│ │ ├── __init__.py
│ │ ├── notification.yang
│ │ └── run.py
│ ├── openconfig-bgp-juniper/
│ │ ├── __init__.py
│ │ ├── openconfig-bgp-juniper.yang
│ │ └── run.py
│ ├── presence/
│ │ ├── __init__.py
│ │ ├── presence.yang
│ │ └── run.py
│ ├── rpc/
│ │ ├── __init__.py
│ │ ├── rpc.yang
│ │ └── run.py
│ ├── serialise/
│ │ ├── __init__.py
│ │ ├── ietf-json-deserialise/
│ │ │ ├── __init__.py
│ │ │ ├── ietf-json-deserialise.yang
│ │ │ ├── json/
│ │ │ │ ├── chlist.json
│ │ │ │ ├── complete-obj.json
│ │ │ │ ├── mkeylist.json
│ │ │ │ ├── nonexistkey.json
│ │ │ │ └── skeylist.json
│ │ │ ├── remote.yang
│ │ │ └── run.py
│ │ ├── ietf-json-serialise/
│ │ │ ├── __init__.py
│ │ │ ├── augment.yang
│ │ │ ├── ietf-json-serialise.yang
│ │ │ ├── json/
│ │ │ │ └── obj.json
│ │ │ ├── remote.yang
│ │ │ └── run.py
│ │ ├── json-deserialise/
│ │ │ ├── __init__.py
│ │ │ ├── json/
│ │ │ │ ├── alltypes.json
│ │ │ │ ├── list-items.json
│ │ │ │ ├── list.json
│ │ │ │ ├── nonexist.json
│ │ │ │ ├── orderedlist-no-order.json
│ │ │ │ └── orderedlist-order.json
│ │ │ ├── json-deserialise.yang
│ │ │ └── run.py
│ │ ├── json-serialise/
│ │ │ ├── __init__.py
│ │ │ ├── json/
│ │ │ │ ├── container.json
│ │ │ │ └── expected-output.json
│ │ │ ├── json-serialise.yang
│ │ │ └── run.py
│ │ ├── openconfig-serialise/
│ │ │ ├── __init__.py
│ │ │ ├── json/
│ │ │ │ ├── interfaces_ph.False-flt.False-m.default.json
│ │ │ │ ├── interfaces_ph.False-flt.False-m.ietf.json
│ │ │ │ ├── interfaces_ph.False-flt.True-m.default.json
│ │ │ │ ├── interfaces_ph.False-flt.True-m.ietf.json
│ │ │ │ ├── interfaces_ph.True-flt.False-m.default.json
│ │ │ │ ├── interfaces_ph.True-flt.False-m.ietf.json
│ │ │ │ ├── interfaces_ph.True-flt.True-m.default.json
│ │ │ │ └── interfaces_ph.True-flt.True-m.ietf.json
│ │ │ └── run.py
│ │ ├── roundtrip/
│ │ │ ├── __init__.py
│ │ │ ├── remote.yang
│ │ │ ├── roundtrip.yang
│ │ │ └── run.py
│ │ ├── xml-deserialise/
│ │ │ ├── __init__.py
│ │ │ ├── augment.yang
│ │ │ ├── ietf-xml-deserialise.yang
│ │ │ ├── remote.yang
│ │ │ ├── run.py
│ │ │ └── xml/
│ │ │ └── obj.xml
│ │ ├── xml-serialise/
│ │ │ ├── __init__.py
│ │ │ ├── augment.yang
│ │ │ ├── ietf-xml-serialise.yang
│ │ │ ├── remote.yang
│ │ │ ├── run.py
│ │ │ └── xml/
│ │ │ └── obj.xml
│ │ └── xml_utils.py
│ ├── split-classes/
│ │ ├── __init__.py
│ │ ├── run.py
│ │ └── split-classes.yang
│ ├── strings/
│ │ ├── __init__.py
│ │ ├── run.py
│ │ └── string.yang
│ ├── submodules/
│ │ ├── __init__.py
│ │ ├── mod-a.yang
│ │ ├── mod-c.yang
│ │ ├── run.py
│ │ └── subm-b.yang
│ ├── typedef/
│ │ ├── __init__.py
│ │ ├── remote.yang
│ │ ├── run.py
│ │ ├── second-remote.yang
│ │ └── typedef.yang
│ ├── uint/
│ │ ├── __init__.py
│ │ ├── run.py
│ │ └── uint.yang
│ ├── union/
│ │ ├── __init__.py
│ │ ├── run.py
│ │ └── union.yang
│ ├── unit/
│ │ ├── __init__.py
│ │ ├── models/
│ │ │ └── simple.yang
│ │ └── test_api.py
│ └── xpath/
│ ├── 00_pathhelper_base.py
│ ├── 01-list_leaflist/
│ │ ├── __init__.py
│ │ ├── list-tc01.yang
│ │ └── run.py
│ ├── 02-static_ptr/
│ │ ├── __init__.py
│ │ ├── ptr-tc02.yang
│ │ └── run.py
│ ├── 03-current/
│ │ ├── __init__.py
│ │ ├── current-tc03.yang
│ │ └── run.py
│ ├── 04-root/
│ │ ├── __init__.py
│ │ ├── json/
│ │ │ ├── 04-serialise.json
│ │ │ ├── 04b-ietf-serialise.json
│ │ │ ├── 05-deserialise.json
│ │ │ └── 06-deserialise-ietf.json
│ │ ├── root-tc04-a.yang
│ │ ├── root-tc04-b.yang
│ │ └── run.py
│ └── __init__.py
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .coveragerc
================================================
# .coveragerc to control coverage.py
[run]
branch = True
omit =
*/.tox/*
*/.virtualenvs/*
*/tests/*
*/env/*
# We don't currently have a way to track code coverage of the plugin itself.
pyangbind/plugin/*
source = pyangbind
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
ignore_errors = True
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
tab_width = 2
max_line_length = 119
# The JSON files contain newlines inconsistently
[*.json]
insert_final_newline = ignore
# Minified JavaScript files shouldn't be changed
[**.min.js]
indent_style = ignore
insert_final_newline = ignore
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .github/workflows/pypi.yml
================================================
name: Python package and publish
on:
release:
types: [published]
jobs:
pypi-publish:
name: Package and upload release to PyPI
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install building dependencies
run: pip -q install build
- name: Build package
run: python -m build --outdir dist/ .
- name: Install package
run: pip -q install dist/pyangbind-*.whl
- name: Test bind with pyang
run: |
export PYBINDPLUGIN=`/usr/bin/env python -c 'import pyangbind; import os; print ("{}/plugin".format(os.path.dirname(pyangbind.__file__)))'`
pyang -V --plugindir $PYBINDPLUGIN -f pybind tests/base-test.yang
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: .github/workflows/python-test.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Run TOX tests
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
- name: Install testing dependency
run: pip install tox
- name: Lint with black
run: tox -e black
unit-tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install testing dependencies
run: pip install tox
- name: Run tox for ${{ matrix.python-version }}
# Run tox using the version of Python in `PATH`
run: tox -e py
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
tests/pyvirtualenv
env/
# Editor-specific files
*.swp
/.project
/.pydevproject
.vscode
.idea/
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
coverage-*.xml
junit-*.xml
*,cover
reports/
*.PEP8-ERRORS
.python-version
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Integration/split-class test files
tests/integration/openconfig-bgp/include/
tests/integration/openconfig-bgp/ocbind/
tests/integration/openconfig-bgp/openconfig/
tests/integration/openconfig-interfaces/include/
tests/integration/openconfig-interfaces/ocbind/
tests/integration/openconfig-interfaces/openconfig/
tests/notification/bindings/
tests/serialise/juniper-json-examples/include/
tests/serialise/juniper-json-examples/ocbind/
tests/serialise/juniper-json-examples/openconfig/
tests/serialise/openconfig-serialise/include/
tests/serialise/openconfig-serialise/ocbind/
tests/serialise/openconfig-serialise/openconfig/
tests/split-classes/bindings/
tests/base-binding-out.py
================================================
FILE: CONTRIBUTING.md
================================================
## Contributing to PyangBind
Contributions to PyangBind are very welcome, either directly via pull requests, or as feature suggestions or bug reports.
### Code Style
To avoid unnecessary discussions about coding style we are currently enforcing it with [black](https://github.com/ambv/black). Before pushing code:
* make sure you are running the correct version of `black` as per `requirements.DEVELOPER.txt`.
* reformat your code with `black` passing the option `--line-length 119`.
### Testing
* New code should be covered by tests, and run under both Python 2 and 3.
* To ease the testing of generated bindings, there is the `tests.base.PyangBindTestCase` class which
you may subclass in order to automate the process. A simple example of its usage can be seen in
`tests/strings/run.py`.
* Tests can be run via the `tox` command, or `python3 -m pytest -q tests`.
### Other Issues
* If you have an issue with generated code/odd errors during build -- please do just e-mail this over or open an issue.
If you can't share the YANG itself, then anonymised YANG is very welcome.
* If you'd like to discuss the best design for a feature, or don't get how a feature fits in, please open an issue,
send an e-mail, or join us in the #pyangbind channel on the
[NetworkToCode Slack](https://networktocode.slack.com/).
And most of all, thanks for contributions :-)
================================================
FILE: CONTRIBUTORS
================================================
Rob Shakir <robjs@google.com>
David Barrosso <dbarrosop@dravetech.com>
Kirk Byers (GitHub: ktbyers)
Tim Martin (GitHub: timmartin)
Mark Paine (mpainenz@gmail.com)
Mark Sutherland (GitHub: marksutherland)
David Lamparter (equinox@opensourcerouting.org)
Daniam Henriques (GitHub: dhenza)
Adam Sloboda (GitHub: asloboda-cisco)
Vikas Kumar (GitHub: kvikas)
Lluis Gifre (lluis.gifre@uam.es)
================================================
FILE: LICENSE
================================================
Copyright 2015 Rob Shakir, Jive Communications, Inc.
rjs@jive.com, rjs@rob.sh
Modifications copyright 2016, Google Inc.
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.
================================================
FILE: MANIFEST.in
================================================
include *.md
include *.txt
include LICENSE
================================================
FILE: README.md
================================================
[![#PyangBind][img-pyangbind]][pyangbind-docs]
[![PyPI][img-pypi]][pypi-project]
[![PyPI - License][img-license]][license]
[![PyPI - Python Version][img-pyversion]][pypi-project]
**PyangBind** is a plugin for [Pyang][pyang] that generates a Python class hierarchy from a YANG data model. The resulting classes can be directly interacted with in Python. Particularly, **PyangBind** will allow you to:
* Create new data instances - through setting values in the Python class hierarchy.
* Load data instances from external sources - taking input data from an external source and allowing it to be addressed through the Python classes.
* Serialise populated objects into formats that can be stored, or sent to another system (e.g., a network element).
Development of **PyangBind** has been motivated by consuming the [OpenConfig][openconfig] data models; and is intended to be well-tested against these models. The Python classes generated, and serialisation methods are intended to provide network operators with a starting point for loading data instances from network elements, manipulating them, and sending them to a network device. **PyangBind** classes also have functionality which allows additional methods to be associated with the classes, such that it can be used for the foundation of a NMS.
## Contents
* [Getting Started](#getting-started)
* [Generating a Set of Classes](#generating-classes)
* [Using the Classes in a Python Program](#using-in-python)
* [Creating a Data Instance](#create-instance)
* [Serialising a Data Instance](#serialising)
* [Deserialising a Data Instance to Classes](#deserialising)
* [Example Code](#example-code)
* [Further Documentation](#documentation)
* [Licensing](#licensing)
* [Acknowledgements](#acks)
* [Test Status](#tests)
## Getting Started <a name="getting-started"></a>
**PyangBind** is distributed through [PyPI][pypi], it can be installed by simply running:
```
$ pip install pyangbind
```
The `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.
### Generating a Set of Classes <a name="generating-classes"></a>
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., `tests/base-test.yang`).
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 ("{}/plugin".format(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
```
where:
* `$PYBINDPLUGIN` is the location that was exported from the above command.
* `binding.py` is the desired output file.
* `doc/base-test.yang` is 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 `docs/` directory.
### Using the Classes in a Python Program <a name="using-in-python"></a>
**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-network-instance.yang` becomes `openconfig_network_instance` as a module name.
Primarily, we need to generate a set of classes for the model(s) that we are interested in. The OpenConfig network instance module will be used as an example for this walkthrough.
The bindings can be simply generated by running the `docs/example/oc-network-instance/generate_bindings.sh` script. This script clones the openconfig/public repo 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:
```python
# Using the binding file generated by the `generate_bindings.sh` script
# Note that CWD is the file containing the binding.py file.
# Alternatively, you can use sys.path.append to add the CWD to the PYTHONPATH
from binding import openconfig_network_instance
ocni = openconfig_network_instance()
```
### Creating a Data Instance <a name="create-instance"></a>
At this point, the `ocni` object can be used to manipulate the YANG data tree that is expressed by the module.
A subset of `openconfig-network-instance` looks like the following tree:
```
module: openconfig-network-instance
+--rw network-instances
+--rw network-instance* [name]
+--rw name -> ../config/name
+--rw config
| +--rw name? string
...
+--rw protocols
+--rw protocol* [identifier name]
+--rw identifier -> ../config/identifier
+--rw name -> ../config/name
+--rw config
| +--rw identifier? identityref
| +--rw name? string
| +--rw enabled? boolean
| +--rw default-metric? uint32
+--ro state
| +--ro identifier? identityref
| +--ro name? string
| +--ro enabled? boolean
| +--ro default-metric? uint32
+--rw static-routes
| +--rw static* [prefix]
| +--rw prefix -> ../config/prefix
| +--rw config
| | +--rw prefix? inet:ip-prefix
| | +--rw set-tag? oc-pt:tag-type
| | +--rw description? string
...
```
To add an entry to the `network-instance` list, the `add` method of the `network-instance` object is used to create instance `a`. Similarly a protocol of type `STATIC` and name of `DEFAULT` is added. Finally, a `static` route is added:
```python
ocni.network_instances.network_instance.add('a')
ocni.network_instances.network_instance['a'].protocols
ocni.network_instances.network_instance['a'].protocols.protocol.add(identifier='STATIC', name='DEFAULT')
rt = ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static.add("192.0.2.1/32")
```
The `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 `protocols` container, which is a member of the `network-instances` container.
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:
```python
rt.config.set_tag = 42
```
The tag value can then be accessed directly via the `rt` object, or via the original `ocni` object (which both refer to the same object in memory):
```python
# Retrieve the tag value
print(rt.config.set_tag)
# output: 42
# Retrieve the tag value through the original object
print(ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static["192.0.2.1/32"].config.set_tag)
# output: 42
```
In addition, PyangBind classes which represent `container` or `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:
```python
print(ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static["192.0.2.1/32"].get(filter=True))
# returns {'prefix': '192.0.2.1/32', 'config': {'set-tag': 42}}
```
The `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.
The `next-hop` element in this model is another list. This keyed data structure acts like a Python dictionary, and has the special method `add` to add items to it. YANG `leaf-list` types use the standard Python list `append` method to add items to it. Equally, a `list` can be iterated through using the same methods as a dictionary, for example, using `items()`:
```python
# Add a set of next_hops
for nhop in [(0, "192.168.0.1"), (1, "10.0.0.1")]:
nh = rt.next_hops.next_hop.add(nhop[0])
nh.config.next_hop = nhop[1]
# Iterate through the next-hops added
for index, nh in rt.next_hops.next_hop.items():
print("{}: {}".format(index, nh.config.next_hop))
```
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:
```python
# Try and set an invalid tag type
try:
rt.config.set_tag = "INVALID-TAG"
except ValueError as m:
print("Cannot set tag: {}".format(m))
```
### Serialising a Data Instance <a name="serialising"></a>
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:
* **XML** - the rules for this mapping are defined in [RFC 7950][rfc7950] - supported.
* **OpenConfig-suggested JSON** - the rules for this mapping are currently being written into a formal specification. This is the standard (`default`) format used by PyangBind. Some network equipment vendors utilise this serialisation format.
* **IETF JSON** - the rules for this mapping are defined in [RFC 7951][rfc7951] - some network equipment vendors use this 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:
```python
from pyangbind.lib.serialise import pybindIETFXMLEncoder
# Dump the entire instance as RFC 7950 XML
print(pybindIETFXMLEncoder.serialise(ocni))
```
This outputs the following XML fragment:
```xml
<openconfig-network-instance xmlns="http://openconfig.net/yang/network-instance">
<network-instances>
<network-instance>
<name>a</name>
<protocols>
<protocol>
<identifier>STATIC</identifier>
<name>DEFAULT</name>
<static-routes>
<static>
<prefix>192.0.2.1/32</prefix>
<config>
<set-tag>42</set-tag>
</config>
<next-hops>
<next-hop>
<index>0</index>
<config>
<next-hop>192.168.0.1</next-hop>
</config>
</next-hop>
<next-hop>
<index>1</index>
<config>
<next-hop>10.0.0.1</next-hop>
</config>
</next-hop>
</next-hops>
</static>
</static-routes>
</protocol>
</protocols>
</network-instance>
</network-instances>
</openconfig-network-instance>
```
Or, similarly, using OpenConfig-suggested JSON:
```python
import pyangbind.lib.pybindJSON as pybindJSON
print(pybindJSON.dumps(ocni, indent=2))
```
This outputs the following JSON structured text:
```json
{
"network-instances": {
"network-instance": {
"a": {
"name": "a",
"protocols": {
"protocol": {
"STATIC DEFAULT": {
"identifier": "STATIC",
"name": "DEFAULT",
"static-routes": {
"static": {
"192.0.2.1/32": {
"prefix": "192.0.2.1/32",
"config": {
"set-tag": 42
},
"next-hops": {
"next-hop": {
"0": {
"index": "0",
"config": {
"next-hop": "192.168.0.1"
}
},
"1": {
"index": "1",
"config": {
"next-hop": "10.0.0.1"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
```
Note here that the `static` list and all parents are represented as a JSON object (such that if this JSON is loaded elsewhere, a prefix can be referenced using `obj['network-instances']['network-instance']['a']['protocols']['protocol']['STATIC DEFAULT']['static-routes']['static']['192.0.2.1/32']`).
It is also possible to serialise a subset of the data, e.g., only one `list` or `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(ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'], mode='ietf', indent=2))
```
And the corresponding output:
```json
{
"openconfig-network-instance:identifier": "STATIC",
"openconfig-network-instance:name": "DEFAULT",
"openconfig-network-instance:static-routes": {
"static": [
{
"prefix": "192.0.2.1/32",
"config": {
"set-tag": 42
},
"next-hops": {
"next-hop": [
{
"index": "0",
"config": {
"next-hop": "192.168.0.1"
}
},
{
"index": "1",
"config": {
"next-hop": "10.0.0.1"
}
}
]
}
}
]
}
}
```
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.
### Deserialising a Data Instance <a name="deserialising"></a>
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 ingest 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:
```python
import binding
new_ocni = pybindJSON.load(os.path.join("json", "oc-ni.json"), binding, "openconfig_network_instance")
```
This creates a new instance of the `openconfig_network_instance` class that is within the `binding` module, and loads the data from `json/oc-ni.json` into it. The `new_ocni` object can then be manipulated as per any other class:
```python
# Manipulate the data loaded
print("Current tag: %d" % new_ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static['192.0.2.1/32'].config.set_tag)
# Outputs: 'Current tag: 42'
new_ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static['192.0.2.1/32'].config.set_tag += 1
print("New tag: %d" % new_ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static['192.0.2.1/32'].config.set_tag)
# 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 `pybindJSONDecoder`:
```python
# Load JSON into an existing class structure
from pyangbind.lib.serialise import pybindJSONDecoder
import json
ietf_json = json.load(open(os.path.join("json", "oc-ni_ietf.json"), 'r'))
pybindJSONDecoder.load_ietf_json(ietf_json, None, None, obj=new_ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'])
```
The direct `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:
```python
# Iterate through the classes - both the 192.0.2.1/32 prefix and 192.0.2.2/32
# prefix are now in the objects
for prefix, route in new_ocni.network_instances.network_instance['a'].protocols.protocol['STATIC DEFAULT'].static_routes.static.items():
print("Prefix: {}, tag: {}".format(prefix, route.config.set_tag))
# Output:
# Prefix: 192.0.2.2/32, tag: 256
# Prefix: 192.0.2.1/32, tag: 42
```
### Example Code <a name="example-code"></a>
This worked example can be found in the `docs/example/oc-network-instance` directory.
## Further Documentation <a name="documentation"></a>
Further information as to the implementation and usage of PyangBind can be found in the `docs/` directory -- the [README](docs/README.md) provides a list of documents and examples container therein.
## Licensing <a name="licensing"></a>
```
Copyright 2015, Rob Shakir (rjs@rob.sh)
Modifications copyright, the Pyangbind contributors.
This project has been supported by:
* Jive Communications, Inc.
* BT plc.
* Google, Inc.
* GoDaddy, LLC.
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.
```
## Acknowledgements <a name="acks"></a>
* This project was initiated as part of BT plc. Network Architecture 'future network management' projects.
* Additional development efforts were supported by [Jive Communications, Inc][jive].
* Current maintenance is supported by [Google][google].
* Key contributions have been made to this project by the following developers, and companies. Many thanks
are extended to them:
* [GoDaddy][godaddy], particularly Joey Wilhelm's herculean efforts to refactor test code to use the `unittest` framework.
* David Barroso, who initiated efforts to address Python 3 compatibility, and a number of other enhancements.
* Design, debugging, example code, and ideas have been contributed by:
* Members of the [OpenConfig][openconfig] working group.
* The managability team at Juniper Networks.
[img-pyangbind]: https://cdn.rob.sh/img/pyblogo_gh.png
[img-travis]: https://img.shields.io/travis/robshakir/pyangbind.svg
[img-codecov]: https://img.shields.io/codecov/c/github/robshakir/pyangbind.svg
[img-pypi]: https://img.shields.io/pypi/v/pyangbind.svg
[img-license]: https://img.shields.io/pypi/l/pyangbind.svg
[img-pyversion]: https://img.shields.io/pypi/pyversions/pyangbind.svg
[pyangbind-docs]: https://github.com/robshakir/pyangbind/tree/master/docs
[travis]: https://travis-ci.org/robshakir/pyangbind
[codecov]: https://codecov.io/gh/robshakir/pyangbind
[pypi-project]: https://pypi.org/project/pyangbind/
[license]: http://www.apache.org/licenses/LICENSE-2.0
[pyang]: https://github.com/mbj4668/pyang
[openconfig]: http://www.openconfig.net
[pypi]: https://pypi.org/
[rfc7950]: https://tools.ietf.org/html/rfc7950
[rfc7951]: https://tools.ietf.org/html/rfc7951
[jive]: https://www.jive.com/
[google]: https://www.google.com/
[godaddy]: https://www.godaddy.com/
================================================
FILE: README.rst
================================================
PyangBind
=========
PyangBind is a plugin for pyang which converts YANG data models into a Python class hierarchy, such that Python can be used to manipulate data that conforms with a YANG model.
This module provides the supporting classes and functions that PyangBind modules utilise, particularly:
* pyangbind.base.PybindBase - which is the parent class inherited by all container or module YANG objects.
* pyangbind.pybindJSON - which containers wrapper functions which can be used to help with serialisation of YANG to JSON.
* pyangbind.serialise.pybindJSONEncoder - a class that can be used as a custom encoder for the JSON module to serialise PyangBind class hierarchies to JSON.
* pyangbind.serialise.pybindJSONDecoder - a class that can be used as a custom decoder to load JSON-encoded instances of YANG models into a PyangBind class hierarchy.
* pyangbind.xpathhelper.YANGPathHelper - a class which can have objects registered against it, and subsequently retrieved from it using XPATH expressions. This module also includes parent classes that can be used to implement other helper modules of this nature.
* pyangbind.yangtypes: The various functions which generate python types that are used to represent YANG types, and some helper methods.
- pyangbind.yangtypes.is_yang_list and is_yang_leaflist are self explainatory, but may be useful.
- pyangbind.yangtypes.safe_name is used throughout PyangBind to determine how to map YANG element names into Python attribute names safely.
- pyangbind.yangtypes.RestrictedPrecisionDecimalType - generates wrapped Decimal types that has a restricted set of decimal digits - i.e., can deal with fraction-digits arguments in YANG.
- pyangbind.yangtypes.RestrictedClassType - generates types which wrap a 'base' type (e.g., integer) with particular restrictions. The restrictions are supplied as a dictionary, or with specific arguments if single restrictions are required. Currently, the restrictions supported are regexp matches, ranges, lengths, and restrictions to a set of values (provided as keys to a dict).
- pyangbind.yangtypes.TypedListType - generates types which wrap a list to restrict the objects that it may contain.
- pyangbind.yangtypes.YANGListType - generates types which wrap a class representing a container, such that it acts as a YANG list.
- pyangbind.yangtypes.YANGBool - a boolean class.
- pyangbind.yangtypes.YANGDynClass - generates types which consist of a wrapper (YANGDynClass) and a wrapped object which may be any other class. YANGDynClass is a meta-class that provides additional data on top of the attributes and functions of the wrapped class.
- pyangbind.yangtypes.ReferenceType - generates types which can use a pyangbind.xpathhelper.PybindXpathHelper instance to look up values - particularly to support leafrefs in YANG.
Usage documentation for PyangBind itself can be found on GitHub: https://github.com/robshakir/pyangbind
================================================
FILE: docs/README.md
================================================

# PyangBind Documentation
If you haven't already - it's best to start with the README.md file in the main repository. This provides a quick-start guide to PyangBind, including a walk-through of how to use PyangBind to manipulate an OpenConfig model. Reading this will give some context around where you might want to start reading in this documentation.
## Documentation
The `docs` directory contains the following documents:
* [Errors](errors.md) -- explains the errors that PyangBind classes will throw.
* [Extension Methods](extmethods.md) -- usage and intention of the `extmethods` functionality in PyangBind
* [Generic Methods](generic_methods.md) -- the methods that the PyangBind meta-class defines, as well as methods that are added for YANG-specific types such as `container` and `list`.
* [RPC](rpc.md) -- explains PyangBind's support for the YANG `rpc` statement, and how one may use this functionality.
* [Serialisation and Deserialisation](serialisation.md) -- covers how PyangBind's `lib.serialise` and `lib.pybindJSON` classes can be used to output and load instances of data that have been created with a program using PyangBind's classes.
* [Usage](usage.md) -- documents the command-line switches that PyangBind uses.
* [XPathHelper](xpathhelper.md) -- provides information relating to PyangBind's optional `XPathHelper` classes which are used to resolve XPATH expressions and can be used to traverse a data tree consisting on multiple models.
* [YANG](yang.md) -- gives an overview of how various YANG language features are supported in PyangBind.
## Examples
In order to allow new users to quickly see how PyangBind might work for them, some examples are included in this directory:
* [`example/oc-network-instance`](example/oc-network-instance) uses the OpenConfig `network-instance` module as an example and shows how one can build static routes using this module. The main directory's README.md provides a worked example of this.
* [`example/simple-rpc`](example/simple-rpc) shows how a YANG `rpc` definition can be manipulated when PyangBind classes are generated for it. The RPC document provides further explanation of this example.
* [`example/simple-serialise`](example/simple-serialise) shows how PyangBind's serialisation and deserialisation capabilities work. The serialisation document walks through this example.
In order to understand some of the internals of PyangBind a bit better, the `tests` directory may also be useful - this provides numerous test cases intended to ensure PyangBind keeps working the way one would expect, but can be a valuable source of pointers as to how things might work.
## If you're stuck...
Please open an issue. The author (singular for the moment!) tries to help out where he can.
================================================
FILE: docs/errors.md
================================================
# Errors thrown by PyangBind
**Note**: the functionality specified in this document is currently subject to some change. Feedback as to useful functionality is appreciated. Please open an issue.
PyangBind re-uses Python error types where possible, particularly:
* `KeyError` will be raised when an element does not have a particular key. The arguments to this error are a string.
* `ValueError` is raised when the supplied data does not match the YANG data type. This value is only raised where the input cannot be cast to the type that is stored in the data. For example, a YANG integer type (`int8`, `int16`, ... etc.) will accept `True` as an input but store the value `1`. This intentional and aims to provide a balance between duck-typing in Python, and the more strict typing in YANG:
* As of [docs@21/03/2016](https://github.com/robshakir/pyangbind/commit/0c28c057eeb7034c23c94a4e7ec09a9fd2ae00d0), the argument passed by PyangBind setters to ValueError is a dictionary - this can be accessed using code such as:
```python
try:
pybindobj.value = "anInvalidValue"
except ValueError as e:
# Check the args and types, just in case we have
# old bindings
if len(e.args) and isinstance(e.args[0], dict):
print "Hit a PyangBind ValueError"
for k, v in e.args[0].iteritems():
print "%s->%s" % (k, v)
else:
print unicode(e)
```
* The keys of the dictionary are:
- `error-string`: a simple error string that provides some insight (but not all information) as to the type that was not matched. It will currently capture the original YANG type that was specified, but no additional restrictions.
- `generated-type`: the dynamic class specification that PyangBind tried to generate for this type - this is often unwieldy, but tends to be useful for debugging.
- `defined-type`: the simple defined type, resolved to module if it is not native that the leaf is. Again this does not include all information.
* `AttributeError` will be raised when an invalid member of a YANG object is specified, or a method does not exist. In some cases, since PyangBind's meta-class defines some methods which are used to modify mutable objects in place (to capture changes) then `dir(...)` for the object may show methods that return `AttributeError` when they are passed to the super-class.
================================================
FILE: docs/example/.gitignore
================================================
binding.py
================================================
FILE: docs/example/oc-network-instance/generate_bindings.sh
================================================
#!/bin/bash
SDIR="$(cd -P "$(dirname "$[0]")" && pwd)"
DDIR=$SDIR/models
mkdir $DDIR
git clone https://github.com/openconfig/public $DDIR
export PYBINDPLUGIN=`/usr/bin/env python3 -c 'import pyangbind; import os; print ("{}/plugin".format(os.path.dirname(pyangbind.__file__)))'`
pyang --plugindir $PYBINDPLUGIN -f pybind -o $SDIR/binding.py -p $DDIR/ $DDIR/release/models/network-instance/openconfig-network-instance.yang
echo "bindings.py successfully generated on current directory!"
rm -rf $DDIR
================================================
FILE: docs/example/oc-network-instance/json/oc-ni.json
================================================
{
"network-instances": {
"network-instance": {
"a": {
"name": "a",
"protocols": {
"protocol": {
"STATIC DEFAULT": {
"identifier": "STATIC",
"name": "DEFAULT",
"static-routes": {
"static": {
"192.0.2.1/32": {
"prefix": "192.0.2.1/32",
"config": {
"set-tag": 42
},
"next-hops": {
"next-hop": {
"0": {
"index": "0",
"config": {
"next-hop": "192.168.0.1"
}
},
"1": {
"index": "1",
"config": {
"next-hop": "10.0.0.1"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
================================================
FILE: docs/example/oc-network-instance/json/oc-ni_ietf.json
================================================
{
"openconfig-network-instance:static-routes": {
"static": [
{
"prefix": "192.0.2.1/32",
"config": {
"set-tag": 42
},
"next-hops": {
"next-hop": [
{
"index": "0",
"config": {
"next-hop": "192.168.0.1"
}
},
{
"index": "1",
"config": {
"next-hop": "10.0.0.1"
}
}
]
}
},
{
"prefix": "192.0.2.2/32",
"config": {
"set-tag": 256
},
"next-hops": {
"next-hop": [
{
"index": "0",
"config": {
"next-hop": "192.168.0.1"
}
},
{
"index": "1",
"config": {
"next-hop": "10.0.0.1"
}
}
]
}
}
]
}
}
================================================
FILE: docs/example/oc-network-instance/static_route_example.py
================================================
#!/usr/bin/env python
from __future__ import print_function, unicode_literals
from binding import openconfig_network_instance
import pyangbind.lib.pybindJSON as pybindJSON
import os
# Instantiate a copy of the pyangbind-kettle module and add a network instance.
ocni = openconfig_network_instance()
ocni.network_instances.network_instance.add("a")
ocni.network_instances.network_instance["a"].protocols.protocol.add(identifier="STATIC", name="DEFAULT")
# Add an entry to the static route list
rt = (
ocni.network_instances.network_instance["a"]
.protocols.protocol["STATIC DEFAULT"]
.static_routes.static.add("192.0.2.1/32")
)
# Set a tag for the route
rt.config.set_tag = 42
# Retrieve the tag value
print(rt.config.set_tag)
# Retrieve the tag value through the original object
print(
ocni.network_instances.network_instance["a"]
.protocols.protocol["STATIC DEFAULT"]
.static_routes.static["192.0.2.1/32"]
.config.set_tag
)
# Use the get() method to see the content of the classes
# using the filter=True keyword to get only elements that
# are not empty or the default
print(
ocni.network_instances.network_instance["a"]
.protocols.protocol["STATIC DEFAULT"]
.static_routes.static["192.0.2.1/32"]
.get(filter=True)
)
# Add a set of next_hops
for nhop in [(0, "192.168.0.1"), (1, "10.0.0.1")]:
nh = rt.next_hops.next_hop.add(nhop[0])
nh.config.next_hop = nhop[1]
# Iterate through the next-hops added
for index, nh in rt.next_hops.next_hop.items():
print("%s: %s" % (index, nh.config.next_hop))
# Try and set an invalid tag type
try:
rt.config.set_tag = "INVALID-TAG"
except ValueError as m:
print("Cannot set tag: %s" % m)
# Dump the entire instance as JSON in PyangBind format
print(pybindJSON.dumps(ocni, indent=2))
# Dump the static routes instance as JSON in IETF format
print(
pybindJSON.dumps(
ocni.network_instances.network_instance["a"].protocols.protocol["STATIC DEFAULT"], mode="ietf", indent=2
)
)
# Load the "json/oc-ni.json" file into a new instance of
# "openconfig_network_instance". We import the module here, such that a new
# instance of the class can be created by the deserialisation code.
# Note that you may need to provide the absolute path to oc-ni.json.
import binding
new_ocni = pybindJSON.load(os.path.join("json", "oc-ni.json"), binding, "openconfig_network_instance")
# Manipulate the data loaded
print(
"Current tag: %d"
% new_ocni.network_instances.network_instance["a"]
.protocols.protocol["STATIC DEFAULT"]
.static_routes.static["192.0.2.1/32"]
.config.set_tag
)
new_ocni.network_instances.network_instance["a"].protocols.protocol["STATIC DEFAULT"].static_routes.static[
"192.0.2.1/32"
].config.set_tag += 1
print(
"New tag: %d"
% new_ocni.network_instances.network_instance["a"]
.protocols.protocol["STATIC DEFAULT"]
.static_routes.static["192.0.2.1/32"]
.config.set_tag
)
# Load JSON into an existing class structure
from pyangbind.lib.serialise import pybindJSONDecoder
import json
# Provide absolute path to oc-ni_ietf.json if needed.
ietf_json = json.load(open(os.path.join("json", "oc-ni_ietf.json"), "r"))
pybindJSONDecoder.load_ietf_json(
ietf_json, None, None, obj=new_ocni.network_instances.network_instance["a"].protocols.protocol["STATIC DEFAULT"]
)
# Iterate through the classes - both the 192.0.2.1/32 prefix and 192.0.2.2/32
# prefix are now in the objects
for prefix, route in (
new_ocni.network_instances.network_instance["a"].protocols.protocol["STATIC DEFAULT"].static_routes.static.items()
):
print("Prefix: %s, tag: %d" % (prefix, route.config.set_tag))
================================================
FILE: docs/example/simple-rpc/.gitignore
================================================
rbindings
rbindings/*
================================================
FILE: docs/example/simple-rpc/generate_bindings.sh
================================================
#!/bin/bash
SDIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYBINDPLUGIN=`/usr/bin/env python -c 'import pyangbind; import os; print "%s/plugin" % os.path.dirname(pyangbind.__file__)'`
pyang --plugindir $PYBINDPLUGIN -f pybind --build-rpcs --split-class-dir $SDIR/rbindings simple_rpc.yang
echo "Bindings successfully generated!"
================================================
FILE: docs/example/simple-rpc/json/rpc-output.json
================================================
{
"simple_rpc:response-id": 32,
"elements": [
{ "response-value": "return-one" },
{ "response-value": "return-two" }
]
}
================================================
FILE: docs/example/simple-rpc/simple-rpc.py
================================================
#!/usr/bin/env python
from __future__ import print_function, unicode_literals
from rbindings.simple_rpc_rpc.test.input import input
from rbindings.simple_rpc_rpc.test.output import output
from pyangbind.lib.serialise import pybindJSONDecoder
from pyangbind.lib.pybindJSON import dumps
import pprint
import os
import json
pp = pprint.PrettyPrinter(indent=4)
# Create an input instance
rpc_input = input()
rpc_input.input_container.argument_one = "test_call"
rpc_input.input_container.argument_two = 32
print(dumps(rpc_input, mode="ietf"))
# Load an output from IETF JSON
rpc_output = output()
fn = os.path.join("json", "rpc-output.json")
json_obj = json.load(open(fn, "r"))
pybindJSONDecoder.load_ietf_json(json_obj, None, None, obj=rpc_output)
print(rpc_output.response_id)
pp.pprint(rpc_output.get(filter=True))
================================================
FILE: docs/example/simple-rpc/simple_rpc.yang
================================================
module simple_rpc {
yang-version "1";
namespace "http://rob.sh/yang/examples/rpc";
prefix "srpc";
rpc test {
input {
container input-container {
leaf argument-one {
type string;
}
leaf argument-two {
type uint8;
}
}
}
output {
leaf response-id {
type uint32;
}
list elements {
leaf response-value {
type string;
}
}
}
}
}
================================================
FILE: docs/example/simple-serialise/.gitignore
================================================
sbindings.*
================================================
FILE: docs/example/simple-serialise/generate_bindings.sh
================================================
#!/bin/bash
SDIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYBINDPLUGIN=`/usr/bin/env python -c 'import pyangbind; import os; print "%s/plugin" % os.path.dirname(pyangbind.__file__)'`
pyang --plugindir $PYBINDPLUGIN -f pybind -o $SDIR/sbindings.py simple_serialise.yang
echo "Bindings successfully generated!"
================================================
FILE: docs/example/simple-serialise/json/simple-instance-additional.json
================================================
{
"a-list": {
"entry-three": {
"the-key": "entry-three"
}
}
}
================================================
FILE: docs/example/simple-serialise/json/simple-instance-ietf.json
================================================
{
"simple_serialise:a-container": {
"a-value": 8
},
"simple_serialise:a-list": [
{"the-key": "entry-one"},
{"the-key": "entry-two"}
]
}
================================================
FILE: docs/example/simple-serialise/json/simple-instance.json
================================================
{
"a-container": {
"a-value": 8
},
"a-list": {
"entry-one": {
"the-key": "entry-one"
},
"entry-two": {
"the-key": "entry-two"
}
}
}
================================================
FILE: docs/example/simple-serialise/simple-serialise.py
================================================
#!/usr/bin/env python
from __future__ import unicode_literals, print_function
import pprint
import pyangbind.lib.pybindJSON as pbJ
import sbindings
import os
pp = pprint.PrettyPrinter(indent=4)
# Load an instance from file using PyBind's native JSON format
loaded_object = pbJ.load(os.path.join("json", "simple-instance.json"), sbindings, "simple_serialise")
pp.pprint(loaded_object.get(filter=True))
# Load an instance from a corresponding string using the native JSON format
string_to_load = open(os.path.join("json", "simple-instance.json"), "r")
string_to_load = string_to_load.read().replace("\n", "")
loaded_object_two = pbJ.loads(string_to_load, sbindings, "simple_serialise")
pp.pprint(loaded_object_two.get(filter=True))
# Load an instance from an IETF-JSON file
loaded_ietf_obj = pbJ.load_ietf(os.path.join("json", "simple-instance-ietf.json"), sbindings, "simple_serialise")
pp.pprint(loaded_ietf_obj.get(filter=True))
# Load an instance from an IETF-JSON string
string_to_load = open(os.path.join("json", "simple-instance-ietf.json"), "r")
string_to_load = string_to_load.read().replace("\n", "")
loaded_ietf_obj_two = pbJ.loads_ietf(string_to_load, sbindings, "simple_serialise")
pp.pprint(loaded_ietf_obj_two.get(filter=True))
# Load into an existing instance
from pyangbind.lib.serialise import pybindJSONDecoder
import json
# Create a new instance
existing_instance = sbindings.simple_serialise()
existing_instance.a_list.add("entry-one")
existing_instance.a_list.add("entry-two")
fn = os.path.join("json", "simple-instance-additional.json")
data_to_load = json.load(open(fn, "r"))
pybindJSONDecoder.load_json(data_to_load, None, None, obj=existing_instance)
pp.pprint(existing_instance.a_list.keys())
pp.pprint(existing_instance.get(filter=True))
# Serialise objects to JSON
print(pbJ.dumps(existing_instance, filter=True))
print(pbJ.dumps(existing_instance, filter=True, mode="ietf"))
print(pbJ.dumps(existing_instance.a_list, select={"the-key": "entry-one"}))
print(pbJ.dumps(existing_instance.a_list, select={"the-key": "entry-one"}, mode="ietf"))
================================================
FILE: docs/example/simple-serialise/simple_serialise.yang
================================================
module simple_serialise {
yang-version "1";
namespace "http://rob.sh/yang/examples/ss";
prefix "ss";
container a-container {
leaf a-value {
type int8;
}
}
list a-list {
key "the-key";
leaf the-key {
type string;
}
}
}
================================================
FILE: docs/extmethods.md
================================================
# Extension Methods in PyangBind
PyangBind is designed both as a means to generate data instances for YANG modules, but also as a software component that can be used in an NMS implementation. To that end, there can be requirements to tie methods to particular data instances in the tree.
The extension methods (`extmethods`) functionality provides a means to tie arbitrary methods to a particular path in the tree.
## Contents
* [Initialisation of classes with `extmethods`](#initialisation)
* [Example calls with `extmethods`](#example-calls)
## Initialising Classes with `extmethods` <a name="initialisation"></a>
To use extension methods, the PyangBind bindings must be generated with `--use-extmethods`. This ensures that the `extmethods` dictionary is propagated from parent to child objects as they are instantiated.
The `extmethods` dictionary is of the form:
```python
{
"/path/to/object/one": <Class Instance>,
"/path/to/object/two": <Class instance>
}
```
Where `/path/to/object/one` is defined as the XPATH to the object that the method is to be bound to *without* any filtering attributes. That is to say, for `/bgp/global/config/as` the path specified is simply `/bgp/global/config/as` whereas for `/bgp/neighbors/neighbor[peer-addr='192.0.2.1']/config/peer-as` then the path specified is `/bgp/neighbors/neighbor/config/peer-as`. It is not possible to bind an extension method to a single instance.
Each object, as it is instantiated, then consults the `extmethods` dictionary, if it finds an entry which corresponds to its exact path, it inherits all methods of the class instance provided - and will proxy any calls to itself to that class. The names of the methods are prefixed by an underscore in order to avoid collisions between actual data element names and method names.
## Example Calls with `extmethods` <a name="example-calls"></a>
If one has the following class definition:
```python
from openconfig import openconfig_bgp
class BgpNeighborHelper(object):
def soft_reset(self, *args, **kwargs):
# Do a soft reset of the neighbor
pass
def hard_reset(self, *args, **kwargs):
# Do a hard reset of the neighbor
pass
```
A set of PyangBind classes (e.g., OpenConfig BGP) can be initialised with an `extmethods` dictionary that provides a mapping between an instance of the `BGPNeighborHelper` class and an XPATH expression. For example, between the `config/enabled` leaf of each BGP neighbor and this class:
```python
bgp_helper = BGPNeighborHelper()
extmethods = {
'/bgp/neighbors/neighbor/neighbor/config/enabled': bgp_helper
}
ocbgp = openconfig_bgp(extmethods=extmethods)
```
Each entry within the `/bgp/neighbors/neighbor` list would have methods named `soft_reset` and `hard_reset` bound to their `config/enabled` leaf.
i.e., a hard reset or soft reset could be initiated by calling:
```python
ocbgp.bgp.neighbors.neighbor["192.0.2.1"].config.enabled._hard_reset()
ocbgp.bgp.neighbors.neighbor["192.0.2.1"].config.enabled._soft_reset()
```
When this call is made, the instance of the `BgpNeighborHelper` class named `bgp_helper` (which was supplied in the `extmethods` dictionary) will receive a call to the `soft_reset` or `hard_reset` method).
In addition to the arguments and keyword arguments that are supplied to the function (which are directly proxied through), two additional `kwargs` are added:
* `caller` - this is a list which provides the components of the path of the actual object that the method was called against. For example in the above case this would correspond to `/bgp/neighbors/neighbor[peer-addr="192.0.2.1"].config.enabled` - and hence be `['bgp', 'neighbors', 'neighbor[peer-addr='192.0.2.1'], 'config', 'enabled']`. This allows disambiguation of calls that may come from multiple sources.
* `path_helper` which provides a reference to the `path_helper` class that is being used by the classes. This can allow the `extmethod` to retrieve the data instance that called it if required.
================================================
FILE: docs/generic_methods.md
================================================
# Generic Methods Provided through PyangBind
PyangBind's `YANGDynClass` function generates meta-classes around the class that is defined as the `base_type`/`base` when the class is generated. The wrapper that is provided gives a set of functions and variables that allow YANG-specific information to be stored alongside the value of the class.
Some of these methods are generically useful when handling the classes, as well as internally to PyangBind.
In general, methods are defined as `_<methodname>` such that clashes with the elements within YANG containers can be avoided - although for historical reasons, in some cases the `_` is omitted.
## Contents
* [YANGDynClass Methods](#ydcmethods) - generic to all PyangBind wrapped objects other than those corresponding to YANG modules.
* [YANG Container Methods](#containermethods) - methods defined for PyangBind objects corresponding to YANG `container` items.
* [YANG List Methods](#listmethods) - methods defined for PyangBind objects corresponding to YANG `list` items.
## Methods/Variables Defined in YANGDynClass <a name="#ydcmethods"></a>
### `default()`
Where a YANG type has a default value specified, the default method returns this value. The actual class' value is set to the null value of the base class (e.g., `unicode` objets return `''`, `int` objects return 0), whereas the default value is stored in `_default`. If there is no default defined, then `_default` is set to False.
### `_changed()`
Returns `True` when the class (or a child of the class if it represents a container) has been set. This allows subsets of the data tree that have been manipulated to be retrieved as opposed to all elements.
### `yang_name()`
Returns the name of the data element as defined in the YANG module rather than the `safe_name` returned value.
### `_add_metadata()` & `_metadata`
The `_metadata` element is a dictionary which can stores any meta-data that was provided as part of the data instance. For example, the Juniper example JSON instance for BGP global configuration may be akin to:
```python
"config" : {
"@router-id" : {
"inactive" : true
},
"router-id" : "10.10.10.10"
}
```
In this case, the `router_id` member of the `config` class will have a dictionary of the form `{"inactive": True}` stored with it.
The `_add_metadata()` method allows new metadata to be added. The arguments expected are a key, followed by a metadata value, e.g.:
```python
config.router_id._add_metadata("inactive", True)
```
will add the metadata shown above.
### `_register_path()` & `_path()`
Both of these methods currently return the same data: a list defining the elements of the path to the data element in the tree. For example, the entry for `/bgp/neighbors/neighbor[peer-address='192.0.2.1']/config/peer-as` will return `['bgp', 'neighbors', 'neighbor[peer-address='192.0.2.1']', 'config', 'peer-as']`.
### `_yang_path()`
Returns the path to the YANG object as a string.
### `_namespace`
Returns the namespace (from the `namespace` statement) of the YANG module that defined the element.
### `_defining_module`
Returns the module name from the `module` statement of the YANG module that defined the element. This is used in a number of cases within IETF JSON serialisation/deserialisation.
### `_choice`
If the element is defined within a `choice` statement, then this value carries the name of the `case` that it is a part of. This is used to call `_unset_X` where X is the element's `safe_name` when a member of another `case` is set (since two `case` elements of a choice cannot co-exist).
### `_parent`
Returns a reference to the element's parent class - for example, if one has a list entry at `/bgp/neighbors/neighbor[peer-address='2001:DB8::1']/config/peer-as` then `_parent` of the `peer-as` object refers to the `config` object, and the corresponding `_parent` of the `config` object refers to the list entry for the neighbor.
This is generally useful in the cases where one has a `leafref` value that refers to the key of a list and the application requires the list entry itself (i.e., in this case one can do: `leafref_leaf._parent` to get to the list entry of a list).
### `_is_keyval`
Set to `True` if this value is the key of a list.
### `_is_config`
Set to True if the node is configurable within the YANG schema - reflecting the YANG `config` statement.
## YANG Container (`PybindBase`) defined Methods <a name="containermethods"></a>
### `elements()`
Returns a list of the names of the elements of the container. This can be used when iterating, although `for child in container` can also be used.
### `get(filter=<bool>)`
Returns a nested set of dictionaries that represent the current container. The filter argument provides a means to get only those elements that have changed during the current manipulation of the data tree (including being deserialised from a data instance):
```python
# Setup of data omitted
>> import pprint
>> pp = pprint.PrettyPrinter(indent=4)
>> pp.pprint(r.tables.get(filter=True))
{ 'table': { 'AGGREGATE': { 'config': { 'table-name': u'AGGREGATE'},
'table-name': u'AGGREGATE'},
'BGP': { 'config': { 'table-name': u'BGP'},
'table-name': u'BGP'},
'STATIC': { 'config': { 'table-name': u'STATIC'},
'table-name': u'STATIC'}}}
```
This may be used as an alternative to serialising/deserialising instances especially for debugging. In general, the serailisation classes will use this format as a intermediary to be able to retrieve data from the classes.
## YANG List Methods <a name="listmethods"></a>
PyangBind provides two special methods for YANG `list` objects:
### `add(<keyspec>)`
Adds a new entry to the list. In the case that the list is a keyed list - then the value returned is a reference to the newly created list entry. In the case that the list is not keyed, the value returned is the key value (a UUID) that has been defined internally by PyangBind.
The key specification can be of three forms:
* A value representing the key - in the case of a list with a single key, then the entire value is used as a key.
* A space-separated string representing multiple keys. In this case, the key ordering is as specified in the `key` leaf in the YANG module, and the string is split at each space. For example, a list two with a key specification of `key "srcip index"` supplies with `.add("192.0.2.1 1")` would set `srcip=192.0.2.1` and `index=1`. The key values will cast the split string into the relevant type for storage in the corresponding list entry.
* A set of keyword arguments for each key. For example, if the same list as above were called with `.add(index=1, srcip="192.0.2.1")` then the keyword arguments for each key would be extracted. In this case, order does not matter.
### `delete(<keyspec>)`
Removes the key value specified by `keyspec` from the list. The logic for the format of `keyspec` is the same as `add`.
### `_new_item()`
In some cases it is preferable to create a valid object outside of the context of it being added to the list (for example, in cases where there is some action performed around the `add()` call by the program consuming PyangBind's classes). To this end, the `_new_item()` method (called as `yang_list._new_item())` returns an empty instance of a member of the list, which can be populated.
### `append(object)`
Where an item has been created without being added to the list, it can be added using the `append()` function. The object supplied as the `obj` argument is used to extract the list key which is to be used for the item. As per a standard Python `list` item's `append()` method, there is no return from this function.
================================================
FILE: docs/rpc.md
================================================
# RPCs in PyangBind
PyangBind generates bindings for RPCs that are specified within a YANG module. The assumption is made that an RPC is not bound to any particular location within the data tree (the YANG 1.1 `action` statement is intended to meet this requirement). To this end, bindings are generated within a module named `<yang module name>_rpcs`.
All RPC bindings have the property `register_paths` set to `False`. This results in them never using a `path_helper` object that is handed to them for `register()` or `unregister()` purposes. A `path_helper` class will still be used to resolve `leafref` values (and other XPATH expressions) if required.
An class generated for an RPC has two member containers - `input` and `output` as per the specification provided in RFC6020. The corresponding data definitions are within these two elements (which act as per YANG containers).
## Contents
* [Example RPC](#examplerpc)
* [Generating an RPC `input`](#exinput)
* [Parsing an RPC `output`](#exoutput)
* [Example Code](#excode)
## Example RPC <a name="examplerpc"></a>
An example simple RPC could be defined as:
```yang
rpc test {
input {
container input-container {
leaf argument-one {
type string;
}
leaf argument-two {
type uint8;
}
}
}
output {
leaf response-id {
type uint32;
}
list elements {
leaf response-value {
type string;
}
}
}
}
```
In this definition, the RPC `test` has an input that takes a `container` with two arguments (`argument-one` and `argument-two`) specified within it. It outputs an object that has a single `response-id` and a list of `elements` wtihin the reply. This list is not keyed (RPC outputs are defined to be `config false`).
### Generating an RPC Input <a name="exinput"></a>
To generate an input for an RPC, the `input` container can be directly imported. If the example module above is generated with `--split-class-dir` into a module directory named `rbindings` then, for example:
```python
from rbindings.simple_rpc_rpc.test.input import input
```
The `input` class can be instantiated and populated as per any other PyangBind class that represents a container:
```python
rpc_input = input()
rpc_input.input_container.argument_one = "test_call"
rpc_input.input_container.argument_two = 32
```
The object generated can be serialised to IETF JSON as per any other container using `dumps` from `pyangbind.lib.pybindJSON`:
```python
>> print(dumps(rpc_input, mode="ietf"))
{
"simple_rpc:input-container": {
"argument-two": 32,
"argument-one": "test_call"
}
}
```
### Parsing an RPC Output <a name="exoutput"></a>
In a similar manner, an RPC output can be read back into the corresponding `output` class using the standard deserialisation functionality in PyangBind. For example:
```python
from rbindings.simple_rpc_rpc.test.output import output
rpc_output = output()
fn = os.path.join("json", "rpc-output.json")
json_obj = json.load(open(fn, 'r'))
pybindJSONDecoder.load_ietf_json(json_obj, None, None, obj=rpc_output)
```
The output class can then be manipulated and/or read in Python:
```python
>>> print(rpc_output.response_id)
32
```
### Example Code
The RPC example here can be found in `docs/example/simple-rpc`.
================================================
FILE: docs/serialisation.md
================================================
# Serialisation and Deserialisation of YANG-modelled Data
PyangBind provides a set of helper classes which allow data to be loaded from, or serialised to a loaded data format. At the time of writing, the supported formats are:
* XML - defined in [RFC 7950](https://tools.ietf.org/html/rfc7950)
* IETF JSON - defined in `draft-ietf-netmod-yang-json-08`.
* OpenConfig/PyangBind JSON - which does not currently have a published specification.
In the future, it is expected that an XML serialisation module may be required, given the current bias of devices towards this serialisation format.
## Contents
* [Loading from JSON - Entire Module](#load-json-module)
- [Load Functions](#load-functions)
- [`loads` and `loads_ietf`](#json-loads)
- [`load` and `load_ietf`](#json-load)
- [Example Loading (Deserialisation)](#example-load)
- [Loading OpenConfig/PyangBind JSON-encoded Data](#example-load-oc)
- [Loading IETF-JSON encoded Data](#example-load-ietf)
* [Loading Data into an Existing Instance](#load-json-existing)
- [Example of Loading to Existing Instances](#load-json-existing-example)
* [Serialising Data into XML](#serialising-xml)
* [Serialising Data into JSON](#serialising-json)
- [Example Serialisation](#example-serialisation)
* [Example Code](#example-code)
## Loading from JSON - Entire Module <a name="load-json-module"></a>
The module `pyangbind.lib.pyangbindJSON` provides a wrapper around the encoder and decoder classes that are defined in `pyangbind.lib.serialise`. These methods are heavily biased towards loading an entire module into a new instance of the data tree.
The functions `loads`, `load`, `loads_ietf` and `load_ietf` are analagous to the Python `json` module's functions. The `loads.*` functions load from a string which is expected to be valid JSON, whereas the `load.*` functions load from a file name that is specified to the modules.
### Load Functions <a name="load-json-module"></a>
#### `loads(data, python_module, class_name)` and `loads_ietf(data, python_module, class_name)` <a name="json-loads"></a>
The arguments to the `loads` functions are expected to be:
* `data` - a JSON-encoded string that can be loaded using Python's `json.load()` function.
* `python_module` - the module within which the class that is to be instantiated is defined. In the case that `-o <filename>` is used, then this is `filename` as this code is a Python module that can be loaded with `import`. In the case that `--split-class-dir <directory>` has been used then it is `directory`.
* `class_name` - the name of the Python class that is found within `module`. This is the safe name (i.e., Python-safe - using no reserved Python keywords and substituting hyphens for underscores) of the YANG module that has been compiled - e.g., `openconfig_bgp`.
These functions return a PyangBind class instance with the data from `data` loaded into it.
#### `load(filename, python_module, class_name)` and `load_ietf(filename, python_module, class_name)` <a name="json-load"></a>
The arguments to the `load` and `load_ietf` functions are identitical to those of the `loads` functions, other than the `filename` argument is a path to a file that can be loaded using `open(filename, 'r')`.
These functions return a PyangBind class instance with the data from `filename` loaded.
### Example Loading (Deserialisation) <a name="example-load"></a>
With a simple module - such as the following:
```yang
module simple_serialise {
yang-version "1";
namespace "http://rob.sh/yang/test/ss";
prefix "ss";
container a-container {
leaf a-value {
type int8;
}
}
list a-list {
key "the-key";
leaf the-key {
type string;
}
}
}
```
And bindings generated using:
```
$ pyang --plugindir $PYBINDPLUGIN -f pybind -o sbindings.py simple_serialise.yang
```
#### Loading OpenConfig/PyangBind JSON-encoded Data <a name="example-load-oc"></a>
An example instance of data for the above module encoded as PyangBind JSON looks like the following:
```json
{
"a-container": {
"a-value": 8
},
"a-list": {
"entry-one": {
"the-key": "entry-one"
},
"entry-two": {
"the-key": "entry-two"
}
}
}
```
To load this, using `load` and `loads` the following code can be used:
```python
#!/usr/bin/env python
import pprint
import pyangbind.lib.pybindJSON as pbJ
import sbindings
pp = pprint.PrettyPrinter(indent=4)
loaded_object = pbJ.load("json/simple-instance.json", sbindings, "simple_serialise")
pp.pprint(loaded_object.get(filter=True))
string_to_load = open('json/simple-instance.json', 'r').read().replace('\n', '')
loaded_object_two = pbJ.loads(string_to_load, sbindings, "simple_serialise")
pp.pprint(loaded_object_two.get(filter=True))
```
In both cases, the `python_module` name used is the name of the bindings file generated (`sbindings`) and the modulename is as specified in the YANG module (i.e., `simple_serialise`).
Both loading methods will return the same output:
``python
{ 'a-container': { 'a-value': 8},
'a-list': { u'entry-one': { 'the-key': u'entry-one'},
u'entry-two': { 'the-key': u'entry-two'}}}
```
#### Loading IETF JSON-encoded Data <a name="example-load-ietf"></a>
Loading IETF encoded data is almost identical, other than using the `loads_ietf` and `load_ietf` functions in place of `loads` and `load` respectively.
The corresponding example IETF-encoded JSON object for the above data is:
```json
{
"simple_serialise:a-container": {
"a-value": 8
},
"simple_serialise:a-list": [
{"the-key": "entry-one"},
{"the-key": "entry-two"}
]
}
```
This can be loaded in the same way using the following Python:
```python
# Load an instance from an IETF-JSON file
loaded_ietf_obj = pbJ.load_ietf("simple-instance-ietf.json", sbindings,
"simple_serialise")
pp.pprint(loaded_ietf_obj.get(filter=True))
# Load an instance from an IETF-JSON string
string_to_load = open('json/simple-instance-ietf.json', 'r').read().replace('\n',
'')
loaded_ietf_obj_two = pbJ.loads_ietf(string_to_load, sbindings,
"simple_serialise")
pp.pprint(loaded_ietf_obj_two.get(filter=True))
```
Again, the data loaded is idential to that shown above.
## Loading Data into an Existing Instance <a name="load-json-existing"></a>
In a number of cases, it is desirable to load data from a serialised JSON input into an existing set of PyangBind classes - such as when accepting input from an external API. Doing this requires direct access of the `pyangbind.lib.serialise` classes, rather than the `pyangbind.lib.pybindJSON` helper functions. The relevant functions are those within `pybindJSONDecoder` - particularly `load_ietf_json` and `load_json`.
In order to use these functions (which are generally directly-called by the corresponding `pyangbind.lib.pybindJSON` functions - then there is a requirement to specify an already existing object, and skip the class instantiation stage of the loading functions.
When calling the load functions, the following format is expected to load into an existing object:
```python
load_json(input_data, None, None, obj=existing_object, path_helper=path_helper,
extmethods=extmethods, overwrite=overwrite):
load_ietf_json(input_data, None, None, obj=existing_object, path_helper=path_helper,
extmethods=extmethods, overwrite=overwrite)
```
Where:
* `input_data` is a iterable object that corresponds to loaded JSON data.
* `existing_object` is the object that should be loaded into - i.e., an instantiated set of PyangBind classes.
* `path_helper`, `extmethods` - are the corresponding XPathHelper and extension methods that are to be used if required. In the case that these do not differ from the parent, they will be inherited.
* `overwrite` determines whether the existing instance's data should be overwritten by the loaded data.
#### Example of Loading to Existing Instances <a name="load-json-existing-example"></a>
Using the same module as above, with the loaded instance in question (defining `/a-list[the-key='entry-one']` and `/a-list[the-key='entry-two']` then data can be loaded using the following `load_json` call:
```python
data_to_load = json.load(open('json/simple-instance-additional.json','r'))
pybindJSONDecoder.load_json(data_to_load, None, None, obj=existing_instance)
```
The `existing_instance` object is modified in-place, and hence the list acquires the additional `entry-three` data defined in the `simple-instance-additional.json` file:
```python
pp.pprint(existing_instance.a_list.keys())
# Outputs:
# [u'entry-two', u'entry-three', u'entry-one']
```
## Serialising Data into XML <a name="serialising-xml"></a>
In order to serialise a PyangBind class instance JSON, the `pybindIETFXMLEncoder` class defined in `pyangbind.lib.serialise` can be used. There are two relevant class methods `pybindIETFXMLEncoder.serialise` and `pybindIETFXMLEncoder.encode`,
which emit a string or an [`lxml.objectify`](https://lxml.de/objectify.html) instance, respectively.
```
pybindIETFXMLEncoder.serialise(obj, filter=<bool>, pretty_print=<bool>)
```
* `obj` - which is a PyangBind class instance that is to be dumped. It is expected to have a `get` method, hence be a list or a container.
* `filter` - analagous to the `filter` argument to a PyangBind class' `get` method (see the documentation relating to generic methods), which determines whether the entire tree, or just the changed elements are to be dumped.
* `pretty_print` - determines whether to apply pretty-printing to the emitted XML string (e.g. newlines and 2-space indentation).
```
pybindIETFXMLEncoder.encode(obj, filter=<bool>, pretty_print=<bool>)
```
* The `obj` and `filter` arguments are as per `pybindIETFXMLEncoder.serialise`.
## Serialising Data into JSON <a name="serialising-json"></a>
In order to serialise a PyangBind class instance into JSON, the `dump`, `dumps` functions defined in `pyangbind.lib.pybindJSON` are used. These functions take a `mode` keyword argument which determines whether they dump IETF-specified JSON or PyangBind JSON.
```
dump(obj, filename, indent=<int>, filter=<bool>, skip_subtrees=<list>, mode="default")
```
* `obj` - which is a PyangBind class instance that is to be dumped. It is expected to have a `get` method, hence be a list or a container.
* `filename` - the file to which the JSON should be written.
* `indent` - the number of spaces to use to indent the JSON.
* `filter` - analagous to the `filter` argument to a PyangBind class' `get` method (see the documentation relating to generic methods), which determines whether the entire tree, or just the changed elements are to be dumped.
* `skip_subtrees` - a list of paths (absolute rather than relative) that should be pruned from the output JSON. This is useful if multiple output files are used to save data instances.
* `mode` - a string specifying the JSON encoding to be output -- currently either "default" or "ietf".
```
dumps(obj, indent=4, filter=True, skip_subtrees=[], select=False, mode="default")
```
* The `obj`, `indent`, `filter`, `skip_subtrees` and `mode` arguments of dumps are as per `dump`.
* `select` - when provided is expected to be a dictionary of the form `{ "element_name": value }`. When this is specified only elements where `obj.element_name == value` are output. This is useful when using query parameters to select subsets of an object, or list.
### Example Serialisation <a name="example-serialisation"></a>
In order to serialise an instance of the `simple_serialise` module used above - the following call is used:
```python
pyangbindJSON.dumps(existing_instance, filter=True)
```
This outputs the entire module as JSON:
```json
{
"a-list": {
"entry-two": {
"the-key": "entry-two"
},
"entry-three": {
"the-key": "entry-three"
},
"entry-one": {
"the-key": "entry-one"
}
}
}
```
The JSON format can be switched to IETF-encoded JSON by using the `mode="ietf"` argument to dumps.
To select an entry from the list where the `the-key` leaf is equal to "entry-one" (although this is a gratiutous example), the `select` dictionary can be used:
```python
pbJ.dumps(existing_instance.a_list, select={'the-key': 'entry-one'}, mode="ietf")
```
This outputs only the `entry-one` output of the list being shown:
```json
[
{
"simple_serialise:the-key": "entry-one"
}
]
```
## Example Code <a name="example-code"></a>
The example used throughout this document is included under `docs/example/simple-serialise`.
================================================
FILE: docs/usage.md
================================================
# PyangBind CLI usage
PyangBind adds a number of command-line options to Pyang:
* [Output options](#output-options) - `-o`, `--split-class-dir`
* [XPathHelper options](#xpathhelper) - `--use-xpathhelper`
* [Extensions options](#extensions) - `--interesting-extension`
* [RPC options](#rpcs) -- `--build-rpcs`
* [Extended Methods](#extmethods) -- `--use-extmethods`
* [YANG Module Arguments](#yangmods)
## Output Options <a name="output-options"></a>
PyangBind has three output modes:
* A file for all generated classes:
* Written to `stdout`
* Written to a single .py file
* A Python module hierarchy.
### stdout
When no options are specified, PyangBind will write a single file to `stdout`, this is considered to be self-contained and can be redirected to a particular location by the shell.
### -o <filename>
When `-o <filename>` is specified (a standard Pyang option), then the single file output is redirected to the filename specified. Within this single file, to ensure that class names remain unique then the class naming used for all non-top-level classes is of the form `yc_<lastcontainername>_<modulename>__<object path, replacing "/" with "_">`. Clearly, this results in relatively complex class names such as `yc_config_openconfig_bgp__bgp_global_config` corresponding to the `/bgp/global/config` container in the OpenConfig BGP module.
### --split-class-dir <directory>
When `--split-class-dir <directory>` is specified then PyangBind will create a Python module hierarchy in `directory`. This will result in each level of hierarchy in the YANG module becoming its own sub-module.
For example, the OpenConfig BGP module has the following hierarchy:
```
module: openconfig-bgp
+--rw bgp!
+--rw global
| +--rw config
| | +--rw as inet:as-number
| | +--rw router-id? inet:ipv4-address
| +--ro state
| | +--ro as inet:as-number
| | +--ro router-id? inet:ipv4-address
| | +--ro total-paths? uint32
| | +--ro total-prefixes? uint32
```
When `--split-class-dir openconfig` is specified, then the class corresponding to the `bgp` container will be output in a Python module named `openconfig`. This module can then be imported via:
```python
from openconfig import bgp
ocbgp = bgp()
```
At deeper levels of hierarchy a class is output within a sub-module of the same name, such that to import the BGP `global` container class, then the `global_` module is imported, with the `global_` class within it being instantiated:
```python
from openconfig.bgp import global_ # global is a reserved word
from openconfig.bgp.global_ import config
bgp_global = global_.global_()
bgp_global_config = config.config()
```
## XPathHelper Options <a name="xpathhelper"></a>
If `--use-xpathhelper` is _not_ specified, then all XPATH references throughout the classes generated will act as strings - such that any element that relies in XPATH (`when`/`leaf-ref` statements etc.) will simply take on any value that they are set to.
When` --use-xpathhelper` is specified, then references to the `path_helper` object that is supplied at the time of class instantiation will be passed to all of the classes children. This object is then used to provide lookup capabilities for XPATH expressions. This behaviour is further documented in (the XPathHelper documentation](xpathhelper.md).
## Extension Options
When `--interesting-extension <modulename>` is specified then PyangBind will look for extensions from the module name provided that are included in the YANG module. These extensions are then placed in a dictionary that is accessible through each class' `_extensions()` method.
For example, with the following YANG:
```yang
import example-extension { prefix "egx"; }
leaf description {
egx:descr-flag "d";
egx:descr-order 100;
type string;
description
"A human-readable text description";
}
```
If `--interesting-extension example-extension` is specified, then the `description` object's `_extensions()` method will return a dictionary:
```python
>>> print(cls.description._extensions())
{u'example-extensions': {u'descr-flag': u'd', u'descr-order': u'100'}}
```
These extensions can then be consumed by the program manipulating the classes.
## RPC Options <a name="rpcs"></a>
By default, PyangBind will ignore all RPC definitions within a YANG file. When `--build-rpcs` is specified, then each RPC, with its corresponding `input` and `output` containers will be generated into a class which corresponds to `<modulename>_rpc` at the root of the data tree.
See the [RPC documentation](rpc.md) for more detail as to the usage of generated RPC classes.
## Extended Methods <a name="extmethods"></a>
When the `--use-extmethods` command-line option is specified, PyangBind will propagate the dictionary that is provided as the option `extmethods=` argument during class initialisation to the children objects. If this option is not specified, this option is always `False`.
See the [Extension Methods](extmethods.md) documentation for detail of this functionality.
## YANG Module Arguments <a name="yangmods"></a>
As per Pyang - when using the PyangBind plugin, the YANG modules to be compiled are specified on the command line, along with `-p <path>` to specify where Pyang should look for other modules that are included. However, unlike Pyang, PyangBind needs to be able to resolve all base typedefs - in some cases this may involve specifying additional modules to be compiled if they included `identity` or `typedef` statements. In the case that a definition cannot be resolved, PyangBind will not generate bindings and will return a list of the known definitions at the time of the error. The current error language is not particularly user friendly - if PyangBind is unable to resolve a type definition or identity statement, please open a bug with the YANG modules being used such that this can be examined.
================================================
FILE: docs/xpathhelper.md
================================================
# XPATH and Data Tree Structure in PyangBind
* [Overview](#overview)
* [PyangBind's XPathHelper Classes](#xpathhelpercls)
* [Usage of YANGPathHelper](#yangpathhelper)
## Overview <a name="overview"></a>
YANG data models describe a tree structure - where there is a single root for all modules, and each module creates schema nodes (e.g., containers or leaves) at that root. Essentially (based on YANG's historical ties to XML) this tree structure is conceptually an XML document - and hence XPATH expressions are used to provide references between different elements in the tree.
For example:
```yang
leaf reference {
type leafref {
path "/path/to/another/node";
}
}
augment "/bgp" {
uses some-new-grouping;
}
```
Both the augment and leafref statements here utilise XPATH expressions to refer to a remote node. When the value of a leafref is set then there is a requirement to check the value it is set to against the path that it refers to.
## PyangBind's XPathHelper Classes <a name="xpathhelpercls"></a>
To allow such references to be looked up, all PyangBind classes take an argument of `path_helper` which points to an object that they can use to resolve an XPATH expression into the data instances that that path refers to.
Generically, this helper class is described in `pyangbind.lib.xpathhelper` as the `PyangbindXpathHelper` class. This is a skeleton class (or interface, essentially) - that specifies the methods that PyangBind classes expect of this helper module. These are:
* `register(self, path, object_ptr, caller=False)` - this method is called when a PyangBind object is created such that a pointer between the `path` argument and the object referred to by `object_ptr` can be maintained by the XPathHelper class. The `caller` argument specifies the path to the object that is making the `register()` call - with the logic that the `path` argument may be relative in some cases.
* `unregister(self, path, caller=False)` - this function is the partner to `register()` and is called when a PyangBind object is removed to remove the mapping for its path.
* `get(self, path, caller=False)` - this function is used by PyangBind to retrieve all data nodes that correspond to a certain path.
It is intended that there can be multiple implementations of the XPathHelper interface such that one can use it to provide database backing if required (e.g., the XPathHelper class' `register` method could be used to serialise the corresponding data instances and insert them into a database).
## PyangBind's YANGPathHelper Class
`pyangbind.lib.xpathhelper` provides an implementation of an XPathHelper class named `YANGPathHelper`. This class implements an in-memory mapping between paths and the corresponding PyangBind object. It does this by constructing a lightweight XML document of the form:
```xml
<root>
<bgp object_ptr='(str)'>
<neighbors object_ptr='(str)'>
<neighbor peer-addr='192.0.2.1' object_ptr='(str)'>
...
</neighbor>
</neighbors>
</bgp>
</root>
```
The `object_ptr` attribute of each XML Element provides a reference to an entry in `_library` dictionary which stores references to the PyangBind classes. The contents of the document can be viewed using the `tostring()` method of any YANGPathHelper instance.
The YANGPathHelper provides a `get()` and `get_unique()` method - the latter raises an exception if there is >1 object corresponding to the path that is specified.
## Usage of YANGPathHelper <a name="yangpathhelper"></a>
To initialise a YANGPathHelper class and use it with PyangBind-generated classes, the bindings must have been specified with the `--use-xpathhelper` argument. This ensures that the bindings are configured to pass the `path_helper` reference to one another as new classes are instantiated.
In order to then use the YANGPathHelper, an instance should be created and then handed to the PyangBind class as it is created through the `path_helper` kwarg:
```python
>>>
>>> from openconfig import openconfig_bgp
>>> from pyangbind.lib.xpathhelper import YANGPathHelper
>>>
>>> ph = YANGPathHelper()
>>> ob = openconfig_bgp(path_helper=ph)
>>>
```
Following this initialisation, the classes are then utilised as per the normal operation:
```python
>>> peer = ob.bgp.neighbors.neighbor.add("192.0.2.1")
>>> peer.config.peer_as = 15169
```
After creating an entry such as this, it is then possible to view the data using the standard `.get()` method, and see the XML document that has been created by the `YANGPathHelper` `ph` object:
```python
>>> print peer.get(filter=True)
{'neighbor-address': u'192.0.2.1', 'config': {'neighbor-address': u'192.0.2.1', 'peer-as': 15169}}
>>> print ph.tostring(pretty_print=True)
<root>
<bgp obj_ptr="cedd3b87-edf1-11e5-92c1-acbc32aad1a5">
<neighbors obj_ptr="cee14035-edf1-11e5-a7be-acbc32aad1a5">
<neighbor obj_ptr="d329332b-edf1-11e5-9868-acbc32aad1a5" neighbor-address="192.0.2.1">
<route-reflector obj_ptr="d3294b63-edf1-11e5-b14d-acbc32aad1a5">
...
```
It is rare that there is a requirement to interact directly with this XML. Rather the `get()` method of the `ph` object can now be utilised to retrieve values by their path.
For instance, if another peer is added (`10.0.0.1`, `config.peer_as = 6643`), then the following XPATH expressions retrieve the possible neighbors, and a particular neighbor's AS number:
```
>>> ph.get("/bgp/neighbors/neighbor/neighbor-address")
[u'192.0.2.1', u'10.0.0.1']
>>> ph.get("/bgp/neighbors/neighbor[neighbor-address='192.0.2.1']/config/peer-as")
[15169]
>>> ph.get_unique("/bgp/neighbors/neighbor[neighbor-address='10.0.0.1']/config/peer-as")
6643
```
The `get()` method will return a list of all objects that match the expression, whereas `get_unique` will return an individual object.
When looking up a certain neighbor, it is possible to utilise the `_parent` attribute of a particular object to traverse up the tree to retrieve a wider object. For instance:
```python
>>> peer_as = ph.get_unique("/bgp/neighbors/neighbor[neighbor-address='10.0.0.1']/config/peer-as")
>>> peer_as._parent._parent.get(filter=True)
{'neighbor-address': u'10.0.0.1', 'config': {'neighbor-address': u'10.0.0.1', 'peer-as': 6643}}
```
No action is required to ensure that objects unregister themselves as they are removed from the data tree:
```python
>>> ob.bgp.neighbors.neighbor.delete("10.0.0.1")
>>> ph.get("/bgp/neighbors/neighbor/neighbor-address")
[u'192.0.2.1']
```
================================================
FILE: docs/yang.md
================================================
# Python to YANG mapping
PyangBind makes a number of design decisions about how to map YANG data types of Python types, and how to carry the additional attributes that are required for serialisation and deserialisation; and other interaction with YANG-modelled data in Python.
* [High Level Design for Mapped Classes](#hld) - how PyangBind handles YANG classes
* [Items With No Direct Type Mapping](#nodirect) - Classes defined by PyangBind to represent YANG types.
* [config false](#configfalse) - Special properties of `config false` items.
* [YANG Data Types and Feature Support](#yangfeature) - An overview of the YANG types and features supported in PyangBind
## High-Level Design for Mapped Classes <a name="hld"></a>
Where possible, a built-in Python type is utilised for each class - for example, mapping a YANG `string` to Python `unicode`. All methods of `unicode` are kept such that the YANG `string` can be manipulated as per a standard Python string.
However, running `type(yang_string)` will yield that the type of the string is a `<class 'pyangbind.lib.yangtypes.YANGBaseClass'>`. PyangBind wraps each class in a meta-class which provides a number of other attributes. Particularly, elements such as storing the original YANG name of the object, tracking its path in the data tree, determining whether it has changed from its default, its namespace etc. These are defined in `pyangbind.lib.yangtypes` as a part of the `YANGBaseClass`.
Each type is dynamically generated at instantiation time using the `YANGDynClass` function. This function takes the relevant arguments and generates a dynamic type which can be used to represent the YANG data type.
## Items with no Direct Type Mapping <a name="nodirect"></a>
Some YANG types, e.g., `leaf-list`, do not have a direct analogue in Python (a `leaf-list` is a Python `list` which restricts the type/values of items that can be added to it). To this end, `pyangbind.lib.yangtypes` defines a number of classes which provide restrictions such as those that are required for a `leaf-list`. The types defined are:
- `RestrictedPrecisionDecimalType` - used for YANG's `decimal64` - this class represents a Decimal that can only have a restricted number of fractional digits (as specified by YANG's `fraction-digits` statement).
- `RestrictedClassType` - used as a flexible wrapper around a number of native types to restrict their values. A dictionary of restrictions is provided to the class. Made up of entries specifying:
* `pattern` - used for any type that can be restricted using a regular expression.
* `range` - used for any type that can be restricted using a range of possible numeric values.
* `length` - used for any type where the length of the input can be restricted.
* `dict_key` - used for `enumeration` and `identityref` types - a dictionary is provided and the valid values are restricted to keys of the supplied dictionary. Each dictionary value can hold metadata elements such as `@namespace` and `@defining_module` which are used in serialisation.
- `TypedListType` - which is used for leaf-list values where the only values in the list must correspond to the types allowed in the `leaf-list` `type` statement.
- `YANGListType` - implements a YANG list as a keyed data structure (internally a Python `dict`), which can only hold instances of a particular class (which represents the list's children), and the key value must be valid within the enclosed object.
- `YANGBool` - a boolean type. This is generally required because `bool` is not extensible in Python.
- `ReferenceType` - a type represeting a `leafref` which provides a lookup against the data tree for valid values, and allows pointer `leafref` items.
## `config false` Elements <a name="configfalse"></a>
PyangBind objects are generically set through simply defining the value using `container.leaf = value`, however, for `config false` elements, PyangBind restricts how these can be set by the progammer. The intention of this behaviour is to ensure that a user is specifically aware that they are setting a value that is not intended to be writable in the module. However, back-end code may need to populate such values.
To set a `config false` leaf, the setter method must be accessed directly, this can be done through the method `_set_X` where X is the Python-safe name of the element to be set (e.g., `total-paths` becomes `total_paths` in the `openconfig_bgp.global.state` container). Attempting to set the `total_paths` value directly will result in an `AttributeError` being raised. For example:
```python
>>>ocbgp.bgp.global_.state.total_paths = 500
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> ocbgp.bgp.global_.state._set_total_paths(500)
>>> ocbgp.bgp.global_.state.total_paths
500
```
## YANG Data Types and Feature Support <a name="yangfeature"></a>
PyangBind does not currently try and be feature complete against the YANG language. Contributions to add new features are appreciated. The table below attempts to map the design choices that PyangBind makes, and provide pointers to tests which demonstrate how this functionality can be used:
**Type** | **Sub-Statement** | **Supported Type** | **Unit Tests**
--------------------|--------------------|--------------------------|---------------
**binary** | - | [bytes](https://docs.python.org/3/library/stdtypes.html?#bytes) | tests/binary
\- | length | Supported | tests/binary
**bits** | - | Set\[str\] | tests/bits
\- | position | Supported |
**boolean** | - | YANGBool | tests/boolean-empty
**empty** | - | YANGBool | tests/boolean-empty
**decimal64** | - | [Decimal](https://docs.python.org/2/library/decimal.html) | tests/decimal64
\- | fraction-digits | Supported | tests/decimal64
**enumeration** | - | Supported | tests/enumeration
**identityref** | - | Supported | tests/identityref
**int{8,16,32,64}** | - | [numpy int](http://docs.scipy.org/doc/numpy/user/basics.types.html) | tests/int
\- | range | Supported | tests/int
**uint{8,16,32,64}**| - | [numpy uint](http://docs.scipy.org/doc/numpy/user/basics.types.html) | tests/int
\- | range | Supported | tests/int
**leafref** | - | Supported | tests/xpath/...
**string** | - | *str* | tests/string
\- | pattern | Using python *re.match* | tests/string
\- | length | Supported using *len* | tests/string
**typedef** | - | Supported | tests/typedef
**container** | - | *class* | tests/*
**list** | - | YANGList | tests/list
**leaf-list** | - | TypedList | tests/leaf-list
**union** | - | Supported | tests/union
**choice** | - | Supported | tests/choice
**rpc** | - | Supported | tests/rpc
**extension** | - | Supported | *TODO*
================================================
FILE: pyangbind/__init__.py
================================================
__version__ = "0.8.7"
================================================
FILE: pyangbind/helpers/__init__.py
================================================
================================================
FILE: pyangbind/helpers/identity.py
================================================
"""
Copyright 2016, Google Inc.
Author: robjs@google.com
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.
"""
from pyang import util
from .misc import module_import_prefixes
class Identity(object):
def __init__(self, name):
self.name = name
self.source_module = None
self._imported_prefixes = []
self.source_namespace = None
self.base = None
self.children = []
def add_prefix(self, prefix):
if self.source_module not in self._imported_prefixes:
self._imported_prefixes.append(self.source_module)
if prefix not in self._imported_prefixes:
self._imported_prefixes.append(prefix)
def add_child(self, child):
if not isinstance(child, Identity):
raise ValueError("Must supply a identity as a child")
self.children.append(child)
def __str__(self):
return "%s:%s" % (self.source_module, self.name)
def prefixes(self):
return self._imported_prefixes
class IdentityStore(object):
def __init__(self):
self._store = []
def find_identity_by_source_name(self, s, n):
for i in self._store:
if i.source_module == s and i.name == n:
return i
def add_identity(self, i):
if isinstance(i, Identity):
if not self.find_identity_by_source_name(i.source_module, i.name):
self._store.append(i)
else:
raise ValueError("Must specify an identity")
def identities(self):
return ["%s:%s" % (i.source_module, i.name) for i in self._store]
def __iter__(self):
return iter(self._store)
def build_store_from_definitions(self, ctx, defnd):
unresolved_identities = list(defnd.keys())
unresolved_identity_count = {k: 0 for k in defnd}
error_ids = []
mod_ref_prefixes = module_import_prefixes(ctx)
while len(unresolved_identities):
this_id = unresolved_identities.pop(0)
iddef = defnd[this_id]
try:
mainmod = iddef.main_module()
except AttributeError:
mainmod = None
if mainmod is not None:
defmod = mainmod
defining_module = defmod.arg
# Check we don't already have this identity defined
if self.find_identity_by_source_name(defining_module, iddef.arg) is not None:
continue
namespace = defmod.search_one("namespace").arg
prefix = defmod.search_one("prefix").arg
base_ids = []
bases = iddef.search("base")
for base in bases:
# Determine what the name of the base and the prefix for
# the base should be
if ":" in base.arg:
base_pfx, base_name = base.arg.split(":")
else:
base_pfx, base_name = prefix, base.arg
parent_module = util.prefix_to_module(defmod, base_pfx, base.pos, ctx.errors)
# Find whether we have the base in the store
base_id = self.find_identity_by_source_name(parent_module.arg, base_name)
if base_id is None:
# and if not, then push this identity back onto the stack
unresolved_identities.append(this_id)
unresolved_identity_count[this_id] += 1
break
# FIXME(Joey Wilhelm): `unresolved_idc` and `ident` are both undefined. What is the intent of this code?
# if unresolved_identity_count[this_id] > 1000:
# if unresolved_idc[ident] > 1000:
# sys.stderr.write("could not find a match for %s base: %s\n" %
# (iddef.arg, base_name))
# error_ids.append(ident)
base_ids.append(base_id)
else:
# We found all the bases (of which there may be none).
# Create a new identity that reflects this one.
tid = Identity(iddef.arg)
tid.source_module = defining_module
tid.source_namespace = namespace
tid.add_prefix(prefix)
self.add_identity(tid)
for base_id in base_ids:
base_id.add_child(tid)
if defining_module in mod_ref_prefixes:
for i in mod_ref_prefixes[defining_module]:
tid.add_prefix(i)
if error_ids:
raise TypeError("could not resolve identities %s" % error_ids)
self._build_inheritance()
def _recurse_children(self, identity, children):
for child in identity.children:
children.append(child)
self._recurse_children(child, children)
return children
def _build_inheritance(self):
for i in self._store:
ch = list()
self._recurse_children(i, ch)
i.children = ch
================================================
FILE: pyangbind/helpers/misc.py
================================================
"""
Copyright 2015, Rob Shakir
Modifications copyright 2016, Google Inc.
Author: robjs@google.com
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.
"""
import sys
def module_import_prefixes(ctx):
mod_ref_prefixes = {}
for mod in ctx.modules:
m = ctx.search_module(0, mod[0])
for importstmt in m.search("import"):
if importstmt.arg not in mod_ref_prefixes:
mod_ref_prefixes[importstmt.arg] = []
mod_ref_prefixes[importstmt.arg].append(importstmt.search_one("prefix").arg)
return mod_ref_prefixes
def find_child_definitions(obj, defn, prefix, definitions):
for i in obj.search(defn):
if i.arg in definitions:
sys.stderr.write("WARNING: duplicate definition of %s" % i.arg)
else:
definitions["%s:%s" % (prefix, i.arg)] = i
possible_parents = ["grouping", "container", "list", "rpc", "input", "output", "notification"]
for parent_type in possible_parents:
for ch in obj.search(parent_type):
if ch.i_children:
find_child_definitions(ch, defn, prefix, definitions)
return definitions
def find_definitions(defn, ctx, module, prefix):
# Find the statements within a module that map to a particular type of
# statement, for instance - find typedefs, or identities, and reutrn them
# as a dictionary to the calling function.
definitions = {}
defin = find_child_definitions(module, defn, prefix, definitions)
return defin
================================================
FILE: pyangbind/lib/__init__.py
================================================
================================================
FILE: pyangbind/lib/base.py
================================================
"""
Copyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)
This project has been supported by:
* Jive Communcations, 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.
"""
class PybindBase(object):
__slots__ = ()
def elements(self):
return self._pyangbind_elements
def __str__(self):
return str(self.elements())
def get(self, filter=False):
def error():
return NameError, "element does not exist"
d = {}
# for each YANG element within this container.
for element_name in self._pyangbind_elements:
element = getattr(self, element_name, error)
if hasattr(element, "yang_name"):
# retrieve the YANG name method
yang_name = getattr(element, "yang_name", error)
element_id = yang_name()
else:
element_id = element_name
if hasattr(element, "get"):
# this is a YANG container that has its own
# get method
d[element_id] = element.get(filter=filter)
if filter is True:
# if the element hadn't changed but we were
# filtering unchanged elements, remove it
# from the dictionary
if isinstance(d[element_id], dict):
for entry in d[element_id].keys():
changed, present = False, False
if hasattr(d[element_id][entry], "_changed"):
if d[element_id][entry]._changed():
changed = True
else:
changed = None
if hasattr(d[element_id][entry], "_present"):
if not d[element_id][entry]._present() is True:
present = True
else:
present = None
if present is False and changed is False:
del d[element_id][entry]
if len(d[element_id]) == 0:
if element._presence and element._present():
pass
else:
del d[element_id]
elif isinstance(d[element_id], list):
for list_entry in d[element_id]:
if hasattr(list_entry, "_changed"):
if not list_entry._changed():
d[element_id].remove(list_entry)
if len(d[element_id]) == 0:
del d[element_id]
else:
# this is an attribute that does not have get()
# method
if filter is False and not element._changed() and not element._present() is True:
if element._default is not False and element._default:
d[element_id] = element._default
else:
d[element_id] = element
elif element._changed() or element._present() is True:
d[element_id] = element
else:
# changed = False, and filter = True
pass
return d
def __getitem__(self, k):
def error():
raise KeyError("Key %s does not exist" % k)
element = getattr(self, k, error)
return element()
def __iter__(self):
for elem in self._pyangbind_elements:
yield (elem, getattr(self, elem))
================================================
FILE: pyangbind/lib/pybindJSON.py
================================================
"""
Copyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)
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.
"""
from __future__ import unicode_literals
import copy
import json
from collections import OrderedDict
from pyangbind.lib.serialise import pybindIETFJSONEncoder, pybindJSONDecoder, pybindJSONEncoder, pybindJSONIOError
def remove_path(tree, path):
this_part = path.pop(0)
if len(path) == 0:
try:
del tree[this_part]
return tree
except KeyError:
# ignore missing dictionary key
pass
else:
try:
tree[this_part] = remove_path(tree[this_part], path)
except KeyError:
pass
return tree
def loads(d, parent_pymod, yang_base, path_helper=None, extmethods=None, overwrite=False):
# This check is not really logical - since one would expect 'd' to be
# a string, given that this is loads. However, a previous issue meant
# that this really expected a dict, so this check simply makes sure
# that if the user really did give us a string, we're happy with that
# without breaking other code.
if isinstance(d, (str,)):
d = json.loads(d, object_pairs_hook=OrderedDict)
return pybindJSONDecoder.load_json(
d, parent_pymod, yang_base, path_helper=path_helper, extmethods=extmethods, overwrite=overwrite
)
def loads_ietf(d, parent_pymod, yang_base, path_helper=None, extmethods=None, overwrite=False):
# Same as above, to allow for load_ietf to work the same way
if isinstance(d, (str,)):
d = json.loads(d, object_pairs_hook=OrderedDict)
return pybindJSONDecoder.load_ietf_json(
d, parent_pymod, yang_base, path_helper=path_helper, extmethods=extmethods, overwrite=overwrite
)
def load(fn, parent_pymod, yang_module, path_helper=None, extmethods=None, overwrite=False):
try:
with open(fn, "r") as fp:
f = json.load(fp, object_pairs_hook=OrderedDict)
except IOError as m:
raise pybindJSONIOError("could not open file to read: %s" % m)
return loads(f, parent_pymod, yang_module, path_helper=path_helper, extmethods=extmethods, overwrite=overwrite)
def load_ietf(fn, parent_pymod, yang_module, path_helper=None, extmethods=None, overwrite=False):
try:
f = json.load(open(fn, "r"), object_pairs_hook=OrderedDict)
except IOError as m:
raise pybindJSONIOError("Could not open file to read: %s" % m)
return loads_ietf(f, parent_pymod, yang_module, path_helper, extmethods=extmethods, overwrite=overwrite)
def dumps(obj, indent=4, filter=True, skip_subtrees=[], select=False, mode="default", with_defaults=None):
def lookup_subdict(dictionary, key):
if not isinstance(key, list):
raise AttributeError("keys should be a list")
unresolved_dict = {}
for k, v in dictionary.items():
if ":" in k:
k = k.split(":")[1]
unresolved_dict[k] = v
if not key[0] in unresolved_dict:
raise KeyError("requested non-existent key (%s)" % key[0])
if len(key) == 1:
return unresolved_dict[key[0]]
current = key.pop(0)
return lookup_subdict(dictionary[current], key)
if not isinstance(skip_subtrees, list):
raise AttributeError("the subtrees to be skipped should be a list")
if mode == "ietf":
tree = pybindIETFJSONEncoder.generate_element(obj, flt=filter, with_defaults=with_defaults)
else:
tree = obj.get(filter=filter)
for p in skip_subtrees:
pp = p.split("/")[1:]
# Iterate through the skip path and the object's own path to determine
# whether they match, then skip the relevant subtrees.
match = True
trimmed_path = copy.deepcopy(pp)
for i, j in zip(obj._path(), pp):
# paths may have attributes in them, but the skip dictionary does
# not, so we ensure that the object's absolute path is attribute
# free,
if "[" in i:
i = i.split("[")[0]
if not i == j:
match = False
break
trimmed_path.pop(0)
if match and len(trimmed_path):
tree = remove_path(tree, trimmed_path)
if select:
key_del = []
for t in tree:
keep = True
for k, v in select.items():
v = str(v)
if mode == "default" or isinstance(tree, dict):
if keep and not str(lookup_subdict(tree[t], k.split("."))) == v:
keep = False
else:
# handle ietf case where we have a list and might have namespaces
if keep and not str(lookup_subdict(t, k.split("."))) == v:
keep = False
if not keep:
key_del.append(t)
if mode == "default" or isinstance(tree, dict):
for k in key_del:
if mode == "default":
del tree[k]
else:
for i in key_del:
tree.remove(i)
if mode == "ietf":
cls = pybindIETFJSONEncoder
else:
cls = pybindJSONEncoder
return json.dumps(tree, cls=cls, indent=indent)
def dump(obj, fn, indent=4, filter=True, skip_subtrees=[], mode="default"):
try:
fh = open(fn, "w")
except IOError as m:
raise pybindJSONIOError("could not open file for writing: %s" % m)
fh.write(dumps(obj, indent=indent, filter=filter, skip_subtrees=skip_subtrees, mode=mode))
fh.close()
================================================
FILE: pyangbind/lib/serialise.py
================================================
"""
Copyright 2015 Rob Shakir (rjs@jive.com, rjs@rob.sh)
Modifications copyright 2016, Google, Inc.
This project has been supported by:
* Jive Communcations, 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.
serialise:
* module containing methods to serialise pyangbind class hierarchie
to various data encodings. XML and/or JSON as the primary examples.
"""
from __future__ import unicode_literals
import json
from collections import OrderedDict
from decimal import Decimal
import base64
from enum import IntEnum
from lxml import objectify, etree
from pyangbind.lib.yangtypes import YANGBool, safe_name
long = int
class WithDefaults(IntEnum):
IF_SET = 0
class pybindJSONIOError(Exception):
pass
class pybindLoadUpdateError(Exception):
pass
class pybindJSONDecodeError(Exception):
pass
class UnmappedItem(Exception):
"""Used to simulate an Optional value"""
pass
class SerialisedTypedList(list):
pass
class YangDataSerialiser(object):
"""
This class encapsulates the logic to parse the object tree and generate appropriate
value data types for encoding
"""
def preprocess_element(self, d):
nd = {}
if isinstance(d, OrderedDict) or isinstance(d, dict):
index = 0
for k in d:
if isinstance(d[k], dict) or isinstance(d[k], OrderedDict):
nd[k] = self.preprocess_element(d[k])
if getattr(d, "_user_ordered", False):
nd[k]["__yang_order"] = index
else:
nd[k] = self.default(d[k])
# if we wanted to do this as per draft-ietf-netmod-yang-metadata
# then the encoding is like this
# if not "@%s" % k in nd:
# nd["@%s" % k] = {}
# nd["@%s" % k]['order'] = index
# this has the downside that iterating over the dict gives you
# some elements that do not really exist - there is a need to
# exclude all elements that begin with "@"
index += 1
else:
nd = d
return nd
def default(self, obj):
pybc = getattr(obj, "_pybind_generated_by", None)
elem_name = getattr(obj, "_yang_name", None)
orig_yangt = getattr(obj, "_yang_type", None)
# Expand lists
if isinstance(obj, list):
return [self.default(i) for i in obj]
# Expand dictionaries
elif isinstance(obj, dict):
return {k: self.default(v) for k, v in obj.items()}
if pybc is not None:
# Special cases where the wrapper has an underlying class
if pybc == "RestrictedClassType":
pybc = getattr(obj, "_restricted_class_base")[0]
elif pybc == "TypedListType":
return self.yangt_typed_list(obj)
# Map based on YANG type
if orig_yangt in ["leafref"]:
return self.default(obj._get()) if hasattr(obj, "_get") else str(obj)
elif orig_yangt in ["int64", "uint64"]:
return self.yangt_long(obj)
elif orig_yangt in ["identityref"]:
try:
return self.yangt_identityref(obj)
except UnmappedItem:
pass
elif orig_yangt in ["int8", "int16", "int32", "uint8", "uint16", "uint32"]:
return self.yangt_int(obj)
elif orig_yangt in ["string", "enumeration"]:
return str(obj)
elif orig_yangt in ["binary"]:
return str(base64.b64encode(obj), "ascii")
elif orig_yangt in ["decimal64"]:
return self.yangt_decimal(obj)
elif orig_yangt in ["bool"]:
return True if obj else False
elif orig_yangt in ["empty"]:
return self.yangt_empty(obj)
elif orig_yangt in ["container"]:
return self.preprocess_element(obj.get())
# The value class is actually a pyangbind class, so map it
pyc = getattr(obj, "_pybind_base_class", None) if pybc is None else pybc
if pyc is not None:
val = self.map_pyangbind_type(pyc, orig_yangt, obj)
if val is not None:
return val
# We are left with a native type
if isinstance(obj, list):
nlist = []
for elem in obj:
nlist.append(self.default(elem))
return nlist
elif isinstance(obj, dict):
ndict = {}
for k, v in obj.items():
ndict[k] = self.default(v)
return ndict
elif isinstance(obj, (str,)):
return str(obj)
elif isinstance(obj, (int,)):
return int(obj)
elif isinstance(obj, (YANGBool, bool)):
return bool(obj)
elif isinstance(obj, Decimal):
return self.yangt_decimal(obj)
raise AttributeError(
"Unmapped type: %s, %s, %s, %s, %s, %s" % (elem_name, orig_yangt, pybc, pyc, type(obj), obj)
)
def map_pyangbind_type(self, map_val, original_yang_type, obj):
if map_val in ["pyangbind.lib.yangtypes.RestrictedClass", "RestrictedClassType"]:
map_val = getattr(obj, "_restricted_class_base")[0]
if map_val in ["pyangbind.lib.yangtypes.ReferencePathType", "ReferencePathType"]:
return self.default(obj._get())
elif map_val in ["pyangbind.lib.yangtypes.RestrictedPrecisionDecimal", "RestrictedPrecisionDecimal"]:
# NOTE: this doesn't seem like it needs to be a special case?
return self.yangt_decimal(obj)
elif map_val in ["pyangbind.lib.yangtypes.YANGBinary", "YANGBinary"]:
return str(base64.b64encode(obj), "ascii")
elif map_val in ["unicode"]:
return str(obj)
elif map_val in ["pyangbind.lib.yangtypes.YANGBool"]:
if original_yang_type == "empty":
# NOTE: previously with IETF mode the code would fall-through if obj was falsey
return self.yangt_empty(obj)
else:
return bool(obj)
elif map_val in ["pyangbind.lib.yangtypes.TypedList"]:
return self.yangt_typed_list(obj)
elif map_val in ["int", "long"]:
int_size = getattr(obj, "_restricted_int_size", None)
return self.yangt_long(obj) if int_size == 64 else self.yangt_int(obj)
elif map_val in ["container"]:
return self.preprocess_element(obj.get())
elif map_val in ["decimal.Decimal"]:
return self.yangt_decimal(obj)
elif map_val in ["YANGBits"]:
return str(obj)
def yangt_int(self, obj):
# for values that are 32-bits and under..
return int(obj)
def yangt_long(self, obj):
# don't need to special-case for PY3 because we assign `long = int` in the header
return long(obj)
def yangt_identityref(self, obj):
# we don't want to do anything for non-IETF serialisation, this will fall-through
raise UnmappedItem
def yangt_decimal(self, obj):
return float(obj)
def yangt_empty(self, obj):
return bool(obj)
def yangt_typed_list(self, obj):
return [self.default(i) for i in obj]
class IETFYangDataSerialiser(YangDataSerialiser):
"""
IETF data serialiser overrides some of the data type formats only
"""
def yangt_long(self, obj):
return str(obj)
def yangt_identityref(self, obj):
try:
emod = obj._enumeration_dict[obj]["@module"]
if emod != obj._defining_module:
# if not already prefixed with the type namespace
if not obj.startswith("%s" % emod):
return "%s:%s" % (emod, obj)
except KeyError:
pass
return str(obj)
def yangt_decimal(self, obj):
return str(obj)
def yangt_empty(self, obj):
return [None] if obj else False
class XmlYangDataSerialiser(IETFYangDataSerialiser):
"""
XML can have an empty tag, and a TypedList must be treated specially, here we mark it with a custom type
"""
def yangt_typed_list(self, obj):
# We have already used a standard list to denote a container, so we instead we use a custom list
# type here in the serialised model
return SerialisedTypedList([self.default(i) for i in obj])
def yangt_empty(self, obj):
return None
class _pybindJSONEncoderBase(json.JSONEncoder):
"""
Pybind JSON encoder base class. Implements default `encode()` and `default()` methods
to be used as an encoder class with the deault *json* module.
Do not use directly, subclass and set the `serialiser_class` attribute appropriately
"""
serialiser_class = None
def encode(self, obj):
return json.JSONEncoder.encode(self, self.serialiser_class().preprocess_element(obj))
def default(self, obj):
return self.serialiser_class().default(obj)
class pybindJSONEncoder(_pybindJSONEncoderBase):
"""Default pybind JSON encoder"""
serialiser_class = YangDataSerialiser
class pybindIETFJSONEncoder(_pybindJSONEncoderBase):
"""IETF JSON encoder, we add a special method `generate_element()` that should be used
to restructure the pybind object to fit IETF requirements prior to JSON encoding."""
serialiser_class = IETFYangDataSerialiser
@staticmethod
def yname_ns_func(parent_namespace, element, yname):
if not element._namespace == parent_namespace:
# if the namespace is different, then precede with the module
# name as per spec.
return "%s:%s" % (element._defining_module, yname)
else:
return yname
@staticmethod
def generate_element(obj, parent_namespace=None, flt=False, with_defaults=None):
"""Restructure pybind `obj` to IETF spec"""
ietf_tree_json_func = make_generate_ietf_tree(pybindIETFJSONEncoder.yname_ns_func)
return ietf_tree_json_func(obj, parent_namespace=parent_namespace, flt=flt, with_defaults=with_defaults)
class pybindIETFXMLEncoder(object):
"""
IETF XML encoder for pybind object tree serialisation.
Use the `encode()` method to return an lxml.objectify tree representation of the pybind object.
The `serialise()` method is a helper around that to return a pretty-printed XML string.
"""
class EMF(objectify.ElementMaker):
"""Custome ElementMaker class to ease netconf namespace handling"""
def __init__(self, namespace=None, nsmap=None):
assert namespace or nsmap, "Must set either namespace or nsmap"
if namespace:
nsmap = {None: namespace}
elif nsmap:
namespace = nsmap[None]
super(pybindIETFXMLEncoder.EMF, self).__init__(annotate=False, namespace=namespace, nsmap=nsmap)
@classmethod
def generate_xml_tree(cls, module_name, module_namespace, tree):
"""Map the IETF structured, and value-processed, object tree into an lxml objectify object"""
doc = pybindIETFXMLEncoder.EMF(namespace=module_namespace)(module_name)
def aux(parent, root):
for k, v in root.items():
k, nsmap = k
E = pybindIETFXMLEncoder.EMF(nsmap=dict(nsmap))
if isinstance(v, SerialisedTypedList):
# TypedList (e.g. leaf-list or union-list), process each element as a sibling
for i in v:
el = E(k, str(i))
parent.append(el)
elif isinstance(v, list):
# a container maps to a list, recursively process each element as a child element
for i in v:
el = E(k)
parent.append(el)
aux(el, i)
elif isinstance(v, dict):
el = E(k)
parent.append(el)
aux(el, v)
elif v is None:
el = E(k)
parent.append(el)
elif isinstance(v, bool):
_v = str(v).lower()
parent.append(E(k, _v))
else:
parent.append(E(k, str(v)))
aux(doc, tree)
return doc
@staticmethod
def yname_ns_func(parent_namespace, element, yname):
# to keeps things simple, we augment every key with a complete namespace map
ns_map = [(None, element._namespace)]
if element._yang_type == "identityref" and element._changed():
# configured identityref (i.e. points to a valid identity)
ns_map.append(
(element._enumeration_dict[element]["@module"], element._enumeration_dict[element]["@namespace"])
)
return yname, tuple(ns_map)
@classmethod
def encode(cls, obj, filter=True):
"""return the lxml objectify tree for the pybind object"""
ietf_tree_xml_func = make_generate_ietf_tree(pybindIETFXMLEncoder.yname_ns_func)
tree = ietf_tree_xml_func(obj, flt=filter)
preprocessed = XmlYangDataSerialiser().preprocess_element(tree)
return cls.generate_xml_tree(obj._yang_name, obj._yang_namespace, preprocessed)
@classmethod
def serialise(cls, obj, filter=True, pretty_print=True):
"""return the complete XML document, as pretty-printed string"""
doc = cls.encode(obj, filter=filter)
return etree.tostring(doc, pretty_print=pretty_print).decode("utf8")
def make_generate_ietf_tree(yname_ns_func):
"""
Convert a pyangbind class to a format which encodes to the IETF JSON
specification, rather than the default .get() format, which does not
match this specification.
Modes of operation controlled by with_defaults:
- None: skip data set to default values
- WithDefaults.IF_SET: include all explicitly set data
The implementation is based on draft-ietf-netmod-yang-json-07.
Resulting namespaced key names can be customised via *yname_func*
"""
def generate_ietf_tree(obj, parent_namespace=None, flt=False, with_defaults=None):
generated_by = getattr(obj, "_pybind_generated_by", None)
if generated_by == "YANGListType":
return [generate_ietf_tree(i, flt=flt, with_defaults=with_defaults) for i in obj.itervalues()]
elif generated_by is None:
# This is an element that is not specifically generated by
# pyangbind, so we simply serialise it how we would if it
# were a scalar.
return obj
d = {}
for element_name in obj._pyangbind_elements:
element = getattr(obj, element_name, None)
yang_name = getattr(element, "yang_name", None)
yname = yang_name() if yang_name is not None else element_name
# adjust yname, if necessary, given the current namespace context
yname = yname_ns_func(parent_namespace, element, yname)
generated_by = getattr(element, "_pybind_generated_by", None)
if generated_by == "container":
d[yname] = generate_ietf_tree(
element, parent_namespace=element._namespace, flt=flt, with_defaults=with_defaults
)
present = getattr(element, "_cpresent", False)
if not len(d[yname]) and not present:
del d[yname]
elif generated_by == "YANGListType":
d[yname] = [
generate_ietf_tree(i, parent_namespace=element._namespace, flt=flt, with_defaults=with_defaults)
for i in element.itervalues()
]
if not len(d[yname]):
del d[yname]
else:
if with_defaults is None:
if flt and element._changed():
d[yname] = element
elif not flt:
d[yname] = element
elif with_defaults == WithDefaults.IF_SET:
if element._changed() or element._default == element:
d[yname] = element
return d
return generate_ietf_tree
class pybindIETFXMLDecoder(object):
"""
IETF XML decoder for pybind object tree deserialisation.
Use the `decode()` method to return an pyangbind representation of the yang object.
"""
@classmethod
def decode(cls, xml, bindings, module_name):
# using a custom parser to strip comments (so we don't handle them later)
parser = objectify.makeparser(remove_comments=True, remove_blank_text=True)
doc = objectify.fromstring(xml, parser=parser)
return cls.load_xml(doc, bindings, module_name)
@staticmethod
def load_xml(d, parent, yang_base, obj=None, path_helper=None, extmethods=None):
"""low-level XML deserialisation function, based on pybindJSONDecoder.load_ietf_json()"""
if obj is None:
# we need to find the class to create, as one has not been supplied.
base_mod_cls = getattr(parent, safe_name(yang_base))
tmp = base_mod_cls(path_helper=False)
if path_helper is not None:
# check that this path doesn't already exist in the
# tree, otherwise we create a duplicate.
existing_objs = path_helper.get(tmp._path())
if len(existing_objs) == 0:
obj = base_mod_cls(path_helper=path_helper, extmethods=extmethods)
elif len(existing_objs) == 1:
obj = existing_objs[0]
else:
raise pybindLoadUpdateError("update was attempted to a node that " + "was not unique")
else:
# in this case, we cannot check for an existing object
obj = base_mod_cls(path_helper=path_helper, extmethods=extmethods)
for child in d.getchildren():
# separate element namespace and tag
qn = etree.QName(child)
namespace, ykey = qn.namespace, qn.localname
# need to look up the key in the object to find out what type it should be,
# because we can't tell from the XML structure
attr_get = getattr(obj, "_get_%s" % safe_name(ykey), None)
if attr_get is None:
raise AttributeError("Invalid attribute specified (%s)" % ykey)
chobj = attr_get()
if chobj._yang_type == "container":
if hasattr(chobj, "_presence"):
if chobj._presence:
chobj._set_present()
pybindIETFXMLDecoder.load_xml(
child, None, None, obj=chobj, path_helper=path_helper, extmethods=extmethods
)
elif chobj._yang_type == "list":
if not chobj._keyval:
raise NotImplementedError("keyless list?")
# we just need to find the key value to add it to the list
key_parts = []
add_kwargs = {}
for pkv, ykv in zip(chobj._keyval.split(" "), chobj._yang_keys.split(" ")):
add_kwargs[pkv] = child[ykv]
key_parts.append(str(child[ykv]))
key_str = " ".join(map(str, key_parts))
if key_str not in chobj:
nobj = chobj.add(**add_kwargs)
else:
nobj = chobj[key_str]
# now we have created the nested object element, we add other members
pybindIETFXMLDecoder.load_xml(
child, None, None, obj=nobj, path_helper=path_helper, extmethods=extmethods
)
elif hasattr(chobj, "_pybind_generated_by") and chobj._pybind_generated_by == "TypedListType":
# NOTE: this is a little curious, because we are relying on the coercion of types
# i.e. lxml will "identify" the type based on its own internal model of Python
# types, see: https://lxml.de/2.0/objectify.html#how-data-types-are-matched
# There are limitations which need to be addressed, e.g. hexadecimal strings.
# Already, we have a stringify-fallback: if we fail on the first attempt then
# try again as a pure string (if its allowed).
try:
chobj.append(child.pyval)
except ValueError:
if str in chobj._allowed_type:
chobj.append(str(child.pyval))
else:
raise
else:
if chobj._is_keyval is True:
# we've already added the key
continue
val = child.text
if chobj._yang_type == "empty":
if child.text is None:
val = True
else:
raise ValueError("Invalid value for empty in input XML - key: %s, got: %s" % (ykey, val))
elif chobj._yang_type == "identityref":
if ":" in val:
_, val = val.split(":", 1)
if val is not None:
set_method = getattr(obj, "_set_%s" % safe_name(ykey), None)
if set_method is None:
raise AttributeError("Invalid attribute specified in XML - %s" % (ykey))
set_method(val)
return obj
class pybindJSONDecoder(object):
@staticmethod
def load_json(
d, parent, yang_base, obj=None, path_helper=None, extmethods=None, overwrite=False, skip_unknown=False
):
if obj is None:
# we need to find the class to create, as one has not been supplied.
base_mod_cls = getattr(parent, safe_name(yang_base))
tmp = base_mod_cls(path_helper=False)
if path_helper is not None:
# check that this path doesn't already exist in the
# tree, otherwise we create a duplicate.
existing_objs = path_helper.get(tmp._path())
if len(existing_objs) == 0:
obj = base_mod_cls(path_helper=path_helper, extmethods=extmethods)
elif len(existing_objs) == 1:
obj = existing_objs[0]
else:
raise pybindLoadUpdateError("update was attempted to a node that " + "was not unique")
else:
# in this case, we cannot check for an existing object
obj = base_mod_cls(path_helper=path_helper, extmethods=extmethods)
# Handle the case where we are supplied with a scalar value rather than
# a list
if not isinstance(d, dict) or isinstance(d, list):
set_method = getattr(obj._parent, "_set_%s" % safe_name(obj._yang_name))
set_method(d)
return obj
for key in d:
child = getattr(obj, "_get_%s" % safe_name(key), None)
if child is None and skip_unknown is False:
raise AttributeError("JSON object contained a key that" + "did not exist (%s)" % (key))
elif child is None and skip_unknown:
# skip unknown elements if we are asked to by the user`
continue
chobj = child()
if hasattr(chobj, "_presence"):
if chobj._presence:
chobj._set_present()
set_via_stdmethod = True
pybind_attr = getattr(chobj, "_pybind_generated_by", None)
if pybind_attr in ["container"]:
if overwrite:
for elem in chobj._pyangbind_elements:
unsetchildelem = getattr(chobj, "_unset_%s" % elem)
unsetchildelem()
pybindJSONDecoder.load_json(
d[key], chobj, yang_base, obj=chobj, path_helper=path_helper, skip_unknown=skip_unknown
)
set_via_stdmethod = False
elif pybind_attr in ["YANGListType", "list"]:
# we need to add each key to the list and then skip a level in the
# JSON hierarchy
list_obj = getattr(obj, safe_name(key), None)
if list_obj is None and skip_unknown is False:
raise pybindJSONDecodeError("Could not load list object " + "with name %s" % key)
if list_obj is None and skip_unknown is not False:
continue
ordered_list = getattr(list_obj, "_ordered", None)
if ordered_list:
# Put keys in order:
okeys = []
kdict = {}
for k, v in d[key].items():
if "__yang_order" not in v:
# Element is not specified in terms of order, so
# push to a list that keeps this order
okeys.append(k)
else:
kdict[v["__yang_order"]] = k
# Throw this metadata away
v.pop("__yang_order", None)
okeys.reverse()
key_order = [kdict[k] for k in sorted(kdict)]
for add_element in okeys:
key_order.append(add_element)
else:
key_order = d[key].keys()
for child_key in key_order:
if child_key not in chobj:
chobj.add(child_key)
parent = chobj[child_key]
pybindJSONDecoder.load_json(
d[key][child_key],
parent,
yang_base,
obj=parent,
path_helper=path_helper,
skip_unknown=skip_unknown,
)
set_via_stdmethod = False
if overwrite:
for child_key in chobj:
if child_key not in d[key]:
chobj.delete(child_key)
elif pybind_attr in ["TypedListType"]:
if not overwrite:
list_obj = getattr(obj, "_get_%s" % safe_name(key))()
for item in d[key]:
if item not in list_obj:
list_obj.append(item)
list_copy = []
for elem in list_obj:
list_copy.append(elem)
for e in list_copy:
if e not in d[key]:
list_obj.remove(e)
set_via_stdmethod = False
else:
# use the set method
pass
elif pybind_attr in [
"RestrictedClassType",
"ReferencePathType",
"RestrictedPrecisionDecimal",
"YANGBits",
]:
# normal but valid types - which use the std set method
pass
elif pybind_attr is None:
# not a pybind attribute at all - keep using the std set method
pass
else:
raise pybindLoadUpdateError("unknown pybind type when loading JSON: %s" % pybind_attr)
if set_via_stdmethod:
# simply get the set method and then set the value of the leaf
set_method = getattr(obj, "_set_%s" % safe_name(key))
set_method(d[key], load=True)
return obj
@staticmethod
def check_metadata_add(key, data, obj):
keys = [str(k) for k in data]
if ("@" + key) in keys:
for k, v in data["@" + key].items():
obj._add_metadata(k, v)
@staticmethod
def load_ietf_json(
d, parent, yang_base, obj=None, path_helper=None, extmethods=None, overwrite=False, skip_unknown=False
):
if obj is None:
# we need to find the class to create, as one has not been supplied.
base_mod_cls = getattr(parent, safe_name(yang_base))
tmp = base_mod_cls(path_helper=False)
if path_helper is not None:
# check that this path doesn't already exist in the
# tree, otherwise we create a duplicate.
existing_objs = path_helper.get(tmp._path())
if len(existing_objs) == 0:
obj = base_mod_cls(path_helper=path_helper, extmethods=extmethods)
elif len(existing_objs) == 1:
obj = existing_objs[0]
else:
raise pybindLoadUpdateError("update was attempted to a node that " + "was not unique")
else:
# in this case, we cannot check for an existing object
obj = base_mod_cls(path_helper=path_helper, extmethods=extmethods)
# Handle the case where we are supplied with a scalar value rather than
# a list
if not isinstance(d, dict) or isinstance(d, list):
set_method = getattr(obj._parent, "_set_%s" % safe_name(obj._yang_name))
set_method(d)
return obj
for key in d:
# Fix any namespace that was supplied in the JSON
if ":" in key:
ykey = key.split(":")[-1]
else:
ykey = key
if key == "@":
# Handle whole container metadata object
for k, v in d[key].items():
obj._add_metadata(k, v)
continue
elif "@" in key:
# Don't handle metadata elements, each element
# will look up its own metadata
continue
std_method_set = False
# Handle the case that this is a JSON object
if isinstance(d[key], dict):
# Iterate through attributes and set to that value
attr_get = getattr(obj, "_get_%s" % safe_name(ykey), None)
if attr_get is None and skip_unknown is False:
raise AttributeError("Invalid attribute specified (%s)" % ykey)
elif attr_get is None and skip_unknown is not False:
# Skip unknown JSON keys
continue
chobj = attr_get()
if hasattr(chobj, "_presence"):
if chobj._presence:
chobj._set_present()
pybindJSONDecoder.check_metadata_add(key, d, chobj)
pybindJSONDecoder.load_ietf_json(
d[key],
None,
None,
obj=chobj,
path_helper=path_helper,
extmethods=extmethods,
overwrite=overwrite,
skip_unknown=skip_unknown,
)
elif isinstance(d[key], list):
for elem in d[key]:
# if this is a list, then this is a YANG list
this_attr = getattr(obj, "_get_%s" % safe_name(ykey), None)
if this_attr is None:
raise AttributeError("List specified that did not exist")
this_attr = this_attr()
if hasattr(this_attr, "_keyval"):
if overwrite:
existing_keys = this_attr.keys()
for i in existing_keys:
this_attr.delete(i)
# this handles YANGLists
if this_attr._keyval is False:
# Keyless list, generate a key
k = this_attr.add()
nobj = this_attr[k]
elif " " in this_attr._keyval:
keystr = ""
kwargs = {}
for pkv, ykv in zip(this_attr._keyval.split(" "), this_attr._yang_keys.split(" ")):
kwargs[pkv] = elem[ykv]
keystr += "%s " % elem[ykv]
keystr = keystr.rstrip(" ")
if keystr not in this_attr:
nobj = this_attr.add(**kwargs)
else:
nobj = this_attr[keystr]
else:
k = elem[this_attr._yang_keys]
if k not in this_attr:
nobj = this_attr.add(k)
else:
nobj = this_attr[k]
pybindJSONDecoder.load_ietf_json(
elem,
None,
None,
obj=nobj,
path_helper=path_helper,
extmethods=extmethods,
overwrite=overwrite,
skip_unknown=skip_unknown,
)
pybindJSONDecoder.check_metadata_add(key, d, nobj)
else:
# this is a leaf-list
std_method_set = True
else:
std_method_set = True
if std_method_set:
get_method = getattr(obj, "_get_%s" % safe_name(ykey), None)
if get_method is None and skip_unknown is False:
raise AttributeError("JSON object contained a key that " + "did not exist (%s)" % (ykey))
elif get_method is None and skip_unknown is not False:
continue
chk = get_method()
if chk._is_keyval is True:
pass
else:
val = d[key]
if chk._yang_type == "empty":
# A 'none' value in the JSON means that an empty value is set,
# since this is serialised as [null] in the input JSON.
if val == [None]:
val = True
else:
raise ValueError("Invalid value for empty in input JSON " "key: %s, got: %s" % (ykey, val))
if chk._yang_type == "identityref":
# identityref values in IETF JSON may contain their module name, as a prefix,
# but we don't build identities with these as valid values. If this is the
# case then re-write the value to just be the name of the identity that we
# should know about.
if ":" in val:
_, val = val.split(":", 1)
if val is not None:
set_method = getattr(obj, "_set_%s" % safe_name(ykey), None)
if set_method is None:
raise AttributeError("Invalid attribute specified in JSON - %s" % (ykey))
set_method(val)
pybindJSONDecoder.check_metadata_add(key, d, get_method())
return obj
================================================
FILE: pyangbind/lib/xpathhelper.py
================================================
"""
Copyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)
Modifications copyright 2016, Google Inc.
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.
xpathhelper:
This module maintains an XML ElementTree for the registered Python
classes, so that XPATH can be used to lookup particular items.
"""
from __future__ import unicode_literals
import uuid
from collections import OrderedDict
import regex
from lxml import etree
from .base import PybindBase
from .yangtypes import safe_name
class YANGPathHelperException(Exception):
pass
class XPathError(Exception):
pass
class PybindImplementationError(Exception):
pass
class PybindXpathHelper(object):
def register(self, path, object_ptr, caller=False):
"""
A PybindXpathHelper class should supply a register() method that
takes two mandatory arguments, and one optional.
* path - the path to which the object should be registered. This is
supplied as a list of the names of the elements of the path. For
example, /device/interfaces/interface[name='eth0'] is supplied as
a ["device", "interfaces", "interface[@name='eth0']"].
* object_ptr - a reference to the object that is to be stored at this
location in the tree.
* caller=False - this supplies the path of the object that is currently
trying to perform a register. In general, it will not be used, but it
is supplied to facilitate relative path lookups.
"""
raise PybindImplementationError("The path helper class specified does " + "not implement register()")
def unregister(self, path, caller=False):
"""
A PybindXpathHelper class should supply an unregister() method that
takes one mandatory argument, and one optional.
* path - the path of the object to be unregistered. Supplied as a list()
object of the elements of the path.
* caller=False - the absolute path of the object calling the unregister()
method.
"""
raise PybindImplementationError("The path helper class specified does " + "not implement unregister()")
def get(self, path, caller=False):
"""
A PybindXpathHelper class should supply a get() method that takes one
mandatory argument and one optional.
* path - the path to the object to be retrieved. This may be specified as
a list of parts, or an XPATH expression.
* caller=False - the absolute path of the object calling the get method.
"""
raise PybindImplementationError("The path helper class specified does " + "not implement get()")
# A class which acts as "/" within the hierarchy - it acts as per any other
# PyangBind element for the purposes of get() calls - allowing "/" to be
# serialised to
class FakeRoot(PybindBase):
_pybind_generated_by = "FakeRoot"
def __init__(self):
self._pyangbind_elements = OrderedDict()
class YANGPathHelper(PybindXpathHelper):
_attr_re = regex.compile(r"^(?P<tagname>[^\[]+)(?P<args>(\[[^\]]+\])+)$")
_arg_re = regex.compile(
r"^((and|or) )?[@]?(?P<cmd>[a-zA-Z0-9\-\_:]+)([ ]+)?"
+ r"=([ ]+)?['\"]?(?P<arg>[^ ^'^\"]+)(['\"])?([ ]+)?"
+ "(?P<remainder>.*)"
)
_relative_path_re = regex.compile(r"^(\.|\.\.)")
def __init__(self):
# Initialise an empty library and a new FakeRoot class to act as the
# data tree's root.
self._root = etree.Element("root")
self._library = {}
self._library["root"] = FakeRoot()
self._root.set("obj_ptr", "root")
def _path_parts(self, path):
c = 0
parts = []
buf = ""
in_qstr, in_attr = False, False
while c < len(path):
if path[c] == "/" and not in_qstr and not in_attr:
parts.append(buf)
buf = ""
elif path[c] == '"' and in_qstr:
in_qstr = False
buf += path[c]
elif path[c] == '"':
in_qstr = True
buf += path[c]
elif path[c] == "[":
in_attr = True
buf += path[c]
elif path[c] == "]":
in_attr = False
buf += path[c]
else:
buf += path[c]
c += 1
parts.append(buf)
return parts
def _encode_path(self, path, mode="search", find_parent=False, normalise_namespace=True, caller=False):
if mode not in ["search", "set"]:
raise XPathError("Path can only be encoded based on searching or " + "setting attributes")
parts = path
if len(parts) == 0:
return "/"
elif find_parent and len(parts) == 1:
return []
lastelem = len(parts) - 1 if find_parent else len(parts)
startelem = 1 if parts[0] == "" else 0
if self._relative_path_re.match(parts[0]):
epath = ""
else:
epath = "/"
for i in range(startelem, lastelem):
tagname, attributes = self._tagname_attributes(parts[i], normalise_namespace=normalise_namespace)
if ":" in tagname and normalise_namespace:
tagname = tagname.split(":")[1]
if attributes is not None:
epath += tagname + "["
for k, v in attributes.items():
# handling for rfc6020 current() specification
if "current()" in v:
remaining_path = regex.sub("current\(\)(?P<remaining>.*)", r"\g<remaining>", v).split("/")
# since the calling leaf may not exist, we need to do a
# lookup on a path that will do, which is the parent
if remaining_path[1] == "..":
lookup = caller[:-1] + remaining_path[2:]
else:
lookup = caller + remaining_path[1:]
resolved_current_attr = self.get(lookup)
if not len(resolved_current_attr) == 1:
raise XPathError(
"XPATH specified a current() expression that " + "returned a non-unique list"
)
v = resolved_current_attr[0]
epath += "@%s='%s' " % (k, v)
if mode == "search":
epath += "and "
if mode == "search":
epath = epath.rstrip("and ")
epath = epath.rstrip(" ") + "]"
epath += "/"
else:
epath += tagname + "/"
epath = epath.rstrip("/")
return epath
def _tagname_attributes(self, tag, normalise_namespace=True):
tagname, attributes = tag, None
if self._attr_re.match(tag):
tagname, args = self._attr_re.sub(r"\g<tagname>||\g<args>", tag).split("||")
arg_parts = [i.strip("[") for i in args.split("]")]
attributes = {}
for arg in arg_parts:
tmp_arg = arg
while len(tmp_arg):
if self._arg_re.match(tmp_arg):
c, a, r = self._arg_re.sub(r"\g<cmd>||\g<arg>||\g<remainder>", tmp_arg).split("||")
if ":" in c and normalise_namespace:
c = c.split(":")[1]
attributes[c] = a
tmp_arg = r
else:
raise XPathError(
"invalid attribute string specified"
+ "for %s" % tagname
+ "(err part: %s (%s))" % (arg, tmp_arg)
)
return (tagname, attributes)
def register(self, object_path, object_ptr, caller=False):
if isinstance(object_path, str):
raise XPathError("not meant to receive strings as input to register()")
if regex.match(r"^\.\.", object_path[0]):
raise XPathError("unhandled relative path in register()")
# This is a hack to register anything that is a top-level object,
# it allows modules to register themselves against the FakeRoot
# class which acts as per other PyangBind objects.
if len(object_path) == 1:
setattr(self._library["root"], object_path[0], object_ptr)
setattr(self._library["root"], "_get_%s" % safe_name(object_path[0]), lambda: object_ptr)
self._library["root"]._pyangbind_elements[object_path[0]] = None
# check whether we're updating
this_obj_existing = self._get_etree(object_path)
if len(this_obj_existing) > 1:
raise XPathError("duplicate objects in tree - %s" % object_path)
if this_obj_existing is not None and not this_obj_existing == []:
this_obj_existing = this_obj_existing[0]
if self._library[this_obj_existing.get("obj_ptr")] == object_ptr:
return True
else:
del self._library[this_obj_existing.get("obj_ptr")]
new_uuid = str(uuid.uuid1())
self._library[new_uuid] = object_ptr
this_obj_existing.set("obj_ptr", new_uuid)
return True
this_obj_id = str(uuid.uuid1())
self._library[this_obj_id] = object_ptr
parent = object_path[:-1]
tagname, attributes = self._tagname_attributes(object_path[-1])
if parent == []:
parent_o = self._root
else:
parent_o = self._get_etree(parent)
if len(parent_o) > 1:
raise XPathError(
"multiple elements returned for parent %s, must be "
+ "exact path for registration" % "/"
+ "/".join(parent)
)
if parent_o == []:
raise XPathError("parent node did not exist for %s @ %s" % (tagname, "/" + "/".join(parent)))
parent_o = parent_o[0]
added_item = etree.SubElement(parent_o, tagname, obj_ptr=this_obj_id)
if attributes is not None:
for k, v in attributes.items():
added_item.set(k, v)
def unregister(self, object_path, caller=False):
if isinstance(object_path, str):
raise XPathError("should not receive paths as a str in unregister()")
if regex.match(r"^(\.|\.\.|\/)", object_path[0]):
raise XPathError("unhandled relative path in unregister()")
existing_objs = self._get_etree(object_path)
if len(existing_objs) == 0:
raise XPathError("object did not exist to unregister - %s" % object_path)
for obj in existing_objs:
ref = obj.get("obj_ptr")
del self._library[ref]
obj.getparent().remove(obj)
def _get_etree(self, object_path, caller=False):
fx_q = self._encode_path(object_path, caller=caller)
if self._relative_path_re.match(fx_q) and caller:
fx_q = "." + self._encode_path(caller)
fx_q += "/" + self._encode_path(object_path, caller=caller)
else:
if not fx_q == "/":
fx_q = "." + fx_q
retr_obj = self._root.xpath(fx_q)
return retr_obj
def get(self, object_path, caller=False):
if isinstance(object_path, (str,)):
object_path = self._path_parts(object_path)
return [self._library[i.get("obj_ptr")] for i in self._get_etree(object_path, caller=caller)]
def get_unique(self, object_path, caller=False, exception_to_raise=YANGPathHelperException):
obj = self.get(object_path, caller=caller)
if len(obj) == 0:
raise exception_to_raise("Supplied path for %s found no results!" % object_path)
if len(obj) != 1:
raise exception_to_raise("Supplied path for %s was not unique!" % object_path)
return obj[0]
def get_list(self, object_path, caller=False, exception_to_raise=YANGPathHelperException):
if isinstance(object_path, (str,)):
object_path = self._path_parts(object_path)
parent_obj = self.get_unique(object_path[:-1], caller=caller, exception_to_raise=exception_to_raise)
list_get_attr = getattr(parent_obj, "_get_%s" % safe_name(object_path[-1]), None)
if list_get_attr is None:
raise exception_to_raise(
"Element %s does not have an attribute named %s" % ("/".join(object_path[:-1]), object_path[-1])
)
return list_get_attr()
def tostring(self, pretty_print=False):
return etree.tostring(self._root, pretty_print=pretty_print)
================================================
FILE: pyangbind/lib/yangtypes.py
================================================
"""
Copyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)
Modifications copyright 2016, Google Inc.
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.
"""
from __future__ import unicode_literals
import base64
import collections
from collections import abc
import copy
import uuid
from decimal import Decimal
import regex
# Words that could turn up in YANG definition files that are actually
# reserved names in Python, such as being builtin types. This list is
# not complete, but will probably continue to grow.
reserved_name = [
"list",
"str",
"int",
"global",
"decimal",
"float",
"as",
"if",
"else",
"elif",
"map",
"set",
"class",
"from",
"import",
"pass",
"return",
"is",
"exec",
"pop",
"insert",
"remove",
"add",
"delete",
"local",
"get",
"default",
"yang_name",
"def",
"print",
"del",
"break",
"continue",
"raise",
"in",
"assert",
"while",
"for",
"try",
"finally",
"with",
"except",
"lambda",
"or",
"and",
"not",
"yield",
"property",
"min",
"max",
"async",
]
def is_yang_list(arg):
if isinstance(arg, list):
return True
elif hasattr(arg, "_pybind_generated_by"):
pygen = getattr(arg, "_pybind_generated_by")
if pygen in ["TypedListType", "YANGListType"]:
return True
return False
def is_yang_leaflist(arg):
pygen = getattr(arg, "_pybind_generated_by", None)
if pygen is None:
return False
elif pygen == "TypedListType":
return True
return False
def remove_path_attributes(p):
new_path = []
for i in p:
if "[" in i:
new_path.append(i.split("[")[0])
else:
new_path.append(i)
return new_path
def safe_name(arg):
"""
Make a leaf or container name safe for use in Python.
"""
arg = arg.replace("-", "_")
arg = arg.replace(".", "_")
if arg in reserved_name:
arg += "_"
# store the unsafe->original version mapping
# so that we can retrieve it when get() is called.
return arg
def RestrictedPrecisionDecimalType(*args, **kwargs):
"""
Function to return a new type that is based on decimal.Decimal with
an arbitrary restricted precision.
"""
precision = kwargs.pop("precision", False)
class RestrictedPrecisionDecimal(Decimal):
"""
Class extending decimal.Decimal to restrict the precision that is
stored, supporting the fraction-digits argument of the YANG decimal64
type.
"""
_precision = 10.0 ** (-1.0 * int(precision))
_pybind_generated_by = "RestrictedPrecisionDecimal"
def __new__(self, *args, **kwargs):
"""
Overloads the decimal __new__ function in order to round the input
value to the new value.
"""
if self._precision is not None:
if len(args):
value = Decimal(args[0]).quantize(Decimal(str(self._precision)))
else:
value = Decimal(0)
elif len(args):
value = Decimal(args[0])
else:
value = Decimal(0)
obj = Decimal.__new__(self, value, **kwargs)
return obj
return type(RestrictedPrecisionDecimal(*args, **kwargs))
def RestrictedClassType(*args, **kwargs):
"""
Function to return a new type that restricts an arbitrary base_type with
a specified restriction. The restriction_type specified determines the
type of restriction placed on the class, and the restriction_arg gives
any input data that this function needs.
"""
base_type = kwargs.pop("base_type", str)
restriction_type = kwargs.pop("restriction_type", None)
restriction_arg = kwargs.pop("restriction_arg", None)
restriction_dict = kwargs.pop("restriction_dict", None)
int_size = kwargs.pop("int_size", None)
# this gives deserialisers some hints as to how to encode/decode this value
# it must be a list since a restricted class can encapsulate a restricted
# class
current_restricted_class_type = regex.sub("<(type|class) '(?P<class>.*)'>", r"\g<class>", str(base_type))
if hasattr(base_type, "_restricted_class_base"):
restricted_class_hint = getattr(base_type, "_restricted_class_base")
restricted_class_hint.append(current_restricted_class_type)
else:
restricted_class_hint = [current_restricted_class_type]
class RestrictedClass(base_type):
"""
A class that restricts the base_type class with a new function that the
input value is validated against before being applied. The function is
a static method which is assigned to _restricted_test.
"""
_pybind_generated_by = "RestrictedClassType"
_restricted_class_base = restricted_class_hint
_restricted_int_size = int_size
def __init__(self, *args, **kwargs):
"""
Overloads the base_class __init__ method to check the input argument
against the validation function - returns on instance of the base_type
class, which can be manipulated as per a usual Python object.
"""
try:
self.__check(args[0])
except IndexError:
pass
try:
super(RestrictedClass, self).__init__(*args, **kwargs)
except TypeError:
super(RestrictedClass, self).__init__()
def __new__(self, *args, **kwargs):
self._restriction_dict = restriction_dict
self._restriction_tests = []
"""
Create a new class instance, and dynamically define the
_restriction_test method so that it can be called by other functions.
"""
range_regex = regex.compile(r"(?P<low>\-?[0-9\.]+|min)([ ]+)?\.\.([ ]+)?" + r"(?P<high>(\-?[0-9\.]+|max))")
range_single_value_regex = regex.compile(r"(?P<value>\-?[0-9\.]+)")
def convert_regexp(pattern):
# Some patterns include a $ character in them in some IANA modules, this
# is not escaped. Do some logic to escape them, whilst leaving one at the
# end of the string if it's there.
trimmed = False
if pattern[-1] == "$":
tmp_pattern = pattern[:-1]
trimmed = True
else:
tmp_pattern = pattern
tmp_pattern = tmp_pattern.replace("$", r"\$")
pattern = tmp_pattern
if trimmed:
pattern += "$"
if not pattern[0] == "^":
pattern = "^%s" % pattern
if not pattern[len(pattern) - 1] == "$":
pattern = "%s$" % pattern
return pattern
def build_length_range_tuples(range, length=False, multiplier=1):
if range_regex.match(range_spec):
low, high = range_regex.sub(r"\g<low>,\g<high>", range_spec).split(",")
if not length:
high = base_type(high) if not high == "max" else None
low = base_type(low) if not low == "min" else None
else:
high = int(high) * multiplier if not high == "max" else None
low = int(low) * multiplier if not low == "min" else None
return (low, high)
elif range_single_value_regex.match(range_spec):
eqval = range_single_value_regex.sub(r"\g<value>", range_spec)
if not length:
eqval = base_type(eqval) if eqval not in ["max", "min"] else None
else:
eqval = int(eqval) * multiplier
return (eqval,)
else:
raise ValueError("Invalid range or length argument specified")
def in_range_check(low_high_tuples, length=False):
def range_check(value):
if length:
value = len(value)
range_results = []
for check_tuple in low_high_tuples:
chk = True
if len(check_tuple) == 2:
if check_tuple[0] is not None and value < check_tuple[0]:
chk = False
if check_tuple[1] is not None and value > check_tuple[1]:
chk = False
elif len(check_tuple) == 1:
if value != float(check_tuple[0]):
chk = False
else:
raise AttributeError("Invalid check tuple length specified")
range_results.append(chk)
return True in range_results
return range_check
def match_pattern_check(regexp):
def mp_check(value):
if regex.match(convert_regexp(regexp), str(value)):
return True
return False
return mp_check
def in_dictionary_check(dictionary):
return lambda i: str(i) in dictionary
val = False
try:
val = args[0]
except IndexError:
pass
if self._restriction_dict is None:
if restriction_type is not None and restriction_arg is not None:
self._restriction_dict = {restriction_type: restriction_arg}
else:
raise ValueError("must specify either a restriction dictionary or" + " a type and argument")
for rtype, rarg in self._restriction_dict.items():
if rtype == "pattern":
self._restriction_tests.append(match_pattern_check(rarg))
elif rtype == "range":
ranges = []
for range_spec in rarg:
ranges.append(build_length_range_tuples(range_spec))
self._restriction_tests.append(in_range_check(ranges))
if val:
try:
val = base_type(val)
except Exception:
raise TypeError("must specify a numeric type for a range " + "argument")
elif rtype == "length":
multiplier = 1
lengths = []
for range_spec in rarg:
lengths.append(build_length_range_tuples(range_spec, length=True, multiplier=multiplier))
self._restriction_tests.append(in_range_check(lengths, length=True))
elif rtype == "dict_key":
new_rarg = copy.deepcopy(rarg)
for k in rarg:
if k.startswith("@"):
new_rarg.pop(k, None)
# populate enum values
used_values = []
for k in new_rarg:
if "value" in new_rarg[k]:
used_values.append(int(new_rarg[k]["value"]))
c = 0
for k in new_rarg:
while c in used_values:
c += 1
if "value" not in new_rarg[k]:
new_rarg[k]["value"] = c
c += 1
self._restriction_tests.append(in_dictionary_check(new_rarg))
self._enumeration_dict = new_rarg
else:
raise TypeError("unsupported restriction type")
if val is not False:
for test in self._restriction_tests:
passed = False
if test(val) is not False:
passed = True
break
if not passed:
raise ValueError("%s does not match a restricted type" % val)
try:
obj = base_type.__new__(self, *args, **kwargs)
except TypeError:
obj = base_type.__new__(self)
return obj
def __check(self, v):
"""
Run the _restriction_test static method against the argument v,
returning an error if the value does not validate.
"""
v = base_type(v)
for chkfn in self._restriction_tests:
if not chkfn(v):
raise ValueError("did not match restricted type")
return True
def getValue(self, *args, **kwargs):
"""
For types where there is a dict_key restriction (such as YANG
enumeration), return the value of the dictionary key.
"""
if "dict_key" in self._restriction_dict:
value = kwargs.pop("mapped", False)
if value:
return self._enumeration_dict[self.__str__()]["value"]
return self
return type(RestrictedClass(*args, **kwargs))
def TypedListType(*args, **kwargs):
"""
Return a type that consists of a list object where only
certain types (specified by allowed_type kwarg to the function)
can be added to the list.
"""
allowed_type = kwargs.pop("allowed_type", str)
if not isinstance(allowed_type, list):
allowed_type = [allowed_type]
class TypedList(abc.MutableSequence):
_pybind_generated_by = "TypedListType"
_list = list()
def __init__(self, *args, **kwargs):
self._unique = kwargs.pop("unique", False)
self._allowed_type = allowed_type
self._list = list()
if len(args):
if isinstance(args[0], list):
tmp = []
for i in args[0]:
if not self._unique or i not in tmp:
tmp.append(self.check(i))
self._list.extend(tmp)
else:
tmp = self.check(args[0])
self._list.append(tmp)
def check(self, v):
# Short circuit uniqueness check
if self._unique and v in self._list:
raise ValueError("Values in this list must be unique.")
passed = False
count = 0
for i in self._allowed_type:
if isinstance(v, i):
tmp = v
passed = True
break
try:
if hasattr(i, "_pybind_generated_by"):
attr = getattr(i, "_pybind_generated_by")
if attr == "RestrictedClassType":
tmp = i(v)
passed = True
break
elif attr == "ReferencePathType":
tmp = i(v)
passed = True
break
elif attr == "RestrictedPrecisionDecimal":
tmp = i(v)
passed = True
break
elif i == str and isinstance(v, (str,)):
tmp = str(v)
passed = True
break
elif i not in (str,):
# for anything other than string we try
# and cast. Using things for string or
# unicode gives us strange results because we get
# class name represetnations
tmp = i(v)
passed = True
break
except Exception:
# we catch all exceptions because we duck-type as
# much as possible and some types - e.g., decimal do
# not use builtins.
pass
count += 1
if not passed:
raise ValueError("Cannot add %s to TypedList (accepts only %s)" % (v, self._allowed_type))
else:
return tmp
def __len__(self):
return len(self._list)
def __getitem__(self, i):
return self._list[i]
def __delitem__(self, i):
del self._list[i]
def __setitem__(self, i, v):
self.insert(i, v)
def insert(self, i, v):
val = self.check(v)
self._list.insert(i, val)
def append(self, v):
if not self._unique or v not in self._list:
val = self.check(v)
self._list.append(val)
def __str__(self):
return str(self._list)
def __iter__(self):
return iter(self._list)
def __eq__(self, other):
if self._list == other:
return True
return False
def get(self, filter=False):
return self._list
return type(TypedList(*args, **kwargs))
def YANGListType(*args, **kwargs):
"""
Return a type representing a YANG list, with a contained class.
An ordered dict is used to store the list, and the
returned object behaves akin to a dictionary.
Additional checks are performed to ensure that the keys of the
list are valid before adding the value.
.add(key) - initialises a new member of the list
.delete(key) - removes it.
Where a list exists that does not have a key - which can be the
case for 'config false' lists - a uuid is generated and used
as the key for the list.
"""
try:
keyname = args[0]
listclass = args[1]
except Exception:
raise TypeError("A YANGList must be specified with a key value and a " + "contained class")
is_container = kwargs.pop("is_container", False)
parent = kwargs.pop("parent", False)
yang_name = kwargs.pop("yang_name", False)
yang_keys = kwargs.pop("yang_keys", False)
user_ordered = kwargs.pop("user_ordered", False)
path_helper = kwargs.pop("path_helper", None)
extensions = kwargs.pop("extensions", None)
class YANGList(object):
__slots__ = ("_members", "_keyval", "_contained_class", "_path_helper", "_yang_keys", "_ordered")
_pybind_generated_by = "YANGListType"
def __init__(self, *args, **kwargs):
self._ordered = True if user_ordered else False
self._members = collections.OrderedDict()
self._members._user_ordered = True if user_ordered else False
self._keyval = keyname
if not type(listclass) == type(int):
raise ValueError("contained class of a YANGList must be a class")
self._contained_class = listclass
self._path_helper = path_helper
self._yang_keys = yang_keys
def __str__(self):
return str(self._members)
def __repr__(self):
return repr(self._members)
def __check__(self, v):
if self._contained_class is None:
return False
try:
tmp = YANGDynClass(
base=self._contained_class,
parent=parent,
yang_name=yang_name,
is_container=is_container,
path_helper=False,
)
valid = False
if not tmp.__slots__ == v.__slots__:
valid = True
elif self._contained_class.__slots__ == v.__slots__:
valid = True
if valid is False:
return valid
except Exception:
return False
return True
def iteritems(self):
return self._members.items()
def itervalues(self):
return self._members.values()
def _key_to_native_key_type(self, k):
if self._keyval is False:
raise AttributeError("List does not have a key")
elif " " in self._keyval:
raise AttributeError("Multiple key, string type should be used")
else:
member = self._members[k]
getfn = getattr(member, "_get_%s" % self._keyval)
return getfn()
def __iter__(self):
return iter(self._members)
def __contains__(self, k):
if k in self._members:
return True
return False
def __getitem__(self, k):
return self._members[k]
def __setitem__(self, k, v):
self.__set(_k=k, _v=v)
def __set(self, *args, **kwargs):
k = kwargs.pop("_k", None)
v = kwargs.pop("_v", None)
named_set = kwargs.pop("_named_set", False)
if k is None and self._keyval and not named_set:
k = args[0]
elif k is None:
# this is a list that does not have a key specified, and hence
# we generate a uuid that is used as the key, the method then
# returns the uuid for the upstream process to use
k = str(uuid.uuid1())
update = False
if v is not None:
if not self.__check__(v):
raise ValueError("value must be set to an instance of %s" % (self._contained_class))
else:
update = True
if k in self._members:
update = True
if self._keyval:
try:
tmp = YANGDynClass(
base=self._contained_class,
parent=parent,
yang_name=yang_name,
is_container="container",
path_helper=False,
)
keydict = None
if " " in self._keyval and not named_set:
keys = self._keyval.split(" ")
keyparts = k.split(" ")
keydict = {}
for kp, kv in zip(keys, keyparts):
keydict[kp] = kv
if not len(keyparts) == len(keys):
raise KeyError(
"YANGList key must contain all key elements (%s)" % (self._keyval.split(" "))
)
elif named_set:
k = kwargs.pop("_python_key", None)
keydict = copy.copy(kwargs)
else:
if k == "":
raise KeyError("Cannot set a null key for a list entry!")
keydict = {self._keyval: k}
kv_obj = getattr(tmp, self._keyval)
path_keystring = "[%s='%s']" % (kv_obj.yang_name(), k)
if keydict is not None:
keys = self._keyval.split(" ")
path_keystring = "["
for kv in keys:
kv_obj = getattr(tmp, kv)
path_keystring += "%s='%s' " % (kv_obj.yang_name(), keydict[kv])
path_keystring = path_keystring[:-1]
path_keystring += "]"
if not update:
tmp = YANGDynClass(
base=self._contained_class,
parent=parent,
yang_name=yang_name,
is_container="container",
path_helper=path_helper,
register_path=(self._parent._path() + [self._yang_name + path_keystring]),
extmethods=self._parent._extmethods,
extensions=extensions,
)
else:
# hand the value to the init, rather than simply creating an empty
# object.
tmp = YANGDynClass(
v,
base=self._contained_class,
parent=parent,
yang_name=yang_name,
is_container="container",
path_helper=path_helper,
register_path=(self._parent._path() + [self._yang_name + path_keystring]),
extmethods=self._parent._extmethods,
load=True,
extensions=extensions,
)
if keydict is not None:
for kn in keydict:
key = getattr(tmp, "_set_%s" % safe_name(kn))
key(keydict[kn], load=True)
if hasattr(k, "_referenced_object") and k._referenced_object is not None:
k = k._referenced_object
self._members[k] = tmp
except ValueError as m:
raise KeyError("key value %s must be valid, %s" % (self._keyval, m))
else:
self._members[k] = YANGDynClass(
base=self._contained_class,
parent=parent,
yang_name=yang_name,
is_container=is_container,
path_helper=path_helper,
extmethods=self._parent._extmethods,
extensions=extensions,
)
return k
def __delitem__(self, k):
del self._members[k]
def __len__(self):
return len(self._members)
def keys(self):
return self._members.keys()
def items(self):
return self._members.items()
def values(self):
return self._members.values()
def _generate_key(self, *args, **kwargs):
keyargs = None
if len(args):
k = args[0]
elif len(kwargs):
keyargs = {}
k = ""
for kn in self._keyval.split(" "):
try:
keyargs[kn] = kwargs[kn]
except KeyError as m:
raise AttributeError(
"Keyword list add function must have all " + "keys specified - cannot find %s" % m
)
k += "%s " % kwargs[kn]
k = k[:-1]
else:
k = None
return (k, keyargs)
def _extract_key(self, obj):
kp = self._keyval.split(" ")
if len(kp) > 1:
ks = ""
for k in kp:
kv = getattr(obj, "_get_%s" % safe_name(k), None)
if kv is None:
raise KeyError("Invalid key attribute specified for object")
ks += "%s " % str(kv())
return ks.rstrip(" ")
else:
kv = getattr(obj, "_get_%s" % safe_name(self._keyval), None)
if kv is None:
raise KeyError("Invalid key attribute specified for object: %s" % self._keyval)
return kv()
def append(self, obj):
self.__set(_k=self._extract_key(obj), _v=obj)
def _new_item(self):
return self._contained_class()
def add(self, *args, **kwargs):
if len(args) and len(kwargs):
raise AttributeError("Cannot add an entry to a list based on both " + " keywords and string args")
value = kwargs.pop("_v", None)
k, keyargs = self._generate_key(*args, **kwargs)
if k in self._members:
raise KeyError("%s is already defined as a list entry" % k)
if self._keyval and keyargs is None:
if k is None:
raise KeyError("a list with a key value must have a key specified")
self.__set(_k=k)
return self._members[k]
elif self._keyval and keyargs is not None:
keyargs["_python_key"] = k
keyargs["_named_set"] = True
if value is not None:
keyargs["_v"] = value
self.__set(**keyargs)
return self._members[k]
else:
k = self.__set()
return k
def delete(self, *args, **kwargs):
k, _ = self._generate_key(*args, **kwargs)
if self._path_helper:
current_item = self._members[k]
if " " in self._keyval:
keyparts = self._keyval.split(" ")
keyargs = k.split(" ")
key_string = "["
for key, val in zip(keyparts, keyargs):
kv_o = getattr(current_item, key)
key_string += "%s=%s " % (kv_o.yang_name(), val)
key_string = key_string.rstrip(" ")
key_string += "]"
else:
kv_o = getattr(current_item, self._keyval)
key_string = "[@%s=%s]" % (kv_o.yang_name(), k)
obj_path = self._parent._path() + [self._yan
gitextract_6v8090ee/ ├── .coveragerc ├── .editorconfig ├── .github/ │ └── workflows/ │ ├── pypi.yml │ └── python-test.yml ├── .gitignore ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── docs/ │ ├── README.md │ ├── errors.md │ ├── example/ │ │ ├── .gitignore │ │ ├── oc-network-instance/ │ │ │ ├── generate_bindings.sh │ │ │ ├── json/ │ │ │ │ ├── oc-ni.json │ │ │ │ └── oc-ni_ietf.json │ │ │ └── static_route_example.py │ │ ├── simple-rpc/ │ │ │ ├── .gitignore │ │ │ ├── generate_bindings.sh │ │ │ ├── json/ │ │ │ │ └── rpc-output.json │ │ │ ├── simple-rpc.py │ │ │ └── simple_rpc.yang │ │ └── simple-serialise/ │ │ ├── .gitignore │ │ ├── generate_bindings.sh │ │ ├── json/ │ │ │ ├── simple-instance-additional.json │ │ │ ├── simple-instance-ietf.json │ │ │ └── simple-instance.json │ │ ├── simple-serialise.py │ │ └── simple_serialise.yang │ ├── extmethods.md │ ├── generic_methods.md │ ├── rpc.md │ ├── serialisation.md │ ├── usage.md │ ├── xpathhelper.md │ └── yang.md ├── pyangbind/ │ ├── __init__.py │ ├── helpers/ │ │ ├── __init__.py │ │ ├── identity.py │ │ └── misc.py │ ├── lib/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── pybindJSON.py │ │ ├── serialise.py │ │ ├── xpathhelper.py │ │ └── yangtypes.py │ └── plugin/ │ ├── __init__.py │ └── pybind.py ├── pyproject.toml ├── requirements.DEVELOPER.txt ├── requirements.txt ├── scripts/ │ └── release.sh ├── setup.cfg ├── tests/ │ ├── __init__.py │ ├── base-test.yang │ ├── base.py │ ├── binary/ │ │ ├── __init__.py │ │ ├── binary.yang │ │ └── run.py │ ├── bits/ │ │ ├── __init__.py │ │ ├── bits.yang │ │ └── run.py │ ├── boolean-empty/ │ │ ├── __init__.py │ │ ├── boolean-empty.yang │ │ └── run.py │ ├── choice/ │ │ ├── __init__.py │ │ ├── choice.yang │ │ └── run.py │ ├── config-false/ │ │ ├── __init__.py │ │ ├── config-false.yang │ │ └── run.py │ ├── decimal64/ │ │ ├── __init__.py │ │ ├── decimal.yang │ │ └── run.py │ ├── enumeration/ │ │ ├── __init__.py │ │ ├── enumeration.yang │ │ └── run.py │ ├── extensions/ │ │ ├── __init__.py │ │ ├── extdef-irr.yang │ │ ├── extdef-two.yang │ │ ├── extdef.yang │ │ ├── extensions.yang │ │ └── run.py │ ├── extmethods/ │ │ ├── __init__.py │ │ ├── extmethods.yang │ │ └── run.py │ ├── identityref/ │ │ ├── __init__.py │ │ ├── identityref.yang │ │ ├── remote-two.yang │ │ ├── remote.yang │ │ └── run.py │ ├── include-import/ │ │ ├── __init__.py │ │ ├── include-import.yang │ │ ├── remote.yang │ │ ├── run.py │ │ └── subm.yang │ ├── int/ │ │ ├── __init__.py │ │ ├── int.yang │ │ └── run.py │ ├── integration/ │ │ ├── __init__.py │ │ └── openconfig-interfaces/ │ │ ├── __init__.py │ │ └── run.py │ ├── leaf-list/ │ │ ├── __init__.py │ │ ├── leaflist.yang │ │ └── run.py │ ├── list/ │ │ ├── __init__.py │ │ ├── list.yang │ │ └── run.py │ ├── misc/ │ │ ├── __init__.py │ │ ├── misc.yang │ │ └── run.py │ ├── nested-containers/ │ │ ├── __init__.py │ │ ├── nested.yang │ │ └── run.py │ ├── notification/ │ │ ├── __init__.py │ │ ├── notification.yang │ │ └── run.py │ ├── openconfig-bgp-juniper/ │ │ ├── __init__.py │ │ ├── openconfig-bgp-juniper.yang │ │ └── run.py │ ├── presence/ │ │ ├── __init__.py │ │ ├── presence.yang │ │ └── run.py │ ├── rpc/ │ │ ├── __init__.py │ │ ├── rpc.yang │ │ └── run.py │ ├── serialise/ │ │ ├── __init__.py │ │ ├── ietf-json-deserialise/ │ │ │ ├── __init__.py │ │ │ ├── ietf-json-deserialise.yang │ │ │ ├── json/ │ │ │ │ ├── chlist.json │ │ │ │ ├── complete-obj.json │ │ │ │ ├── mkeylist.json │ │ │ │ ├── nonexistkey.json │ │ │ │ └── skeylist.json │ │ │ ├── remote.yang │ │ │ └── run.py │ │ ├── ietf-json-serialise/ │ │ │ ├── __init__.py │ │ │ ├── augment.yang │ │ │ ├── ietf-json-serialise.yang │ │ │ ├── json/ │ │ │ │ └── obj.json │ │ │ ├── remote.yang │ │ │ └── run.py │ │ ├── json-deserialise/ │ │ │ ├── __init__.py │ │ │ ├── json/ │ │ │ │ ├── alltypes.json │ │ │ │ ├── list-items.json │ │ │ │ ├── list.json │ │ │ │ ├── nonexist.json │ │ │ │ ├── orderedlist-no-order.json │ │ │ │ └── orderedlist-order.json │ │ │ ├── json-deserialise.yang │ │ │ └── run.py │ │ ├── json-serialise/ │ │ │ ├── __init__.py │ │ │ ├── json/ │ │ │ │ ├── container.json │ │ │ │ └── expected-output.json │ │ │ ├── json-serialise.yang │ │ │ └── run.py │ │ ├── openconfig-serialise/ │ │ │ ├── __init__.py │ │ │ ├── json/ │ │ │ │ ├── interfaces_ph.False-flt.False-m.default.json │ │ │ │ ├── interfaces_ph.False-flt.False-m.ietf.json │ │ │ │ ├── interfaces_ph.False-flt.True-m.default.json │ │ │ │ ├── interfaces_ph.False-flt.True-m.ietf.json │ │ │ │ ├── interfaces_ph.True-flt.False-m.default.json │ │ │ │ ├── interfaces_ph.True-flt.False-m.ietf.json │ │ │ │ ├── interfaces_ph.True-flt.True-m.default.json │ │ │ │ └── interfaces_ph.True-flt.True-m.ietf.json │ │ │ └── run.py │ │ ├── roundtrip/ │ │ │ ├── __init__.py │ │ │ ├── remote.yang │ │ │ ├── roundtrip.yang │ │ │ └── run.py │ │ ├── xml-deserialise/ │ │ │ ├── __init__.py │ │ │ ├── augment.yang │ │ │ ├── ietf-xml-deserialise.yang │ │ │ ├── remote.yang │ │ │ ├── run.py │ │ │ └── xml/ │ │ │ └── obj.xml │ │ ├── xml-serialise/ │ │ │ ├── __init__.py │ │ │ ├── augment.yang │ │ │ ├── ietf-xml-serialise.yang │ │ │ ├── remote.yang │ │ │ ├── run.py │ │ │ └── xml/ │ │ │ └── obj.xml │ │ └── xml_utils.py │ ├── split-classes/ │ │ ├── __init__.py │ │ ├── run.py │ │ └── split-classes.yang │ ├── strings/ │ │ ├── __init__.py │ │ ├── run.py │ │ └── string.yang │ ├── submodules/ │ │ ├── __init__.py │ │ ├── mod-a.yang │ │ ├── mod-c.yang │ │ ├── run.py │ │ └── subm-b.yang │ ├── typedef/ │ │ ├── __init__.py │ │ ├── remote.yang │ │ ├── run.py │ │ ├── second-remote.yang │ │ └── typedef.yang │ ├── uint/ │ │ ├── __init__.py │ │ ├── run.py │ │ └── uint.yang │ ├── union/ │ │ ├── __init__.py │ │ ├── run.py │ │ └── union.yang │ ├── unit/ │ │ ├── __init__.py │ │ ├── models/ │ │ │ └── simple.yang │ │ └── test_api.py │ └── xpath/ │ ├── 00_pathhelper_base.py │ ├── 01-list_leaflist/ │ │ ├── __init__.py │ │ ├── list-tc01.yang │ │ └── run.py │ ├── 02-static_ptr/ │ │ ├── __init__.py │ │ ├── ptr-tc02.yang │ │ └── run.py │ ├── 03-current/ │ │ ├── __init__.py │ │ ├── current-tc03.yang │ │ └── run.py │ ├── 04-root/ │ │ ├── __init__.py │ │ ├── json/ │ │ │ ├── 04-serialise.json │ │ │ ├── 04b-ietf-serialise.json │ │ │ ├── 05-deserialise.json │ │ │ └── 06-deserialise-ietf.json │ │ ├── root-tc04-a.yang │ │ ├── root-tc04-b.yang │ │ └── run.py │ └── __init__.py └── tox.ini
SYMBOL INDEX (573 symbols across 52 files)
FILE: pyangbind/helpers/identity.py
class Identity (line 23) | class Identity(object):
method __init__ (line 24) | def __init__(self, name):
method add_prefix (line 32) | def add_prefix(self, prefix):
method add_child (line 38) | def add_child(self, child):
method __str__ (line 43) | def __str__(self):
method prefixes (line 46) | def prefixes(self):
class IdentityStore (line 50) | class IdentityStore(object):
method __init__ (line 51) | def __init__(self):
method find_identity_by_source_name (line 54) | def find_identity_by_source_name(self, s, n):
method add_identity (line 59) | def add_identity(self, i):
method identities (line 66) | def identities(self):
method __iter__ (line 69) | def __iter__(self):
method build_store_from_definitions (line 72) | def build_store_from_definitions(self, ctx, defnd):
method _recurse_children (line 148) | def _recurse_children(self, identity, children):
method _build_inheritance (line 154) | def _build_inheritance(self):
FILE: pyangbind/helpers/misc.py
function module_import_prefixes (line 23) | def module_import_prefixes(ctx):
function find_child_definitions (line 34) | def find_child_definitions(obj, defn, prefix, definitions):
function find_definitions (line 51) | def find_definitions(defn, ctx, module, prefix):
FILE: pyangbind/lib/base.py
class PybindBase (line 22) | class PybindBase(object):
method elements (line 25) | def elements(self):
method __str__ (line 28) | def __str__(self):
method get (line 31) | def get(self, filter=False):
method __getitem__ (line 100) | def __getitem__(self, k):
method __iter__ (line 107) | def __iter__(self):
FILE: pyangbind/lib/pybindJSON.py
function remove_path (line 30) | def remove_path(tree, path):
function loads (line 47) | def loads(d, parent_pymod, yang_base, path_helper=None, extmethods=None,...
function loads_ietf (line 60) | def loads_ietf(d, parent_pymod, yang_base, path_helper=None, extmethods=...
function load (line 69) | def load(fn, parent_pymod, yang_module, path_helper=None, extmethods=Non...
function load_ietf (line 78) | def load_ietf(fn, parent_pymod, yang_module, path_helper=None, extmethod...
function dumps (line 86) | def dumps(obj, indent=4, filter=True, skip_subtrees=[], select=False, mo...
function dump (line 160) | def dump(obj, fn, indent=4, filter=True, skip_subtrees=[], mode="default"):
FILE: pyangbind/lib/serialise.py
class WithDefaults (line 42) | class WithDefaults(IntEnum):
class pybindJSONIOError (line 46) | class pybindJSONIOError(Exception):
class pybindLoadUpdateError (line 50) | class pybindLoadUpdateError(Exception):
class pybindJSONDecodeError (line 54) | class pybindJSONDecodeError(Exception):
class UnmappedItem (line 58) | class UnmappedItem(Exception):
class SerialisedTypedList (line 64) | class SerialisedTypedList(list):
class YangDataSerialiser (line 68) | class YangDataSerialiser(object):
method preprocess_element (line 74) | def preprocess_element(self, d):
method default (line 98) | def default(self, obj):
method map_pyangbind_type (line 173) | def map_pyangbind_type(self, map_val, original_yang_type, obj):
method yangt_int (line 204) | def yangt_int(self, obj):
method yangt_long (line 208) | def yangt_long(self, obj):
method yangt_identityref (line 212) | def yangt_identityref(self, obj):
method yangt_decimal (line 216) | def yangt_decimal(self, obj):
method yangt_empty (line 219) | def yangt_empty(self, obj):
method yangt_typed_list (line 222) | def yangt_typed_list(self, obj):
class IETFYangDataSerialiser (line 226) | class IETFYangDataSerialiser(YangDataSerialiser):
method yangt_long (line 231) | def yangt_long(self, obj):
method yangt_identityref (line 234) | def yangt_identityref(self, obj):
method yangt_decimal (line 245) | def yangt_decimal(self, obj):
method yangt_empty (line 248) | def yangt_empty(self, obj):
class XmlYangDataSerialiser (line 252) | class XmlYangDataSerialiser(IETFYangDataSerialiser):
method yangt_typed_list (line 257) | def yangt_typed_list(self, obj):
method yangt_empty (line 262) | def yangt_empty(self, obj):
class _pybindJSONEncoderBase (line 266) | class _pybindJSONEncoderBase(json.JSONEncoder):
method encode (line 276) | def encode(self, obj):
method default (line 279) | def default(self, obj):
class pybindJSONEncoder (line 283) | class pybindJSONEncoder(_pybindJSONEncoderBase):
class pybindIETFJSONEncoder (line 289) | class pybindIETFJSONEncoder(_pybindJSONEncoderBase):
method yname_ns_func (line 296) | def yname_ns_func(parent_namespace, element, yname):
method generate_element (line 305) | def generate_element(obj, parent_namespace=None, flt=False, with_defau...
class pybindIETFXMLEncoder (line 311) | class pybindIETFXMLEncoder(object):
class EMF (line 318) | class EMF(objectify.ElementMaker):
method __init__ (line 321) | def __init__(self, namespace=None, nsmap=None):
method generate_xml_tree (line 330) | def generate_xml_tree(cls, module_name, module_namespace, tree):
method yname_ns_func (line 366) | def yname_ns_func(parent_namespace, element, yname):
method encode (line 377) | def encode(cls, obj, filter=True):
method serialise (line 385) | def serialise(cls, obj, filter=True, pretty_print=True):
function make_generate_ietf_tree (line 391) | def make_generate_ietf_tree(yname_ns_func):
class pybindIETFXMLDecoder (line 455) | class pybindIETFXMLDecoder(object):
method decode (line 462) | def decode(cls, xml, bindings, module_name):
method load_xml (line 469) | def load_xml(d, parent, yang_base, obj=None, path_helper=None, extmeth...
class pybindJSONDecoder (line 572) | class pybindJSONDecoder(object):
method load_json (line 574) | def load_json(
method check_metadata_add (line 711) | def check_metadata_add(key, data, obj):
method load_ietf_json (line 718) | def load_ietf_json(
FILE: pyangbind/lib/xpathhelper.py
class YANGPathHelperException (line 39) | class YANGPathHelperException(Exception):
class XPathError (line 43) | class XPathError(Exception):
class PybindImplementationError (line 47) | class PybindImplementationError(Exception):
class PybindXpathHelper (line 51) | class PybindXpathHelper(object):
method register (line 52) | def register(self, path, object_ptr, caller=False):
method unregister (line 71) | def unregister(self, path, caller=False):
method get (line 84) | def get(self, path, caller=False):
class FakeRoot (line 100) | class FakeRoot(PybindBase):
method __init__ (line 103) | def __init__(self):
class YANGPathHelper (line 107) | class YANGPathHelper(PybindXpathHelper):
method __init__ (line 116) | def __init__(self):
method _path_parts (line 124) | def _path_parts(self, path):
method _encode_path (line 151) | def _encode_path(self, path, mode="search", find_parent=False, normali...
method _tagname_attributes (line 202) | def _tagname_attributes(self, tag, normalise_namespace=True):
method register (line 226) | def register(self, object_path, object_ptr, caller=False):
method unregister (line 280) | def unregister(self, object_path, caller=False):
method _get_etree (line 295) | def _get_etree(self, object_path, caller=False):
method get (line 307) | def get(self, object_path, caller=False):
method get_unique (line 313) | def get_unique(self, object_path, caller=False, exception_to_raise=YAN...
method get_list (line 321) | def get_list(self, object_path, caller=False, exception_to_raise=YANGP...
method tostring (line 335) | def tostring(self, pretty_print=False):
FILE: pyangbind/lib/yangtypes.py
function is_yang_list (line 92) | def is_yang_list(arg):
function is_yang_leaflist (line 102) | def is_yang_leaflist(arg):
function remove_path_attributes (line 111) | def remove_path_attributes(p):
function safe_name (line 121) | def safe_name(arg):
function RestrictedPrecisionDecimalType (line 134) | def RestrictedPrecisionDecimalType(*args, **kwargs):
function RestrictedClassType (line 171) | def RestrictedClassType(*args, **kwargs):
function TypedListType (line 400) | def TypedListType(*args, **kwargs):
function YANGListType (line 517) | def YANGListType(*args, **kwargs):
class YANGBool (line 874) | class YANGBool(int):
method __new__ (line 884) | def __new__(self, *args, **kwargs):
method __repr__ (line 895) | def __repr__(self):
method __str__ (line 898) | def __str__(self):
function YANGDynClass (line 902) | def YANGDynClass(*args, **kwargs):
function ReferenceType (line 1220) | def ReferenceType(*args, **kwargs):
class YANGBinary (line 1352) | class YANGBinary(bytes):
method __new__ (line 1357) | def __new__(self, *args, **kwargs):
method __repr__ (line 1370) | def __repr__(self):
method __str__ (line 1373) | def __str__(self, encoding="ascii", errors="replace"):
function YANGBitsType (line 1377) | def YANGBitsType(allowed_bits):
FILE: pyangbind/plugin/pybind.py
function pyang_plugin_init (line 191) | def pyang_plugin_init():
class PyangBindClass (line 195) | class PyangBindClass(plugin.PyangPlugin):
method add_output_format (line 196) | def add_output_format(self, fmts):
method emit (line 201) | def emit(self, ctx, modules, fd):
method add_opts (line 205) | def add_opts(self, optparser):
function build_pybind (line 287) | def build_pybind(ctx, modules, fd):
function build_identities (line 440) | def build_identities(ctx, defnd):
function build_typedefs (line 482) | def build_typedefs(ctx, defnd):
function get_children_elements (line 648) | def get_children_elements(
function get_children (line 702) | def get_children(ctx, fd, i_children, module, parent, path=str(), parent...
function build_elemtype (line 1185) | def build_elemtype(ctx, et, prefix=False):
function find_absolute_default_type (line 1370) | def find_absolute_default_type(default_type, default_value, elemname):
function get_element (line 1388) | def get_element(ctx, fd, element, module, parent, path, parent_cfg=True,...
FILE: tests/__init__.py
function test_suite (line 6) | def test_suite():
FILE: tests/base.py
class PyangBindTestCase (line 15) | class PyangBindTestCase(unittest.TestCase):
method _fetch_remote_yang_files (line 24) | def _fetch_remote_yang_files(cls):
method setUpClass (line 52) | def setUpClass(cls):
method tearDownClass (line 92) | def tearDownClass(cls):
FILE: tests/binary/run.py
class BinaryTests (line 8) | class BinaryTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_binary_leafs_exist (line 14) | def test_binary_leafs_exist(self):
method test_set_binary_from_different_datatypes (line 21) | def test_set_binary_from_different_datatypes(self):
method test_binary_leaf_default_value (line 31) | def test_binary_leaf_default_value(self):
method test_binary_leaf_is_empty_by_default (line 39) | def test_binary_leaf_is_empty_by_default(self):
method test_binary_leaf_is_not_changed_by_default (line 47) | def test_binary_leaf_is_not_changed_by_default(self):
method test_set_binary_stores_value (line 54) | def test_set_binary_stores_value(self):
method test_setting_binary_set_changed (line 63) | def test_setting_binary_set_changed(self):
method test_set_specific_length_binary_leaf (line 70) | def test_set_specific_length_binary_leaf(self):
method test_set_binary_leaf_with_length_range (line 84) | def test_set_binary_leaf_with_length_range(self):
method test_set_binary_leaf_with_complex_length (line 98) | def test_set_binary_leaf_with_complex_length(self):
FILE: tests/bits/run.py
class BitsTests (line 9) | class BitsTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_default_bits (line 15) | def test_default_bits(self):
method test_default_bits_with_in (line 22) | def test_default_bits_with_in(self):
method test_default_bits_is_unchanged (line 29) | def test_default_bits_is_unchanged(self):
method test_set_flags (line 32) | def test_set_flags(self):
method test_check_flags_unset (line 42) | def test_check_flags_unset(self):
method test_check_flags_set (line 47) | def test_check_flags_set(self):
method test_bits_position (line 53) | def test_bits_position(self):
method test_bits_no_position (line 59) | def test_bits_no_position(self):
FILE: tests/boolean-empty/run.py
class BooleanEmptyTests (line 8) | class BooleanEmptyTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_leafs_exist (line 14) | def test_leafs_exist(self):
method test_boolean_leaf_accepts_boolean_values (line 21) | def test_boolean_leaf_accepts_boolean_values(self):
method test_boolean_leaf_sets_boolean_values_correctly (line 31) | def test_boolean_leaf_sets_boolean_values_correctly(self):
method test_empty_leaf_accepts_boolean_values (line 56) | def test_empty_leaf_accepts_boolean_values(self):
method test_empty_leaf_sets_boolean_values_correctly (line 66) | def test_empty_leaf_sets_boolean_values_correctly(self):
method test_boolean_leaf_default_value (line 91) | def test_boolean_leaf_default_value(self):
method test_boolean_leaf_is_not_changed_by_default (line 97) | def test_boolean_leaf_is_not_changed_by_default(self):
method test_boolean_leaf_sets_changed (line 103) | def test_boolean_leaf_sets_changed(self):
method test_get (line 110) | def test_get(self):
FILE: tests/choice/run.py
class ChoicesTests (line 8) | class ChoicesTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_class_has_container (line 14) | def test_class_has_container(self):
method test_class_has_choice_containers (line 17) | def test_class_has_choice_containers(self):
method test_class_does_not_have_choices_as_attributes (line 25) | def test_class_does_not_have_choices_as_attributes(self):
method test_case_leaf_default_values (line 39) | def test_case_leaf_default_values(self):
method test_set_choice_value (line 46) | def test_set_choice_value(self):
method test_set_choice_value_doesnt_set_other_choices (line 57) | def test_set_choice_value_doesnt_set_other_choices(self):
method test_change_choice_value (line 66) | def test_change_choice_value(self):
method test_change_choice_value_resets_other_side (line 76) | def test_change_choice_value_resets_other_side(self):
method test_add_to_choice_list_leaf (line 86) | def test_add_to_choice_list_leaf(self):
method test_change_choice_list_to_other_side (line 94) | def test_change_choice_list_to_other_side(self):
method test_change_choice_list_resets_other_side (line 103) | def test_change_choice_list_resets_other_side(self):
method test_set_nested_choice (line 112) | def test_set_nested_choice(self):
FILE: tests/config-false/run.py
class ConfigFalseTests (line 8) | class ConfigFalseTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_container_is_configurable_by_default (line 14) | def test_container_is_configurable_by_default(self):
method test_set_configurable_leaf_with_non_configurable_sibling (line 17) | def test_set_configurable_leaf_with_non_configurable_sibling(self):
method test_leaf_is_configurable_by_default (line 25) | def test_leaf_is_configurable_by_default(self):
method test_set_non_configurable_leaf (line 28) | def test_set_non_configurable_leaf(self):
method test_container_reports_not_configurable_with_config_false (line 36) | def test_container_reports_not_configurable_with_config_false(self):
method test_leaf_reports_not_configurable_with_config_false (line 39) | def test_leaf_reports_not_configurable_with_config_false(self):
method test_set_leaf_in_non_configurable_container (line 42) | def test_set_leaf_in_non_configurable_container(self):
method test_leaf_in_non_configurable_container_reports_not_configurable (line 50) | def test_leaf_in_non_configurable_container_reports_not_configurable(s...
method test_set_leaf_in_sub_container_of_non_configurable_container (line 53) | def test_set_leaf_in_sub_container_of_non_configurable_container(self):
method test_container_in_non_configurable_container_reports_not_configurable (line 61) | def test_container_in_non_configurable_container_reports_not_configura...
method test_leaf_in_sub_container_of_non_configurable_container_reports_not_configurable (line 64) | def test_leaf_in_sub_container_of_non_configurable_container_reports_n...
FILE: tests/decimal64/run.py
class DecimalTests (line 9) | class DecimalTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_container_has_all_leafs (line 15) | def test_container_has_all_leafs(self):
method test_decimal_precision (line 20) | def test_decimal_precision(self):
method test_decimal_rounding (line 28) | def test_decimal_rounding(self):
method test_decimal_default_with_extra_precision (line 36) | def test_decimal_default_with_extra_precision(self):
method test_decimal_default_with_less_precision (line 43) | def test_decimal_default_with_less_precision(self):
method test_various_values_with_complex_range (line 50) | def test_various_values_with_complex_range(self):
FILE: tests/enumeration/run.py
class EnumerationTests (line 8) | class EnumerationTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_container_has_all_leafs (line 14) | def test_container_has_all_leafs(self):
method test_assign_to_enum (line 21) | def test_assign_to_enum(self):
method test_enum_does_not_allow_invalid_value (line 29) | def test_enum_does_not_allow_invalid_value(self):
method test_enum_default_value (line 39) | def test_enum_default_value(self):
method test_static_enum_value (line 46) | def test_static_enum_value(self):
FILE: tests/extensions/run.py
class ExtensionsTests (line 9) | class ExtensionsTests(PyangBindTestCase):
method setUp (line 14) | def setUp(self):
method test_extensions_get_added_to_container (line 17) | def test_extensions_get_added_to_container(self):
method test_extensions_are_not_added_to_leaf_with_none_specified (line 24) | def test_extensions_are_not_added_to_leaf_with_none_specified(self):
method test_extensions_are_not_added_to_container_with_none_specified (line 30) | def test_extensions_are_not_added_to_container_with_none_specified(self):
method test_extensions_get_added_to_leaf (line 37) | def test_extensions_get_added_to_leaf(self):
method test_extensions_get_added_to_list (line 44) | def test_extensions_get_added_to_list(self):
method test_extensions_get_added_to_list_member (line 51) | def test_extensions_get_added_to_list_member(self):
method test_proper_extensions_get_added_to_list_leaf (line 59) | def test_proper_extensions_get_added_to_list_leaf(self):
FILE: tests/extmethods/run.py
class extmethodcls (line 8) | class extmethodcls(object):
method commit (line 9) | def commit(self, *args, **kwargs):
method presave (line 12) | def presave(self, *args, **kwargs):
method postsave (line 15) | def postsave(self, *args, **kwargs):
method oam_check (line 18) | def oam_check(self, *args, **kwargs):
method echo (line 21) | def echo(self, *args, **kwargs):
class ExtMethodsTests (line 25) | class ExtMethodsTests(PyangBindTestCase):
method setUp (line 29) | def setUp(self):
method test_extmethods_get_created_on_leafs (line 32) | def test_extmethods_get_created_on_leafs(self):
method test_extmethods_return_expected_values (line 44) | def test_extmethods_return_expected_values(self):
method test_args_and_kwargs_pass_to_extmethods_properly (line 55) | def test_args_and_kwargs_pass_to_extmethods_properly(self):
method test_kwargs_passed_to_extmethods_do_not_set_invalid_attributes (line 59) | def test_kwargs_passed_to_extmethods_do_not_set_invalid_attributes(self):
FILE: tests/identityref/run.py
class IdentityRefTests (line 11) | class IdentityRefTests(PyangBindTestCase):
method setUp (line 14) | def setUp(self):
method test_identityref_leafs_get_created (line 17) | def test_identityref_leafs_get_created(self):
method test_cant_assign_invalid_string_to_identityref (line 22) | def test_cant_assign_invalid_string_to_identityref(self):
method test_identityref_leafs_are_blank_by_default (line 26) | def test_identityref_leafs_are_blank_by_default(self):
method test_identityref_accepts_valid_identity_values (line 31) | def test_identityref_accepts_valid_identity_values(self):
method test_remote_identityref_accepts_valid_identity_values (line 41) | def test_remote_identityref_accepts_valid_identity_values(self):
method test_set_ancestral_identities_one (line 51) | def test_set_ancestral_identities_one(self):
method test_set_ancestral_identities_two (line 70) | def test_set_ancestral_identities_two(self):
method test_set_ancestral_identities_three (line 90) | def test_set_ancestral_identities_three(self):
method test_set_ancestral_identities_four (line 100) | def test_set_ancestral_identities_four(self):
method test_set_ancestral_identities_five (line 118) | def test_set_ancestral_identities_five(self):
method test_grouping_identity_inheritance (line 133) | def test_grouping_identity_inheritance(self):
method test_set_identityref_from_imported_module (line 148) | def test_set_identityref_from_imported_module(self):
method test_set_identityref_from_imported_module_referencing_local (line 162) | def test_set_identityref_from_imported_module_referencing_local(self):
method test_json_ietf_serialise_namespace_handling_remote (line 172) | def test_json_ietf_serialise_namespace_handling_remote(self):
method test_json_ietf_serialise_namespace_handling_local (line 185) | def test_json_ietf_serialise_namespace_handling_local(self):
method test_load_identityref_with_module_prefix (line 198) | def test_load_identityref_with_module_prefix(self):
FILE: tests/include-import/run.py
class IncludeImportTests (line 8) | class IncludeImportTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_all_the_things_build (line 14) | def test_all_the_things_build(self):
FILE: tests/int/run.py
class IntTests (line 8) | class IntTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_all_leafs_are_present (line 14) | def test_all_leafs_are_present(self):
method test_default_values (line 23) | def test_default_values(self):
method test_set_int_values (line 38) | def test_set_int_values(self):
method test_set_int_values_marks_changed (line 45) | def test_set_int_values_marks_changed(self):
method test_leaf_math_and_negatives (line 52) | def test_leaf_math_and_negatives(self):
method test_set_restricted_values_within_allowed_range (line 68) | def test_set_restricted_values_within_allowed_range(self):
method test_set_values_outside_allowed_range (line 78) | def test_set_values_outside_allowed_range(self):
method test_int8_max_range (line 99) | def test_int8_max_range(self):
method test_int8_min_range (line 113) | def test_int8_min_range(self):
method test_int8_min_range_alias (line 127) | def test_int8_min_range_alias(self):
method test_complex_range_with_two_segments (line 142) | def test_complex_range_with_two_segments(self):
method test_complex_range_with_three_segments_and_spaces (line 156) | def test_complex_range_with_three_segments_and_spaces(self):
method test_complex_range_with_negative (line 171) | def test_complex_range_with_negative(self):
method test_int8_range_with_negatives_and_spaces (line 186) | def test_int8_range_with_negatives_and_spaces(self):
method test_complex_range_with_equals_case (line 201) | def test_complex_range_with_equals_case(self):
method test_setters_exist (line 216) | def test_setters_exist(self):
method test_set_int_sizes_at_lower_bounds (line 222) | def test_set_int_sizes_at_lower_bounds(self):
method test_set_int_sizes_below_lower_bounds (line 234) | def test_set_int_sizes_below_lower_bounds(self):
method test_set_int_sizes_at_upper_bounds (line 251) | def test_set_int_sizes_at_upper_bounds(self):
method test_set_int_sizes_above_upper_bounds (line 263) | def test_set_int_sizes_above_upper_bounds(self):
method test_set_int8_range_with_min_max_alias_to_lower_bound (line 275) | def test_set_int8_range_with_min_max_alias_to_lower_bound(self):
method test_set_int8_range_with_min_max_alias_to_upper_bound (line 283) | def test_set_int8_range_with_min_max_alias_to_upper_bound(self):
method test_set_int8_range_with_min_max_alias_below_lower_bound (line 291) | def test_set_int8_range_with_min_max_alias_below_lower_bound(self):
method test_set_int8_range_with_min_max_alias_above_upper_bound (line 299) | def test_set_int8_range_with_min_max_alias_above_upper_bound(self):
FILE: tests/integration/openconfig-interfaces/run.py
class OpenconfigInterfacesTests (line 10) | class OpenconfigInterfacesTests(PyangBindTestCase):
method setUp (line 50) | def setUp(self):
method test_001_populated_intf_type (line 54) | def test_001_populated_intf_type(self):
FILE: tests/leaf-list/run.py
class LeafListTests (line 9) | class LeafListTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_container_exists (line 15) | def test_container_exists(self):
method test_leaflist_exists (line 18) | def test_leaflist_exists(self):
method test_leaflist_length_is_zero (line 21) | def test_leaflist_length_is_zero(self):
method test_append_to_leaflist (line 24) | def test_append_to_leaflist(self):
method test_retrieve_leaflist_item_value (line 28) | def test_retrieve_leaflist_item_value(self):
method test_append_int_to_string_leaflist (line 32) | def test_append_int_to_string_leaflist(self):
method test_getitem (line 36) | def test_getitem(self):
method test_setitem (line 42) | def test_setitem(self):
method test_insert (line 49) | def test_insert(self):
method test_leaflist_grows_from_various_modification_methods (line 57) | def test_leaflist_grows_from_various_modification_methods(self):
method test_delete_item_from_leaflist (line 65) | def test_delete_item_from_leaflist(self):
method test_get_full_leaflist (line 75) | def test_get_full_leaflist(self):
method test_leaflist_assignment (line 87) | def test_leaflist_assignment(self):
method test_leaflist_assignment_of_wrong_type (line 92) | def test_leaflist_assignment_of_wrong_type(self):
method test_restricted_string (line 96) | def test_restricted_string(self):
method test_restricted_string_invalid_value (line 100) | def test_restricted_string_invalid_value(self):
method test_union_type (line 104) | def test_union_type(self):
method test_leaf_lists_are_unique_after_assignment (line 114) | def test_leaf_lists_are_unique_after_assignment(self):
method test_leaf_lists_are_unique_after_append (line 118) | def test_leaf_lists_are_unique_after_append(self):
method test_leaf_lists_insert_non_unique_value_raises_keyerror (line 124) | def test_leaf_lists_insert_non_unique_value_raises_keyerror(self):
FILE: tests/list/run.py
class ListTests (line 9) | class ListTests(PyangBindTestCase):
method setUp (line 13) | def setUp(self):
method test_list_element_has_zero_members_by_default (line 16) | def test_list_element_has_zero_members_by_default(self):
method test_cant_add_list_item_with_wrong_type (line 19) | def test_cant_add_list_item_with_wrong_type(self):
method test_cant_add_list_item_with_wrong_type_by_index (line 23) | def test_cant_add_list_item_with_wrong_type_by_index(self):
method test_add_list_item_with_correct_type (line 27) | def test_add_list_item_with_correct_type(self):
method test_look_up_list_element (line 35) | def test_look_up_list_element(self):
method test_list_value_does_not_get_default_value_when_not_set (line 39) | def test_list_value_does_not_get_default_value_when_not_set(self):
method test_set_both_values_in_a_list_item (line 43) | def test_set_both_values_in_a_list_item(self):
method test_list_get (line 50) | def test_list_get(self):
method test_delete_list_items (line 75) | def test_delete_list_items(self):
method test_add_list_item_with_restricted_key (line 82) | def test_add_list_item_with_restricted_key(self):
method test_add_list_item_with_key_restricted_by_union_typedef (line 92) | def test_add_list_item_with_key_restricted_by_union_typedef(self):
method test_add_list_item_with_restricted_key_by_keyword (line 102) | def test_add_list_item_with_restricted_key_by_keyword(self):
method test_list_item_key_value_is_read_only (line 112) | def test_list_item_key_value_is_read_only(self):
method test_adding_items_to_multi_key_list (line 117) | def test_adding_items_to_multi_key_list(self):
method test_ordered_list_maintains_order (line 127) | def test_ordered_list_maintains_order(self):
method test_cant_add_empty_item_to_list_with_key (line 135) | def test_cant_add_empty_item_to_list_with_key(self):
method test_set_value_on_list_item_with_no_key (line 139) | def test_set_value_on_list_item_with_no_key(self):
method test_retrieve_compound_key_with_spaces (line 144) | def test_retrieve_compound_key_with_spaces(self):
method test_retrieve_compound_key_with_spaces_using_item (line 148) | def test_retrieve_compound_key_with_spaces_using_item(self):
method test_delete_list_item_with_keyword_arguments (line 154) | def test_delete_list_item_with_keyword_arguments(self):
method test_list_item_is_removed_when_deleted (line 163) | def test_list_item_is_removed_when_deleted(self):
method test_cant_delete_nonexistent_list_item_by_keywords (line 170) | def test_cant_delete_nonexistent_list_item_by_keywords(self):
method test_add_list_item_with_specified_value (line 175) | def test_add_list_item_with_specified_value(self):
method test_retrieve_list_item_which_was_set_with_v (line 190) | def test_retrieve_list_item_which_was_set_with_v(self):
method test_retrieve_list_element_with_value_set_by_setitem (line 200) | def test_retrieve_list_element_with_value_set_by_setitem(self):
method test_retrieve_list_element_with_value_set_by_setitem_using_named_getitem (line 208) | def test_retrieve_list_element_with_value_set_by_setitem_using_named_g...
method test_cant_set_key_on_already_instantiated_list_item (line 218) | def test_cant_set_key_on_already_instantiated_list_item(self):
method test_append_new_list_item (line 227) | def test_append_new_list_item(self):
method test_list_append_new_items_updates_keys (line 234) | def test_list_append_new_items_updates_keys(self):
method test_append_new_list_item_with_compound_key (line 242) | def test_append_new_list_item_with_compound_key(self):
method test_list_append_new_items_with_compound_keys_updates_keys (line 250) | def test_list_append_new_items_with_compound_keys_updates_keys(self):
method test_append_new_list_item_with_identityref (line 259) | def test_append_new_list_item_with_identityref(self):
method test_append_new_list_item_with_identityref_doesnt_set_unchanged_elements (line 266) | def test_append_new_list_item_with_identityref_doesnt_set_unchanged_el...
method test_cant_set_nonexistent_item (line 272) | def test_cant_set_nonexistent_item(self):
FILE: tests/misc/run.py
class MiscTests (line 10) | class MiscTests(PyangBindTestCase):
method setUp (line 15) | def setUp(self):
method test_001_setleafref (line 21) | def test_001_setleafref(self):
method test_002_checklistkeytype (line 31) | def test_002_checklistkeytype(self):
method test_003_checklistkeytype (line 41) | def test_003_checklistkeytype(self):
FILE: tests/nested-containers/run.py
class NestedContainerTests (line 8) | class NestedContainerTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_subcontainer_is_not_changed_by_default (line 14) | def test_subcontainer_is_not_changed_by_default(self):
method test_container_is_not_changed_by_default (line 17) | def test_container_is_not_changed_by_default(self):
method test_subcontainer_marked_changed (line 20) | def test_subcontainer_marked_changed(self):
method test_subcontainer_get (line 26) | def test_subcontainer_get(self):
method test_container_get (line 30) | def test_container_get(self):
method test_full_get (line 34) | def test_full_get(self):
FILE: tests/notification/run.py
class NotificationTests (line 9) | class NotificationTests(PyangBindTestCase):
method setUp (line 14) | def setUp(self):
method test_set_leaf_inside_notification (line 19) | def test_set_leaf_inside_notification(self):
method test_set_multiple_leafs_inside_notification (line 30) | def test_set_multiple_leafs_inside_notification(self):
method test_set_leafs_on_a_container_inside_a_notification (line 42) | def test_set_leafs_on_a_container_inside_a_notification(self):
method test_set_leafs_on_multiple_containers_inside_a_notification (line 54) | def test_set_leafs_on_multiple_containers_inside_a_notification(self):
method test_set_leafref_inside_notification (line 66) | def test_set_leafref_inside_notification(self):
FILE: tests/openconfig-bgp-juniper/run.py
class OpenconfigBGPJuniperTests (line 8) | class OpenconfigBGPJuniperTests(PyangBindTestCase):
method setUp (line 20) | def setUp(self):
method test_set_unknown_element (line 32) | def test_set_unknown_element(self):
method test_get (line 40) | def test_get(self):
method test_filtered_get (line 69) | def test_filtered_get(self):
FILE: tests/presence/run.py
class PresenceTests (line 11) | class PresenceTests(PyangBindTestCase):
method setUp (line 15) | def setUp(self):
method test_001_check_containers (line 18) | def test_001_check_containers(self):
method test_002_check_leafs (line 37) | def test_002_check_leafs(self):
method test_003_check_presence (line 49) | def test_003_check_presence(self):
method test_004_check_set_present (line 54) | def test_004_check_set_present(self):
method test_005_check_np (line 61) | def test_005_check_np(self):
method test_006_check_hierarchy (line 66) | def test_006_check_hierarchy(self):
method test_007_check_invalid_hierarchy (line 74) | def test_007_check_invalid_hierarchy(self):
method test_008_set_not_present (line 82) | def test_008_set_not_present(self):
method test_009_presence_get (line 88) | def test_009_presence_get(self):
method test_010_presence_serialise (line 98) | def test_010_presence_serialise(self):
method test_011_presence_serialise_ietf (line 116) | def test_011_presence_serialise_ietf(self):
method test_012_presence_deserialise (line 131) | def test_012_presence_deserialise(self):
method test_013_presence_deserialise (line 143) | def test_013_presence_deserialise(self):
class SplitClassesPresenceTests (line 156) | class SplitClassesPresenceTests(PresenceTests):
FILE: tests/rpc/run.py
class RPCTests (line 9) | class RPCTests(PyangBindTestCase):
method setUp (line 14) | def setUp(self):
method test_set_input_argument (line 17) | def test_set_input_argument(self):
method test_set_output_arguments (line 29) | def test_set_output_arguments(self):
method test_set_input_arguments_inside_container (line 41) | def test_set_input_arguments_inside_container(self):
method test_set_output_arguments_with_multiple_containers (line 53) | def test_set_output_arguments_with_multiple_containers(self):
method set_input_and_output_arguments_on_a_single_rpc_check (line 65) | def set_input_and_output_arguments_on_a_single_rpc_check(self):
method test_rpc_attributes_do_not_register_in_path_helper (line 77) | def test_rpc_attributes_do_not_register_in_path_helper(self):
method test_set_input_argument_to_valid_leafref (line 84) | def test_set_input_argument_to_valid_leafref(self):
method test_set_input_argument_to_invalid_leafref (line 99) | def test_set_input_argument_to_invalid_leafref(self):
FILE: tests/serialise/ietf-json-deserialise/run.py
class IETFJSONDeserialiseTests (line 14) | class IETFJSONDeserialiseTests(PyangBindTestCase):
method test_multi_key_list_load (line 18) | def test_multi_key_list_load(self):
method test_single_key_list_load (line 28) | def test_single_key_list_load(self):
method test_list_with_children_load (line 38) | def test_list_with_children_load(self):
method test_all_the_types (line 51) | def test_all_the_types(self):
method test_skip_unknown_keys (line 105) | def test_skip_unknown_keys(self):
method test_dont_skip_unknown_keys (line 116) | def test_dont_skip_unknown_keys(self):
FILE: tests/serialise/ietf-json-serialise/run.py
class IETFJSONDeserialiseTests (line 13) | class IETFJSONDeserialiseTests(PyangBindTestCase):
method setUp (line 17) | def setUp(self):
method test_serialise_single_object (line 21) | def test_serialise_single_object(self):
method test_serialise_full_container (line 33) | def test_serialise_full_container(self):
FILE: tests/serialise/json-deserialise/run.py
class JSONDeserialiseTests (line 17) | class JSONDeserialiseTests(PyangBindTestCase):
method setUp (line 21) | def setUp(self):
method test_load_full_object (line 25) | def test_load_full_object(self):
method test_load_into_existing_object (line 47) | def test_load_into_existing_object(self):
method test_all_the_types (line 54) | def test_all_the_types(self):
method test_load_user_ordered_list (line 97) | def test_load_user_ordered_list(self):
method test_load_json_ordered_list (line 106) | def test_load_json_ordered_list(self):
method test_skip_unknown_keys (line 115) | def test_skip_unknown_keys(self):
method test_dont_skip_unknown_keys (line 124) | def test_dont_skip_unknown_keys(self):
FILE: tests/serialise/json-serialise/run.py
class JSONSerialiseTests (line 13) | class JSONSerialiseTests(PyangBindTestCase):
method setUp (line 17) | def setUp(self):
method test_serialise_container (line 21) | def test_serialise_container(self):
method test_full_serialise (line 30) | def test_full_serialise(self):
FILE: tests/serialise/openconfig-serialise/run.py
class OpenconfigSerialiseTests (line 18) | class OpenconfigSerialiseTests(PyangBindTestCase):
method setUp (line 50) | def setUp(self):
method test_json_generation (line 53) | def test_json_generation(self):
method test_pybind_ietf_json_encoder_serialisation_with_path_helper (line 77) | def test_pybind_ietf_json_encoder_serialisation_with_path_helper(self):
method test_pybind_ietf_json_encoder_serialisation_without_path_helper (line 89) | def test_pybind_ietf_json_encoder_serialisation_without_path_helper(se...
method test_direct_json_serialisation_of_instance_with_path_helper (line 101) | def test_direct_json_serialisation_of_instance_with_path_helper(self):
method test_direct_json_serialisation_of_instance_without_path_helper (line 113) | def test_direct_json_serialisation_of_instance_without_path_helper(self):
FILE: tests/serialise/roundtrip/run.py
class RoundtripTests (line 14) | class RoundtripTests(PyangBindTestCase):
method setUp (line 18) | def setUp(self):
method test_ietf_roundtrip_simple (line 22) | def test_ietf_roundtrip_simple(self):
method test_roundtrip_simple (line 27) | def test_roundtrip_simple(self):
FILE: tests/serialise/xml-deserialise/run.py
class XMLDeserialiseTests (line 13) | class XMLDeserialiseTests(PyangBindTestCase):
method test_deserialise_full_container_roundtrip (line 17) | def test_deserialise_full_container_roundtrip(self):
FILE: tests/serialise/xml-serialise/run.py
class XMLSerialiseTests (line 15) | class XMLSerialiseTests(PyangBindTestCase):
method setUp (line 19) | def setUp(self):
method test_serialise_full_container (line 23) | def test_serialise_full_container(self):
FILE: tests/serialise/xml_utils.py
function xml_tree_equivalence (line 1) | def xml_tree_equivalence(e1, e2):
FILE: tests/split-classes/run.py
class SplitClassesTests (line 8) | class SplitClassesTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_first_container_name_matches_module_name (line 15) | def test_first_container_name_matches_module_name(self):
method test_hierarchy_with_repeating_name (line 23) | def test_hierarchy_with_repeating_name(self):
method test_add_entry_to_case_one_container (line 31) | def test_add_entry_to_case_one_container(self):
method test_adding_entry_to_other_case_after_first_case (line 35) | def test_adding_entry_to_other_case_after_first_case(self):
method test_adding_entry_to_other_case_clears_first_case (line 40) | def test_adding_entry_to_other_case_clears_first_case(self):
FILE: tests/strings/run.py
class StringTests (line 9) | class StringTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_string_leaf_is_not_changed_by_default (line 15) | def test_string_leaf_is_not_changed_by_default(self):
method test_set_basic_string_value_on_string_leaf (line 18) | def test_set_basic_string_value_on_string_leaf(self):
method test_integer_gets_cast_to_string (line 22) | def test_integer_gets_cast_to_string(self):
method test_string_leaf_gets_marked_as_changed (line 26) | def test_string_leaf_gets_marked_as_changed(self):
method test_concatenation_to_string_leaf (line 30) | def test_concatenation_to_string_leaf(self):
method test_string_leaf_with_default_is_blank (line 35) | def test_string_leaf_with_default_is_blank(self):
method test_string_leaf_with_default_has_correct_default_value_hidden (line 38) | def test_string_leaf_with_default_has_correct_default_value_hidden(self):
method test_string_leaf_with_default_and_pattern_has_correct_default_value_hidden (line 41) | def test_string_leaf_with_default_and_pattern_has_correct_default_valu...
method test_set_valid_value_on_restricted_string (line 44) | def test_set_valid_value_on_restricted_string(self):
method test_set_invalid_value_on_restricted_string (line 52) | def test_set_invalid_value_on_restricted_string(self):
method test_fixed_length_string (line 56) | def test_fixed_length_string(self):
method test_fixed_length_string_with_pattern (line 66) | def test_fixed_length_string_with_pattern(self):
method test_string_with_length_as_range_with_max (line 76) | def test_string_with_length_as_range_with_max(self):
method test_string_with_length_as_range_with_upper_bound (line 86) | def test_string_with_length_as_range_with_upper_bound(self):
method test_string_leaf_with_complex_length (line 96) | def test_string_leaf_with_complex_length(self):
method test_string_leaf_pattern_with_dollar (line 112) | def test_string_leaf_pattern_with_dollar(self):
method test_string_leaf_pattern_with_dollar_at_end (line 122) | def test_string_leaf_pattern_with_dollar_at_end(self):
FILE: tests/submodules/run.py
class PyangbindSubmoduleTests (line 8) | class PyangbindSubmoduleTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_001_check_correct_import (line 15) | def test_001_check_correct_import(self):
method test_002_identity_in_submodule (line 19) | def test_002_identity_in_submodule(self):
method test_assign_idref (line 23) | def test_assign_idref(self):
FILE: tests/typedef/run.py
class TypedefTests (line 8) | class TypedefTests(PyangBindTestCase):
method setUp (line 11) | def setUp(self):
method test_types (line 14) | def test_types(self):
method test_string_container (line 30) | def test_string_container(self):
method test_string_default (line 38) | def test_string_default(self):
method test_string_default_from_typedef (line 46) | def test_string_default_from_typedef(self):
method test_int_value_can_be_updated (line 54) | def test_int_value_can_be_updated(self):
method test_int_value_range_restriction (line 58) | def test_int_value_range_restriction(self):
method test_remote_definition (line 62) | def test_remote_definition(self):
method test_remote_local_definition (line 70) | def test_remote_local_definition(self):
method test_inherited_patterns (line 79) | def test_inherited_patterns(self):
method test_inherited_range (line 93) | def test_inherited_range(self):
method test_stacked_union (line 107) | def test_stacked_union(self):
method test_hybrid_typedef_across_modules (line 121) | def test_hybrid_typedef_across_modules(self):
method test_identity_reference (line 136) | def test_identity_reference(self):
method test_union_with_union (line 151) | def test_union_with_union(self):
method test_scoped_leaf (line 166) | def test_scoped_leaf(self):
method test_union_with_identityref (line 174) | def test_union_with_identityref(self):
method test_nested_typedefs (line 189) | def test_nested_typedefs(self):
FILE: tests/uint/run.py
class UIntTests (line 10) | class UIntTests(PyangBindTestCase):
method setUp (line 13) | def setUp(self):
method test_uint_maximum_default_values (line 16) | def test_uint_maximum_default_values(self):
method test_set_uint_values (line 27) | def test_set_uint_values(self):
method test_set_uint_values_marks_changes (line 34) | def test_set_uint_values_marks_changes(self):
method test_uint8_with_restricted_range (line 41) | def test_uint8_with_restricted_range(self):
method test_uint16_with_restricted_range (line 51) | def test_uint16_with_restricted_range(self):
method test_uint32_with_restricted_range (line 61) | def test_uint32_with_restricted_range(self):
method test_uint64_with_restricted_range (line 71) | def test_uint64_with_restricted_range(self):
method test_additional_uint32_range (line 81) | def test_additional_uint32_range(self):
method test_set_uint_values_to_zero (line 91) | def test_set_uint_values_to_zero(self):
method test_set_uint_values_below_zero (line 102) | def test_set_uint_values_below_zero(self):
method test_set_uint_values_above_upper_bounds (line 109) | def test_set_uint_values_above_upper_bounds(self):
FILE: tests/union/run.py
class UnionTests (line 10) | class UnionTests(PyangBindTestCase):
method setUp (line 13) | def setUp(self):
method test_union_of_int_over_string_allows_math_on_integer_value (line 17) | def test_union_of_int_over_string_allows_math_on_integer_value(self):
method test_set_union_of_int_over_string_to_string_after_int (line 22) | def test_set_union_of_int_over_string_to_string_after_int(self):
method test_union_of_int_over_string_allows_concatenation_to_string_value (line 27) | def test_union_of_int_over_string_allows_concatenation_to_string_value...
method test_union_of_string_over_int_with_default_is_empty_string (line 33) | def test_union_of_string_over_int_with_default_is_empty_string(self):
method test_default_value_of_string_over_int_with_default (line 36) | def test_default_value_of_string_over_int_with_default(self):
method test_union_of_string_over_int_performs_string_concatenation (line 39) | def test_union_of_string_over_int_performs_string_concatenation(self):
method test_default_value_of_int_over_string_is_zero (line 45) | def test_default_value_of_int_over_string_is_zero(self):
method test_default_value_of_int_over_string_with_default_string (line 48) | def test_default_value_of_int_over_string_with_default_string(self):
method test_union_of_int_over_string_with_int_default_is_int_type (line 51) | def test_union_of_int_over_string_with_int_default_is_int_type(self):
method test_default_value_gets_set_from_typedef (line 54) | def test_default_value_gets_set_from_typedef(self):
method test_set_typedef_union_of_int_over_string_to_a_string_value (line 57) | def test_set_typedef_union_of_int_over_string_to_a_string_value(self):
method test_set_typedef_union_of_int_over_string_to_int_value_after_string (line 61) | def test_set_typedef_union_of_int_over_string_to_int_value_after_strin...
method test_default_value_of_int_typedef_within_union_typedef (line 66) | def test_default_value_of_int_typedef_within_union_typedef(self):
method test_leaf_list_with_union_of_unions_from_typedefs (line 69) | def test_leaf_list_with_union_of_unions_from_typedefs(self):
method test_leaf_list_with_union_of_unions_from_typedefs_with_restricted_types (line 79) | def test_leaf_list_with_union_of_unions_from_typedefs_with_restricted_...
method test_union_of_unions_from_typedefs_with_local_default_gets_proper_default (line 98) | def test_union_of_unions_from_typedefs_with_local_default_gets_proper_...
method test_union_of_restricted_class_types (line 101) | def test_union_of_restricted_class_types(self):
FILE: tests/unit/test_api.py
class APITests (line 4) | class APITests(PyangBindTestCase):
method setUp (line 7) | def setUp(self):
method test_leaf_unchanged (line 12) | def test_leaf_unchanged(self):
method test_leaf_changed_with_defaults (line 16) | def test_leaf_changed_with_defaults(self):
method test_leaf_changed_with_non_defaults (line 25) | def test_leaf_changed_with_non_defaults(self):
FILE: tests/xpath/00_pathhelper_base.py
class TestObject (line 10) | class TestObject(object):
method __init__ (line 11) | def __init__(self, name):
method name (line 14) | def name(self):
class PathHelperBaseTests (line 18) | class PathHelperBaseTests(unittest.TestCase):
method setUp (line 19) | def setUp(self):
method test_get_returns_same_number_of_objects_as_registered (line 22) | def test_get_returns_same_number_of_objects_as_registered(self):
method test_get_returns_objects_of_same_class_as_registered (line 27) | def test_get_returns_objects_of_same_class_as_registered(self):
method test_get_returns_objects_with_same_attributes_as_registered (line 32) | def test_get_returns_objects_with_same_attributes_as_registered(self):
method test_get_non_existent_path_returns_nothing (line 37) | def test_get_non_existent_path_returns_nothing(self):
method test_register_invalid_path_raises_exception (line 40) | def test_register_invalid_path_raises_exception(self):
method test_retrieve_object_at_bottom_of_hierarchy_returns_single_object (line 44) | def test_retrieve_object_at_bottom_of_hierarchy_returns_single_object(...
method test_retrieve_object_at_bottom_of_hierarchy_has_proper_name (line 50) | def test_retrieve_object_at_bottom_of_hierarchy_has_proper_name(self):
method test_register_object_with_attribute (line 56) | def test_register_object_with_attribute(self):
method test_retrieve_object_by_attribute_returns_single_object (line 65) | def test_retrieve_object_by_attribute_returns_single_object(self):
method test_get_object_by_attribute_returns_object_of_same_class (line 71) | def test_get_object_by_attribute_returns_object_of_same_class(self):
method test_register_object_with_attribute_various_quoting_styles (line 76) | def test_register_object_with_attribute_various_quoting_styles(self):
method test_get_object_with_attribute_various_quoting_styles (line 87) | def test_get_object_with_attribute_various_quoting_styles(self):
FILE: tests/xpath/01-list_leaflist/run.py
class XPathListLeaflistTests (line 9) | class XPathListLeaflistTests(PyangBindTestCase):
method setUp (line 13) | def setUp(self):
method test_leaflist_leafref_with_require_instance_true (line 17) | def test_leaflist_leafref_with_require_instance_true(self):
method test_leaflist_leafref_with_require_instance_false (line 29) | def test_leaflist_leafref_with_require_instance_false(self):
method test_list_leafref_with_require_instance_true (line 41) | def test_list_leafref_with_require_instance_true(self):
method test_get_leaflist_with_xpath_helper_returns_single_element (line 54) | def test_get_leaflist_with_xpath_helper_returns_single_element(self):
method test_find_elements_of_leaflist (line 60) | def test_find_elements_of_leaflist(self):
method test_remove_elements_from_leaflist (line 74) | def test_remove_elements_from_leaflist(self):
method test_xpath_helper_gets_updated_leaflist_after_removing_items (line 87) | def test_xpath_helper_gets_updated_leaflist_after_removing_items(self):
method test_get_list_item_with_xpath_helper_returns_single_element (line 100) | def test_get_list_item_with_xpath_helper_returns_single_element(self):
method test_remove_elements_from_list (line 109) | def test_remove_elements_from_list(self):
method test_xpath_helper_gets_updated_list_after_removing_items (line 122) | def test_xpath_helper_gets_updated_list_after_removing_items(self):
method test_typedef_leaflist_with_require_instance_true (line 138) | def test_typedef_leaflist_with_require_instance_true(self):
method test_typedef_list_with_require_instance_true (line 151) | def test_typedef_list_with_require_instance_true(self):
method test_leaflist_of_leafrefs_with_require_instance_true (line 164) | def test_leaflist_of_leafrefs_with_require_instance_true(self):
method test_standalone_leaflist (line 177) | def test_standalone_leaflist(self):
method test_standlone_list (line 182) | def test_standlone_list(self):
method test_standalone_ref (line 187) | def test_standalone_ref(self):
method test_get_list_retrieves_correct_attribute (line 192) | def test_get_list_retrieves_correct_attribute(self):
method test_get_list_returns_correct_type (line 195) | def test_get_list_returns_correct_type(self):
FILE: tests/xpath/02-static_ptr/run.py
class XPathStaticPtrTests (line 8) | class XPathStaticPtrTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_list_key_pointer (line 18) | def test_list_key_pointer(self):
method test_list_key_value (line 28) | def test_list_key_value(self):
FILE: tests/xpath/03-current/run.py
class XPathCurrentTests (line 8) | class XPathCurrentTests(PyangBindTestCase):
method setUp (line 12) | def setUp(self):
method test_referencing_list_source_val (line 21) | def test_referencing_list_source_val(self):
method test_referencing_list_reference (line 24) | def test_referencing_list_reference(self):
method test_src_list_referenced (line 27) | def test_src_list_referenced(self):
method test_src_list_value (line 30) | def test_src_list_value(self):
FILE: tests/xpath/04-root/run.py
class XPathRootTests (line 14) | class XPathRootTests(PyangBindTestCase):
method setUp (line 18) | def setUp(self):
method test_001_check_containers (line 23) | def test_001_check_containers(self):
method test_002_base_gets (line 27) | def test_002_base_gets(self):
method test_003_base_sets (line 33) | def test_003_base_sets(self):
method test_004_serialise (line 41) | def test_004_serialise(self):
method test_005_deserialise (line 54) | def test_005_deserialise(self):
method test_006_ietf_deserialise (line 63) | def test_006_ietf_deserialise(self):
Condensed preview — 229 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (612K chars).
[
{
"path": ".coveragerc",
"chars": 717,
"preview": "# .coveragerc to control coverage.py\n[run]\nbranch = True\nomit =\n */.tox/*\n */.virtualenvs/*\n */tests/*\n */en"
},
{
"path": ".editorconfig",
"chars": 454,
"preview": "# http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_traili"
},
{
"path": ".github/workflows/pypi.yml",
"chars": 1014,
"preview": "\nname: Python package and publish\n\non:\n release:\n types: [published]\n\njobs: \n pypi-publish:\n name: Package and "
},
{
"path": ".github/workflows/python-test.yml",
"chars": 1149,
"preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
},
{
"path": ".gitignore",
"chars": 1249,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/"
},
{
"path": "CONTRIBUTING.md",
"chars": 1381,
"preview": "## Contributing to PyangBind\n\nContributions to PyangBind are very welcome, either directly via pull requests, or as feat"
},
{
"path": "CONTRIBUTORS",
"chars": 386,
"preview": "Rob Shakir <robjs@google.com>\nDavid Barrosso <dbarrosop@dravetech.com>\nKirk Byers (GitHub: ktbyers)\nTim Martin (GitHub: "
},
{
"path": "LICENSE",
"chars": 663,
"preview": "Copyright 2015 Rob Shakir, Jive Communications, Inc.\n rjs@jive.com, rjs@rob.sh\n\nModifications copyright "
},
{
"path": "MANIFEST.in",
"chars": 43,
"preview": "include *.md\ninclude *.txt\ninclude LICENSE\n"
},
{
"path": "README.md",
"chars": 20478,
"preview": "[![#PyangBind][img-pyangbind]][pyangbind-docs]\n\n[![PyPI][img-pypi]][pypi-project]\n[![PyPI - License][img-license]][licen"
},
{
"path": "README.rst",
"chars": 2947,
"preview": "PyangBind\n=========\n\nPyangBind is a plugin for pyang which converts YANG data models into a Python class hierarchy, such"
},
{
"path": "docs/README.md",
"chars": 2829,
"preview": "\n\n# PyangBind Documentation\n\nIf you haven't already - it's best to start "
},
{
"path": "docs/errors.md",
"chars": 2358,
"preview": "# Errors thrown by PyangBind\n\n**Note**: the functionality specified in this document is currently subject to some change"
},
{
"path": "docs/example/.gitignore",
"chars": 11,
"preview": "binding.py\n"
},
{
"path": "docs/example/oc-network-instance/generate_bindings.sh",
"chars": 500,
"preview": "#!/bin/bash\nSDIR=\"$(cd -P \"$(dirname \"$[0]\")\" && pwd)\"\nDDIR=$SDIR/models\nmkdir $DDIR\n\ngit clone https://github.com/openc"
},
{
"path": "docs/example/oc-network-instance/json/oc-ni.json",
"chars": 1130,
"preview": "{\n \"network-instances\": {\n \"network-instance\": {\n \"a\": {\n \"name\": \"a\",\n \"protocols\": {\n "
},
{
"path": "docs/example/oc-network-instance/json/oc-ni_ietf.json",
"chars": 985,
"preview": "{\n \"openconfig-network-instance:static-routes\": {\n \"static\": [\n {\n \"prefix\": \"192.0.2.1/32\",\n \"co"
},
{
"path": "docs/example/oc-network-instance/static_route_example.py",
"chars": 3664,
"preview": "#!/usr/bin/env python\n\nfrom __future__ import print_function, unicode_literals\nfrom binding import openconfig_network_in"
},
{
"path": "docs/example/simple-rpc/.gitignore",
"chars": 22,
"preview": "rbindings\nrbindings/*\n"
},
{
"path": "docs/example/simple-rpc/generate_bindings.sh",
"chars": 340,
"preview": "#!/bin/bash\nSDIR=\"$(cd -P \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nPYBINDPLUGIN=`/usr/bin/env python -c 'import pyangb"
},
{
"path": "docs/example/simple-rpc/json/rpc-output.json",
"chars": 134,
"preview": "{\n \"simple_rpc:response-id\": 32,\n \"elements\": [\n { \"response-value\": \"return-one\" },\n { \"response-value\": \"retur"
},
{
"path": "docs/example/simple-rpc/simple-rpc.py",
"chars": 818,
"preview": "#!/usr/bin/env python\n\nfrom __future__ import print_function, unicode_literals\nfrom rbindings.simple_rpc_rpc.test.input "
},
{
"path": "docs/example/simple-rpc/simple_rpc.yang",
"chars": 623,
"preview": "module simple_rpc {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/examples/rpc\";\n prefix \"srpc\";\n\n rpc t"
},
{
"path": "docs/example/simple-serialise/.gitignore",
"chars": 12,
"preview": "sbindings.*\n"
},
{
"path": "docs/example/simple-serialise/generate_bindings.sh",
"chars": 321,
"preview": "#!/bin/bash\nSDIR=\"$(cd -P \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nPYBINDPLUGIN=`/usr/bin/env python -c 'import pyangb"
},
{
"path": "docs/example/simple-serialise/json/simple-instance-additional.json",
"chars": 98,
"preview": "{\n \"a-list\": {\n \"entry-three\": {\n \"the-key\": \"entry-three\"\n }\n }\n}\n"
},
{
"path": "docs/example/simple-serialise/json/simple-instance-ietf.json",
"chars": 176,
"preview": "{\n \"simple_serialise:a-container\": {\n \"a-value\": 8\n },\n \"simple_serialise:a-list\": [\n {\"the-key\":"
},
{
"path": "docs/example/simple-serialise/json/simple-instance.json",
"chars": 212,
"preview": "{\n \"a-container\": {\n \"a-value\": 8\n },\n \"a-list\": {\n \"entry-one\": {\n \"the-key\": \"entry-"
},
{
"path": "docs/example/simple-serialise/simple-serialise.py",
"chars": 2080,
"preview": "#!/usr/bin/env python\n\nfrom __future__ import unicode_literals, print_function\nimport pprint\nimport pyangbind.lib.pybind"
},
{
"path": "docs/example/simple-serialise/simple_serialise.yang",
"chars": 313,
"preview": "module simple_serialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/examples/ss\";\n prefix \"ss\";\n\n co"
},
{
"path": "docs/extmethods.md",
"chars": 4007,
"preview": "# Extension Methods in PyangBind\n\nPyangBind is designed both as a means to generate data instances for YANG modules, but"
},
{
"path": "docs/generic_methods.md",
"chars": 7803,
"preview": "# Generic Methods Provided through PyangBind\n\nPyangBind's `YANGDynClass` function generates meta-classes around the clas"
},
{
"path": "docs/rpc.md",
"chars": 3498,
"preview": "# RPCs in PyangBind\n\nPyangBind generates bindings for RPCs that are specified within a YANG module. The assumption is ma"
},
{
"path": "docs/serialisation.md",
"chars": 12901,
"preview": "# Serialisation and Deserialisation of YANG-modelled Data\n\n\nPyangBind provides a set of helper classes which allow data "
},
{
"path": "docs/usage.md",
"chars": 5980,
"preview": "# PyangBind CLI usage\n\nPyangBind adds a number of command-line options to Pyang:\n\n * [Output options](#output-options) -"
},
{
"path": "docs/xpathhelper.md",
"chars": 6482,
"preview": "# XPATH and Data Tree Structure in PyangBind\n\n * [Overview](#overview)\n * [PyangBind's XPathHelper Classes](#xpathhelper"
},
{
"path": "docs/yang.md",
"chars": 7741,
"preview": "# Python to YANG mapping\n\nPyangBind makes a number of design decisions about how to map YANG data types of Python types,"
},
{
"path": "pyangbind/__init__.py",
"chars": 22,
"preview": "__version__ = \"0.8.7\"\n"
},
{
"path": "pyangbind/helpers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pyangbind/helpers/identity.py",
"chars": 5543,
"preview": "\"\"\"\nCopyright 2016, Google Inc.\n\nAuthor: robjs@google.com\n\nLicensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "pyangbind/helpers/misc.py",
"chars": 1981,
"preview": "\"\"\"\nCopyright 2015, Rob Shakir\nModifications copyright 2016, Google Inc.\n\nAuthor: robjs@google.com\n\nLicensed under the A"
},
{
"path": "pyangbind/lib/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pyangbind/lib/base.py",
"chars": 4257,
"preview": "\"\"\"\nCopyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)\n\nThis project has been supported by:\n * Jive Communca"
},
{
"path": "pyangbind/lib/pybindJSON.py",
"chars": 6184,
"preview": "\"\"\"\nCopyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)\n\nThis project has been supported by:\n * Jive Communic"
},
{
"path": "pyangbind/lib/serialise.py",
"chars": 36213,
"preview": "\"\"\"\nCopyright 2015 Rob Shakir (rjs@jive.com, rjs@rob.sh)\n\nModifications copyright 2016, Google, Inc.\n\nThis project has "
},
{
"path": "pyangbind/lib/xpathhelper.py",
"chars": 13378,
"preview": "\"\"\"\nCopyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)\n\nModifications copyright 2016, Google Inc.\n\nThis project has b"
},
{
"path": "pyangbind/lib/yangtypes.py",
"chars": 54035,
"preview": "\"\"\"\nCopyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)\n\nModifications copyright 2016, Google Inc.\n\nThis project has b"
},
{
"path": "pyangbind/plugin/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "pyangbind/plugin/pybind.py",
"chars": 74631,
"preview": "\"\"\"\nCopyright 2015, Rob Shakir (rjs@jive.com, rjs@rob.sh)\n\nModifications copyright 2016, Google Inc.\n\nThis project has b"
},
{
"path": "pyproject.toml",
"chars": 99,
"preview": "[build-system]\nrequires = [\n \"setuptools\",\n \"wheel\"\n]\nbuild-backend = \"setuptools.build_meta\""
},
{
"path": "requirements.DEVELOPER.txt",
"chars": 63,
"preview": "black\nbuild\ncoverage\npytest\nrequests\nsetuptools<80.9\ntox\nwheel\n"
},
{
"path": "requirements.txt",
"chars": 24,
"preview": "pyang\nlxml\nregex\nenum34\n"
},
{
"path": "scripts/release.sh",
"chars": 915,
"preview": "#!/bin/bash\n\nTHISDIR=\"$(cd -P \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nBASEDIR=$THISDIR/..\n\nvirtualenv --clear $BASEDIR"
},
{
"path": "setup.cfg",
"chars": 2001,
"preview": "[flake8]\n# The following errors codes are ignored:\n# * E111 - indentation is not a multiple of four\n# * E114 - inden"
},
{
"path": "tests/__init__.py",
"chars": 232,
"preview": "import os\n\nimport unittest\n\n\ndef test_suite():\n os.environ.setdefault(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n test_loader"
},
{
"path": "tests/base-test.yang",
"chars": 557,
"preview": "module base-test {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/base-test\";\n prefix \"foo\";\n organi"
},
{
"path": "tests/base.py",
"chars": 4296,
"preview": "import distutils\nimport importlib\nimport inspect\nimport os.path\nimport requests\nimport shutil\nimport subprocess\nimport s"
},
{
"path": "tests/binary/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/binary/binary.yang",
"chars": 926,
"preview": "module binary {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/binary\";\n prefix \"foo\";\n organization"
},
{
"path": "tests/binary/run.py",
"chars": 4553,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass BinaryTests(PyangBindTestCase):"
},
{
"path": "tests/bits/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/bits/bits.yang",
"chars": 926,
"preview": "module bits {\n yang-version 1.1;\n namespace \"http://rob.sh/yang/test/bits\";\n prefix \"foo\";\n organization \"BugReports"
},
{
"path": "tests/bits/run.py",
"chars": 2048,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase"
},
{
"path": "tests/boolean-empty/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/boolean-empty/boolean-empty.yang",
"chars": 736,
"preview": "module boolean-empty {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/boolean-empty\";\n prefix \"foo\";\n "
},
{
"path": "tests/boolean-empty/run.py",
"chars": 4259,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass BooleanEmptyTests(PyangBindTest"
},
{
"path": "tests/choice/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/choice/choice.yang",
"chars": 1812,
"preview": "module choice {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/nested\";\n prefix \"foo\";\n organization"
},
{
"path": "tests/choice/run.py",
"chars": 5068,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass ChoicesTests(PyangBindTestCase)"
},
{
"path": "tests/config-false/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/config-false/config-false.yang",
"chars": 1330,
"preview": "module config-false {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/nested\";\n prefix \"foo\";\n organi"
},
{
"path": "tests/config-false/run.py",
"chars": 2381,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass ConfigFalseTests(PyangBindTestC"
},
{
"path": "tests/decimal64/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/decimal64/decimal.yang",
"chars": 1188,
"preview": "module decimal {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/decimal\";\n prefix \"foo\";\n organizati"
},
{
"path": "tests/decimal64/run.py",
"chars": 2468,
"preview": "#!/usr/bin/env python\n\nfrom decimal import Decimal\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass Dec"
},
{
"path": "tests/enumeration/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/enumeration/enumeration.yang",
"chars": 776,
"preview": "module enumeration {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/enumeration\";\n prefix \"foo\";\n or"
},
{
"path": "tests/enumeration/run.py",
"chars": 1714,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass EnumerationTests(PyangBindTestC"
},
{
"path": "tests/extensions/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/extensions/extdef-irr.yang",
"chars": 385,
"preview": "module extdef-irr {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/extdef-irr\";\n prefix \"foo\";\n orga"
},
{
"path": "tests/extensions/extdef-two.yang",
"chars": 388,
"preview": "module extdef-two {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/extdef-two\";\n prefix \"foo\";\n orga"
},
{
"path": "tests/extensions/extdef.yang",
"chars": 442,
"preview": "module extdef {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/extdef\";\n prefix \"foo\";\n organization"
},
{
"path": "tests/extensions/extensions.yang",
"chars": 1113,
"preview": "module extensions {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/extensions\";\n prefix \"foo\";\n\n imp"
},
{
"path": "tests/extensions/run.py",
"chars": 2719,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase"
},
{
"path": "tests/extmethods/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/extmethods/extmethods.yang",
"chars": 402,
"preview": "module extmethods {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/extmethods\";\n prefix \"foo\";\n orga"
},
{
"path": "tests/extmethods/run.py",
"chars": 2217,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass extmethodcls(object):\n def c"
},
{
"path": "tests/identityref/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/identityref/identityref.yang",
"chars": 3038,
"preview": "module identityref {\n yang-version \"1.1\";\n namespace \"http://rob.sh/yang/test/identityref\";\n prefix \"foo\";\n\n "
},
{
"path": "tests/identityref/remote-two.yang",
"chars": 434,
"preview": "module remote-two {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/typedef/remote-two\";\n prefix \"remote"
},
{
"path": "tests/identityref/remote.yang",
"chars": 714,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/typedef/remote\";\n prefix \"remote\";\n o"
},
{
"path": "tests/identityref/run.py",
"chars": 8143,
"preview": "#!/usr/bin/env python\n\nimport json\nimport unittest\n\nfrom pyangbind.lib import pybindJSON\nfrom pyangbind.lib.serialise im"
},
{
"path": "tests/include-import/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/include-import/include-import.yang",
"chars": 793,
"preview": "module include-import {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/typedef/remote\";\n prefix \"iil\";\n"
},
{
"path": "tests/include-import/remote.yang",
"chars": 413,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/include-import/remote\";\n prefix \"remote\""
},
{
"path": "tests/include-import/run.py",
"chars": 400,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass IncludeImportTests(PyangBindTes"
},
{
"path": "tests/include-import/subm.yang",
"chars": 263,
"preview": "submodule subm {\n belongs-to include-import { prefix ii; }\n\n description\n \"A test module\";\n revision 201"
},
{
"path": "tests/int/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/int/int.yang",
"chars": 4008,
"preview": "module int {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/uint\";\n prefix \"foo\";\n organization \"Bug"
},
{
"path": "tests/int/run.py",
"chars": 13063,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass IntTests(PyangBindTestCase):\n "
},
{
"path": "tests/integration/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/integration/openconfig-interfaces/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/integration/openconfig-interfaces/run.py",
"chars": 2081,
"preview": "#!/usr/bin/env python\n\nimport os.path\nimport unittest\n\nfrom pyangbind.lib.xpathhelper import YANGPathHelper\nfrom tests.b"
},
{
"path": "tests/leaf-list/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/leaf-list/leaflist.yang",
"chars": 1005,
"preview": "module leaflist {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/leaflist\";\n prefix \"foo\";\n organiza"
},
{
"path": "tests/leaf-list/run.py",
"chars": 5353,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nfrom tests.base import PyangBindTestCase\n\nimport unittest"
},
{
"path": "tests/list/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/list/list.yang",
"chars": 3645,
"preview": "module list {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/list\";\n prefix \"foo\";\n organization \"Bu"
},
{
"path": "tests/list/run.py",
"chars": 12164,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase"
},
{
"path": "tests/misc/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/misc/misc.yang",
"chars": 1130,
"preview": "module misc {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/misc\";\n prefix \"foo\";\n organization \"Bu"
},
{
"path": "tests/misc/run.py",
"chars": 1422,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom pyangbind.lib.xpathhelper import YA"
},
{
"path": "tests/nested-containers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/nested-containers/nested.yang",
"chars": 630,
"preview": "module nested {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/nested\";\n prefix \"foo\";\n organization"
},
{
"path": "tests/nested-containers/run.py",
"chars": 1528,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass NestedContainerTests(PyangBindT"
},
{
"path": "tests/notification/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/notification/notification.yang",
"chars": 1768,
"preview": "module notification {\n yang-version \"1.1\";\n namespace \"http://rob.sh/yang/test/notification\";\n prefix \"foo\";\n "
},
{
"path": "tests/notification/run.py",
"chars": 2903,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom pyangbind.lib.xpathhelper import YANGPathHelper\nfrom tests.base import Pyan"
},
{
"path": "tests/openconfig-bgp-juniper/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/openconfig-bgp-juniper/openconfig-bgp-juniper.yang",
"chars": 1374,
"preview": "module openconfig-bgp-juniper {\nnamespace \"http://juniper.net/opencfg/bgp\";\n prefix opencfg-bgp;\n description \"Example"
},
{
"path": "tests/openconfig-bgp-juniper/run.py",
"chars": 3676,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass OpenconfigBGPJuniperTests(Pyang"
},
{
"path": "tests/presence/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/presence/presence.yang",
"chars": 907,
"preview": "module presence {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/presence\";\n prefix \"foo\";\n organiza"
},
{
"path": "tests/presence/run.py",
"chars": 6242,
"preview": "#!/usr/bin/env python\n\nimport json\nimport unittest\n\nimport pyangbind.lib.pybindJSON as pbJ\nfrom pyangbind.lib.yangtypes "
},
{
"path": "tests/rpc/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/rpc/rpc.yang",
"chars": 2515,
"preview": "module rpc {\n yang-version \"1.1\";\n namespace \"http://rob.sh/yang/test/rpc\";\n prefix \"foo\";\n organization \"Bu"
},
{
"path": "tests/rpc/run.py",
"chars": 3575,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom pyangbind.lib.xpathhelper import YANGPathHelper\nfrom tests.base import Pyan"
},
{
"path": "tests/serialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/ietf-json-deserialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/ietf-json-deserialise/ietf-json-deserialise.yang",
"chars": 3332,
"preview": "module ietf-json-deserialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-json-deserialise\""
},
{
"path": "tests/serialise/ietf-json-deserialise/json/chlist.json",
"chars": 226,
"preview": "{\n \"chlist\": [\n {\n \"keyleaf\": 1,\n \"child\": {\n \"number\": 1,\n \"string\": \"one\"\n }\n },\n "
},
{
"path": "tests/serialise/ietf-json-deserialise/json/complete-obj.json",
"chars": 1763,
"preview": "{\n \"ietf-json-serialise:c1\": {\n \"t1\": [\n {\n \"target\": \"16\"\n }, \n "
},
{
"path": "tests/serialise/ietf-json-deserialise/json/mkeylist.json",
"chars": 136,
"preview": "{\n \"mkey\": [\n {\n \"leaf-one\": \"one\",\n \"leaf-two\": 1\n },\n {\n \"leaf-one\": \"three\",\n \"leaf-two"
},
{
"path": "tests/serialise/ietf-json-deserialise/json/nonexistkey.json",
"chars": 207,
"preview": "{\n \"skey\": [\n {\n \"leaf-one\": \"one\",\n \"non-exist\": true\n },\n {\n \"leaf-one\": \"two\",\n \"non-ex"
},
{
"path": "tests/serialise/ietf-json-deserialise/json/skeylist.json",
"chars": 131,
"preview": "{\n \"skey\": [\n {\n \"leaf-one\": \"one\"\n },\n {\n \"leaf-one\": \"two\"\n },\n {\n \"leaf-one\": \"three\"\n"
},
{
"path": "tests/serialise/ietf-json-deserialise/remote.yang",
"chars": 428,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-json-deserialise/remote\";\n pref"
},
{
"path": "tests/serialise/ietf-json-deserialise/run.py",
"chars": 5302,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport json\nimport os.path\nimport unittest\nfrom collectio"
},
{
"path": "tests/serialise/ietf-json-serialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/ietf-json-serialise/augment.yang",
"chars": 451,
"preview": "module augment {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-json-serialise/augment\";\n pref"
},
{
"path": "tests/serialise/ietf-json-serialise/ietf-json-serialise.yang",
"chars": 3761,
"preview": "module ietf-json-serialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-json\";\n prefix \"fo"
},
{
"path": "tests/serialise/ietf-json-serialise/json/obj.json",
"chars": 2079,
"preview": "{\n \"ietf-json-serialise:c1\": {\n \"t1\": [\n {\n \"target\": \"16\"\n }, \n "
},
{
"path": "tests/serialise/ietf-json-serialise/remote.yang",
"chars": 415,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-json-serialise/remote\";\n prefix"
},
{
"path": "tests/serialise/ietf-json-serialise/run.py",
"chars": 3583,
"preview": "#!/usr/bin/env python\n\nimport json\nimport os.path\nimport unittest\nfrom decimal import Decimal\n\nfrom pyangbind.lib.serial"
},
{
"path": "tests/serialise/json-deserialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/json-deserialise/json/alltypes.json",
"chars": 1120,
"preview": "{\n \"c1\": {\n \"t1\": {\n \"16\": {\n \"target\": \"16\"\n }, \n \"32\": {\n "
},
{
"path": "tests/serialise/json-deserialise/json/list-items.json",
"chars": 183,
"preview": "{\n \"load-list\": {\n \"4\": {\n \"index\": 4,\n \"value\": \"four\"\n },\n \"5\": {\n "
},
{
"path": "tests/serialise/json-deserialise/json/list.json",
"chars": 260,
"preview": "{\n \"load-list\": {\n \"1\": {\n \"index\": 1,\n \"value\": \"one\"\n },\n \"2\": {\n "
},
{
"path": "tests/serialise/json-deserialise/json/nonexist.json",
"chars": 327,
"preview": "{\n \"load-list\": {\n \"1\": {\n \"index\": 1,\n \"value\": \"one\",\n \"no\": \"thisdoesnotex"
},
{
"path": "tests/serialise/json-deserialise/json/orderedlist-no-order.json",
"chars": 73,
"preview": "{\n\t\"ordered\": {\n\t\t\"one\": {\"index\": \"one\"},\n\t\t\"two\": {\"index\": \"two\"}\n\t}\n}"
},
{
"path": "tests/serialise/json-deserialise/json/orderedlist-order.json",
"chars": 111,
"preview": "{\n\t\"ordered\": {\n\t\t\"one\": {\"index\": \"one\", \"__yang_order\": 2},\n\t\t\"two\": {\"index\": \"two\", \"__yang_order\": 1}\n\t}\n}"
},
{
"path": "tests/serialise/json-deserialise/json-deserialise.yang",
"chars": 2885,
"preview": "module json-deserialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/json-deserialise\";\n prefix"
},
{
"path": "tests/serialise/json-deserialise/run.py",
"chars": 5344,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport json\nimport os.path\nimport unittest\nfrom decimal i"
},
{
"path": "tests/serialise/json-serialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/json-serialise/json/container.json",
"chars": 36,
"preview": "{\n \"string-test\": \"twenty-two\"\n}\n"
},
{
"path": "tests/serialise/json-serialise/json/expected-output.json",
"chars": 2254,
"preview": "{\n \"c1\": {\n \"t1\": {\n \"16\": {\n \"target\": \"16\"\n }, \n \"32\": {\n "
},
{
"path": "tests/serialise/json-serialise/json-serialise.yang",
"chars": 3658,
"preview": "module json-serialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/json\";\n prefix \"foo\";\n orga"
},
{
"path": "tests/serialise/json-serialise/run.py",
"chars": 3185,
"preview": "#!/usr/bin/env python\n\nimport json\nimport os.path\nimport unittest\nfrom decimal import Decimal\n\nfrom pyangbind.lib.pybind"
},
{
"path": "tests/serialise/openconfig-serialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.False-flt.False-m.default.json",
"chars": 2949,
"preview": "{\n \"interfaces\": {\n \"interface\": {\n \"eth0\": {\n \"hold-time\": {\n \"s"
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.False-flt.False-m.ietf.json",
"chars": 2906,
"preview": "{\n \"openconfig-interfaces:interfaces\": {\n \"interface\": [\n {\n \"hold-time\": {\n "
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.False-flt.True-m.default.json",
"chars": 129,
"preview": "{\n \"interfaces\": {\n \"interface\": {\n \"eth0\": {\n \"name\": \"eth0\"\n }\n "
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.False-flt.True-m.ietf.json",
"chars": 143,
"preview": "{\n \"openconfig-interfaces:interfaces\": {\n \"interface\": [\n {\n \"name\": \"eth0\"\n "
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.True-flt.False-m.default.json",
"chars": 2952,
"preview": "{\n \"interfaces\": {\n \"interface\": {\n \"eth0\": {\n \"hold-time\": {\n \"s"
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.True-flt.False-m.ietf.json",
"chars": 2911,
"preview": "{\n \"openconfig-interfaces:interfaces\": {\n \"interface\": [\n {\n \"hold-time\": {\n "
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.True-flt.True-m.default.json",
"chars": 212,
"preview": "{\n \"interfaces\": {\n \"interface\": {\n \"eth0\": {\n \"config\": {\n \"name"
},
{
"path": "tests/serialise/openconfig-serialise/json/interfaces_ph.True-flt.True-m.ietf.json",
"chars": 226,
"preview": "{\n \"openconfig-interfaces:interfaces\": {\n \"interface\": [\n {\n \"config\": {\n "
},
{
"path": "tests/serialise/openconfig-serialise/run.py",
"chars": 4890,
"preview": "#!/usr/bin/env python\n\nimport json\nimport os.path\nimport unittest\n\nimport regex\n\nfrom pyangbind.lib.pybindJSON import du"
},
{
"path": "tests/serialise/roundtrip/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/roundtrip/remote.yang",
"chars": 391,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/remote\";\n prefix \"foo\";\n\n import ro"
},
{
"path": "tests/serialise/roundtrip/roundtrip.yang",
"chars": 459,
"preview": "module roundtrip {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/roundtrip\";\n prefix \"foo\";\n orga"
},
{
"path": "tests/serialise/roundtrip/run.py",
"chars": 989,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport json\nimport unittest\n\nimport pyangbind.lib.pybindJ"
},
{
"path": "tests/serialise/xml-deserialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/xml-deserialise/augment.yang",
"chars": 455,
"preview": "module augment {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/deserialise/ietf-xml-deserialise/augment\";\n p"
},
{
"path": "tests/serialise/xml-deserialise/ietf-xml-deserialise.yang",
"chars": 4568,
"preview": "module ietf-xml-deserialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/deserialise/ietf-xml\";\n prefix \""
},
{
"path": "tests/serialise/xml-deserialise/remote.yang",
"chars": 418,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/deserialise/ietf-xml-deserialise/remote\";\n pre"
},
{
"path": "tests/serialise/xml-deserialise/run.py",
"chars": 958,
"preview": "#!/usr/bin/env python\n\nimport os.path\nimport unittest\n\nfrom lxml import objectify\n\nfrom pyangbind.lib.serialise import p"
},
{
"path": "tests/serialise/xml-deserialise/xml/obj.xml",
"chars": 2408,
"preview": "<ietf-xml-deserialise xmlns=\"http://rob.sh/yang/test/deserialise/ietf-xml\">\n <c1>\n <l1>\n <k1>1</k1>\n <empt"
},
{
"path": "tests/serialise/xml-serialise/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/serialise/xml-serialise/augment.yang",
"chars": 449,
"preview": "module augment {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-xml-serialise/augment\";\n prefi"
},
{
"path": "tests/serialise/xml-serialise/ietf-xml-serialise.yang",
"chars": 4195,
"preview": "module ietf-xml-serialise {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-xml\";\n prefix \"foo\""
},
{
"path": "tests/serialise/xml-serialise/remote.yang",
"chars": 414,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/serialise/ietf-xml-serialise/remote\";\n prefix "
},
{
"path": "tests/serialise/xml-serialise/run.py",
"chars": 3180,
"preview": "#!/usr/bin/env python\n\nimport os.path\nimport unittest\nfrom decimal import Decimal\n\nfrom lxml import objectify\n\nfrom pyan"
},
{
"path": "tests/serialise/xml-serialise/xml/obj.xml",
"chars": 2005,
"preview": "<ietf-xml-serialise xmlns=\"http://rob.sh/yang/test/serialise/ietf-xml\">\n <c1>\n <l1>\n <k1>1</k1>\n <empty/>\n"
},
{
"path": "tests/serialise/xml_utils.py",
"chars": 1149,
"preview": "def xml_tree_equivalence(e1, e2):\n \"\"\"\n Rough XML comparison function based on https://stackoverflow.com/a/2434991"
},
{
"path": "tests/split-classes/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/split-classes/run.py",
"chars": 1599,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass SplitClassesTests(PyangBindTest"
},
{
"path": "tests/split-classes/split-classes.yang",
"chars": 1299,
"preview": "module split-classes {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/split-classes\";\n prefix \"remote\";"
},
{
"path": "tests/strings/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/strings/run.py",
"chars": 5630,
"preview": "#!/usr/bin/env python\nfrom __future__ import unicode_literals\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase"
},
{
"path": "tests/strings/string.yang",
"chars": 2667,
"preview": "module string {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/string\";\n prefix \"foo\";\n organization"
},
{
"path": "tests/submodules/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/submodules/mod-a.yang",
"chars": 211,
"preview": "module mod-a {\n namespace \"http://example.com/a\";\n prefix a;\n include subm-b;\n\n container a {\n uses TEST;\n }\n\n "
},
{
"path": "tests/submodules/mod-c.yang",
"chars": 102,
"preview": "module mod-c {\n namespace \"http://example.com/c\";\n prefix \"c\";\n\n typedef t {\n type string;\n }\n}"
},
{
"path": "tests/submodules/run.py",
"chars": 812,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass PyangbindSubmoduleTests(PyangBi"
},
{
"path": "tests/submodules/subm-b.yang",
"chars": 186,
"preview": "submodule subm-b {\n belongs-to mod-a { prefix a; }\n import mod-c { prefix \"c\"; }\n\n grouping TEST {\n leaf b {\n "
},
{
"path": "tests/typedef/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/typedef/remote.yang",
"chars": 641,
"preview": "module remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/typedef/remote\";\n prefix \"remote\";\n\n "
},
{
"path": "tests/typedef/run.py",
"chars": 7526,
"preview": "#!/usr/bin/env python\n\nimport unittest\n\nfrom tests.base import PyangBindTestCase\n\n\nclass TypedefTests(PyangBindTestCase)"
},
{
"path": "tests/typedef/second-remote.yang",
"chars": 450,
"preview": "module second-remote {\n yang-version \"1\";\n namespace \"http://rob.sh/yang/test/typedef/second-remote\";\n prefix \""
},
{
"path": "tests/typedef/typedef.yang",
"chars": 4290,
"preview": "module typedef {\n yang-version \"1.1\";\n namespace \"http://rob.sh/yang/test/list\";\n prefix \"foo\";\n\n import rem"
}
]
// ... and 29 more files (download for full content)
About this extraction
This page contains the full source code of the robshakir/pyangbind GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 229 files (556.0 KB), approximately 134.1k tokens, and a symbol index with 573 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.