debian-zeep/src/zeep/wsdl/messages/mime.py

204 lines
6.9 KiB
Python

"""
zeep.wsdl.messages.mime
~~~~~~~~~~~~~~~~~~~~~~~
"""
import six
from defusedxml.lxml import fromstring
from lxml import etree
from zeep import ns, xsd
from zeep.helpers import serialize_object
from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
from zeep.wsdl.utils import etree_to_string
__all__ = [
'MimeContent',
'MimeXML',
'MimeMultipart',
]
class MimeMessage(ConcreteMessage):
_nsmap = {
'mime': ns.MIME,
}
def __init__(self, wsdl, name, operation, part_name):
super(MimeMessage, self).__init__(wsdl, name, operation)
self.part_name = part_name
def resolve(self, definitions, abstract_message):
"""Resolve the body element
The specs are (again) not really clear how to handle the message
parts in relation the message element vs type. The following strategy
is chosen, which seem to work:
- If the message part has a name and it maches then set it as body
- If the message part has a name but it doesn't match but there are no
other message parts, then just use that one.
- If the message part has no name then handle it like an rpc call,
in other words, each part is an argument.
"""
self.abstract = abstract_message
if self.part_name and self.abstract.parts:
if self.part_name in self.abstract.parts:
message = self.abstract.parts[self.part_name]
elif len(self.abstract.parts) == 1:
message = list(self.abstract.parts.values())[0]
else:
raise ValueError(
"Multiple parts for message %r while no matching part found" % self.part_name)
if message.element:
self.body = message.element
else:
elm = xsd.Element(self.part_name, message.type)
self.body = xsd.Element(
self.operation.name, xsd.ComplexType(xsd.Sequence([elm])))
else:
children = []
for name, message in self.abstract.parts.items():
if message.element:
elm = message.element.clone(name)
else:
elm = xsd.Element(name, message.type)
children.append(elm)
self.body = xsd.Element(
self.operation.name, xsd.ComplexType(xsd.Sequence(children)))
class MimeContent(MimeMessage):
"""WSDL includes a way to bind abstract types to concrete messages in some
MIME format.
Bindings for the following MIME types are defined:
- multipart/related
- text/xml
- application/x-www-form-urlencoded
- Others (by specifying the MIME type string)
The set of defined MIME types is both large and evolving, so it is not a
goal for WSDL to exhaustively define XML grammar for each MIME type.
:param wsdl: The main wsdl document
:type wsdl: zeep.wsdl.wsdl.Document
:param name:
:param operation: The operation to which this message belongs
:type operation: zeep.wsdl.bindings.soap.SoapOperation
:param part_name:
:type type: str
"""
def __init__(self, wsdl, name, operation, content_type, part_name):
super(MimeContent, self).__init__(wsdl, name, operation, part_name)
self.content_type = content_type
def serialize(self, *args, **kwargs):
value = self.body(*args, **kwargs)
headers = {
'Content-Type': self.content_type
}
data = ''
if self.content_type == 'application/x-www-form-urlencoded':
items = serialize_object(value)
data = six.moves.urllib.parse.urlencode(items)
elif self.content_type == 'text/xml':
document = etree.Element('root')
self.body.render(document, value)
data = etree_to_string(document.getchildren()[0])
return SerializedMessage(
path=self.operation.location, headers=headers, content=data)
def deserialize(self, node):
node = fromstring(node)
part = list(self.abstract.parts.values())[0]
return part.type.parse_xmlelement(node)
@classmethod
def parse(cls, definitions, xmlelement, operation):
name = xmlelement.get('name')
part_name = content_type = None
content_node = xmlelement.find('mime:content', namespaces=cls._nsmap)
if content_node is not None:
content_type = content_node.get('type')
part_name = content_node.get('part')
obj = cls(definitions.wsdl, name, operation, content_type, part_name)
return obj
class MimeXML(MimeMessage):
"""To specify XML payloads that are not SOAP compliant (do not have a SOAP
Envelope), but do have a particular schema, the mime:mimeXml element may be
used to specify that concrete schema.
The part attribute refers to a message part defining the concrete schema of
the root XML element. The part attribute MAY be omitted if the message has
only a single part. The part references a concrete schema using the element
attribute for simple parts or type attribute for composite parts
:param wsdl: The main wsdl document
:type wsdl: zeep.wsdl.wsdl.Document
:param name:
:param operation: The operation to which this message belongs
:type operation: zeep.wsdl.bindings.soap.SoapOperation
:param part_name:
:type type: str
"""
def serialize(self, *args, **kwargs):
raise NotImplementedError()
def deserialize(self, node):
node = fromstring(node)
part = next(iter(self.abstract.parts.values()), None)
return part.element.parse(node, self.wsdl.types)
@classmethod
def parse(cls, definitions, xmlelement, operation):
name = xmlelement.get('name')
part_name = None
content_node = xmlelement.find('mime:mimeXml', namespaces=cls._nsmap)
if content_node is not None:
part_name = content_node.get('part')
obj = cls(definitions.wsdl, name, operation, part_name)
return obj
class MimeMultipart(MimeMessage):
"""The multipart/related MIME type aggregates an arbitrary set of MIME
formatted parts into one message using the MIME type "multipart/related".
The mime:multipartRelated element describes the concrete format of such a
message::
<mime:multipartRelated>
<mime:part> *
<-- mime element -->
</mime:part>
</mime:multipartRelated>
The mime:part element describes each part of a multipart/related message.
MIME elements appear within mime:part to specify the concrete MIME type for
the part. If more than one MIME element appears inside a mime:part, they
are alternatives.
:param wsdl: The main wsdl document
:type wsdl: zeep.wsdl.wsdl.Document
:param name:
:param operation: The operation to which this message belongs
:type operation: zeep.wsdl.bindings.soap.SoapOperation
:param part_name:
:type type: str
"""
pass