diff --git a/tests/test_model.py b/tests/test_model.py index 37c6bb87374b24df99537abda6b43e54de8a55f3..d9a8f770a4b8d809cdfb2a315ba3f7d8377cd95d 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -177,14 +177,8 @@ def test_schema(data_model): assert lj.config == ca.config == cha.config == (not ll.config) == True assert la.ns == ld.ns assert lc.ns == "testb" - assert la.default_value() == 11 - assert ld.default_value() == 199 assert ld.type.default == 111 - assert lla.default_value() == ArrayValue([42, 54]) assert lla.type.default == 11 - assert lo.default_value() == True - assert lp.default_value() == 42 - assert lsta.default_value() == ObjectValue() assert la.type.parse_value("99") == 99 with pytest.raises(YangTypeError): ld.type.parse_value("99") diff --git a/yangson/instance.py b/yangson/instance.py index a9053a81b1cec2b5df99a76f0053d9cba34cb1c0..39c629078cead0c2185493ec6e14d038b52418b2 100644 --- a/yangson/instance.py +++ b/yangson/instance.py @@ -97,7 +97,7 @@ class InstanceNode: def put_member(self, name: InstanceName, value: Value, raw: bool = False) -> "InstanceNode": - """Return a copy of the receiver with a new value of a member. + """Return receiver's member with a new value. If the member is permitted by the schema but doesn't exist, it is created. @@ -118,7 +118,7 @@ class InstanceNode: newval = self.value.copy() newval[name] = csn.from_raw(value) if raw else value ts = datetime.now() - return self._copy(ObjectValue(newval, ts) , ts) + return self._copy(ObjectValue(newval, ts) , ts).member(name) def delete_member(self, name: InstanceName) -> "InstanceNode": """Return a copy of the receiver with a member deleted from its value. @@ -294,31 +294,6 @@ class InstanceNode: """ self.schema_node.validate(self, ctype) - def add_defaults(self) -> "InstanceNode": - """Return a copy of the receiver with defaults added to its value.""" - sn = self.schema_node - if isinstance(self.value, ArrayValue) and isinstance(sn, ListNode): - try: - inst = self.entry(0) - except NonexistentInstance: - return self - try: - while True: - ninst = inst.add_defaults() - inst = ninst.next() - except NonexistentInstance: - return ninst.up() - if isinstance(sn, InternalNode): - res = self - if self.value: - for mn in self.value: - m = res.member(mn) if res is self else res.sibling(mn) - res = m.add_defaults() - res = res.up() - sn._apply_defaults(res.value) - return res - return self - def raw_value(self) -> RawValue: """Return receiver's value in a raw form (ready for JSON encoding).""" if isinstance(self.value, ObjectValue): @@ -338,6 +313,25 @@ class InstanceNode: res = self.schema_node.type.to_raw(self.value) return res + def add_defaults(self, ctype: ContentType = None) -> "InstanceNode": + """Return the receiver with defaults added recursively to its value. + + Args: + ctype: Content type of the defaults to be added. If it is + ``None``, the content type will be the same as receiver's. + """ + sn = self.schema_node + val = self.value + if not (isinstance(val, ObjectValue) and isinstance(sn, InternalNode)): + return self + res = self + if val: + for mn in val: + m = res.member(mn) if res is self else res.sibling(mn) + res = m.add_defaults(ctype) + res = res.up() + return sn._add_defaults(res, ctype) + def _peek_schema_route(self, sroute: SchemaRoute) -> Value: irt = InstanceRoute() sn = self.schema_node @@ -363,8 +357,8 @@ class InstanceNode: return [ self.entry(i) for i in range(len(val)) ] return [self] - def _children(self, - qname: Union[QualName, bool] = None) -> List["InstanceNode"]: + def _children(self, qname: + Union[QualName, bool] = None) -> List["InstanceNode"]: """XPath - return the list of receiver's children.""" sn = self.schema_node if not isinstance(sn, InternalNode): return [] @@ -374,31 +368,20 @@ class InstanceNode: iname = cn.iname() if iname in self.value: return self.member(iname)._node_set() - res = cn._default_nodes(self) - if not res: return res + wd = cn._default_instance(self, ContentType.all, lazy=True) + if iname not in wd.value: return [] while True: cn = cn.parent - if cn is sn: return res - if ((cn.when is None or cn.when.evaluate(self)) and - (not isinstance(cn, CaseNode) or - cn.qual_name == cn.parent.default_case)): - continue - return [] + if cn is sn: + return wd.member(iname)._node_set() + if (cn.when and not cn.when.evaluate(self) or + isinstance(cn, CaseNode) and + cn.qual_name != cn.parent.default_case): + return [] res = [] - for cn in sn.children: - if isinstance(cn, ChoiceNode): - cin = cn._child_inst_names().intersection(self.value) - if cin: - for i in cin: - res.extend(self.member(i)._node_set()) - else: - res.extend(cn._default_nodes(inst)) - else: - iname = cn.iname() - if iname in self.value: - res.extend(self.member(iname)._node_set()) - else: - res.extend(cn._default_nodes(self)) + wd = sn._add_defaults(self, ContentType.all, lazy=True) + for mn in wd.value: + res.extend(wd.member(mn)._node_set()) return res def _descendants(self, qname: Union[QualName, bool] = None, diff --git a/yangson/schema.py b/yangson/schema.py index 7fb3b061d467e66c2271049ea032e8723f90d993..10c940dab8574f16dabb9f9fcf2150067796f176 100644 --- a/yangson/schema.py +++ b/yangson/schema.py @@ -70,15 +70,15 @@ class SchemaNode: """Does the receiver (also) represent configuration?""" return self.content_type().value & ContentType.config.value != 0 - def content_type(self) -> ContentType: - """Receiver's content type.""" - return self._ctype if self._ctype else self.parent.content_type() - @property def mandatory(self) -> bool: """Is the receiver a mandatory node?""" return False + def content_type(self) -> ContentType: + """Receiver's content type.""" + return self._ctype if self._ctype else self.parent.content_type() + def data_parent(self) -> Optional["InternalNode"]: """Return the closest ancestor data node.""" parent = self.parent @@ -122,10 +122,6 @@ class SchemaNode: """Return a list of data paths to descendant state data roots.""" return [r.data_path() for r in self._state_roots()] - def default_value(self) -> Optional[Value]: - """Return default content as defined by the receiver and descendants.""" - raise NotImplementedError - def validate(self, inst: "InstanceNode", ctype: ContentType) -> None: """Validate instance against the receiver. @@ -259,7 +255,6 @@ class InternalNode(SchemaNode): super().__init__() self.children = [] # type: List[SchemaNode] self._new_ns = None # type: Optional[ModuleId] - self.default_children = [] # type: List[SchemaNode] self._mandatory_children = set() # type: MutableSet[SchemaNode] @property @@ -323,38 +318,16 @@ class InternalNode(SchemaNode): res = c.get_data_child(name, ns) if res: return res - def filter_children(self, ctype: ContentType = None, - cnode: "InstanceNode" = None) -> List[SchemaNode]: - """Return list of valid children with matching content type. + def filter_children(self, ctype: ContentType = None) -> List[SchemaNode]: + """Return receiver's children based on content type. Args: ctype: Content type. - cnode: Context instance node for evaluating "when" statements. - If it is ``None``, "when" conditions are ignored. """ if ctype is None: - ctype = (ContentType.nonconfig if - not isinstance(self, InternalNode) else - self.content_type()) - res = [] - for child in self.children: - if (child.content_type().value & ctype.value == 0 or - isinstance(child, (RpcActionNode, NotificationNode))): - continue - if cnode is None or child.when is None: - res.append(child) - elif isinstance(child, DataNode): - mn = child.iname() - dummy = cnode.put_member(mn, (None,)) - if child.when.evaluate(dummy.member(mn)): - res.append(child) - else: - dval = cnode.value.copy() - for cc in child.data_children(): - dval.pop(cc.iname(), None) - if child.when.evaluate(cnode.update(dval)): - res.append(child) - return res + ctype = self.content_type() + return [c for c in self.children if + c.content_type().value & ctype.value != 0] def data_children(self) -> List["DataNode"]: """Return the set of all data nodes directly under the receiver.""" @@ -366,15 +339,6 @@ class InternalNode(SchemaNode): res.extend(child.data_children()) return res - def default_value(self) -> ObjectValue: - """Override the superclass method.""" - res = ObjectValue() - for c in self.default_children: - dflt = c.default_value() - if dflt is not None: - res[c.iname()] = dflt - return res - def validate(self, inst: "InstanceNode", ctype: ContentType) -> None: """Extend the superclass method.""" if not isinstance(inst.value, ObjectValue): @@ -442,27 +406,18 @@ class InternalNode(SchemaNode): for c in self.children: c._post_process() - def _add_default_child(self, node: SchemaNode) -> None: - """Add `node` to the list of default children.""" - self.default_children.append(node) - def _add_mandatory_child(self, node: SchemaNode) -> None: """Add `node` to the set of mandatory children.""" self._mandatory_children.add(node) - def _apply_defaults(self, value: ObjectValue) -> None: - """Return a copy of `value` with added default contents.""" - for c in self.default_children: - if isinstance(c, ChoiceNode): - ac = c.active_case(value) - if ac: - ac._apply_defaults(value) - elif c.default_case: - value.update(c.get_child(*c.default_case).default_value()) - else: - cn = c.iname() - if cn not in value: - value[cn] = c.default_value() + def _add_defaults(self, inst: "InstanceNode", ctype: ContentType, + lazy: bool = False) -> "InstanceNode": + for c in self.filter_children(ctype): + if isinstance(c, DataNode): + inst = c._default_instance(inst, ctype, lazy) + elif not isinstance(c, (RpcActionNode, NotificationNode)): + inst = c._add_defaults(inst, ctype) + return inst def _state_roots(self) -> List[SchemaNode]: if self.content_type() == ContentType.nonconfig: @@ -625,6 +580,17 @@ class DataNode(SchemaNode): self._check_must(inst) super().validate(inst, ctype) + def _default_instance(self, pnode: "InstanceNode", ctype: ContentType, + lazy: bool = False) -> "InstanceNode": + iname = self.iname() + if iname in pnode.value: return pnode + nm = pnode.put_member(iname, (None,)) + if not self.when or self.when.evaluate(nm): + wd = self._default_value(nm, ctype, lazy) + if wd.value is not None: + return wd.up() + return pnode + def _check_must(self, inst: "InstanceNode") -> None: """Check that all receiver's "must" constraints for the instance. @@ -704,15 +670,9 @@ class TerminalNode(SchemaNode): self.type.ref_type = ref.type def _default_nodes(self, inst: "InstanceNode") -> List["InstanceNode"]: - dflt = self.default_value() - if dflt is None: return [] - iname = self.iname() - ni = inst.put_member(iname, (None,)) - res = ni.member(iname) - if self.when is None or self.when.evaluate(res): - res.value = dflt - return res._node_set() - return [] + di = self._default_instance(inst, ContentType.all) + return [] if di is None else [self] + return inst.put_member(self.iname(), dflt)._node_set() def _ascii_tree(self, indent: str) -> str: return "" @@ -733,22 +693,25 @@ class ContainerNode(DataNode, InternalNode): """Is the receiver a mandatory node?""" return not self.presence and super().mandatory - def _add_default_child(self, node: SchemaNode) -> None: - """Extend the superclass method.""" - if not (self.presence or self.default_children): - self.parent._add_default_child(self) - super()._add_default_child(node) - def _add_mandatory_child(self, node: SchemaNode): if not (self.presence or self.mandatory): self.parent._add_mandatory_child(self) super()._add_mandatory_child(node) + def _default_instance(self, pnode: "InstanceNode", ctype: ContentType, + lazy: bool = False) -> "InstanceNode": + if self.presence: + return pnode + return super()._default_instance(pnode, ctype, lazy) + + def _default_value(self, inst: "InstanceNode", ctype: ContentType, + lazy: bool) -> Optional["InstanceNode"]: + inst.value = ObjectValue() + return inst if lazy else self._add_defaults(inst, ctype) + def _default_nodes(self, inst: "InstanceNode") -> List["InstanceNode"]: if self.presence: return [] - iname = self.iname() - ni = inst.put_member(iname, ObjectValue()) - res = ni.member(iname) + res = inst.put_member(self.iname(), ObjectValue()) if self.when is None or self.when.evaluate(res): return [res] return [] @@ -894,9 +857,9 @@ class ListNode(SequenceNode, InternalNode): except NonexistentInstance: continue - def _add_default_child(self, node: SchemaNode) -> None: - if node.qual_name not in self.keys: - super()._add_default_child(node) + def _default_instance(self, pnode: "InstanceNode", ctype: ContentType, + lazy: bool = False) -> "InstanceNode": + return pnode def _post_process(self) -> None: super()._post_process() @@ -937,6 +900,19 @@ class ChoiceNode(InternalNode): """Is the receiver a mandatory node?""" return self._mandatory + def _add_defaults(self, inst: "InstanceNode", + ctype: ContentType) -> "InstanceNode": + if self.when and not self.when.evaluate(inst): + return inst + ac = self.active_case(inst.value) + if ac: + return ac._add_defaults(inst, ctype) + elif self.default_case: + dc = self.get_child(*self.default_case) + if not dc.when or dc.when.evaluate(inst): + return dc._add_defaults(inst, ctype) + return inst + def active_case(self, value: ObjectValue) -> Optional["CaseNode"]: """Return receiver's case that's active in an instance node value. @@ -949,11 +925,6 @@ class ChoiceNode(InternalNode): or cc.iname() in value): return case - def default_value(self) -> Optional[ObjectValue]: - """Override the superclass method.""" - if self.default_case in [c.qual_name for c in self.default_children]: - return self.get_child(*self.default_case).default_value() - def _pattern_entry(self) -> SchemaPattern: if not self.children: return Empty() @@ -965,12 +936,6 @@ class ChoiceNode(InternalNode): prev = SchemaPattern.optional(prev) return ConditionalPattern(prev, self.when) if self.when else prev - def _add_default_child(self, node: "CaseNode") -> None: - """Extend the superclass method.""" - if not (self._mandatory or self.default_children): - self.parent._add_default_child(self) - super()._add_default_child(node) - def _post_process(self) -> None: super()._post_process() if self._mandatory: @@ -1014,12 +979,6 @@ class ChoiceNode(InternalNode): class CaseNode(InternalNode): """Case node.""" - def _add_default_child(self, node: SchemaNode) -> None: - """Extend the superclass method.""" - if not self.default_children: - self.parent._add_default_child(self) - super()._add_default_child(node) - def _pattern_entry(self) -> SchemaPattern: return super()._schema_pattern() @@ -1041,16 +1000,22 @@ class LeafNode(DataNode, TerminalNode): """Is the receiver a mandatory node?""" return self._mandatory - def default_value(self) -> Optional[ScalarValue]: - """Override the superclass method.""" - return self.type.default if self.default is None else self.default + def _default_value(self, inst: "InstanceNode", ctype: ContentType, + lazy: bool) -> "InstanceNode": + if self.mandatory: + inst.value = None + elif self.default is not None: + inst.value = self.default + elif self.type.default is not None: + inst.value = self.type.default + else: + inst.value = None + return inst def _post_process(self) -> None: super()._post_process() if self._mandatory: self.parent._add_mandatory_child(self) - elif self.default_value() is not None: - self.parent._add_default_child(self) def _tree_line(self) -> str: return super()._tree_line() + ("" if self._mandatory else "?") @@ -1061,23 +1026,23 @@ class LeafNode(DataNode, TerminalNode): class LeafListNode(SequenceNode, TerminalNode): """Leaf-list node.""" - def default_value(self) -> Optional[ArrayValue]: - """Override the superclass method.""" - if self.default is not None: - return self.default - if self.type.default is not None: - return ArrayValue([self.type.default]) + def _default_value(self, inst: "InstanceNode", ctype: ContentType, + lazy: bool) -> "InstanceNode": + if self.mandatory: + inst.value = None + elif self.default is not None: + inst.value = self.default + elif self.type.default is not None: + inst.value = ArrayValue([self.type.default]) + else: + inst.value = None + return inst def _check_unique(self, inst: "InstanceNode") -> None: if (self.content_type() == ContentType.config and len(set(inst.value)) < len(inst.value)): raise SemanticError(inst, "non-unique leaf-list values") - def _post_process(self) -> None: - super()._post_process() - if self.min_elements == 0 and self.default_value() is not None: - self.parent._add_default_child(self) - def _default_stmt(self, stmt: Statement, mid: ModuleId) -> None: val = self.type.parse_value(stmt.argument) if self.default is None: @@ -1116,6 +1081,10 @@ class AnydataNode(DataNode): return res return convert(rval) + def _default_instance(self, pnode: "InstanceNode", ctype: ContentType, + lazy: bool = False) -> "InstanceNode": + return pnode + def _tree_line(self) -> str: return super()._tree_line() + ("" if self._mandatory else "?") @@ -1254,4 +1223,5 @@ class SemanticError(ValidationError): pass from .xpathast import Expr, LocationPath, Step, Root -from .instance import InstanceNode, ArrayEntry, NonexistentInstance +from .instance import (InstanceNode, ArrayEntry, + NonexistentInstance, ObjectMember) diff --git a/yangson/schpattern.py b/yangson/schpattern.py index f8f0fd58344e2eed3f93490462ff086a279dfadb..9784d55484ca47f6c5e5d8ce5f8c567deff40414 100644 --- a/yangson/schpattern.py +++ b/yangson/schpattern.py @@ -125,7 +125,7 @@ class Member(Typeable, Conditional): def _eval_when(self, cnode: "InstanceNode") -> None: if self.when: dummy = cnode.put_member(self.name, (None,)) - super()._eval_when(dummy.member(self.name)) + super()._eval_when(dummy) def nullable(self, ctype: ContentType) -> bool: """Override the superclass method."""