source: trunk/suds/wsdl.py @ 358

Revision 358, 24.8 KB checked in by jortel, 5 years ago (diff)

Add support for <wsdl:import/> of schema files into the wsdl root <definitions/>. This clearly violates the WSDL 1.1 and 2.0 specifications (including the wsdl schema: http://www.w3.org/2007/06/wsdl/wsdl20.xsd ) but since ( http://www.w3.org/TR/wsdl#_style ) show doing this as an example, then it must be supported. Bump trunk to beta (R358-20081029)

Line 
1# This program is free software; you can redistribute it and/or modify
2# it under the terms of the (LGPL) GNU Lesser General Public License as
3# published by the Free Software Foundation; either version 3 of the
4# License, or (at your option) any later version.
5#
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9# GNU Library Lesser General Public License for more details at
10# ( http://www.gnu.org/licenses/lgpl.html ).
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program; if not, write to the Free Software
14# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15# written by: Jeff Ortel ( jortel@redhat.com )
16
17"""
18The I{wsdl} module provides an objectification of the WSDL.
19The primary class is I{Definitions} as it represends the root element
20found in the document.
21"""
22
23from logging import getLogger
24from suds import *
25from suds.sax import splitPrefix
26from suds.sax.parser import Parser
27from suds.sax.element import Element
28from suds.bindings.document import Document
29from suds.bindings.rpc import RPC
30from suds.xsd import qualify, Namespace
31from suds.xsd.schema import Schema, SchemaCollection
32from suds.sudsobject import Object
33from suds.sudsobject import Factory as SFactory
34from urlparse import urljoin
35
36log = getLogger(__name__)
37
38wsdlns = (None, "http://schemas.xmlsoap.org/wsdl/")
39soapns = (None, 'http://schemas.xmlsoap.org/wsdl/soap/')
40soap12ns = (None, 'http://schemas.xmlsoap.org/wsdl/soap12/')
41
42
43class Factory:
44    """
45    Simple WSDL object factory.
46    @cvar tags: Dictionary of tag->constructor mappings.
47    @type tags: dict
48    """
49
50    tags =\
51    {
52        'import' : lambda x,y: Import(x,y), 
53        'types' : lambda x,y: Types(x,y), 
54        'message' : lambda x,y: Message(x,y), 
55        'portType' : lambda x,y: PortType(x,y),
56        'binding' : lambda x,y: Binding(x,y),
57        'service' : lambda x,y: Service(x,y),
58    }
59   
60    @classmethod
61    def create(cls, root, definitions):
62        """
63        Create an object based on the root tag name.
64        @param root: An XML root element.
65        @type root: L{Element}
66        @param definitions: A definitions object.
67        @type definitions: L{Definitions}
68        @return: The created object.
69        @rtype: L{WObject}
70        """
71        fn = cls.tags.get(root.name)
72        if fn is not None:
73            return fn(root, definitions)
74        else:
75            return None
76
77
78class WObject(Object):
79    """
80    Base object for wsdl types.
81    @ivar root: The XML I{root} element.
82    @type root: L{Element}
83    """
84   
85    def __init__(self, root, definitions=None):
86        """
87        @param root: An XML root element.
88        @type root: L{Element}
89        @param definitions: A definitions object.
90        @type definitions: L{Definitions}
91        """
92        Object.__init__(self)
93        self.root = root
94        pmd = SFactory.metadata()
95        pmd.excludes = ['root']
96        pmd.wrappers = dict(qname=lambda x: repr(x))
97        self.__metadata__.__print__ = pmd
98       
99    def resolve(self, definitions):
100        """
101        Resolve named references to other WSDL objects.
102        @param definitions: A definitions object.
103        @type definitions: L{Definitions}
104        """
105        pass
106
107       
108class NamedObject(WObject):
109    """
110    A B{named} WSDL object.
111    @ivar name: The name of the object.
112    @type name: str
113    @ivar qname: The I{qualified} name of the object.
114    @type qname: (name, I{namespace-uri}).
115    """
116
117    def __init__(self, root, definitions):
118        """
119        @param root: An XML root element.
120        @type root: L{Element}
121        @param definitions: A definitions object.
122        @type definitions: L{Definitions}
123        """
124        WObject.__init__(self, root, definitions)
125        self.name = root.get('name')
126        self.qname = (self.name, definitions.tns[1])
127        pmd = self.__metadata__.__print__
128        pmd.wrappers['qname'] = lambda x: repr(x)
129
130
131class Definitions(WObject):
132    """
133    Represents the I{root} container of the WSDL objects as defined
134    by <wsdl:definitions/>
135    @ivar id: The object id.
136    @type id: str
137    @ivar url: The URL used to load the object.
138    @type url: str
139    @ivar tns: The target namespace for the WSDL.
140    @type tns: str
141    @ivar schema: The collective WSDL schema object.
142    @type schema: L{SchemaCollection}
143    @ivar children: The raw list of child objects.
144    @type children: [L{WObject},...]
145    @ivar imports: The list of L{Import} children.
146    @type imports: [L{Import},...]
147    @ivar messages: The dictionary of L{Message} children key'd by I{qname}
148    @type messages: [L{Message},...]
149    @ivar port_types: The dictionary of L{PortType} children key'd by I{qname}
150    @type port_types: [L{PortType},...]
151    @ivar bindings: The dictionary of L{Binding} children key'd by I{qname}
152    @type bindings: [L{Binding},...]
153    @ivar service: The service object.
154    @type service: L{Service}
155    """
156   
157    Tag = 'definitions'
158
159    def __init__(self, url, opener=None):
160        """
161        @param url: A URL to the WSDL.
162        @type url: str
163        @param opener: A urllib2 opener (may be None).
164        @type opener: urllib2.Opener
165        """
166        log.debug('reading wsdl at: %s ...', url)
167        p = Parser(opener)
168        root = p.parse(url=url).root()
169        WObject.__init__(self, root)
170        self.id = objid(self)
171        self.url = url
172        self.tns = self.mktns(root)
173        self.types = []
174        self.schema = None
175        self.children = []
176        self.imports = []
177        self.messages = {}
178        self.port_types = {}
179        self.bindings = {}
180        self.service = None
181        self.add_children(self.root)
182        self.children.sort()
183        pmd = self.__metadata__.__print__
184        pmd.excludes.append('children')
185        pmd.excludes.append('wsdl')
186        pmd.wrappers['schema'] = lambda x: repr(x)
187        self.open_imports(opener)
188        self.resolve()
189        self.build_schema()
190        if self.service is not None:
191            self.add_methods()
192        log.debug("wsdl at '%s' loaded:\n%s", url, self)
193       
194    def mktns(self, root):
195        """ Get/create the target namespace """
196        tns = root.get('targetNamespace')
197        prefix = root.findPrefix(tns)
198        if prefix is None:
199            log.debug('warning: tns (%s), not mapped to prefix', tns)
200            prefix = 'tns'
201        return (prefix, tns)
202       
203    def add_children(self, root):
204        """ Add child objects using the factory """
205        for c in root.getChildren(ns=wsdlns):
206            child = Factory.create(c, self)
207            if child is None: continue
208            self.children.append(child)
209            if isinstance(child, Import):
210                self.imports.append(child)
211                continue
212            if isinstance(child, Types):
213                self.types.append(child)
214                continue
215            if isinstance(child, Message):
216                self.messages[child.qname] = child
217                continue
218            if isinstance(child, PortType):
219                self.port_types[child.qname] = child
220                continue
221            if isinstance(child, Binding):
222                self.bindings[child.qname] = child
223                continue
224            if isinstance(child, Service):
225                self.service = child
226                continue
227               
228    def open_imports(self, opener):
229        """ Import the I{imported} WSDLs. """
230        for imp in self.imports:
231            base = self.url
232            imp.load(self, opener)
233               
234    def resolve(self):
235        """ Tell all children to resolve themselves """
236        for c in self.children:
237            c.resolve(self)
238               
239    def build_schema(self):
240        """ Process L{Types} objects and create the schema collection """
241        container = SchemaCollection(self)
242        for t in self.types:
243            for s in t.schemas():
244                entry = (s, self)
245                container.add(entry)
246        if not len(container): # empty
247            s = Element.buildPath(self.root, 'types/schema')
248            entry = (s, self)
249            container.add(entry)
250        self.schema = container.load()
251               
252    def add_methods(self):
253        bindings = {
254            'document/literal' : Document(self),
255            'rpc/literal' : RPC(self),
256            'rpc/encoded' : RPC(self).use_encoded()
257        }
258        for p in self.service.ports:
259            binding = p.binding
260            ptype = p.binding.type
261            operations = p.binding.type.operations.values()
262            for name in [op.name for op in operations]:
263                m = SFactory.object('Method')
264                m.name = name
265                m.location = p.location
266                m.binding = SFactory.object('binding')
267                op = binding.operation(name)
268                m.soap = op.soap
269                key = '/'.join((op.soap.style, op.soap.input.body.use))
270                m.binding.input = bindings.get(key)
271                key = '/'.join((op.soap.style, op.soap.output.body.use))
272                m.binding.output = bindings.get(key)
273                op = ptype.operation(name)
274                m.message = SFactory.object('message')
275                m.message.input = op.input
276                m.message.output = op.output
277                m.qname = ':'.join((p.name, name))
278                self.service.methods[m.name] = m
279                self.service.methods[m.qname] = m
280
281
282class Import(WObject):
283    """
284    Represents the <wsdl:import/>.
285    @ivar location: The value of the I{location} attribute.
286    @type location: str
287    @ivar ns: The value of the I{namespace} attribute.
288    @type ns: str
289    @ivar imported: The imported object.
290    @type imported: L{Definitions}
291    """
292   
293    def __init__(self, root, definitions):
294        """
295        @param root: An XML root element.
296        @type root: L{Element}
297        @param definitions: A definitions object.
298        @type definitions: L{Definitions}
299        """
300        WObject.__init__(self, root, definitions)
301        self.location = root.get('location')
302        self.ns = root.get('namespace')
303        self.imported = None
304        pmd = self.__metadata__.__print__
305        pmd.wrappers['imported'] = ( lambda x: x.id )
306       
307    def load(self, definitions, opener):
308        """ Load the object by opening the URL """
309        url = self.location
310        log.debug('importing (%s)', url)
311        if '://' not in url:
312            url = urljoin(definitions.url, url)
313        d = Definitions(url, opener)
314        if d.root.match(Definitions.Tag, wsdlns):
315            self.import_definitions(definitions, d)
316            return
317        if d.root.match(Schema.Tag, Namespace.xsdns):
318            self.import_schema(definitions, d)
319            return
320        raise Exception('document at "%s" is unknown' % url)
321   
322    def import_definitions(self, definitions, d):
323        """ import/merge wsdl definitions """
324        definitions.types += d.types
325        definitions.messages.update(d.messages)
326        definitions.port_types.update(d.port_types)
327        definitions.bindings.update(d.bindings)
328        self.imported = d
329        log.debug('imported (WSDL):\n%s', d)
330       
331    def import_schema(self, definitions, d):
332        """ import schema as <types/> content """
333        if not len(definitions.types):
334            types = Types.create(definitions)
335            definitions.types.append(types)
336        else:
337            types = definitions.types[:-1]
338        types.root.append(d.root)
339        log.debug('imported (XSD):\n%s', d.root)
340   
341    def __gt__(self, other):
342        return False
343       
344
345class Types(WObject):
346    """
347    Represents <types><schema/></types>.
348    """
349   
350    @classmethod
351    def create(cls, definitions):
352        root = Element('types', ns=wsdlns)
353        definitions.root.insert(root)
354        return Types(root, definitions)
355
356    def __init__(self, root, definitions):
357        """
358        @param root: An XML root element.
359        @type root: L{Element}
360        @param definitions: A definitions object.
361        @type definitions: L{Definitions}
362        """
363        WObject.__init__(self, root, definitions)
364        self.definitions = definitions
365       
366    def schemas(self):
367        return self.root.getChildren('schema', Namespace.xsdns)
368       
369    def __gt__(self, other):
370        return isinstance(other, Import)
371   
372
373class Part(NamedObject):
374    """
375    Represents <message><part/></message>.
376    @ivar element: The value of the {element} attribute.
377        Stored as a I{qref} as converted by L{suds.xsd.qualify}.
378    @type element: str
379    @ivar type: The value of the {type} attribute.
380        Stored as a I{qref} as converted by L{suds.xsd.qualify}.
381    @type type: str
382    """
383
384    def __init__(self, root, definitions):
385        """
386        @param root: An XML root element.
387        @type root: L{Element}
388        @param definitions: A definitions object.
389        @type definitions: L{Definitions}
390        """
391        NamedObject.__init__(self, root, definitions)
392        pmd = SFactory.metadata()
393        pmd.wrappers = \
394            dict(element=lambda x: repr(x), type=lambda x: repr(x))
395        self.__metadata__.__print__ = pmd
396        tns = definitions.tns
397        self.element = self.__getref('element', tns)
398        self.type = self.__getref('type', tns)
399       
400    def __getref(self, a, tns):
401        """ Get the qualified value of attribute named 'a'."""
402        s = self.root.get(a)
403        if s is None:
404            return s
405        else:
406            return qualify(s, self.root, tns) 
407
408
409class Message(NamedObject):
410    """
411    Represents <message/>.
412    @ivar parts: A list of message parts.
413    @type parts: [I{Part},...]
414    """
415
416    def __init__(self, root, definitions):
417        """
418        @param root: An XML root element.
419        @type root: L{Element}
420        @param definitions: A definitions object.
421        @type definitions: L{Definitions}
422        """
423        NamedObject.__init__(self, root, definitions)
424        self.parts = []
425        for p in root.getChildren('part'):
426            part = Part(p, definitions)
427            self.parts.append(part)
428           
429    def __gt__(self, other):
430        return isinstance(other, (Import, Types))
431
432
433class PortType(NamedObject):
434    """
435    Represents <portType/>.
436    @ivar operations: A list of contained operations.
437    @type operations: list
438    """
439
440    def __init__(self, root, definitions):
441        """
442        @param root: An XML root element.
443        @type root: L{Element}
444        @param definitions: A definitions object.
445        @type definitions: L{Definitions}
446        """
447        NamedObject.__init__(self, root, definitions)
448        self.operations = {}
449        for c in root.getChildren('operation'):
450            op = SFactory.object('Operation')
451            op.name = c.get('name')
452            op.tns = definitions.tns
453            input = c.getChild('input')
454            op.input = input.get('message')
455            output = c.getChild('output', default=input)
456            if output is None:
457                op.output = None
458            else:
459                op.output = output.get('message')
460            self.operations[op.name] = op
461           
462    def resolve(self, definitions):
463        """
464        Resolve named references to other WSDL objects.
465        @param definitions: A definitions object.
466        @type definitions: L{Definitions}
467        """
468        for op in self.operations.values():
469            qref = qualify(op.input, self.root, wsdlns)
470            msg = definitions.messages.get(qref)
471            if msg is None:
472                raise Exception("msg '%s', not-found" % op.input)
473            else:
474                op.input = msg
475            qref = qualify(op.output, self.root, wsdlns)
476            msg = definitions.messages.get(qref)
477            if msg is None:
478                raise Exception("msg '%s', not-found" % op.output)
479            else:
480                op.output = msg
481               
482    def operation(self, name):
483        """
484        Shortcut used to get a contained operation by name.
485        @param name: An operation name.
486        @type name: str
487        @return: The named operation.
488        @rtype: Operation
489        @raise L{MethodNotFound}: When not found.
490        """
491        try:
492            return self.operations[name]
493        except Exception, e:
494            raise MethodNotFound(name)
495               
496    def __gt__(self, other):
497        return isinstance(other, (Import, Types, Message))
498
499
500class Binding(NamedObject):
501    """
502    Represents <binding/>
503    @ivar operations: A list of contained operations.
504    @type operations: list
505    """
506
507    def __init__(self, root, definitions):
508        """
509        @param root: An XML root element.
510        @type root: L{Element}
511        @param definitions: A definitions object.
512        @type definitions: L{Definitions}
513        """
514        NamedObject.__init__(self, root, definitions)
515        self.operations = {}
516        self.type = root.get('type')
517        sr = self.soaproot()
518        if sr is None:
519            self.soap = None
520            log.debug('binding: "%s" not a soap binding', self.name)
521            return
522        soap = SFactory.object('soap')
523        self.soap = soap
524        self.soap.style = sr.get('style', default='document')
525        self.add_operations(self.root, definitions)
526       
527    def soaproot(self):
528        """ get the soap:binding """
529        for ns in (soapns, soap12ns):
530            sr =  self.root.getChild('binding', ns=ns)
531            if sr is not None:
532                return sr
533        return None
534       
535    def add_operations(self, root, definitions):
536        """ Add <operation/> children """
537        for c in root.getChildren('operation'):
538            op = SFactory.object('Operation')
539            op.name = c.get('name')
540            sr = c.getChild('operation')
541            soap = SFactory.object('soap')
542            soap.action = '"%s"' % sr.get('soapAction', default='')
543            soap.style = sr.get('style', default=self.soap.style)
544            soap.input = SFactory.object('Input')
545            soap.input.body = SFactory.object('Body')
546            soap.input.header = None
547            soap.output = SFactory.object('Output')
548            soap.output.body = SFactory.object('Body')
549            soap.output.header = None
550            op.soap = soap
551            input = c.getChild('input')
552            body = input.getChild('body')
553            self.body(definitions, soap.input.body, body)
554            header = input.getChild('header')
555            self.header(definitions, soap.input, header)
556            output = c.getChild('output')
557            body = output.getChild('body')
558            self.body(definitions, soap.output.body, output)
559            header = output.getChild('header')
560            self.header(definitions, soap.output, header)
561            self.operations[op.name] = op
562           
563    def body(self, definitions, body, root):
564        """ add the input/output body properties """
565        if root is None:
566            body.use = 'literal'
567            body.namespace = definitions.tns
568            return
569        body.use = root.get('use', default='literal')
570        ns = root.get('namespace')
571        if ns is None:
572            body.namespace = definitions.tns
573        else:
574            prefix = root.findPrefix(ns)
575            body.namespace = (prefix, ns)
576           
577    def header(self, definitions, parent, root):
578        """ add the input/output header properties """
579        if root is None:
580            return
581        header = SFactory.object('Header')
582        parent.header = header
583        header.use = root.get('use', default='literal')
584        ns = root.get('namespace')
585        if ns is None:
586            header.namespace = definitions.tns
587        else:
588            prefix = root.findPrefix(ns)
589            header.namespace = (prefix, ns)
590        msg = root.get('message')
591        if msg is not None:
592            header.message = msg
593        part = root.get('part')
594        if part is not None:
595            header.part = part
596           
597    def resolve(self, definitions):
598        """
599        Resolve named references to other WSDL objects.
600        @param definitions: A definitions object.
601        @type definitions: L{Definitions}
602        """
603        self.resolveport(definitions)
604        self.resolveheaders(definitions)
605       
606    def resolveport(self, definitions):
607        """
608        Resolve port_type reference.
609        @param definitions: A definitions object.
610        @type definitions: L{Definitions}
611        """
612        ref = qualify(self.type, self.root, wsdlns)
613        port_type = definitions.port_types.get(ref)
614        if port_type is None:
615            raise Exception("portType '%s', not-found" % self.type)
616        else:
617            self.type = port_type
618           
619    def resolveheaders(self, definitions):
620        """
621        Resolve soap header I{message} references.
622        @param definitions: A definitions object.
623        @type definitions: L{Definitions}
624        """
625        for op in self.operations.values():
626            soap = op.soap
627            for header in (soap.input.header, soap.output.header):
628                if header is None:
629                    continue
630                mn = header.message
631                ref = qualify(mn, self.root, wsdlns)
632                message = definitions.messages.get(ref)
633                if message is None:
634                    raise Exception("message'%s', not-found" % mn)
635                if header.part is None:
636                    header.message = message
637                    continue
638                header.message = SFactory.object('Message')
639                header.message.name = message.name
640                header.message.qname = message.qname
641                header.message.parts = []
642                for p in message.parts:
643                    if p.name == header.part:
644                        header.message.parts.append(p)
645                        break
646           
647    def operation(self, name):
648        """
649        Shortcut used to get a contained operation by name.
650        @param name: An operation name.
651        @type name: str
652        @return: The named operation.
653        @rtype: Operation
654        @raise L{MethodNotFound}: When not found.
655        """
656        try:
657            return self.operations[name]
658        except:
659            raise MethodNotFound(name)
660           
661    def __gt__(self, other):
662        return ( not isinstance(other, Service) )
663
664
665class Service(NamedObject):
666    """
667    Represents <service/>.
668    @ivar port: The contained ports.
669    @type port: [Port,..]
670    @ivar methods: The contained methods for all ports.
671    @type methods: [Method,..]
672    """
673   
674    def __init__(self, root, definitions):
675        """
676        @param root: An XML root element.
677        @type root: L{Element}
678        @param definitions: A definitions object.
679        @type definitions: L{Definitions}
680        """
681        NamedObject.__init__(self, root, definitions)
682        self.ports = []
683        self.methods = {}
684        for p in root.getChildren('port'):
685            port = SFactory.object('Port')
686            port.name = p.get('name')
687            port.binding = p.get('binding')
688            address = p.getChild('address')
689            port.location = address.get('location').encode('utf-8')
690            self.ports.append(port)
691           
692    def port(self, name):
693        """
694        Locate a port by name.
695        @param name: A port name.
696        @type name: str
697        @return: The port object.
698        @rtype: I{Port}
699        """
700        for p in self.ports:
701            if p.name == name:
702                return p
703        return None
704   
705    def method(self, name):
706        """
707        Get a method defined an one of the portTypes by name.
708        @param name: A method name.
709        @type name: str
710        @return: The requested method object.
711        @rtype: I{Method}
712        """
713        return self.methods.get(name)
714   
715    def setlocation(self, url, names=None):
716        """
717        Override the invocation location (url) for service method.
718        @param url: A url location.
719        @type url: A url.
720        @param names:  A list of method names.  None=ALL
721        @type names: [str,..]
722        """
723        for m in self.methods.values():
724            if names is None or m.name in names:
725                m.location = url
726       
727    def resolve(self, definitions):
728        """
729        Resolve named references to other WSDL objects.
730        Ports without soap bindings are discarded.
731        @param definitions: A definitions object.
732        @type definitions: L{Definitions}
733        """
734        filtered = []
735        for p in self.ports:
736            ref = qualify(p.binding, self.root, wsdlns)
737            binding = definitions.bindings.get(ref)
738            if binding is None:
739                raise Exception("binding '%s', not-found" % p.binding)
740            if binding.soap is None:
741                log.debug('binding "%s" - not a soap, discarded', binding.name)
742                continue
743            p.binding = binding
744            filtered.append(p)
745        self.ports = filtered
746       
747    def __gt__(self, other):
748        return True
Note: See TracBrowser for help on using the repository browser.