265 lines
7.4 KiB
Python
265 lines
7.4 KiB
Python
from collections import OrderedDict, namedtuple
|
|
|
|
from six import python_2_unicode_compatible
|
|
|
|
MessagePart = namedtuple('MessagePart', ['element', 'type'])
|
|
|
|
|
|
class AbstractMessage(object):
|
|
"""Messages consist of one or more logical parts.
|
|
|
|
Each part is associated with a type from some type system using a
|
|
message-typing attribute. The set of message-typing attributes is
|
|
extensible. WSDL defines several such message-typing attributes for use
|
|
with XSD:
|
|
|
|
element: Refers to an XSD element using a QName.
|
|
type: Refers to an XSD simpleType or complexType using a QName.
|
|
|
|
"""
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.parts = OrderedDict()
|
|
|
|
def __repr__(self):
|
|
return '<%s(name=%r)>' % (self.__class__.__name__, self.name.text)
|
|
|
|
def resolve(self, definitions):
|
|
pass
|
|
|
|
def add_part(self, name, element):
|
|
self.parts[name] = element
|
|
|
|
|
|
class AbstractOperation(object):
|
|
"""Abstract operations are defined in the wsdl's portType elements."""
|
|
|
|
def __init__(self, name, input_message=None, output_message=None,
|
|
fault_messages=None, parameter_order=None):
|
|
"""Initialize the abstract operation.
|
|
|
|
:param name: The name of the operation
|
|
:type name: str
|
|
:param input_message: Message to generate the request XML
|
|
:type input_message: AbstractMessage
|
|
:param output_message: Message to process the response XML
|
|
:type output_message: AbstractMessage
|
|
:param fault_messages: Dict of messages to handle faults
|
|
:type fault_messages: dict of str: AbstractMessage
|
|
|
|
"""
|
|
self.name = name
|
|
self.input_message = input_message
|
|
self.output_message = output_message
|
|
self.fault_messages = fault_messages
|
|
self.parameter_order = parameter_order
|
|
|
|
|
|
class PortType(object):
|
|
def __init__(self, name, operations):
|
|
self.name = name
|
|
self.operations = operations
|
|
|
|
def __repr__(self):
|
|
return '<%s(name=%r)>' % (
|
|
self.__class__.__name__, self.name.text)
|
|
|
|
def resolve(self, definitions):
|
|
pass
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Binding(object):
|
|
"""Base class for the various bindings (SoapBinding / HttpBinding)
|
|
|
|
Binding
|
|
|
|
|
+-> Operation
|
|
|
|
|
+-> ConcreteMessage
|
|
|
|
|
+-> AbstractMessage
|
|
|
|
"""
|
|
def __init__(self, wsdl, name, port_name):
|
|
"""Binding
|
|
|
|
:param wsdl:
|
|
:type wsdl:
|
|
:param name:
|
|
:type name: string
|
|
:param port_name:
|
|
:type port_name: string
|
|
|
|
"""
|
|
self.name = name
|
|
self.port_name = port_name
|
|
self.port_type = None
|
|
self.wsdl = wsdl
|
|
self._operations = {}
|
|
|
|
def resolve(self, definitions):
|
|
self.port_type = definitions.get('port_types', self.port_name.text)
|
|
for operation in self._operations.values():
|
|
operation.resolve(definitions)
|
|
|
|
def _operation_add(self, operation):
|
|
# XXX: operation name is not unique
|
|
self._operations[operation.name] = operation
|
|
|
|
def __str__(self):
|
|
return '%s: %s' % (self.__class__.__name__, self.name.text)
|
|
|
|
def __repr__(self):
|
|
return '<%s(name=%r, port_type=%r)>' % (
|
|
self.__class__.__name__, self.name.text, self.port_type)
|
|
|
|
def get(self, key):
|
|
try:
|
|
return self._operations[key]
|
|
except KeyError:
|
|
raise ValueError("No such operation %r on %s" % (key, self.name))
|
|
|
|
@classmethod
|
|
def match(cls, node):
|
|
raise NotImplementedError()
|
|
|
|
@classmethod
|
|
def parse(cls, definitions, xmlelement):
|
|
raise NotImplementedError()
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Operation(object):
|
|
"""Concrete operation
|
|
|
|
Contains references to the concrete messages
|
|
|
|
"""
|
|
def __init__(self, name, binding):
|
|
self.name = name
|
|
self.binding = binding
|
|
self.abstract = None
|
|
self.style = None
|
|
self.input = None
|
|
self.output = None
|
|
self.faults = {}
|
|
|
|
def resolve(self, definitions):
|
|
self.abstract = self.binding.port_type.operations[self.name]
|
|
|
|
def __repr__(self):
|
|
return '<%s(name=%r, style=%r)>' % (
|
|
self.__class__.__name__, self.name, self.style)
|
|
|
|
def __str__(self):
|
|
if not self.input:
|
|
return u'%s(missing input message)' % (self.name)
|
|
|
|
retval = u'%s(%s)' % (self.name, self.input.signature())
|
|
if self.output:
|
|
retval += u' -> %s' % (self.output.signature(as_output=True))
|
|
return retval
|
|
|
|
def create(self, *args, **kwargs):
|
|
return self.input.serialize(*args, **kwargs)
|
|
|
|
def process_reply(self, envelope):
|
|
raise NotImplementedError()
|
|
|
|
@classmethod
|
|
def parse(cls, wsdl, xmlelement, binding):
|
|
"""
|
|
<wsdl:operation name="nmtoken"> *
|
|
<-- extensibility element (2) --> *
|
|
<wsdl:input name="nmtoken"? > ?
|
|
<-- extensibility element (3) -->
|
|
</wsdl:input>
|
|
<wsdl:output name="nmtoken"? > ?
|
|
<-- extensibility element (4) --> *
|
|
</wsdl:output>
|
|
<wsdl:fault name="nmtoken"> *
|
|
<-- extensibility element (5) --> *
|
|
</wsdl:fault>
|
|
</wsdl:operation>
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Port(object):
|
|
def __init__(self, name, binding_name, xmlelement):
|
|
self.name = name
|
|
self._resolve_context = {
|
|
'binding_name': binding_name,
|
|
'xmlelement': xmlelement,
|
|
}
|
|
|
|
# Set during resolve()
|
|
self.binding = None
|
|
self.binding_options = None
|
|
|
|
def __repr__(self):
|
|
return '<%s(name=%r, binding=%r, %r)>' % (
|
|
self.__class__.__name__, self.name, self.binding,
|
|
self.binding_options)
|
|
|
|
def __str__(self):
|
|
return u'Port: %s (%s)' % (self.name, self.binding)
|
|
|
|
def resolve(self, definitions):
|
|
if self._resolve_context is None:
|
|
return
|
|
|
|
try:
|
|
self.binding = definitions.get(
|
|
'bindings', self._resolve_context['binding_name'].text)
|
|
except IndexError:
|
|
return False
|
|
|
|
if definitions.location:
|
|
force_https = definitions.location.startswith('https')
|
|
else:
|
|
force_https = False
|
|
|
|
self.binding_options = self.binding.process_service_port(
|
|
self._resolve_context['xmlelement'],
|
|
force_https)
|
|
self._resolve_context = None
|
|
return True
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Service(object):
|
|
|
|
def __init__(self, name):
|
|
self.ports = OrderedDict()
|
|
self.name = name
|
|
self._is_resolved = False
|
|
|
|
def __str__(self):
|
|
return u'Service: %s' % self.name
|
|
|
|
def __repr__(self):
|
|
return '<%s(name=%r, ports=%r)>' % (
|
|
self.__class__.__name__, self.name, self.ports)
|
|
|
|
def resolve(self, definitions):
|
|
if self._is_resolved:
|
|
return
|
|
|
|
unresolved = []
|
|
for name, port in self.ports.items():
|
|
is_resolved = port.resolve(definitions)
|
|
if not is_resolved:
|
|
unresolved.append(name)
|
|
|
|
# Remove unresolved bindings (http etc)
|
|
for name in unresolved:
|
|
del self.ports[name]
|
|
|
|
self._is_resolved = True
|
|
|
|
def add_port(self, port):
|
|
self.ports[port.name] = port
|