From 9927dfa2c9986c2e3ebdd67a43de8d8959104c8b Mon Sep 17 00:00:00 2001 From: Stefan Gula Date: Fri, 5 Apr 2024 12:08:31 +0200 Subject: [PATCH] schema: adds Identity and PIdentity classes This patch introduces Identity and PIdentity classes. It also adds identities() API to get list of identities from Module Signed-off-by: Stefan Gula --- cffi/cdefs.h | 20 ++++ libyang/__init__.py | 4 + libyang/schema.py | 164 +++++++++++++++++++++++++--- tests/test_schema.py | 43 ++++++++ tests/yang/yolo/yolo-nodetypes.yang | 32 ++++++ 5 files changed, 249 insertions(+), 14 deletions(-) diff --git a/cffi/cdefs.h b/cffi/cdefs.h index b3d8b587..e9dfb7e8 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -470,6 +470,16 @@ struct lysp_ext_instance { char rev[LY_REV_SIZE]; }; +struct lysp_ident { + const char *name; + struct lysp_qname *iffeatures; + const char **bases; + const char *dsc; + const char *ref; + struct lysp_ext_instance *exts; + uint16_t flags; +}; + struct lysp_feature { const char *name; struct lysp_qname *iffeatures; @@ -939,6 +949,16 @@ struct lysp_restr { struct lysp_ext_instance *exts; }; +struct lysc_ident { + const char *name; + const char *dsc; + const char *ref; + struct lys_module *module; + struct lysc_ident **derived; + struct lysc_ext_instance *exts; + uint16_t flags; +}; + struct lysc_type_num { const char *name; struct lysc_ext_instance *exts; diff --git a/libyang/__init__.py b/libyang/__init__.py index 762225de..10049ee4 100644 --- a/libyang/__init__.py +++ b/libyang/__init__.py @@ -71,6 +71,7 @@ ExtensionCompiled, ExtensionParsed, Feature, + Identity, IfAndFeatures, IfFeature, IfFeatureExpr, @@ -89,6 +90,7 @@ PContainer, PEnum, PGrouping, + PIdentity, PLeaf, PLeafList, PList, @@ -150,6 +152,7 @@ "ExtensionPlugin", "ExtensionRemoved", "Feature", + "Identity", "IfAndFeatures", "IfFeature", "IfFeatureExpr", @@ -183,6 +186,7 @@ "PContainer", "PEnum", "PGrouping", + "PIdentity", "PLeaf", "PLeafList", "PList", diff --git a/libyang/schema.py b/libyang/schema.py index a77d1d50..733b7503 100644 --- a/libyang/schema.py +++ b/libyang/schema.py @@ -184,6 +184,16 @@ def notifications(self) -> Iterator["PNotif"]: for n in ly_list_iter(self.cdata.parsed.notifs): yield PNotif(self.context, n, self) + def identities( + self, parsed: bool = False + ) -> Union[Iterator["Identity"], Iterator["PIdentity"]]: + if parsed: + for i in ly_array_iter(self.cdata.parsed.identities): + yield PIdentity(self.context, i, self) + else: + for i in ly_array_iter(self.cdata.identities): + yield Identity(self.context, i) + def __str__(self) -> str: return self.name() @@ -423,13 +433,16 @@ def name(self) -> str: def module(self) -> Module: return self._module_from_parsed() - def parent_node(self) -> Optional["PNode"]: - if not bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK): - return None - try: - return PNode.new(self.context, self.cdata.parent, self.module_parent) - except LibyangError: - return None + def parent_node(self) -> Optional[Union["PNode", "PIdentity"]]: + if self.cdata.parent_stmt == lib.LY_STMT_IDENTITY: + cdata = ffi.cast("struct lysp_ident *", self.cdata.parent) + return PIdentity(self.context, cdata, self.module_parent) + if bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK): + try: + return PNode.new(self.context, self.cdata.parent, self.module_parent) + except LibyangError: + return None + return None # ------------------------------------------------------------------------------------- @@ -448,13 +461,16 @@ def module(self) -> Module: raise self.context.error("cannot get module") return Module(self.context, self.cdata_def.module) - def parent_node(self) -> Optional["SNode"]: - if not bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK): - return None - try: - return SNode.new(self.context, self.cdata.parent) - except LibyangError: - return None + def parent_node(self) -> Optional[Union["SNode", "Identity"]]: + if self.cdata.parent_stmt == lib.LY_STMT_IDENTITY: + cdata = ffi.cast("struct lysc_ident *", self.cdata.parent) + return Identity(self.context, cdata) + if bool(self.cdata.parent_stmt & lib.LY_STMT_NODE_MASK): + try: + return SNode.new(self.context, self.cdata.parent) + except LibyangError: + return None + return None # ------------------------------------------------------------------------------------- @@ -632,6 +648,13 @@ def leafref_path(self) -> Optional["str"]: lr = ffi.cast("struct lysc_type_leafref *", self.cdata) return c2str(lib.lyxp_get_expr(lr.path)) + def identity_bases(self) -> Iterator["Identity"]: + if self.cdata.basetype != lib.LY_TYPE_IDENT: + return + ident = ffi.cast("struct lysc_type_identityref *", self.cdata) + for b in ly_array_iter(ident.bases): + yield Identity(self.context, b) + def typedef(self) -> "Typedef": if ":" in self.name(): module_prefix, type_name = self.name().split(":") @@ -878,6 +901,68 @@ def __str__(self): return self.name() +# ------------------------------------------------------------------------------------- +class Identity: + __slots__ = ("context", "cdata") + + def __init__(self, context: "libyang.Context", cdata): + self.context = context + self.cdata = cdata # C type: "struct lysc_ident *" + + def name(self) -> str: + return c2str(self.cdata.name) + + def description(self) -> Optional[str]: + return c2str(self.cdata.dsc) + + def reference(self) -> Optional[str]: + return c2str(self.cdata.ref) + + def module(self) -> Module: + return Module(self.context, self.cdata.module) + + def derived(self) -> Iterator["Identity"]: + for i in ly_array_iter(self.cdata.derived): + yield Identity(self.context, i) + + def extensions(self) -> Iterator[ExtensionCompiled]: + for ext in ly_array_iter(self.cdata.exts): + yield ExtensionCompiled(self.context, ext) + + def get_extension( + self, name: str, prefix: Optional[str] = None, arg_value: Optional[str] = None + ) -> Optional[ExtensionCompiled]: + for ext in self.extensions(): + if ext.name() != name: + continue + if prefix is not None and ext.module().name() != prefix: + continue + if arg_value is not None and ext.argument() != arg_value: + continue + return ext + return None + + def deprecated(self) -> bool: + return bool(self.cdata.flags & lib.LYS_STATUS_DEPRC) + + def obsolete(self) -> bool: + return bool(self.cdata.flags & lib.LYS_STATUS_OBSLT) + + def status(self) -> str: + if self.cdata.flags & lib.LYS_STATUS_OBSLT: + return "obsolete" + if self.cdata.flags & lib.LYS_STATUS_DEPRC: + return "deprecated" + return "current" + + def __repr__(self): + cls = self.__class__ + return "<%s.%s: %s>" % (cls.__module__, cls.__name__, str(self)) + + def __str__(self): + return self.name() + + # ------------------------------------------------------------------------------------- class Feature: __slots__ = ("context", "cdata", "__dict__") @@ -1899,6 +1984,57 @@ def extensions(self) -> Iterator["ExtensionParsed"]: yield ExtensionParsed(self.context, ext, self.module) +# ------------------------------------------------------------------------------------- +class PIdentity: + __slots__ = ("context", "cdata", "module") + + def __init__(self, context: "libyang.Context", cdata, module: Module) -> None: + self.context = context + self.cdata = cdata # C type: "struct lysp_ident *" + self.module = module + + def name(self) -> str: + return c2str(self.cdata.name) + + def if_features(self) -> Iterator[IfFeatureExpr]: + for f in ly_array_iter(self.cdata.iffeatures): + yield IfFeatureExpr(self.context, f, list(self.module.features())) + + def bases(self) -> Iterator[str]: + for b in ly_array_iter(self.cdata.bases): + yield c2str(b) + + def description(self) -> Optional[str]: + return c2str(self.cdata.dsc) + + def reference(self) -> Optional[str]: + return c2str(self.cdata.ref) + + def extensions(self) -> Iterator[ExtensionParsed]: + for ext in ly_array_iter(self.cdata.exts): + yield ExtensionParsed(self.context, ext, self.module) + + def deprecated(self) -> bool: + return bool(self.cdata.flags & lib.LYS_STATUS_DEPRC) + + def obsolete(self) -> bool: + return bool(self.cdata.flags & lib.LYS_STATUS_OBSLT) + + def status(self) -> str: + if self.cdata.flags & lib.LYS_STATUS_OBSLT: + return "obsolete" + if self.cdata.flags & lib.LYS_STATUS_DEPRC: + return "deprecated" + return "current" + + def __repr__(self): + cls = self.__class__ + return "<%s.%s: %s>" % (cls.__module__, cls.__name__, str(self)) + + def __str__(self): + return self.name() + + # ------------------------------------------------------------------------------------- class PNode: CONTAINER = lib.LYS_CONTAINER diff --git a/tests/test_schema.py b/tests/test_schema.py index b9277bea..017b261f 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -7,6 +7,7 @@ from libyang import ( Context, Extension, + Identity, IfFeature, IfOrFeatures, IOType, @@ -22,6 +23,7 @@ PChoice, PContainer, PGrouping, + PIdentity, PLeaf, PLeafList, PList, @@ -854,3 +856,44 @@ def test_notification_parsed(self): self.assertIsNone(next(pnode.typedefs(), None)) self.assertIsNone(next(pnode.groupings(), None)) self.assertIsNotNone(next(iter(pnode))) + + +# ------------------------------------------------------------------------------------- +class IdentityTest(unittest.TestCase): + def setUp(self): + self.ctx = Context(YANG_DIR) + self.module = self.ctx.load_module("yolo-nodetypes") + + def tearDown(self): + self.ctx.destroy() + self.ctx = None + + def test_identity_compiled(self): + snode = next(self.module.identities(parsed=False)) + self.assertIsInstance(snode, Identity) + self.assertEqual("", repr(snode)) + self.assertIsNone(snode.description()) + self.assertIsNone(snode.reference()) + self.assertIsInstance(snode.module(), Module) + derived = list(snode.derived()) + self.assertEqual(2, len(derived)) + for i in derived: + self.assertIsInstance(i, Identity) + self.assertIsNone(next(snode.extensions(), None)) + self.assertIsNone(snode.get_extension('ext1')) + self.assertFalse(snode.deprecated()) + self.assertFalse(snode.obsolete()) + self.assertEqual("current", snode.status()) + + def test_identity_parsed(self): + pnode = next(self.module.identities(parsed=True)) + self.assertIsInstance(pnode, PIdentity) + self.assertEqual("", repr(pnode)) + self.assertIsNone(next(pnode.if_features(), None)) + self.assertIsNone(next(pnode.bases(), None)) + self.assertIsNone(pnode.description()) + self.assertIsNone(pnode.reference()) + self.assertIsNone(next(pnode.extensions(), None)) + self.assertFalse(pnode.deprecated()) + self.assertFalse(pnode.obsolete()) + self.assertEqual("current", pnode.status()) diff --git a/tests/yang/yolo/yolo-nodetypes.yang b/tests/yang/yolo/yolo-nodetypes.yang index c2690dc9..f55cf285 100644 --- a/tests/yang/yolo/yolo-nodetypes.yang +++ b/tests/yang/yolo/yolo-nodetypes.yang @@ -112,4 +112,36 @@ module yolo-nodetypes { anydata any1 { when "../cont2"; } + + identity base1; + identity base2; + + identity derived1 { + base base1; + } + + identity derived2 { + base base1; + } + + identity derived3 { + base derived1; + } + + identity derived4 { + base base2; + } + + leaf identity_ref1 { + type identityref { + base base1; + } + } + + leaf identity_ref2 { + type identityref { + base base1; + base base2; + } + } }