175 lines
6.1 KiB
Python
175 lines
6.1 KiB
Python
import six
|
|
from defusedxml.lxml import fromstring
|
|
from lxml import etree
|
|
|
|
from zeep import 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': 'http://schemas.xmlsoap.org/wsdl/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.
|
|
|
|
"""
|
|
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
|
|
|
|
"""
|
|
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.
|
|
|
|
"""
|
|
pass
|