Skip to content

Commit

Permalink
schema: adds Identity and PIdentity classes
Browse files Browse the repository at this point in the history
This patch introduces Identity and PIdentity classes. It also adds
identities() API to get list of identities from Module

Signed-off-by: Stefan Gula <[email protected]>
  • Loading branch information
steweg committed Apr 12, 2024
1 parent 7c451a5 commit 9927dfa
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 14 deletions.
20 changes: 20 additions & 0 deletions cffi/cdefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions libyang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
ExtensionCompiled,
ExtensionParsed,
Feature,
Identity,
IfAndFeatures,
IfFeature,
IfFeatureExpr,
Expand All @@ -89,6 +90,7 @@
PContainer,
PEnum,
PGrouping,
PIdentity,
PLeaf,
PLeafList,
PList,
Expand Down Expand Up @@ -150,6 +152,7 @@
"ExtensionPlugin",
"ExtensionRemoved",
"Feature",
"Identity",
"IfAndFeatures",
"IfFeature",
"IfFeatureExpr",
Expand Down Expand Up @@ -183,6 +186,7 @@
"PContainer",
"PEnum",
"PGrouping",
"PIdentity",
"PLeaf",
"PLeafList",
"PList",
Expand Down
164 changes: 150 additions & 14 deletions libyang/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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


# -------------------------------------------------------------------------------------
Expand All @@ -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


# -------------------------------------------------------------------------------------
Expand Down Expand Up @@ -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(":")
Expand Down Expand Up @@ -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__")
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from libyang import (
Context,
Extension,
Identity,
IfFeature,
IfOrFeatures,
IOType,
Expand All @@ -22,6 +23,7 @@
PChoice,
PContainer,
PGrouping,
PIdentity,
PLeaf,
PLeafList,
PList,
Expand Down Expand Up @@ -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("<libyang.schema.Identity: base1>", 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("<libyang.schema.PIdentity: base1>", 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())
32 changes: 32 additions & 0 deletions tests/yang/yolo/yolo-nodetypes.yang
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

0 comments on commit 9927dfa

Please sign in to comment.