Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Yang metadata extension (RFC 7952) #4

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PROJECT = yangson
VERSION = 1.3.24
VERSION = 1.3.26
.PHONY = tags deps install-deps test

tags:
Expand Down
8 changes: 8 additions & 0 deletions docs/examples/ex2/example-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
{
"number": 3,
"in-words": "three"
},
{
"number": 7,
"in-words": "seven"
},
{
"number": 8,
"in-words": "eight"
}
],
"bar": true
Expand Down
10 changes: 0 additions & 10 deletions docs/examples/ex2/test.py

This file was deleted.

48 changes: 27 additions & 21 deletions docs/instance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,13 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html
>>> foo6 = foo[0]
>>> foo6.value['number']
6
>>> foo3 = foo[-1]
>>> foo3.value['in-words']
'three'
>>> foo[2]
>>> fool = foo[-1]
>>> fool.value['in-words']
'eight'
>>> foo[4]
Traceback (most recent call last):
...
yangson.instance.NonexistentInstance: [/example-2:bag/foo] entry 2
yangson.instance.NonexistentInstance: [/example-2:bag/foo] entry 4

.. method:: __iter__()

Expand All @@ -246,7 +246,7 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html
.. doctest::

>>> [e.json_pointer() for e in foo]
['/example-2:bag/foo/0', '/example-2:bag/foo/1']
['/example-2:bag/foo/0', '/example-2:bag/foo/1', '/example-2:bag/foo/2', '/example-2:bag/foo/3']

An attempt to iterate over an :class:`InstanceNode` that has a
scalar value raises :exc:`~.InstanceValueError`.
Expand Down Expand Up @@ -309,9 +309,9 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html
['bar', 'baz', 'foo']
>>> xfoo = foo.delete_item(0)
>>> len(xfoo.value)
1
3
>>> len(foo.value) # foo is unchanged
2
4

.. method:: look_up(**keys: Dict[InstanceName, ScalarValue]) -> ArrayEntry

Expand All @@ -329,9 +329,9 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html

.. doctest::

>>> foo3 = foo.look_up(number=3)
>>> foo3.json_pointer()
'/example-2:bag/foo/1'
>>> foo8 = foo.look_up(number=8)
>>> foo8.json_pointer()
'/example-2:bag/foo/3'

.. method:: up() -> InstanceNode

Expand Down Expand Up @@ -376,9 +376,15 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html
flag is set, the :meth:`update` method “cooks” the raw value
first into the Python's :class:`decimal.Decimal` type.

>>> e3baz = e2bag['baz'].update_from_raw('2.7182818')
>>> e3baz.value
Decimal('2.7182818')
.. doctest::

>>> e3baz = e2bag['baz'].update('2.7182818', raw=True)
>>> e3baz.value
Decimal('2.7182818')
>>> e2bag['foo'][0]['in-words'].update(66, raw=True)
Traceback (most recent call last):
...
yangson.exceptions.RawTypeError: [/example-2:bag/foo/0/in-words] expected string value

.. method:: goto(iroute: InstanceRoute) -> InstanceNode

Expand Down Expand Up @@ -607,8 +613,8 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html

.. doctest::

>>> foo3.previous().json_pointer()
'/example-2:bag/foo/0'
>>> foo8.previous().json_pointer()
'/example-2:bag/foo/2'
>>> foo6.previous()
Traceback (most recent call last):
...
Expand All @@ -626,10 +632,10 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html

>>> foo6.next().json_pointer()
'/example-2:bag/foo/1'
>>> foo3.next()
>>> foo8.next()
Traceback (most recent call last):
...
yangson.instance.NonexistentInstance: [/example-2:bag/foo/1] next of last
yangson.instance.NonexistentInstance: [/example-2:bag/foo/3] next of last

.. method:: insert_before(value: Union[RawValue, Value], raw: bool \
= False) -> ArrayEntry
Expand All @@ -641,9 +647,9 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html

.. doctest::

>>> foo4 = foo3.insert_before({'number': 4, 'in-words': 'four'}, raw=True)
>>> foo4 = foo8.insert_before({'number': 4, 'in-words': 'four'}, raw=True)
>>> [en['number'] for en in foo4.up().value]
[6, 4, 3]
[6, 3, 7, 4, 8]

.. method:: insert_after(value: Union[RawValue, Value], raw: bool \
= False) -> ArrayEntry
Expand All @@ -657,7 +663,7 @@ __ http://www.sphinx-doc.org/en/stable/ext/doctest.html

>>> foo5 = foo4.insert_after({'number': 5, 'in-words': 'five'}, raw=True)
>>> [en['number'] for en in foo5.up().value]
[6, 4, 5, 3]
[6, 3, 7, 4, 5, 8]

.. autoclass:: InstanceRoute
:show-inheritance:
Expand Down
80 changes: 80 additions & 0 deletions yang-modules/ietf/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module ietf-yang-metadata {
namespace "urn:ietf:params:xml:ns:yang:ietf-yang-metadata";
prefix md;

organization
"IETF NETMOD (NETCONF Data Modeling Language) Working Group";
contact
"WG Web: <https://datatracker.ietf.org/wg/netmod/>

WG List: <mailto:[email protected]>

WG Chair: Lou Berger
<mailto:[email protected]>

WG Chair: Kent Watsen
<mailto:[email protected]>

Editor: Ladislav Lhotka
<mailto:[email protected]>";
description
"This YANG module defines an 'extension' statement that allows
for defining metadata annotations.

Copyright (c) 2016 IETF Trust and the persons identified as
authors of the code. All rights reserved.

Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject to
the license terms contained in, the Simplified BSD License set
forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(http://trustee.ietf.org/license-info).

This version of this YANG module is part of RFC 7952
(http://www.rfc-editor.org/info/rfc7952); see the RFC itself
for full legal notices.";

revision 2016-08-05 {
description
"Initial revision.";
reference
"RFC 7952: Defining and Using Metadata with YANG";
}

extension annotation {
argument name;
description
"This extension allows for defining metadata annotations in
YANG modules. The 'md:annotation' statement can appear only
at the top level of a YANG module or submodule, i.e., it
becomes a new alternative in the ABNF production rule for
'body-stmts' (Section 14 in RFC 7950).

The argument of the 'md:annotation' statement defines the name
of the annotation. Syntactically, it is a YANG identifier as
defined in Section 6.2 of RFC 7950.

An annotation defined with this 'extension' statement inherits
the namespace and other context from the YANG module in which
it is defined.

The data type of the annotation value is specified in the same
way as for a leaf data node using the 'type' statement.

The semantics of the annotation and other documentation can be
specified using the following standard YANG substatements (all
are optional): 'description', 'if-feature', 'reference',
'status', and 'units'.

A server announces support for a particular annotation by
including the module in which the annotation is defined among
the advertised YANG modules, e.g., in a NETCONF <hello>
message or in the YANG library (RFC 7950). The annotation can
then be attached to any instance of a data node defined in any
YANG module that is advertised by the server.

XML encoding and JSON encoding of annotations are defined in
RFC 7952.";
}
}
25 changes: 25 additions & 0 deletions yang-modules/test_metadata/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module meta {

yang-version "1.1";

namespace "http://example.com/meta";

prefix "m";

import ietf-yang-types {
prefix "yang";
revision-date 2013-07-15;
}
import ietf-yang-metadata {
prefix "md";
}

revision 2016-04-26;

md:annotation last-modified {
type yang:date-and-time;
description
"This annotation contains the date and time when the
annotated instance was last modified (or created).";
}
}
24 changes: 24 additions & 0 deletions yang-modules/test_metadata/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module other {

yang-version "1.1";

namespace "http://example.com/other";

prefix "m";

import ietf-yang-types {
prefix "yang";
revision-date 2013-07-15;
}

revision 2016-04-26;

container contA {
leaf leafA {
type int32;
}
leaf leafB {
type string;
}
}
}
33 changes: 33 additions & 0 deletions yang-modules/test_metadata/yang-library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"ietf-yang-library:modules-state": {
"module-set-id": "b6d7e0614440c5ad8a7370fe46c777254d331983",
"module": [
{
"name": "meta",
"revision": "2016-04-26",
"schema": "https://example.com/meta.yang",
"namespace": "http://example.com/meta",
"conformance-type": "implement"
},
{
"name": "other",
"revision": "2016-04-26",
"schema": "https://example.com/other.yang",
"namespace": "http://example.com/other",
"conformance-type": "implement"
},
{
"name": "ietf-yang-types",
"revision": "2013-07-15",
"namespace": "urn:ietf:params:xml:ns:yang:ietf-yang-types",
"conformance-type": "import"
},
{
"name": "ietf-yang-metadata",
"revision": "2016-08-05",
"namespace": "urn:ietf:params:xml:ns:yang:ietf-yang-metadata",
"conformance-type": "import"
}
]
}
}
11 changes: 7 additions & 4 deletions yangson/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ class LinkedList:
"""Persistent linked list of instance values."""

@classmethod
def from_list(cls, vals: List[Value] = []) -> "LinkedList":
def from_list(cls, vals: List[Value] = [], reverse: bool = False) -> "LinkedList":
"""Create an instance from a standard list.

Args:
vals: Python list of instance values.
"""
res = EmptyList()
for v in vals[::-1]:
for v in (vals if reverse else vals[::-1]):
res = cls(v, res)
return res

Expand Down Expand Up @@ -266,7 +266,8 @@ def update(self, value: Union[RawValue, Value],
Returns:
Copy of the receiver with the updated value.
"""
newval = self.schema_node.from_raw(value, self.json_pointer()) if raw else value
newval = self.schema_node.from_raw(
value, self.json_pointer()) if raw else value
return self._copy(newval)

def goto(self, iroute: "InstanceRoute") -> "InstanceNode":
Expand Down Expand Up @@ -359,7 +360,7 @@ def _entry(self, index: int) -> "ArrayEntry":
val = self.value
try:
i = len(val) + index if index < 0 else index
return ArrayEntry(i, LinkedList.from_list(val[:i]),
return ArrayEntry(i, LinkedList.from_list(val[:i], reverse=True),
LinkedList.from_list(val[i + 1:]),
val[index], self, self.schema_node,
val.timestamp)
Expand All @@ -378,6 +379,8 @@ def _peek_schema_route(self, sroute: SchemaRoute) -> Value:
return self.peek(irt)

def _member_schema_node(self, name: InstanceName) -> "DataNode":
if name.startswith("@"):
return self.schema_node.schema_root()
qname = self.schema_node._iname2qname(name)
res = self.schema_node.get_data_child(*qname)
if res is None:
Expand Down
2 changes: 1 addition & 1 deletion yangson/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Parser:

# Regular expressions

ident_re = re.compile("[a-zA-Z_][a-zA-Z0-9_.-]*")
ident_re = re.compile(r"(@?[a-zA-Z_][a-zA-Z0-9_.-]*|@)")
"""Regular expression for YANG identifier."""

ws_re = re.compile(r"[ \n\t\r]*")
Expand Down
Loading