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

Closes: #118
Signed-off-by: Stefan Gula <[email protected]>
Signed-off-by: Samuel Gauthier <[email protected]>
  • Loading branch information
steweg authored and samuel-gauthier committed Dec 12, 2024
1 parent a19cab6 commit 76504c4
Show file tree
Hide file tree
Showing 5 changed files with 256 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 @@ -475,6 +475,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 @@ -976,6 +986,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 @@ -151,6 +153,7 @@
"ExtensionPlugin",
"ExtensionRemoved",
"Feature",
"Identity",
"IfAndFeatures",
"IfFeature",
"IfFeatureExpr",
Expand Down Expand Up @@ -184,6 +187,7 @@
"PContainer",
"PEnum",
"PGrouping",
"PIdentity",
"PLeaf",
"PLeafList",
"PList",
Expand Down
162 changes: 148 additions & 14 deletions libyang/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ def notifications(self) -> Iterator["PNotif"]:
for n in ly_list_iter(self.cdata.parsed.notifs):
yield PNotif(self.context, n, self)

def identities(self) -> Iterator["Identity"]:
for i in ly_array_iter(self.cdata.identities):
yield Identity(self.context, i)

def parsed_identities(self) -> Iterator["PIdentity"]:
for i in ly_array_iter(self.cdata.parsed.identities):
yield PIdentity(self.context, i, self)

def __str__(self) -> str:
return self.name()

Expand Down Expand Up @@ -415,13 +423,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 @@ -440,13 +451,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 @@ -624,6 +638,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 @@ -870,6 +891,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 @@ -1890,6 +1973,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
51 changes: 51 additions & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Context,
Extension,
ExtensionParsed,
Identity,
IfFeature,
IfOrFeatures,
IOType,
Expand All @@ -23,6 +24,7 @@
PChoice,
PContainer,
PGrouping,
PIdentity,
PLeaf,
PLeafList,
PList,
Expand Down Expand Up @@ -868,3 +870,52 @@ 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):
sidentity = next(self.module.identities())
self.assertIsInstance(sidentity, Identity)
self.assertEqual(sidentity.name(), "base1")
self.assertEqual(sidentity.description(), "Base 1.")
self.assertEqual(sidentity.reference(), "Some reference.")
self.assertIsInstance(sidentity.module(), Module)
derived = list(sidentity.derived())
self.assertEqual(2, len(derived))
for i in derived:
self.assertIsInstance(i, Identity)
self.assertEqual(derived[0].name(), "derived1")
self.assertEqual(derived[1].name(), "derived2")
self.assertEqual(next(derived[1].extensions()).name(), "identity-name")
self.assertIsNone(next(sidentity.extensions(), None))
self.assertIsNone(sidentity.get_extension("ext1"))
self.assertFalse(sidentity.deprecated())
self.assertFalse(sidentity.obsolete())
self.assertEqual("current", sidentity.status())

snode = next(self.ctx.find_path("/yolo-nodetypes:identity_ref"))
identities = list(snode.type().identity_bases())
self.assertEqual(identities[0].name(), sidentity.name())
self.assertEqual(identities[1].name(), "base2")

def test_identity_parsed(self):
pidentity = next(self.module.parsed_identities())
self.assertIsInstance(pidentity, PIdentity)
self.assertEqual(pidentity.name(), "base1")
self.assertIsNone(next(pidentity.if_features(), None))
self.assertIsNone(next(pidentity.bases(), None))
self.assertEqual(pidentity.description(), "Base 1.")
self.assertEqual(pidentity.reference(), "Some reference.")
self.assertIsNone(next(pidentity.extensions(), None))
self.assertFalse(pidentity.deprecated())
self.assertFalse(pidentity.obsolete())
self.assertEqual("current", pidentity.status())
33 changes: 33 additions & 0 deletions tests/yang/yolo/yolo-nodetypes.yang
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,39 @@ module yolo-nodetypes {
when "../cont2";
}

extension identity-name {
description
"Extend an identity to provide an alternative name.";
argument name;
}

identity base1 {
description
"Base 1.";
reference "Some reference.";
}
identity base2;

identity derived1 {
base base1;
}

identity derived2 {
base base1;
sys:identity-name "Derived2";
}

identity derived3 {
base derived1;
}

leaf identity_ref {
type identityref {
base base1;
base base2;
}
}

leaf ip-address {
type inet:ipv4-address;
}
Expand Down

0 comments on commit 76504c4

Please sign in to comment.