Skip to content

Commit 7a0c440

Browse files
nicolasmorinirjarry
authored andcommitted
Add support for notifications
* Related to: - sysrepo/sysrepo-python#10 - sysrepo/sysrepo-python#11 * Special recognition to @ishidawataru for providing the seed code for this new feature
1 parent dc9675b commit 7a0c440

File tree

10 files changed

+101
-11
lines changed

10 files changed

+101
-11
lines changed

cffi/cdefs.h

100644100755
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ typedef enum {
407407
#define LYD_OPT_DATA_NO_YANGLIB ...
408408
#define LYD_OPT_RPC ...
409409
#define LYD_OPT_RPCREPLY ...
410+
#define LYD_OPT_NOTIF ...
410411
#define LYD_OPT_EXPLICIT ...
411412
#define LYD_OPT_DESTRUCT ...
412413
#define LYD_OPT_NOSIBLINGS ...

libyang/__init__.py

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
from .context import Context
7-
from .data import DContainer, DDiff, DLeaf, DLeafList, DList, DNode, DRpc
7+
from .data import DContainer, DDiff, DLeaf, DLeafList, DList, DNode, DNotif, DRpc
88
from .diff import (
99
BaseTypeAdded,
1010
BaseTypeRemoved,

libyang/context.py

100644100755
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def create_data_path(
193193

194194
return DNode.new(self, dnode)
195195

196-
def parse_data_mem(
196+
def parse_data_mem( # pylint: disable=too-many-arguments
197197
self,
198198
d: Union[str, bytes],
199199
fmt: str,
@@ -204,6 +204,7 @@ def parse_data_mem(
204204
edit: bool = False,
205205
rpc: bool = False,
206206
rpcreply: bool = False,
207+
notification: bool = False,
207208
strict: bool = False,
208209
trusted: bool = False,
209210
no_yanglib: bool = False,
@@ -220,6 +221,7 @@ def parse_data_mem(
220221
edit=edit,
221222
rpc=rpc,
222223
rpcreply=rpcreply,
224+
notification=notification,
223225
strict=strict,
224226
trusted=trusted,
225227
no_yanglib=no_yanglib,
@@ -234,7 +236,7 @@ def parse_data_mem(
234236
if rpc_request is None:
235237
raise ValueError("rpc_request node is required when rpcreply=True")
236238
args.append(rpc_request.cdata)
237-
if rpc or rpcreply:
239+
if rpc or rpcreply or notification:
238240
if data_tree is not None:
239241
args.append(data_tree.cdata)
240242
else:

libyang/data.py

100644100755
Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@
66

77
from _libyang import ffi, lib
88
from .keyed_list import KeyedList
9-
from .schema import Module, SContainer, SLeaf, SLeafList, SList, SNode, SRpc, Type
9+
from .schema import (
10+
Module,
11+
SContainer,
12+
SLeaf,
13+
SLeafList,
14+
SList,
15+
SNode,
16+
SNotif,
17+
SRpc,
18+
Type,
19+
)
1020
from .util import LibyangError, c2str, deprecated, str2c
1121

1222

@@ -69,16 +79,20 @@ def parser_flags(
6979
edit: bool = False,
7080
rpc: bool = False,
7181
rpcreply: bool = False,
82+
notification: bool = False,
7283
strict: bool = False,
7384
trusted: bool = False,
7485
no_yanglib: bool = False,
7586
destruct: bool = False,
7687
no_siblings: bool = False,
7788
explicit: bool = False,
7889
) -> int:
79-
if (data, config, get, getconfig, edit, rpc, rpcreply).count(True) > 1:
90+
if (data, config, get, getconfig, edit, rpc, rpcreply, notification).count(
91+
True
92+
) > 1:
8093
raise ValueError(
81-
"Only one of data, config, get, getconfig, edit, rpc, rpcreply can be True"
94+
"Only one of data, config, get, getconfig, edit, rpc, rpcreply, "
95+
"notification can be True"
8296
)
8397
flags = 0
8498
if data:
@@ -95,6 +109,8 @@ def parser_flags(
95109
flags |= lib.LYD_OPT_RPC
96110
if rpcreply:
97111
flags |= lib.LYD_OPT_RPCREPLY
112+
if notification:
113+
flags |= lib.LYD_OPT_NOTIF
98114
if strict:
99115
flags |= lib.LYD_OPT_STRICT
100116
if trusted:
@@ -261,6 +277,7 @@ def validate(
261277
edit: bool = False,
262278
rpc: bool = False,
263279
rpcreply: bool = False,
280+
notification: bool = False,
264281
no_yanglib: bool = False,
265282
) -> None:
266283
if self.cdata.parent:
@@ -273,6 +290,7 @@ def validate(
273290
edit=edit,
274291
rpc=rpc,
275292
rpcreply=rpcreply,
293+
notification=notification,
276294
no_yanglib=no_yanglib,
277295
)
278296
node_p = ffi.new("struct lyd_node **")
@@ -476,7 +494,9 @@ def _to_dict(node, parent_dic):
476494
if name not in parent_dic:
477495
parent_dic[name] = _init_yang_list(node.schema)
478496
parent_dic[name].append(list_element)
479-
elif node.schema.nodetype & (SNode.CONTAINER | SNode.RPC | SNode.ACTION):
497+
elif node.schema.nodetype & (
498+
SNode.CONTAINER | SNode.RPC | SNode.ACTION | SNode.NOTIF
499+
):
480500
container = {}
481501
child = node.child
482502
while child:
@@ -666,6 +686,12 @@ class DLeafList(DLeaf):
666686
pass
667687

668688

689+
# -------------------------------------------------------------------------------------
690+
@DNode.register(SNode.NOTIF)
691+
class DNotif(DContainer):
692+
pass
693+
694+
669695
# -------------------------------------------------------------------------------------
670696
def dict_to_dnode(
671697
dic: Dict[str, Any],
@@ -678,6 +704,7 @@ def dict_to_dnode(
678704
edit: bool = False,
679705
rpc: bool = False,
680706
rpcreply: bool = False,
707+
notification: bool = False,
681708
strict: bool = False,
682709
no_yanglib: bool = False,
683710
validate: bool = True,
@@ -709,6 +736,8 @@ def dict_to_dnode(
709736
Data represents RPC or action input parameters.
710737
:arg rpcreply:
711738
Data represents RPC or action output parameters.
739+
:arg notification:
740+
Data represents notification parameters.
712741
:arg strict:
713742
Instead of ignoring (with a warning message) data without schema definition,
714743
raise an error.
@@ -874,6 +903,10 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
874903
n = _create_container(_parent, module, name, in_rpc_output)
875904
_to_dnode(v, s, n, in_rpc_output)
876905

906+
elif isinstance(s, SNotif):
907+
n = _create_container(_parent, module, name, in_rpc_output)
908+
_to_dnode(value, s, n, in_rpc_output)
909+
877910
result = None
878911

879912
try:
@@ -900,6 +933,7 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
900933
edit=edit,
901934
rpc=rpc,
902935
rpcreply=rpcreply,
936+
notification=notification,
903937
no_yanglib=no_yanglib,
904938
)
905939
except:

libyang/diff.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Any, Callable, Iterator, Optional
55

66
from .context import Context
7-
from .schema import SContainer, SLeaf, SLeafList, SList, SNode, SRpc, SRpcInOut
7+
from .schema import SContainer, SLeaf, SLeafList, SList, SNode, SNotif, SRpc, SRpcInOut
88

99

1010
# -------------------------------------------------------------------------------------
@@ -40,7 +40,7 @@ def flatten(node, dic):
4040
if exclude_node_cb(node):
4141
return
4242
dic[node.schema_path()] = node
43-
if isinstance(node, (SContainer, SList, SRpc, SRpcInOut)):
43+
if isinstance(node, (SContainer, SList, SNotif, SRpc, SRpcInOut)):
4444
for child in node:
4545
flatten(child, dic)
4646

libyang/schema.py

100644100755
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def parse_data_dict(
136136
edit: bool = False,
137137
rpc: bool = False,
138138
rpcreply: bool = False,
139+
notification: bool = False,
139140
strict: bool = False,
140141
no_yanglib: bool = False,
141142
validate: bool = True,
@@ -163,6 +164,8 @@ def parse_data_dict(
163164
Data represents RPC or action input parameters.
164165
:arg rpcreply:
165166
Data represents RPC or action output parameters.
167+
:arg notification:
168+
Data represents a NETCONF notification.
166169
:arg strict:
167170
Instead of ignoring (with a warning message) data without schema definition,
168171
raise an error.
@@ -185,6 +188,7 @@ def parse_data_dict(
185188
edit=edit,
186189
rpc=rpc,
187190
rpcreply=rpcreply,
191+
notification=notification,
188192
strict=strict,
189193
no_yanglib=no_yanglib,
190194
validate=validate,
@@ -731,6 +735,7 @@ class SNode:
731735
ACTION = lib.LYS_ACTION
732736
INPUT = lib.LYS_INPUT
733737
OUTPUT = lib.LYS_OUTPUT
738+
NOTIF = lib.LYS_NOTIF
734739
KEYWORDS = {
735740
CONTAINER: "container",
736741
LEAF: "leaf",
@@ -740,6 +745,7 @@ class SNode:
740745
ACTION: "action",
741746
INPUT: "input",
742747
OUTPUT: "output",
748+
NOTIF: "notification",
743749
}
744750

745751
def __init__(self, context: "libyang.Context", cdata):
@@ -1055,6 +1061,16 @@ def children(self, types: Optional[Tuple[int, ...]] = None) -> Iterator[SNode]:
10551061
return iter_children(self.context, self.cdata, types=types)
10561062

10571063

1064+
# -------------------------------------------------------------------------------------
1065+
@SNode.register(SNode.NOTIF)
1066+
class SNotif(SNode):
1067+
def __iter__(self) -> Iterator[SNode]:
1068+
return self.children()
1069+
1070+
def children(self, types: Optional[Tuple[int, ...]] = None) -> Iterator[SNode]:
1071+
return iter_children(self.context, self.cdata, types=types)
1072+
1073+
10581074
# -------------------------------------------------------------------------------------
10591075
def iter_children(
10601076
context: "libyang.Context",
@@ -1071,6 +1087,7 @@ def iter_children(
10711087
lib.LYS_RPC,
10721088
lib.LYS_LEAF,
10731089
lib.LYS_LEAFLIST,
1090+
lib.LYS_NOTIF,
10741091
)
10751092

10761093
def _skip(node) -> bool:

tests/test_data.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from unittest.mock import patch
88

99
from _libyang import lib
10-
from libyang import Context, DContainer, DDiff, DNode, DRpc, LibyangError
10+
from libyang import Context, DContainer, DDiff, DNode, DNotif, DRpc, LibyangError
1111

1212

1313
YANG_DIR = os.path.join(os.path.dirname(__file__), "yang")
@@ -490,6 +490,28 @@ def test_data_to_dict_action(self):
490490
},
491491
)
492492

493+
DICT_NOTIF = {
494+
"alarm-triggered": {"description": "An error occurred", "severity": 3}
495+
}
496+
497+
JSON_NOTIF = """{
498+
"yolo-system:alarm-triggered": {
499+
"description": "An error occurred",
500+
"severity": 3
501+
}
502+
}
503+
"""
504+
505+
def test_notification_from_dict_module(self):
506+
module = self.ctx.get_module("yolo-system")
507+
dnotif = module.parse_data_dict(self.DICT_NOTIF, strict=True, notification=True)
508+
self.assertIsInstance(dnotif, DNotif)
509+
try:
510+
j = dnotif.print_mem("json", pretty=True)
511+
finally:
512+
dnotif.free()
513+
self.assertEqual(json.loads(j), json.loads(self.JSON_NOTIF))
514+
493515
XML_DIFF_STATE1 = """<state xmlns="urn:yang:yolo:system">
494516
<hostname>foo</hostname>
495517
<speed>1234</speed>

tests/test_diff.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class DiffTest(unittest.TestCase):
6565
(StatusRemoved, "/yolo-system:conf/yolo-system:obsolete-leaf"),
6666
(StatusRemoved, "/yolo-system:state/yolo-system:deprecated-leaf"),
6767
(StatusRemoved, "/yolo-system:state/yolo-system:obsolete-leaf"),
68+
(SNodeAdded, "/yolo-system:alarm-triggered"),
69+
(SNodeAdded, "/yolo-system:alarm-triggered/yolo-system:severity"),
70+
(SNodeAdded, "/yolo-system:alarm-triggered/yolo-system:description"),
6871
)
6972
)
7073

tests/test_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_mod_filepath(self):
5252

5353
def test_mod_iter(self):
5454
children = list(iter(self.module))
55-
self.assertEqual(len(children), 4)
55+
self.assertEqual(len(children), 5)
5656

5757
def test_mod_children_rpcs(self):
5858
rpcs = list(self.module.children(types=(SNode.RPC,)))

tests/yang/yolo/yolo-system.yang

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,15 @@ module yolo-system {
157157
}
158158
}
159159
}
160+
161+
notification alarm-triggered {
162+
description
163+
"Notification about a new alarm.";
164+
leaf description {
165+
type string;
166+
}
167+
leaf severity {
168+
type uint32;
169+
}
170+
}
160171
}

0 commit comments

Comments
 (0)