From ce098f6426744510f1e6f634a05245096919a8e4 Mon Sep 17 00:00:00 2001 From: Henning Rogge Date: Thu, 9 Jan 2020 13:10:29 +0100 Subject: [PATCH 1/4] Add map access feature for lists --- yangson/instance.py | 27 +++++++++++++++++++++++++++ yangson/instvalue.py | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/yangson/instance.py b/yangson/instance.py index e705ac51..283bc656 100644 --- a/yangson/instance.py +++ b/yangson/instance.py @@ -137,6 +137,9 @@ def __init__(self, key: InstanceKey, value: Value, self.value = value # type: Value """Value of the receiver.""" + """" Mapping from key tuple to children """ + self._childmap = None # type: dict + @property def name(self) -> InstanceName: """Name of the receiver.""" @@ -178,6 +181,8 @@ def __getitem__(self, key: InstanceKey) -> "InstanceNode": `name`. InstanceValueError: If the receiver's value is not an object. """ + if isinstance(self.value, ArrayValue) and isinstance(key, tuple): + return self._mapentry(key) if isinstance(self.value, ObjectValue): return self._member(key) if isinstance(self.value, ArrayValue): @@ -397,6 +402,28 @@ def _entry(self, index: int) -> "ArrayEntry": except (IndexError, TypeError): raise NonexistentInstance(self.json_pointer(), "entry " + str(index)) from None + def _mapentry(self, key: tuple) -> "ArrayEntry": + if self._childmap is None: + self._childmap = {} + keys = self.schema_node._key_members + + # iterate over all childs + for child in self: + keylist = [] + + # collect key values into tuple + for keyit in keys: + keylist.append(child[keyit].value) + + # cache mapping + self._childmap[tuple(keylist)] = child + + try: + return self._childmap[key] + except (KeyError): + raise NonexistentInstance(self.json_pointer(), + f"key '{key}'") from None + def _peek_schema_route(self, sroute: SchemaRoute) -> Value: irt = InstanceRoute() sn = self.schema_node diff --git a/yangson/instvalue.py b/yangson/instvalue.py index 515964f9..9e7c39cb 100644 --- a/yangson/instvalue.py +++ b/yangson/instvalue.py @@ -35,7 +35,7 @@ EntryValue = Union[ScalarValue, "ObjectValue"] """Type of the value a list ot leaf-list entry.""" -InstanceKey = Union[InstanceName, int] +InstanceKey = Union[InstanceName, int, tuple] """Index of an array entry or name of an object member.""" MetadataObject = Dict[PrefName, ScalarValue] From 2a9a9c309be7bcee921eedd199300cb3951098b1 Mon Sep 17 00:00:00 2001 From: Henning Rogge Date: Mon, 20 Jan 2020 10:41:19 +0100 Subject: [PATCH 2/4] Speedup tuple based lookup and add map based lookup of childs --- yangson/instance.py | 68 ++++++++++++++++++++++++++++++++------------ yangson/instvalue.py | 2 +- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/yangson/instance.py b/yangson/instance.py index 283bc656..eeb9b33b 100644 --- a/yangson/instance.py +++ b/yangson/instance.py @@ -137,8 +137,10 @@ def __init__(self, key: InstanceKey, value: Value, self.value = value # type: Value """Value of the receiver.""" - """" Mapping from key tuple to children """ - self._childmap = None # type: dict + """Mapping from key tuple to children""" + self._childmap = {} # type: dict + """Remember at which index we want to start parsing the childs""" + self._parse_next = 0 # type: int @property def name(self) -> InstanceName: @@ -183,12 +185,19 @@ def __getitem__(self, key: InstanceKey) -> "InstanceNode": """ if isinstance(self.value, ArrayValue) and isinstance(key, tuple): return self._mapentry(key) + if isinstance(self.value, ArrayValue) and isinstance(key, dict): + return self._mapentry(self._map2tuple(key)) if isinstance(self.value, ObjectValue): return self._member(key) if isinstance(self.value, ArrayValue): return self._entry(key) raise InstanceValueError(self.json_pointer(), "scalar instance") + def __contains__(self, key: InstanceKey) -> bool: + """Checks if key does exist + """ + return self.get(key) is not None + def __iter__(self): """Return receiver's iterator. @@ -210,6 +219,14 @@ def ita(): return iter(self._member_names()) raise InstanceValueError(self.json_pointer(), "scalar instance") + def get(self, key: InstanceKey, d=None): + """Return member or entry with given key, returns default if it does not exist + """ + try: + return self[key] + except (InstanceValueError, NonexistentInstance): + return d + def is_internal(self) -> bool: """Return ``True`` if the receiver is an instance of an internal node. """ @@ -402,27 +419,42 @@ def _entry(self, index: int) -> "ArrayEntry": except (IndexError, TypeError): raise NonexistentInstance(self.json_pointer(), "entry " + str(index)) from None + def _map2tuple(self, key: dict) -> tuple: + """generate tuple for key""" + keylist = [] + for keyit in self.schema_node._key_members: + keylist.append(key[keyit]) + + return tuple(keylist) + def _mapentry(self, key: tuple) -> "ArrayEntry": - if self._childmap is None: - self._childmap = {} - keys = self.schema_node._key_members + child = self._childmap.get(key) + if child is not None: + return child - # iterate over all childs - for child in self: - keylist = [] + """lazy initialization of mapping from keys to childnodes""" + keys = self.schema_node._key_members - # collect key values into tuple - for keyit in keys: - keylist.append(child[keyit].value) + """iterate over all childs starting with last unparsed index""" + while self._parse_next < len(self.value): + child = self[self._parse_next] - # cache mapping - self._childmap[tuple(keylist)] = child + """generate tuple for key""" + keylist = [] + for keyit in keys: + keylist.append(child[keyit].value) + keytuple = tuple(keylist) - try: - return self._childmap[key] - except (KeyError): - raise NonexistentInstance(self.json_pointer(), - f"key '{key}'") from None + """cache mapping for later use""" + self._childmap[keytuple] = child + + """mark this child as done""" + self._parse_next = self._parse_next + 1; + + if keytuple == key: + return child + + raise NonexistentInstance(self.json_pointer(), f"key '{key}'") from None def _peek_schema_route(self, sroute: SchemaRoute) -> Value: irt = InstanceRoute() diff --git a/yangson/instvalue.py b/yangson/instvalue.py index 9e7c39cb..cdb1bdab 100644 --- a/yangson/instvalue.py +++ b/yangson/instvalue.py @@ -35,7 +35,7 @@ EntryValue = Union[ScalarValue, "ObjectValue"] """Type of the value a list ot leaf-list entry.""" -InstanceKey = Union[InstanceName, int, tuple] +InstanceKey = Union[InstanceName, int, tuple, dict] """Index of an array entry or name of an object member.""" MetadataObject = Dict[PrefName, ScalarValue] From 00ddffad40564f8f3b382367123fa4ef84f778e9 Mon Sep 17 00:00:00 2001 From: Henning Rogge Date: Tue, 21 Apr 2020 09:30:33 +0200 Subject: [PATCH 3/4] Remove caching for member access for now --- yangson/instance.py | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/yangson/instance.py b/yangson/instance.py index eeb9b33b..de5d98f8 100644 --- a/yangson/instance.py +++ b/yangson/instance.py @@ -137,11 +137,6 @@ def __init__(self, key: InstanceKey, value: Value, self.value = value # type: Value """Value of the receiver.""" - """Mapping from key tuple to children""" - self._childmap = {} # type: dict - """Remember at which index we want to start parsing the childs""" - self._parse_next = 0 # type: int - @property def name(self) -> InstanceName: """Name of the receiver.""" @@ -428,30 +423,11 @@ def _map2tuple(self, key: dict) -> tuple: return tuple(keylist) def _mapentry(self, key: tuple) -> "ArrayEntry": - child = self._childmap.get(key) - if child is not None: - return child - - """lazy initialization of mapping from keys to childnodes""" - keys = self.schema_node._key_members - - """iterate over all childs starting with last unparsed index""" - while self._parse_next < len(self.value): - child = self[self._parse_next] - + """iterate over all childs""" + for child in self: """generate tuple for key""" - keylist = [] - for keyit in keys: - keylist.append(child[keyit].value) - keytuple = tuple(keylist) - - """cache mapping for later use""" - self._childmap[keytuple] = child - - """mark this child as done""" - self._parse_next = self._parse_next + 1; - - if keytuple == key: + childkey = tuple(child[singlekey].value for singlekey in self.schema_node._key_members) + if key == childkey: return child raise NonexistentInstance(self.json_pointer(), f"key '{key}'") from None From a04536c0b7128201449d20756bdb0fe6fc1a4384 Mon Sep 17 00:00:00 2001 From: Henning Rogge Date: Thu, 7 May 2020 08:19:17 +0200 Subject: [PATCH 4/4] Add second filter function for adding array elements Cleanup XML element generation --- yangson/instance.py | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/yangson/instance.py b/yangson/instance.py index 56da04c7..277fd188 100644 --- a/yangson/instance.py +++ b/yangson/instance.py @@ -52,10 +52,16 @@ class OutputFilter: - def begin_child(self, parent: "InstanceNode", node: "InstanceNode")->bool: + def begin_member(self, parent: "InstanceNode", node: "InstanceNode")->bool: return True - def end_child(self, parent: "InstanceNode", node: "InstanceNode")->bool: + def end_member(self, parent: "InstanceNode", node: "InstanceNode")->bool: + return True + + def begin_element(self, parent: "InstanceNode", node: "InstanceNode")->bool: + return True + + def end_element(self, parent: "InstanceNode", node: "InstanceNode")->bool: return True @@ -399,18 +405,21 @@ def raw_value(self, filter: OutputFilter = OutputFilter()) -> RawValue: value = {} for m in self.value: member = self[m] - add1 = filter.begin_child(self, member) + add1 = filter.begin_member(self, member) if add1: member_value = member.raw_value(filter) - add2 = filter.end_child(self, member) + add2 = filter.end_member(self, member) if add1 and add2: value[m] = member_value return value if isinstance(self.value, ArrayValue): value = list() for en in self: - member_value = en.raw_value(filter) - if member_value: + add1 = filter.begin_element(self, en) + if add1: + member_value = en.raw_value(filter) + add2 = filter.end_element(self, en) + if add1 and add2 and member_value: value.append(member_value) return value return self.schema_node.type.to_raw(self.value) @@ -435,28 +444,26 @@ def to_xml(self, filter: OutputFilter = OutputFilter(), elem: ET.Element = None) continue m = self[cname] - if filter.begin_child(self, m): + if filter.begin_member(self, m): sn = m.schema_node dp = sn.data_parent() if isinstance(m.schema_node, (ListNode, LeafListNode)): for en in m: - add1 = filter.begin_child(self, en) + add1 = filter.begin_element(m, en) if add1: - child = ET.SubElement(element, sn.name) - childs.append(child) + child = ET.Element(sn.name) if not dp or dp.ns != sn.ns: module = self.schema_data.modules_by_name.get(sn.ns) if not module: raise MissingModuleNamespace(sn.ns) child.attrib['xmlns'] = module.xml_namespace en.to_xml(filter, child) - add2 = filter.end_child(self, en) - if add1 and not add2: - element.remove(child) - childs.remove(child) + add2 = filter.end_element(m, en) + if add1 and add2: + childs.append(child) else: - child = ET.SubElement(element, sn.name) + child = ET.Element(sn.name) childs.append(child) if not dp or dp.ns != sn.ns: module = self.schema_data.modules_by_name.get(sn.ns) @@ -464,9 +471,9 @@ def to_xml(self, filter: OutputFilter = OutputFilter(), elem: ET.Element = None) raise MissingModuleNamespace(sn.ns) child.attrib['xmlns'] = module.xml_namespace m.to_xml(filter, child) - if not filter.end_child(self, m): + if filter.end_member(self, m): for c in childs: - element.remove(c) + element.append(c) if elem is None and len(element) == 0: return None elif isinstance(self.value, ArrayValue):