Skip to content

Commit

Permalink
Merge pull request #25 from robshakir/rpc-parsing
Browse files Browse the repository at this point in the history
Add support for RPC generation.

* (M) pybind.py -- changed paths for RPCs to be modulename_rpc
          to ensure that there are not namespace conflicts.
          Only generate the modulename_rpc bindings when RPCs exist.
            Modify register_paths behaviour to be on a per-leaf element
            basis rather than by making path_helper False, since this
            resulted in the path_helper not being available where required
            to other leaves (e.g., leafrefs)
* (M) lib/yangtypes.py -- check register_paths before determing
          whether to use the path_helper to register.
* (A) tests/rpc/* -- added tests for RPCs.
  • Loading branch information
robshakir committed Jan 8, 2016
2 parents 0401061 + 319e7b6 commit fc87598
Show file tree
Hide file tree
Showing 5 changed files with 370 additions and 11 deletions.
11 changes: 8 additions & 3 deletions lib/yangtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ def YANGDynClass(*args, **kwargs):
extensions = kwargs.pop("extensions", None)
extmethods = kwargs.pop("extmethods", None)
is_keyval = kwargs.pop("is_keyval", False)
register_paths = kwargs.pop("register_paths", True)
if not base_type:
raise TypeError("must have a base type")
if base_type in NUMPY_INTEGER_TYPES and len(args):
Expand Down Expand Up @@ -754,7 +755,8 @@ class YANGBaseClass(base_type):
__slots__ = ('_default', '_mchanged', '_yang_name', '_choice', '_parent',
'_supplied_register_path', '_path_helper', '_base_type',
'_is_leaf', '_is_container', '_extensionsd',
'_pybind_base_class', '_extmethods', '_is_keyval')
'_pybind_base_class', '_extmethods', '_is_keyval',
'_register_paths')

_pybind_base_class = re.sub("<(type|class) '(?P<class>.*)'>", "\g<class>",
str(base_type))
Expand All @@ -777,6 +779,7 @@ def __init__(self, *args, **kwargs):
self._extensionsd = extensions
self._extmethods = extmethods
self._is_keyval = is_keyval
self._register_paths = register_paths

if default:
self._default = default
Expand All @@ -789,9 +792,11 @@ def __init__(self, *args, **kwargs):
if not self._is_container == "list":
if self._path_helper:
if self._supplied_register_path is None:
self._path_helper.register(self._register_path(), self)
if self._register_paths:
self._path_helper.register(self._register_path(), self)
else:
self._path_helper.register(self._supplied_register_path, self)
if self._register_paths:
self._path_helper.register(self._supplied_register_path, self)

if self._is_container == 'list' or self._is_container == 'container':
kwargs['path_helper'] = self._path_helper
Expand Down
40 changes: 32 additions & 8 deletions pybind.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ def add_opts(self, optparser):
help="""Allow a path-keyed dictionary
to be used to specify methods
related to a particular class"""),
optparse.make_option("--build-rpcs",
dest="build_rpcs",
action="store_true",
help="""Generate class bindings for
the input and output of RPCs
defined in each module. These
are placed at the root of
each module"""),
]
g = optparser.add_option_group("pyangbind output specific options")
g.add_options(optlist)
Expand Down Expand Up @@ -355,6 +363,15 @@ def build_pybind(ctx, modules, fd):
if ch.keyword in statements.data_definition_keywords]
get_children(ctx, fd, children, m, m)

if ctx.opts.build_rpcs:
rpcs = [ch for ch in module.i_children
if ch.keyword == 'rpc']
# Build RPCs specifically under the module name, since this
# can be used as a proxy for the namespace.
if len(rpcs):
get_children(ctx, fd, rpcs, module, module, register_paths=False,
path="/%s_rpc" % (safe_name(module.arg)))


def build_identities(ctx, defnd):
# Build dicionaries which determine how identities work. Essentially, an
Expand Down Expand Up @@ -601,7 +618,7 @@ def find_definitions(defn, ctx, module, prefix):


def get_children(ctx, fd, i_children, module, parent, path=str(),
parent_cfg=True, choice=False):
parent_cfg=True, choice=False, register_paths=True):
# Iterative function that is called for all elements that have childen
# data nodes in the tree. This function resolves those nodes into the
# relevant leaf, or container/list configuration and outputs the python
Expand Down Expand Up @@ -671,17 +688,19 @@ def get_children(ctx, fd, i_children, module, parent, path=str(),
# choice specified.
if ctx.opts.split_class_dir:
import_req = []

for ch in i_children:
if ch.keyword == "choice":
for choice_ch in ch.i_children:
# these are case statements
for case_ch in choice_ch.i_children:
elements += get_element(ctx, fd, case_ch, module, parent,
path + "/" + ch.arg, parent_cfg=parent_cfg,
choice=(ch.arg, choice_ch.arg))
choice=(ch.arg, choice_ch.arg), register_paths=register_paths)
else:
elements += get_element(ctx, fd, ch, module, parent, path + "/" + ch.arg,
parent_cfg=parent_cfg, choice=choice)
parent_cfg=parent_cfg, choice=choice, register_paths=register_paths)

if ctx.opts.split_class_dir:
if hasattr(ch, "i_children") and len(ch.i_children):
import_req.append(ch.arg)
Expand All @@ -696,7 +715,8 @@ def get_children(ctx, fd, i_children, module, parent, path=str(),

# 'container', 'module', 'list' and 'submodule' all have their own classes
# generated.
if parent.keyword in ["container", "module", "list", "submodule"]:
if parent.keyword in ["container", "module", "list", "submodule", "input",
"output", "rpc"]:
if ctx.opts.split_class_dir:
nfd.write("class %s(PybindBase):\n" % safe_name(parent.arg))
else:
Expand Down Expand Up @@ -890,6 +910,7 @@ def get_children(ctx, fd, i_children, module, parent, path=str(),
choices[i["choice"][0]][i["choice"][1]].append(i["name"])
class_str["arg"] += ", path_helper=self._path_helper"
class_str["arg"] += ", extmethods=self._extmethods"
class_str["arg"] += ", register_paths=%s" % i["register_paths"]
if "extensions" in i:
class_str["arg"] += ", extensions=%s" % i["extensions"]
if keyval and i["yang_name"] in keyval:
Expand Down Expand Up @@ -1239,7 +1260,7 @@ def find_absolute_default_type(default_type, default_value, elemname):


def get_element(ctx, fd, element, module, parent, path,
parent_cfg=True, choice=False):
parent_cfg=True, choice=False, register_paths=True):
# Handle mapping of an invidual element within the model. This function
# produces a dictionary that can then be mapped into the relevant code that
# dynamically generates a class.
Expand All @@ -1256,9 +1277,10 @@ def get_element(ctx, fd, element, module, parent, path,
elemdescr = elemdescr.arg

# If the element has an i_children attribute then this is a container, list
# leaf-list or choice.
# leaf-list or choice. Alternatively, it can be the 'input' or 'output'
# substmts of an RPC
if hasattr(element, 'i_children'):
if element.keyword in ["container", "list"]:
if element.keyword in ["container", "list", "input", "output"]:
has_children = True
elif element.keyword in ["leaf-list"]:
create_list = True
Expand All @@ -1278,7 +1300,7 @@ def get_element(ctx, fd, element, module, parent, path,
if element.i_children:
chs = element.i_children
get_children(ctx, fd, chs, module, element, npath, parent_cfg=parent_cfg,
choice=choice)
choice=choice, register_paths=register_paths)

elemdict = {
"name": safe_name(element.arg), "origtype": element.keyword,
Expand All @@ -1287,6 +1309,7 @@ def get_element(ctx, fd, element, module, parent, path,
"description": elemdescr,
"yang_name": element.arg,
"choice": choice,
"register_paths": register_paths,
}
# Handle the different cases of class name, this depends on whether we
# were asked to split the bindings into a directory structure or not.
Expand Down Expand Up @@ -1490,6 +1513,7 @@ def get_element(ctx, fd, element, module, parent, path,
"quote_arg": quote_arg,
"description": elemdescr, "yang_name": element.arg,
"choice": choice,
"register_paths": register_paths,
}
if cls == "leafref":
elemdict["referenced_path"] = elemtype["referenced_path"]
Expand Down
1 change: 1 addition & 0 deletions tests/rpc/lib
121 changes: 121 additions & 0 deletions tests/rpc/rpc.yang
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
module rpc {
yang-version "1";
namespace "http://rob.sh/yang/test/rpc";
prefix "foo";
organization "BugReports Inc";
contact "A bug reporter";

description
"A test module";
revision 2014-01-01 {
description "april-fools";
reference "fooled-you";
}

rpc check {
description
"Basic RPC with a single input argument";
input {
leaf argument {
type string;
}
}
}

rpc check-two {
description
"Basic RPC check with two leaves in output";
output {
leaf arg-one {
type int8;
}

leaf arg-two {
type int8;
}
}
}

rpc check-three {
description
"RPC check with a container under input";
input {
container arguments {
leaf arg-one {
type string;
}

leaf arg-two {
type string;
}
}
}
}


rpc check-four {
description
"RPC check with multiple containers under output";
output {
container arguments {
leaf arg-one {
type string;
}
}

container arguments-two {
leaf arg-two {
type string;
}
}
}
}

rpc check-five {
description
"RPC check with input and output structures";

input {
container arguments {
leaf arg-one {
type string;
}
}
}

output {
container return-values {
leaf return-val {
type int8;
}
}
}
}

rpc check-six {
description
"RPC check with input and output values using a leafref which
requires use of the XPATHHELPER";

input {
leaf argument {
type leafref {
path "/test/reference-target";
require-instance true;
}
}
}

output {
leaf out {
type string;
}
}
}

container test {
leaf-list reference-target {
type string;
}
}
}
Loading

0 comments on commit fc87598

Please sign in to comment.