robshakir / pyangbind

A plugin for pyang that creates Python bindings for a YANG model.
Other
204 stars 121 forks source link

nested lists within container don't allow for leaf add #243

Closed igrush closed 5 years ago

igrush commented 5 years ago

In my yang model I have a list within a list within a container. When I generate the python bindings I am unable to modify the leaf elements in the inner list. Here is the yang.

module donkey {
  namespace "urn:com:example:aproject:yang:donkey";
  prefix "donkey";

  import ietf-inet-types { prefix inet; }

  description "donkey container";
  contact "me";
  organization "example.com";

  revision "2018-02-14" {
      description "Some descriptive text";
  }

  container donkey {
    leaf ipfoo {
      type boolean;
      default true;
    }

    container staticstuff {
        config true;
        list substuff{
            key "name";
            description "subset of stuff";
            leaf name {
              type string;
              description  "The name of the subset";
            }
            list route {
                key "prefix";
                leaf prefix {
                    type inet:ip-prefix;
                    mandatory true;
                }
                leaf ifname {
                    type string;
                }
                leaf ifindex {
                    type uint32;
                }
                leaf distance {
                   type uint8;
                   default 1;
                }
            }
        }
    }
  }
}

I create the bindings with: pyang -f pybind -p ~/PycharmProjects/yangtoxml/yangtoxml -o ~/PycharmProjects/yangtoxml/pybind_out/donkey.py ~/PycharmProjects/yangtoxml/yangtoxml/donkey.yang

and I run the following test code:

#! /usr/bin/env python3
"""What can be done with the files created by pyangbind?"""

import pyangbind.lib.pybindJSON as pyang_json
from donkey import donkey

if __name__ == "__main__":

    # use the python class derived from the yang file
    donk = donkey()
    # get the whole object
    dft = donk.donkey
    dft.staticstuff.substuff.route.add(prefix="192.168.0.0/24")
    print(dft)
    # Try a json conversion first.
    print("\n\nTry json Conversion\n")
    jzt = pyang_json.dumps(dft,4)
    print(jzt)
    print("json conversion done")

Run this and you get:

Traceback (most recent call last):
  File "/snap/pycharm-professional/116/helpers/pydev/pydevd.py", line 1741, in <module>
    main()
  File "/snap/pycharm-professional/116/helpers/pydev/pydevd.py", line 1735, in main
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/snap/pycharm-professional/116/helpers/pydev/pydevd.py", line 1135, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/snap/pycharm-professional/116/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/home/iang/PycharmProjects/yangtoxml/pybind_out/donkey_test.py", line 15, in <module>
    dft.staticstuff.substuff.route.add(prefix="192.168.0.0/24")
AttributeError: 'YANGBaseClass' object has no attribute 'route'

This is because the substuff object has no reference to the route element.

chrisy commented 5 years ago

substuff is a list, but you don't instantiate an item on it, you just go straight for something that would only exist as an element of that list.

chrisy commented 5 years ago

Something like, from memory:

thing = dft.staticstuff.substuff.add("mything")
route = thing.route.add("1.2.3.4/5")
igrush commented 5 years ago

I am trying to put together a unit test to demonstrate this but in the meantime, I gave your suggestion a try, adding a line to populate the list name, which I presume would instantiate the list. Thus changing the code to:

#! /usr/bin/env python3
"""What can be done with the files created by pyangbind?"""
import pyangbind.lib.pybindJSON as pyang_json
from donkey import donkey

if __name__ == "__main__":
    # use the python class derived from the yang file
    donk = donkey()
    # get the whole object
    dft = donk.donkey
    dft.staticstuff.substuff.add(name="aname1")
    dft.staticstuff.substuff.route.add(prefix="192.168.0.0/24")
    print(dft)

    # Try a json conversion first.
    print("\n\nTry json Conversion\n")
    jzt = pyang_json.dumps(dft,4)
    print(jzt)
    print("json conversion done")

but the outcome is still the same:

Traceback (most recent call last):
  File "/snap/pycharm-professional/116/helpers/pydev/pydevd.py", line 1741, in <module>
    main()
  File "/snap/pycharm-professional/116/helpers/pydev/pydevd.py", line 1735, in main
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/snap/pycharm-professional/116/helpers/pydev/pydevd.py", line 1135, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/snap/pycharm-professional/116/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/home/iang/PycharmProjects/yangtoxml/pybind_out/donkey_test.py", line 16, in <module>
    dft.staticstuff.substuff.route.add(prefix="192.168.0.0/24")
AttributeError: 'YANGBaseClass' object has no attribute 'route'
chrisy commented 5 years ago

You need to retain the reference to the added object that .add will return. literally: newitem = item.add(key_value)

chrisy commented 5 years ago

Meaning, in your case,

    dft.staticstuff.substuff.add(name="aname1")
    dft.staticstuff.substuff.route.add(prefix="192.168.0.0/24")

becomes

    aname = dft.staticstuff.substuff.add(name="aname1")
    aname.route.add(prefix="192.168.0.0/24")
chrisy commented 5 years ago

Sorry for the multiple-comment spam :) The reason: .add adds an instance of an item to the list. It creates that item, sets the value of the key leaf and then adds it to the parent list. Once added, you can either use the reference it returns, or look it up in the list. Something like dft.staticstuss.substuff["aname"].route.add(...) should also work, and there's also a technique for doing similar for named keys that isn't immediately coming to mind.

igrush commented 5 years ago

Thanks Chris, this has clarified a lot for me and set me on to the correct path. Very helpful. It turns out my main problem was that I was not referencing the list entries correctly. I had some problems when I tried to reference intermediate objects like aname above. I probably just need to work through that some more.

This is what works for me at the moment:

#! /usr/bin/env python3
"""What can be done with the files created by pyangbind?"""

import pyangbind.lib.pybindJSON as pyang_json
from donkey import donkey

if __name__ == "__main__":

    # use the python class derived from the yang file
    donk = donkey()
    # get the whole object
    dft = donk.donkey

    dft.staticstuff.substuff.add(name="aname1")
    dft.staticstuff.substuff["aname1"].route.add(ifindex=5,prefix="192.168.0.0/24")
    dft.staticstuff.substuff["aname1"].route["192.168.0.0/24"].ifindex = 9
    dft.staticstuff.substuff["aname1"].route["192.168.0.0/24"].ifname = "eth4"

    # Try a json conversion first.
    print("\n\nTry json Conversion\n")
    jzt = pyang_json.dumps(dft,4)
    print(jzt)
    print("json conversion done")

Which gives the output:

Try json Conversion

{
    "staticstuff": {
        "substuff": {
            "aname1": {
                "name": "aname1",
                "route": {
                    "192.168.0.0/24": {
                        "prefix": "192.168.0.0/24",
                        "ifname": "eth4",
                        "ifindex": 9
                    }
                }
            }
        }
    }
}
json conversion done

I have noticed one thing which may be a bug. The default value for distance in the route list, is 1, but it seems to not be set. It also does not seem to be instantiated as it does not show up above. I think a default valued leaf should be there.

               leaf distance {
                   type uint8;
                   default 1;
                }