soap.py
changeset 0 ee24ce33ab55
equal deleted inserted replaced
-1:000000000000 0:ee24ce33ab55
       
     1 # minisoap - minimal SOAP client
       
     2 #
       
     3 # Copyright (c) 2011, 2013  Radek Brich <radek.brich@devl.cz>
       
     4 #
       
     5 # Permission is hereby granted, free of charge, to any person obtaining a copy
       
     6 # of this software and associated documentation files (the "Software"), to deal
       
     7 # in the Software without restriction, including without limitation the rights
       
     8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       
     9 # copies of the Software, and to permit persons to whom the Software is
       
    10 # furnished to do so, subject to the following conditions:
       
    11 #
       
    12 # The above copyright notice and this permission notice shall be included in
       
    13 # all copies or substantial portions of the Software.
       
    14 #
       
    15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       
    16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       
    17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       
    18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       
    19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       
    20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       
    21 # THE SOFTWARE.
       
    22 
       
    23 
       
    24 from http.client import HTTPConnection
       
    25 from urllib.parse import urlparse
       
    26 from lxml import etree
       
    27 from collections import OrderedDict
       
    28 
       
    29 debug = 0
       
    30 HTTPConnection.debuglevel = debug
       
    31 
       
    32 
       
    33 class SoapError(Exception):
       
    34     pass
       
    35 
       
    36 
       
    37 class SoapResponse(OrderedDict):
       
    38     def __init__(self, name):
       
    39         OrderedDict.__init__(self)
       
    40         self.name = name
       
    41 
       
    42     def _printout(self, out, res, level):
       
    43         indent = level * 2 * ' '
       
    44         for k in res.keys():
       
    45             if type(res[k]) is list:
       
    46                 for row in res[k]:
       
    47                     out.extend([indent, k, '\n'])
       
    48                     self._printout(out, row, level+1)
       
    49             else:
       
    50                 out.extend([indent, k, ': ', str(res[k]), '\n'])
       
    51 
       
    52     def __str__(self):
       
    53         out = [self.name, '\n']
       
    54         self._printout(out, self, 1)
       
    55         return ''.join(out).rstrip()
       
    56 
       
    57 
       
    58 class SoapClient:
       
    59     def __init__(self, url, namespace):
       
    60         self.url = url
       
    61         self.parsedurl = urlparse(url)
       
    62         self.namespace = namespace
       
    63         self.conn = HTTPConnection(self.parsedurl.netloc)
       
    64 
       
    65     def _build_soap_message(self, name, args):
       
    66         SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'
       
    67         root = etree.Element('{%s}Envelope' % SOAP, nsmap={'soap':SOAP})
       
    68         body = etree.SubElement(root, '{%s}Body' % SOAP)
       
    69         func = etree.SubElement(body, '{%s}%s' % (self.namespace, name), nsmap={None:self.namespace})
       
    70         for key in args.keys():
       
    71             param = etree.SubElement(func, key)
       
    72             param.text = str(args[key])
       
    73         if debug:
       
    74             print(etree.tostring(root, pretty_print=True).decode('utf8'))
       
    75         return root
       
    76 
       
    77     def _break_params(self, func, d):
       
    78         for param in func.iterchildren():
       
    79             if len(param) == 0:
       
    80                 d[param.tag] = param.text
       
    81             else:
       
    82                 if not param.tag in d:
       
    83                     d[param.tag] = []
       
    84                 newd = OrderedDict()
       
    85                 self._break_params(param, newd)
       
    86                 d[param.tag].append(newd)
       
    87 
       
    88     def _break_soap_message(self, root):
       
    89         if not root.tag.endswith('}Envelope'):
       
    90             raise SoapError('Reply has no Envelope.')
       
    91 
       
    92         SOAP = root.tag.split('}')[0][1:]
       
    93 
       
    94         body_list = list(root.iterchildren('{%s}Body' % SOAP))
       
    95         if len(body_list) != 1:
       
    96             raise SoapError('Bad SOAP format (%d Body elements)' % len(body_list))
       
    97         body = body_list[0]
       
    98 
       
    99         func_list =list(body.iterchildren())
       
   100         if len(func_list) != 1:
       
   101             raise SoapError('Bad SOAP format (Body has %d children, should have one)' % len(body_list))
       
   102         func = func_list[0]
       
   103 
       
   104         if debug:
       
   105             print(etree.tostring(func, pretty_print=True).decode('utf8'))
       
   106         res = SoapResponse(func.tag)
       
   107         self._break_params(func, res)
       
   108 
       
   109         return res
       
   110 
       
   111     def _serialize_xml(self, root):
       
   112         return etree.tostring(root,
       
   113             pretty_print=True,
       
   114             encoding='utf-8',
       
   115             xml_declaration=True)
       
   116 
       
   117     def _parse_xml(self, file):
       
   118         tree = etree.parse(file)
       
   119         return tree.getroot()
       
   120 
       
   121     def _send_soap(self, body):
       
   122         headers = {
       
   123             'Content-type': "text/xml;charset='utf-8'",
       
   124             'Soapaction': "''",
       
   125             'Accept': 'text/xml'}
       
   126         self.conn.request('POST', self.parsedurl.path, body, headers)
       
   127         resp = self.conn.getresponse()
       
   128         if resp.status != 200:
       
   129             raise SoapError('Server replied %d %s' % (resp.status, resp.reason))
       
   130         return resp
       
   131 
       
   132     def __getattr__(self, name):
       
   133         def fnc(**kwargs):
       
   134             root = self._build_soap_message(name, kwargs)
       
   135             body = self._serialize_xml(root)
       
   136             resp = self._send_soap(body)
       
   137             root = self._parse_xml(resp)
       
   138             return self._break_soap_message(root)
       
   139 
       
   140         return fnc
       
   141 
       
   142 
       
   143 if __name__ == '__main__':
       
   144     service = SoapClient('http://10.0.0.1:34567/', namespace='http://devl.cz/')
       
   145     res = service.my_test_request()
       
   146     print(res)
       
   147