Skip to content

Commit 1a069b9

Browse files
nvxfsamuel-gauthier
authored andcommitted
data: add support for lyd_attr to DNode
Add new DNodeAttrs class mapped to access the lyd_attr structure in a DNode, and unit tests for it. Fixes: #77 Signed-off-by: nvxf <68589039+nvxf@users.noreply.github.com> Signed-off-by: Samuel Gauthier <samuel.gauthier@6wind.com>
1 parent ae3b320 commit 1a069b9

File tree

4 files changed

+192
-4
lines changed

4 files changed

+192
-4
lines changed

cffi/cdefs.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,5 +1042,56 @@ LY_ERR lyd_new_implicit_all(struct lyd_node **, const struct ly_ctx *, uint32_t,
10421042

10431043
LY_ERR lyd_new_meta(const struct ly_ctx *, struct lyd_node *, const struct lys_module *, const char *, const char *, ly_bool, struct lyd_meta **);
10441044

1045+
struct ly_opaq_name {
1046+
const char *name;
1047+
const char *prefix;
1048+
1049+
union {
1050+
const char *module_ns;
1051+
const char *module_name;
1052+
};
1053+
};
1054+
1055+
struct lyd_node_opaq {
1056+
union {
1057+
struct lyd_node node;
1058+
1059+
struct {
1060+
uint32_t hash;
1061+
uint32_t flags;
1062+
const struct lysc_node *schema;
1063+
struct lyd_node_inner *parent;
1064+
struct lyd_node *next;
1065+
struct lyd_node *prev;
1066+
struct lyd_meta *meta;
1067+
void *priv;
1068+
};
1069+
};
1070+
1071+
struct lyd_node *child;
1072+
1073+
struct ly_opaq_name name;
1074+
const char *value;
1075+
uint32_t hints;
1076+
LY_VALUE_FORMAT format;
1077+
void *val_prefix_data;
1078+
1079+
struct lyd_attr *attr;
1080+
const struct ly_ctx *ctx;
1081+
};
1082+
1083+
struct lyd_attr {
1084+
struct lyd_node_opaq *parent;
1085+
struct lyd_attr *next;
1086+
struct ly_opaq_name name;
1087+
const char *value;
1088+
uint32_t hints;
1089+
LY_VALUE_FORMAT format;
1090+
void *val_prefix_data;
1091+
};
1092+
1093+
LY_ERR lyd_new_attr(struct lyd_node *, const char *, const char *, const char *, struct lyd_attr **);
1094+
void lyd_free_attr_single(const struct ly_ctx *ctx, struct lyd_attr *attr);
1095+
10451096
/* from libc, needed to free allocated strings */
10461097
void free(void *);

libyang/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
DLeafList,
1414
DList,
1515
DNode,
16+
DNodeAttrs,
1617
DNotif,
1718
DRpc,
1819
)

libyang/data.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# SPDX-License-Identifier: MIT
44

55
import logging
6-
from typing import IO, Any, Dict, Iterator, Optional, Union
6+
from typing import IO, Any, Dict, Iterator, Optional, Tuple, Union
77

88
from _libyang import ffi, lib
99
from .keyed_list import KeyedList
@@ -190,13 +190,69 @@ def diff_flags(with_defaults: bool = False) -> int:
190190
return flags
191191

192192

193+
# -------------------------------------------------------------------------------------
194+
class DNodeAttrs:
195+
__slots__ = ("context", "parent", "cdata", "__dict__")
196+
197+
def __init__(self, context: "libyang.Context", parent: "libyang.DNode"):
198+
self.context = context
199+
self.parent = parent
200+
self.cdata = [] # C type: "struct lyd_attr *"
201+
202+
def get(self, name: str) -> Optional[str]:
203+
for attr_name, attr_value in self:
204+
if attr_name == name:
205+
return attr_value
206+
return None
207+
208+
def set(self, name: str, value: str):
209+
attrs = ffi.new("struct lyd_attr **")
210+
ret = lib.lyd_new_attr(
211+
self.parent.cdata,
212+
ffi.NULL,
213+
str2c(name),
214+
str2c(value),
215+
attrs,
216+
)
217+
if ret != lib.LY_SUCCESS:
218+
raise self.context.error("cannot create attr")
219+
self.cdata.append(attrs[0])
220+
221+
def remove(self, name: str):
222+
for attr in self.cdata:
223+
if self._get_attr_name(attr) == name:
224+
lib.lyd_free_attr_single(self.context.cdata, attr)
225+
self.cdata.remove(attr)
226+
break
227+
228+
def __contains__(self, name: str) -> bool:
229+
for attr_name, _ in self:
230+
if attr_name == name:
231+
return True
232+
return False
233+
234+
def __iter__(self) -> Iterator[Tuple[str, str]]:
235+
for attr in self.cdata:
236+
name = self._get_attr_name(attr)
237+
yield (name, c2str(attr.value))
238+
239+
def __len__(self) -> int:
240+
return len(self.cdata)
241+
242+
@staticmethod
243+
def _get_attr_name(cdata) -> str:
244+
if cdata.name.prefix != ffi.NULL:
245+
return f"{c2str(cdata.name.prefix)}:{c2str(cdata.name.name)}"
246+
return c2str(cdata.name.name)
247+
248+
193249
# -------------------------------------------------------------------------------------
194250
class DNode:
195251
"""
196252
Data tree node.
197253
"""
198254

199-
__slots__ = ("context", "cdata", "free_func", "__dict__")
255+
__slots__ = ("context", "cdata", "attributes", "free_func", "__dict__")
200256

201257
def __init__(self, context: "libyang.Context", cdata):
202258
"""
@@ -207,6 +263,7 @@ def __init__(self, context: "libyang.Context", cdata):
207263
"""
208264
self.context = context
209265
self.cdata = cdata # C type: "struct lyd_node *"
266+
self.attributes = None
210267
self.free_func = None # type: Callable[DNode]
211268

212269
def meta(self):
@@ -254,6 +311,11 @@ def new_meta(self, name: str, value: str, clear_dflt: bool = False):
254311
if ret != lib.LY_SUCCESS:
255312
raise self.context.error("cannot create meta")
256313

314+
def attrs(self) -> DNodeAttrs:
315+
if not self.attributes:
316+
self.attributes = DNodeAttrs(self.context, self)
317+
return self.attributes
318+
257319
def add_defaults(
258320
self,
259321
no_config: bool = False,

tests/test_data.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DLeaf,
1717
DList,
1818
DNode,
19+
DNodeAttrs,
1920
DNotif,
2021
DRpc,
2122
IOType,
@@ -950,13 +951,86 @@ def test_dnode_insert_sibling(self):
950951
self.assertIsInstance(sibling, DLeaf)
951952
self.assertEqual(sibling.cdata, dnode2.cdata)
952953

953-
def test_dnode_new_opaq_find_one(self):
954+
def _create_opaq_hostname(self):
954955
root = self.ctx.create_data_path(path="/yolo-system:conf")
955956
root.new_path(
956957
"hostname",
957958
None,
958959
opt_opaq=True,
959960
)
960-
dnode = root.find_one("/yolo-system:conf/hostname")
961+
return root.find_one("/yolo-system:conf/hostname")
962+
963+
def test_dnode_new_opaq_find_one(self):
964+
dnode = self._create_opaq_hostname()
961965

962966
self.assertIsInstance(dnode, DLeaf)
967+
968+
def test_dnode_attrs(self):
969+
dnode = self._create_opaq_hostname()
970+
attrs = dnode.attrs()
971+
972+
self.assertIsInstance(attrs, DNodeAttrs)
973+
974+
def test_dnode_attrs_set(self):
975+
dnode = self._create_opaq_hostname()
976+
attrs = dnode.attrs()
977+
978+
self.assertEqual(len(attrs.cdata), 0)
979+
attrs.set("ietf-netconf:operation", "remove")
980+
981+
self.assertEqual(len(attrs.cdata), 1)
982+
983+
def test_dnode_attrs_get(self):
984+
dnode = self._create_opaq_hostname()
985+
attrs = dnode.attrs()
986+
987+
attrs.set("ietf-netconf:operation", "remove")
988+
989+
value = attrs.get("ietf-netconf:operation")
990+
self.assertEqual(value, "remove")
991+
992+
def test_dnode_attrs__len(self):
993+
dnode = self._create_opaq_hostname()
994+
attrs = dnode.attrs()
995+
996+
self.assertEqual(len(attrs), 0)
997+
attrs.set("ietf-netconf:operation", "remove")
998+
999+
self.assertEqual(len(attrs), 1)
1000+
1001+
def test_dnode_attrs__contains(self):
1002+
dnode = self._create_opaq_hostname()
1003+
attrs = dnode.attrs()
1004+
1005+
attrs.set("ietf-netconf:operation", "remove")
1006+
1007+
self.assertTrue("ietf-netconf:operation" in attrs)
1008+
1009+
def test_dnode_attrs_remove(self):
1010+
dnode = self._create_opaq_hostname()
1011+
attrs = dnode.attrs()
1012+
1013+
attrs.set("ietf-netconf:operation", "remove")
1014+
attrs.remove("ietf-netconf:operation")
1015+
1016+
self.assertEqual(len(attrs), 0)
1017+
1018+
def test_dnode_attrs_set_and_remove_multiple(self):
1019+
dnode = self._create_opaq_hostname()
1020+
attrs = dnode.attrs()
1021+
1022+
attrs.set("ietf-netconf:operation", "remove")
1023+
attrs.set("something:else", "test")
1024+
attrs.set("no_prefix", "test")
1025+
self.assertEqual(len(attrs), 3)
1026+
1027+
attrs.remove("something:else")
1028+
self.assertEqual(len(attrs), 2)
1029+
self.assertIn("no_prefix", attrs)
1030+
self.assertIn("ietf-netconf:operation", attrs)
1031+
1032+
attrs.remove("no_prefix")
1033+
self.assertEqual(len(attrs), 1)
1034+
1035+
attrs.remove("ietf-netconf:operation")
1036+
self.assertEqual(len(attrs), 0)

0 commit comments

Comments
 (0)