Source code for schematics_xml.models

# -*- coding: utf-8 -*-
"""
    schematics_xml.models
    ~~~~~~~~~~~~~~~~~~~~~

    Base models that provide to/from XML methods.
"""

import collections
import numbers

import lxml.builder
import lxml.etree
from schematics import Model
from schematics.types import BaseType, ModelType, CompoundType
from schematics.types.base import MultilingualStringType
from schematics.types.compound import PolyModelType
from xmltodict import parse


[docs]class XMLModel(Model): """ A model that can convert it's fields to and from XML. """ @property def xml_root(self): return type(self).__name__.lower()
[docs] def to_xml(self, *, role: str=None, app_data: dict=None, **kwargs) -> str: """ Return a string of XML that represents this model. Currently all arguments are passed through to schematics.Model.to_primitive. :param role: schematics Model to_primitive role parameter. :param app_data: schematics Model to_primitive app_data parameter. :param kwargs: schematics Model to_primitive kwargs parameter. """ primitive = self.to_primitive(role=role, app_data=app_data, **kwargs) root = self.primitive_to_xml(primitive) return lxml.etree.tostring( # pylint: disable=no-member root, pretty_print=True, xml_declaration=True, encoding='ISO-8859-1' )
[docs] def primitive_to_xml(self, primitive: dict, parent: 'lxml.etree._Element'=None): element_maker = lxml.builder.ElementMaker() if parent is None: parent = getattr(element_maker, self.xml_root)() for key, value in primitive.items(): self.primitive_value_to_xml(key, parent, value) return parent
[docs] def primitive_value_to_xml(self, key, parent, value): element_maker = lxml.builder.ElementMaker() if isinstance(value, bool): parent.append(getattr(element_maker, key)('1' if value else '0')) elif isinstance(value, numbers.Number) or isinstance(value, str): parent.append(getattr(element_maker, key)(str(value))) elif value is None: parent.append(getattr(element_maker, key)('')) elif isinstance(value, dict): _parent = getattr(element_maker, key)() parent.append(self.primitive_to_xml(value, _parent)) elif isinstance(value, collections.abc.Iterable): for _value in value: self.primitive_value_to_xml(key, parent, _value) else: raise TypeError('Unsupported data type: %s (%s)' % (value, type(value).__name__))
@classmethod
[docs] def from_xml(cls, xml: str) -> Model: """ Convert XML into a model. :param xml: A string of XML that represents this Model. """ if model_has_field_type(MultilingualStringType, cls): raise NotImplementedError("Field type 'MultilingualStringType' is not supported.") primitive = parse(xml) if len(primitive) != 1: raise NotImplementedError for _, raw_data in primitive.items(): return cls(raw_data=raw_data)
[docs]def model_has_field_type(needle: BaseType, haystack: Model) -> bool: """ Return True if haystack contains a field of type needle. Iterates over all fields (and into field if appropriate) and searches for field type *needle* in model *haystack*. :param needle: A schematics field class to search for. :param haystack: A schematics model to search within. """ for _, field in haystack._field_list: # pylint: disable=protected-access if field_has_type(needle, field): return True return False
[docs]def field_has_type(needle: BaseType, field: BaseType) -> bool: # pylint: disable=too-many-return-statements, too-many-branches """ Return True if field haystack contains a field of type needle. :param needle: A schematics field class to search for. :param haystack: An instance of a schematics field within a model. """ if isinstance(field, needle): return True elif isinstance(field, ModelType): if model_has_field_type(needle, field.model_class): return True elif isinstance(field, PolyModelType): if needle in [type(obj) for obj in field.model_classes]: return True for obj in [obj for obj in field.model_classes if isinstance(obj, ModelType)]: if model_has_field_type(needle, obj.model_class): return True elif isinstance(field, CompoundType): if needle == type(field.field): return True try: if needle == field.model_class: return True except AttributeError: pass else: if model_has_field_type(needle, field.model_class): return True if field_has_type(needle, field.field): return True return False