Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/openpyxl/descriptors/serialisable.py: 27%
161 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1# copyright openpyxl 2010-2015
3from copy import copy
4from keyword import kwlist
5KEYWORDS = frozenset(kwlist)
7from . import Descriptor
8from . import _Serialiasable
9from .sequence import (
10 Sequence,
11 NestedSequence,
12 MultiSequencePart,
13)
14from .namespace import namespaced
16from openpyxl.compat import safe_string
17from openpyxl.xml.functions import (
18 Element,
19 localname,
20)
22seq_types = (list, tuple)
24class Serialisable(_Serialiasable):
25 """
26 Objects can serialise to XML their attributes and child objects.
27 The following class attributes are created by the metaclass at runtime:
28 __attrs__ = attributes
29 __nested__ = single-valued child treated as an attribute
30 __elements__ = child elements
31 """
33 __attrs__ = None
34 __nested__ = None
35 __elements__ = None
36 __namespaced__ = None
38 idx_base = 0
40 @property
41 def tagname(self):
42 raise(NotImplementedError)
44 namespace = None
46 @classmethod
47 def from_tree(cls, node):
48 """
49 Create object from XML
50 """
51 # strip known namespaces from attributes
52 attrib = dict(node.attrib)
53 for key, ns in cls.__namespaced__: 53 ↛ 54line 53 didn't jump to line 54, because the loop on line 53 never started
54 if ns in attrib:
55 attrib[key] = attrib[ns]
56 del attrib[ns]
58 # strip attributes with unknown namespaces
59 for key in list(attrib):
60 if key.startswith('{'): 60 ↛ 61line 60 didn't jump to line 61, because the condition on line 60 was never true
61 del attrib[key]
62 elif key in KEYWORDS: 62 ↛ 63line 62 didn't jump to line 63, because the condition on line 62 was never true
63 attrib["_" + key] = attrib[key]
64 del attrib[key]
65 elif "-" in key: 65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true
66 n = key.replace("-", "_")
67 attrib[n] = attrib[key]
68 del attrib[key]
70 if node.text and "attr_text" in cls.__attrs__: 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true
71 attrib["attr_text"] = node.text
73 for el in node:
74 tag = localname(el)
75 if tag in KEYWORDS: 75 ↛ 76line 75 didn't jump to line 76, because the condition on line 75 was never true
76 tag = "_" + tag
77 desc = getattr(cls, tag, None)
78 if desc is None or isinstance(desc, property): 78 ↛ 79line 78 didn't jump to line 79, because the condition on line 78 was never true
79 continue
81 if hasattr(desc, 'from_tree'):
82 #descriptor manages conversion
83 obj = desc.from_tree(el)
84 else:
85 if hasattr(desc.expected_type, "from_tree"):
86 #complex type
87 obj = desc.expected_type.from_tree(el)
88 else:
89 #primitive
90 obj = el.text
92 if isinstance(desc, NestedSequence): 92 ↛ 93line 92 didn't jump to line 93, because the condition on line 92 was never true
93 attrib[tag] = obj
94 elif isinstance(desc, Sequence): 94 ↛ 95line 94 didn't jump to line 95, because the condition on line 94 was never true
95 attrib.setdefault(tag, [])
96 attrib[tag].append(obj)
97 elif isinstance(desc, MultiSequencePart): 97 ↛ 98line 97 didn't jump to line 98, because the condition on line 97 was never true
98 attrib.setdefault(desc.store, [])
99 attrib[desc.store].append(obj)
100 else:
101 attrib[tag] = obj
103 return cls(**attrib)
106 def to_tree(self, tagname=None, idx=None, namespace=None):
108 if tagname is None:
109 tagname = self.tagname
111 # keywords have to be masked
112 if tagname.startswith("_"):
113 tagname = tagname[1:]
115 tagname = namespaced(self, tagname, namespace)
116 namespace = getattr(self, "namespace", namespace)
118 attrs = dict(self)
119 for key, ns in self.__namespaced__:
120 if key in attrs:
121 attrs[ns] = attrs[key]
122 del attrs[key]
124 el = Element(tagname, attrs)
125 if "attr_text" in self.__attrs__:
126 el.text = safe_string(getattr(self, "attr_text"))
128 for child_tag in self.__elements__:
129 desc = getattr(self.__class__, child_tag, None)
130 obj = getattr(self, child_tag)
131 if hasattr(desc, "namespace") and hasattr(obj, 'namespace'):
132 obj.namespace = desc.namespace
134 if isinstance(obj, seq_types):
135 if isinstance(desc, NestedSequence):
136 # wrap sequence in container
137 if not obj:
138 continue
139 nodes = [desc.to_tree(child_tag, obj, namespace)]
140 elif isinstance(desc, Sequence):
141 # sequence
142 desc.idx_base = self.idx_base
143 nodes = (desc.to_tree(child_tag, obj, namespace))
144 else: # property
145 nodes = (v.to_tree(child_tag, namespace) for v in obj)
146 for node in nodes:
147 el.append(node)
148 else:
149 if child_tag in self.__nested__:
150 node = desc.to_tree(child_tag, obj, namespace)
151 elif obj is None:
152 continue
153 else:
154 node = obj.to_tree(child_tag)
155 if node is not None:
156 el.append(node)
157 return el
160 def __iter__(self):
161 for attr in self.__attrs__:
162 value = getattr(self, attr)
163 if attr.startswith("_"):
164 attr = attr[1:]
165 elif attr != "attr_text" and "_" in attr:
166 desc = getattr(self.__class__, attr)
167 if getattr(desc, "hyphenated", False):
168 attr = attr.replace("_", "-")
169 if attr != "attr_text" and value is not None:
170 yield attr, safe_string(value)
173 def __eq__(self, other):
174 if not self.__class__ == other.__class__:
175 return False
176 elif not dict(self) == dict(other):
177 return False
178 for el in self.__elements__:
179 if getattr(self, el) != getattr(other, el):
180 return False
181 return True
184 def __ne__(self, other):
185 return not self == other
188 def __repr__(self):
189 s = u"<{0}.{1} object>\nParameters:".format(
190 self.__module__,
191 self.__class__.__name__
192 )
193 args = []
194 for k in self.__attrs__ + self.__elements__:
195 v = getattr(self, k)
196 if isinstance(v, Descriptor):
197 v = None
198 args.append(u"{0}={1}".format(k, repr(v)))
199 args = u", ".join(args)
201 return u"\n".join([s, args])
204 def __hash__(self):
205 fields = []
206 for attr in self.__attrs__ + self.__elements__:
207 val = getattr(self, attr)
208 if isinstance(val, list):
209 val = tuple(val)
210 fields.append(val)
212 return hash(tuple(fields))
215 def __add__(self, other):
216 if type(self) != type(other):
217 raise TypeError("Cannot combine instances of different types")
218 vals = {}
219 for attr in self.__attrs__:
220 vals[attr] = getattr(self, attr) or getattr(other, attr)
221 for el in self.__elements__:
222 a = getattr(self, el)
223 b = getattr(other, el)
224 if a and b:
225 vals[el] = a + b
226 else:
227 vals[el] = a or b
228 return self.__class__(**vals)
231 def __copy__(self):
232 # serialise to xml and back to avoid shallow copies
233 xml = self.to_tree(tagname="dummy")
234 cp = self.__class__.from_tree(xml)
235 # copy any non-persisted attributed
236 for k in self.__dict__:
237 if k not in self.__attrs__ + self.__elements__:
238 v = copy(getattr(self, k))
239 setattr(cp, k, v)
240 return cp