Skip to content

Commit

Permalink
ability to use custom soap headers.
Browse files Browse the repository at this point in the history
The code was written mostly by numion <[email protected]> and ported by Thomas Recouvreux.
(Felix Schwarz: Licensed under the original soapbox BSD license as per comment in private email)
  • Loading branch information
Thomas Recouvreux authored and FelixSchwarz committed Mar 6, 2014
1 parent 72e5010 commit e04a585
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 39 deletions.
16 changes: 8 additions & 8 deletions soapbox/py2wsdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,21 @@ def build_portTypes(wsdl, definitions, service):
def build_messages(wsdl, definitions, service):
for method in service.methods.values():
inputMessage = wsdl.Message(name=method.operationName + 'Input')
inputMessage.part = wsdl.Part()
inputMessage.part.name = 'body'
part = wsdl.Part(name='body')
if isinstance(method.input, basestring):
inputMessage.part.element = 'sns:' + method.input
part.element = 'sns:' + method.input
else:
inputMessage.part.type = 'sns:' + uncapitalize(method.input.__name__)
part.type = 'sns:' + uncapitalize(method.input.__name__)
inputMessage.parts = [part]
definitions.messages.append(inputMessage)

outputMessage = wsdl.Message(name=method.operationName + 'Output')
outputMessage.part = wsdl.Part()
outputMessage.part.name = 'body'
part = wsdl.Part(name='body')
if isinstance(method.output, basestring):
outputMessage.part.element = 'sns:' + method.output
part.element = 'sns:' + method.output
else:
outputMessage.part.type = 'sns:' + uncapitalize(method.output.__name__)
part.type = 'sns:' + uncapitalize(method.output.__name__)
outputMessage.parts = [part]
definitions.messages.append(outputMessage)


Expand Down
20 changes: 8 additions & 12 deletions soapbox/soap.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,33 +140,29 @@ def _handle_response(self, method, response, content):
logger.error(error)
raise SOAPError(error, faultcode=code, faultstring=message, faultactor=actor)

message = envelope.Body.content()
self.response_header = None
if envelope.Header and method.outputHeader:
self.response_header = envelope.Header.parse_as(method.outputHeader)

if isinstance(method.output, basestring):
element = self.SERVICE.schema.get_element_by_name(method.output)
_type = element._type
_type = element._type.__class__
else:
_type = method.output

if self.SERVICE.schema:
return _type.parsexml(message, self.SERVICE.schema)
else:
return _type.parsexml(message)
return envelope.Body.parse_as(_type)

def call(self, operationName, parameter):
def call(self, operationName, parameter, header=None):
'''
:raises: lxml.etree.XMLSyntaxError -- validation problems.
'''
SOAP = self.SERVICE.version
method = self.SERVICE.get_method(operationName)

if isinstance(method.input, basestring):
tagname = method.input
else:
tagname = uncapitalize(parameter.__class__.__name__)
parameter.xml(tagname,
schema=self.SERVICE.schema,
namespace=parameter.SCHEMA.targetNamespace,
elementFormDefault=parameter.SCHEMA.elementFormDefault)

disable_validation = not os.path.exists(settings.CA_CERTIFICATE_FILE)
http = httplib2.Http(
Expand All @@ -179,7 +175,7 @@ def call(self, operationName, parameter):

method = self.SERVICE.get_method(operationName)
headers = SOAP.build_http_request_headers(method.soapAction)
envelope = SOAP.Envelope.response(tagname, parameter)
envelope = SOAP.Envelope.response(tagname, parameter, header=header)

logger.info('Request \'%s\'...' % self.location)
logger.debug('Request Headers:\n\n%s\n' % headers)
Expand Down
13 changes: 12 additions & 1 deletion soapbox/soap11.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ class Header(xsd.ComplexType):
'''
SOAP Envelope Header.
'''
pass
def accept(self, value):
return value

def parse_as(self, ContentType):
return ContentType.parse_xmlelement(self._xmlelement)

def render(self, parent, instance, namespace=None, elementFormDefault=None):
return super(Header, self).render(parent, instance,
namespace=instance.SCHEMA.targetNamespace, elementFormDefault=elementFormDefault)


class Fault(xsd.ComplexType):
Expand All @@ -62,6 +70,9 @@ class Body(xsd.ComplexType):
message = xsd.ClassNamedElement(xsd.NamedType, minOccurs=0)
Fault = xsd.Element(Fault, minOccurs=0)

def parse_as(self, ContentType):
return ContentType.parse_xmlelement(self._xmlelement[0])

def parse_as(self, ContentType):
return ContentType.parse_xmlelement(self._xmlelement[0])

Expand Down
10 changes: 9 additions & 1 deletion soapbox/soap12.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ class Header(xsd.ComplexType):
'''
SOAP Envelope Header.
'''
pass
def accept(self, value):
return value

def parse_as(self, ContentType):
return ContentType.parse_xmlelement(self._xmlelement)

def render(self, parent, instance, namespace=None, elementFormDefault=None):
return super(Header, self).render(parent, instance,
namespace=instance.SCHEMA.targetNamespace, elementFormDefault=elementFormDefault)


class Code(xsd.ComplexType):
Expand Down
52 changes: 44 additions & 8 deletions soapbox/templates/wsdl
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,60 @@ def {{ operation.name }}(request, {{ inputMessage.part.element|remove_namespace
# Methods
{# [blank line] #}
{# [blank line] #}
{% for operation in binding.operations -%}
{%- for operation in binding.operations -%}
{%- set XXX = operation.set_definition(definitions) -%}
{%- set inputMessage = operation.get_InputMessage() -%}
{%- set inputMessageHeaders = operation.get_InputMessageHeaders() -%}
{%- set outputMessage = operation.get_OutputMessage() -%}
{%- set outputMessageHeaders = operation.get_OutputMessageHeaders() -%}
{%- if inputMessageHeaders %}
class {{ inputMessage.name }}Headers(xsd.ComplexType):
SCHEMA = Schema_{{ schema_name(definitions.types.schema.targetNamespace) }}
{%- for part in inputMessageHeaders -%}
{%- if part.element %}
{{ part.name }} = SCHEMA.get_element_by_name('{{ part.element|remove_namespace }}')
{%- else -%}
{{ part.name }} = xsd.Element({{ part.type|remove_namespace|capitalize }}())
{%- endif -%}
{%- endfor %}
{# [blank line] #}
{%- endif -%}
{%- if outputMessageHeaders %}
class {{ outputMessage.name }}Headers(xsd.ComplexType):
SCHEMA = Schema_{{ schema_name(definitions.types.schema.targetNamespace) }}
{%- for part in outputMessageHeaders -%}
{%- if part.element %}
{{ part.name }} = SCHEMA.get_element_by_name('{{ part.element|remove_namespace }}')
{%- else -%}
{{ part.name }} = xsd.Element({{ part.type|remove_namespace|capitalize }}())
{%- endif -%}
{%- endfor %}
{# [blank line] #}
{%- endif %}
{{ operation.name }}_method = xsd.Method(
{%- if is_server %}function={{ operation.name }},{% endif %}
soapAction='{{ operation.operation.soapAction }}',
{%- if inputMessage.parts -%}
{%- if inputMessage.part.element %}
input='{{ inputMessage.part.element|remove_namespace }}',
inputPartName='{{ inputMessage.part.name }}',
{%- else %}
{%- else -%}
input={{ inputMessage.part.type|remove_namespace|capitalize }},
{%- endif %}
{%- if inputMessage.part.element %}
{%- endif -%}
{%- endif -%}
{%- if inputMessageHeaders %}
inputHeader={{ inputMessage.name }}Headers,
{%- endif -%}
{%- if outputMessage.parts -%}
{%- if outputMessage.part.element %}
output='{{ outputMessage.part.element|remove_namespace }}',
outputPartName='{{ outputMessage.part.name }}',
{%- else %}
input={{ outputMessage.part.type|remove_namespace|capitalize }},
{%- else -%}
output={{ outputMessage.part.type|remove_namespace|capitalize }},
{%- endif -%}
{%- endif -%}
{%- if outputMessageHeaders %}
outputHeader={{ outputMessage.name }}Headers,
{%- endif %}
operationName='{{ operation.name }}',
{%- if operation.operation.style %}
Expand Down Expand Up @@ -112,8 +148,8 @@ class {{ port.name }}ServiceStub(soap.Stub):
HOST = '{{ port.address.location|url_component('netloc') }}'
{% for operation in binding.operations %}
{%- set inputMessage = operation.get_InputMessage() %}
def {{ operation.name }}(self, {{ inputMessage.part.element|remove_namespace }}):
return self.call('{{ operation.name}}', {{ inputMessage.part.element|remove_namespace }})
def {{ operation.name }}(self, {{ inputMessage.part.element|remove_namespace }}, header=None):
return self.call('{{ operation.name}}', {{ inputMessage.part.element|remove_namespace }}, header=header)
{% endfor %}
{%- endif %}
{%- endfor %}{# ports #}
Expand Down
37 changes: 36 additions & 1 deletion soapbox/wsdl11.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class SOAP_Operation(xsd.ComplexType):
style = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)


class SOAP_Header(xsd.ComplexType):
message = xsd.Attribute(xsd.String)
part = xsd.Attribute(xsd.String)
use = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)


class SOAP_Body(xsd.ComplexType):
ELEMENT_FORM_DEFAULT = xsd.ElementFormDefault.QUALIFIED
use = xsd.Attribute(xsd.String)
Expand All @@ -39,14 +45,21 @@ class Part(xsd.ComplexType):

class Message(xsd.ComplexType):
name = xsd.Attribute(xsd.String)
part = xsd.Element(Part)
parts = xsd.ListElement(Part, tagname='part', minOccurs=1)

@property
def part(self):
if len(self.parts) != 1:
raise ValueError('expected exactly one part', self.name, self.parts)
return self.parts[0]


# WSDL 1.1 SOAP 1.1

class Input(xsd.ComplexType):
message = xsd.Attribute(xsd.String, use=xsd.Use.OPTIONAL)
body = xsd.Element(SOAP_Body, namespace=ns.wsdl_soap, minOccurs=0)
headers = xsd.ListElement(SOAP_Header, 'header', minOccurs=0)


class Operation(xsd.ComplexType):
Expand Down Expand Up @@ -86,6 +99,28 @@ def get_OutputMessage(self):
messageName = portTypeOperation.output.message
return wsdl.get_by_name(self.definition.messages, messageName)

def get_InputMessageHeaders(self):
operation = wsdl.get_by_name(self.binding.operations, self.name)
return self._get_parts(operation.input.headers)

def get_OutputMessage(self):
portType = self.binding.getPortType()
portTypeOperation = wsdl.get_by_name(portType.operations, self.name)
messageName = portTypeOperation.output.message
return wsdl.get_by_name(self.definition.messages, messageName)

def get_OutputMessageHeaders(self):
operation = wsdl.get_by_name(self.binding.operations, self.name)
return self._get_parts(operation.output.headers)

def _get_parts(self, references):
parts = []
for ref in references:
message = wsdl.get_by_name(self.definition.messages, ref.message)
parts.append(wsdl.get_by_name(message.parts, ref.part))
return parts



class PortType(xsd.ComplexType):
name = xsd.Attribute(xsd.String)
Expand Down
5 changes: 5 additions & 0 deletions soapbox/wsdl12.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class SOAP_Operation(wsdl11.SOAP_Operation):
pass


class SOAP_Header(wsdl11.SOAP_Header):
pass


class SOAP_Body(wsdl11.SOAP_Body):
pass

Expand All @@ -39,6 +43,7 @@ class Message(wsdl11.Message):

class Input(wsdl11.Input):
body = xsd.Element(SOAP_Body, namespace=ns.wsdl_soap12, minOccurs=0)
headers = xsd.ListElement(SOAP_Header, 'header', minOccurs=0)


class Operation(wsdl11.Operation):
Expand Down
12 changes: 8 additions & 4 deletions soapbox/xsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,9 @@ class ListElement(Element):
be in plural form, and tag usually is not.
'''

def __init__(self, clazz, tagname, minOccurs=None, maxOccurs=None, nillable=False):
super(ListElement, self).__init__(clazz, tagname=tagname, nillable=nillable)
def __init__(self, clazz, tagname, minOccurs=None, maxOccurs=None, nillable=False, namespace=None):
super(ListElement, self).__init__(
clazz, tagname=tagname, nillable=nillable, namespace=namespace)
self._maxOccurs = maxOccurs
self._minOccurs = minOccurs

Expand Down Expand Up @@ -1062,8 +1063,9 @@ class Method(object):
messages.
'''

def __init__(self, operationName, soapAction, input, output, function=None,
inputPartName="body", outputPartName="body", style=CallStyle.DOCUMENT):
def __init__(self, operationName, soapAction, input=None, output=None, function=None,
inputPartName="body", outputPartName="body",
inputHeader=None, outputHeader=None, style=CallStyle.DOCUMENT):
'''
:param function: The function that should be called. Required only for
server implementations.
Expand All @@ -1075,6 +1077,8 @@ def __init__(self, operationName, soapAction, input, output, function=None,
self.function = function
self.inputPartName = inputPartName
self.outputPartName = outputPartName
self.inputHeader = inputHeader
self.outputHeader = outputHeader
self.style = style


Expand Down
3 changes: 1 addition & 2 deletions tests/code_generation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
<wsdl:part name="body" element="fds:ops"/>
</wsdl:message>
<wsdl:message name="PutOpsOutput">
<part name="body" element="fds:status"/>
<wsdl:part name="body" element="fds:status"/>
</wsdl:message>
<wsdl:portType name="PutOpsPortType">
<wsdl:operation name="PutOps">
Expand Down Expand Up @@ -277,6 +277,5 @@ def check_reparse_wsdl(self, base, target):
self.assertEqual(sorted(m), sorted(base))



if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit e04a585

Please sign in to comment.