Source code for fastpyxl.descriptors.serialisable

# Copyright (c) 2010-2024 fastpyxl

from copy import copy
from keyword import kwlist

from fastpyxl.compat import safe_string
from fastpyxl.xml.functions import Element, localname

from fastpyxl.descriptors.namespace import namespaced

from . import Descriptor, MetaSerialisable
from .sequence import MultiSequencePart, NestedSequence, Sequence

KEYWORDS = frozenset(kwlist)
seq_types = (list, tuple)


[docs] class Serialisable(metaclass=MetaSerialisable): """ Objects can serialise to XML their attributes and child objects. The following class attributes are created by the metaclass at runtime: __attrs__ = attributes __nested__ = single-valued child treated as an attribute __elements__ = child elements """ __attrs__ = None __nested__ = None __elements__ = None __namespaced__ = None idx_base = 0 @property def tagname(self): raise NotImplementedError namespace = None
[docs] @classmethod def from_tree(cls, node): attrib = dict(node.attrib) for key, ns in cls.__namespaced__ or (): if ns in attrib: attrib[key] = attrib[ns] del attrib[ns] for key in list(attrib): if key.startswith("{"): del attrib[key] elif key in KEYWORDS: attrib["_" + key] = attrib[key] del attrib[key] elif "-" in key: n = key.replace("-", "_") attrib[n] = attrib[key] del attrib[key] cls_attrs = cls.__attrs__ or () if node.text and "attr_text" in cls_attrs: attrib["attr_text"] = node.text for el in node: tag = localname(el) if tag in KEYWORDS: tag = "_" + tag desc = getattr(cls, tag, None) if desc is None or isinstance(desc, property): continue if hasattr(desc, "from_tree"): obj = desc.from_tree(el) else: if hasattr(desc.expected_type, "from_tree"): obj = desc.expected_type.from_tree(el) else: obj = el.text if isinstance(desc, NestedSequence): attrib[tag] = obj elif isinstance(desc, Sequence): attrib.setdefault(tag, []) attrib[tag].append(obj) elif isinstance(desc, MultiSequencePart): attrib.setdefault(desc.store, []) attrib[desc.store].append(obj) else: attrib[tag] = obj return cls(**attrib)
[docs] def to_tree(self, tagname=None, idx=None, namespace=None): del idx if tagname is None: tagname = self.tagname if tagname.startswith("_"): tagname = tagname[1:] tagname = namespaced(self, tagname, namespace) namespace = self.namespace attrs = dict(self) for key, ns in self.__namespaced__ or (): if key in attrs: attrs[ns] = attrs[key] del attrs[key] el = Element(tagname, attrs) if "attr_text" in (self.__attrs__ or ()): el.text = safe_string(getattr(self, "attr_text")) for child_tag in self.__elements__ or (): desc = getattr(self.__class__, child_tag, None) obj = getattr(self, child_tag) if desc is not None and hasattr(desc, "namespace") and hasattr(obj, "namespace"): obj.namespace = desc.namespace if isinstance(obj, seq_types): if isinstance(desc, NestedSequence): if not obj: continue nodes = [desc.to_tree(child_tag, obj, namespace)] elif isinstance(desc, Sequence): desc.idx_base = self.idx_base nodes = desc.to_tree(child_tag, obj, namespace) else: nodes = (v.to_tree(child_tag, namespace) for v in obj) for node in nodes: el.append(node) else: if child_tag in (self.__nested__ or ()): assert desc is not None node = desc.to_tree(child_tag, obj, namespace) elif obj is None: continue else: node = obj.to_tree(child_tag) if node is not None: el.append(node) return el
def __iter__(self): for attr in self.__attrs__ or (): value = getattr(self, attr) if attr.startswith("_"): attr = attr[1:] elif attr != "attr_text" and "_" in attr: desc = getattr(self.__class__, attr) if getattr(desc, "hyphenated", False): attr = attr.replace("_", "-") if attr != "attr_text" and value is not None: yield attr, safe_string(value) def __eq__(self, other): if not self.__class__ == other.__class__: return False elif not dict(self) == dict(other): return False for el in self.__elements__ or (): if getattr(self, el) != getattr(other, el): return False return True def __ne__(self, other): return not self == other def __repr__(self): s = "<{0}.{1} object>\nParameters:".format( self.__module__, self.__class__.__name__, ) args = [] for k in (self.__attrs__ or ()) + (self.__elements__ or ()): v = getattr(self, k) if isinstance(v, Descriptor): v = None args.append("{0}={1}".format(k, repr(v))) args = ", ".join(args) return "\n".join([s, args]) def __hash__(self): fields = [] for attr in (self.__attrs__ or ()) + (self.__elements__ or ()): val = getattr(self, attr) if isinstance(val, list): val = tuple(val) fields.append(val) return hash(tuple(fields)) def __add__(self, other): if type(self) is not type(other): raise TypeError("Cannot combine instances of different types") vals = {} for attr in self.__attrs__ or (): vals[attr] = getattr(self, attr) or getattr(other, attr) for el in self.__elements__ or (): a = getattr(self, el) b = getattr(other, el) if a and b: vals[el] = a + b else: vals[el] = a or b return self.__class__(**vals) def __copy__(self): xml = self.to_tree(tagname="dummy") cp = self.__class__.from_tree(xml) for k in self.__dict__: if k not in (self.__attrs__ or ()) + (self.__elements__ or ()): v = copy(getattr(self, k)) setattr(cp, k, v) return cp