1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 The I{2nd generation} service proxy provides access to web services.
19 See I{README.txt}
20 """
21
22 import suds
23 import suds.metrics as metrics
24 from cookielib import CookieJar
25 from suds import *
26 from suds.reader import DefinitionsReader
27 from suds.transport import TransportError, Request
28 from suds.transport.https import HttpAuthenticated
29 from suds.servicedefinition import ServiceDefinition
30 from suds import sudsobject
31 from sudsobject import Factory as InstFactory
32 from sudsobject import Object
33 from suds.resolver import PathResolver
34 from suds.builder import Builder
35 from suds.wsdl import Definitions
36 from suds.cache import ObjectCache
37 from suds.sax.document import Document
38 from suds.sax.parser import Parser
39 from suds.options import Options
40 from suds.properties import Unskin
41 from urlparse import urlparse
42 from copy import deepcopy
43 from suds.plugin import PluginContainer
44 from logging import getLogger
45
46 log = getLogger(__name__)
50 """
51 A lightweight web services client.
52 I{(2nd generation)} API.
53 @ivar wsdl: The WSDL object.
54 @type wsdl:L{Definitions}
55 @ivar service: The service proxy used to invoke operations.
56 @type service: L{Service}
57 @ivar factory: The factory used to create objects.
58 @type factory: L{Factory}
59 @ivar sd: The service definition
60 @type sd: L{ServiceDefinition}
61 @ivar messages: The last sent/received messages.
62 @type messages: str[2]
63 """
64 @classmethod
66 """
67 Extract the I{items} from a suds object much like the
68 items() method works on I{dict}.
69 @param sobject: A suds object
70 @type sobject: L{Object}
71 @return: A list of items contained in I{sobject}.
72 @rtype: [(key, value),...]
73 """
74 return sudsobject.items(sobject)
75
76 @classmethod
77 - def dict(cls, sobject):
78 """
79 Convert a sudsobject into a dictionary.
80 @param sobject: A suds object
81 @type sobject: L{Object}
82 @return: A python dictionary containing the
83 items contained in I{sobject}.
84 @rtype: dict
85 """
86 return sudsobject.asdict(sobject)
87
88 @classmethod
98
122
124 """
125 Set options.
126 @param kwargs: keyword arguments.
127 @see: L{Options}
128 """
129 p = Unskin(self.options)
130 p.update(kwargs)
131
133 """
134 Add I{static} mapping of an XML namespace prefix to a namespace.
135 This is useful for cases when a wsdl and referenced schemas make heavy
136 use of namespaces and those namespaces are subject to changed.
137 @param prefix: An XML namespace prefix.
138 @type prefix: str
139 @param uri: An XML namespace URI.
140 @type uri: str
141 @raise Exception: when prefix is already mapped.
142 """
143 root = self.wsdl.root
144 mapped = root.resolvePrefix(prefix, None)
145 if mapped is None:
146 root.addPrefix(prefix, uri)
147 return
148 if mapped[1] != uri:
149 raise Exception('"%s" already mapped as "%s"' % (prefix, mapped))
150
152 """
153 Get last sent I{soap} message.
154 @return: The last sent I{soap} message.
155 @rtype: L{Document}
156 """
157 return self.messages.get('tx')
158
160 """
161 Get last received I{soap} message.
162 @return: The last received I{soap} message.
163 @rtype: L{Document}
164 """
165 return self.messages.get('rx')
166
168 """
169 Get a shallow clone of this object.
170 The clone only shares the WSDL. All other attributes are
171 unique to the cloned object including options.
172 @return: A shallow clone.
173 @rtype: L{Client}
174 """
175 class Uninitialized(Client):
176 def __init__(self):
177 pass
178 clone = Uninitialized()
179 clone.options = Options()
180 cp = Unskin(clone.options)
181 mp = Unskin(self.options)
182 cp.update(deepcopy(mp))
183 clone.wsdl = self.wsdl
184 clone.factory = self.factory
185 clone.service = ServiceSelector(clone, self.wsdl.services)
186 clone.sd = self.sd
187 clone.messages = dict(tx=None, rx=None)
188 return clone
189
192
202
205 """
206 A factory for instantiating types defined in the wsdl
207 @ivar resolver: A schema type resolver.
208 @type resolver: L{PathResolver}
209 @ivar builder: A schema object builder.
210 @type builder: L{Builder}
211 """
212
221
223 """
224 create a WSDL type by name
225 @param name: The name of a type defined in the WSDL.
226 @type name: str
227 @return: The requested object.
228 @rtype: L{Object}
229 """
230 timer = metrics.Timer()
231 timer.start()
232 type = self.resolver.find(name)
233 if type is None:
234 raise TypeNotFound(name)
235 if type.enum():
236 result = InstFactory.object(name)
237 for e, a in type.children():
238 setattr(result, e.name, e.name)
239 else:
240 try:
241 result = self.builder.build(type)
242 except Exception, e:
243 log.error("create '%s' failed", name, exc_info=True)
244 raise BuildError(name, e)
245 timer.stop()
246 metrics.log.debug('%s created: %s', name, timer)
247 return result
248
250 """
251 Set the path separator.
252 @param ps: The new path separator.
253 @type ps: char
254 """
255 self.resolver = PathResolver(self.wsdl, ps)
256
259 """
260 The B{service} selector is used to select a web service.
261 In most cases, the wsdl only defines (1) service in which access
262 by subscript is passed through to a L{PortSelector}. This is also the
263 behavior when a I{default} service has been specified. In cases
264 where multiple services have been defined and no default has been
265 specified, the service is found by name (or index) and a L{PortSelector}
266 for the service is returned. In all cases, attribute access is
267 forwarded to the L{PortSelector} for either the I{first} service or the
268 I{default} service (when specified).
269 @ivar __client: A suds client.
270 @type __client: L{Client}
271 @ivar __services: A list of I{wsdl} services.
272 @type __services: list
273 """
275 """
276 @param client: A suds client.
277 @type client: L{Client}
278 @param services: A list of I{wsdl} services.
279 @type services: list
280 """
281 self.__client = client
282 self.__services = services
283
285 """
286 Request to access an attribute is forwarded to the
287 L{PortSelector} for either the I{first} service or the
288 I{default} service (when specified).
289 @param name: The name of a method.
290 @type name: str
291 @return: A L{PortSelector}.
292 @rtype: L{PortSelector}.
293 """
294 default = self.__ds()
295 if default is None:
296 port = self.__find(0)
297 else:
298 port = default
299 return getattr(port, name)
300
302 """
303 Provides selection of the I{service} by name (string) or
304 index (integer). In cases where only (1) service is defined
305 or a I{default} has been specified, the request is forwarded
306 to the L{PortSelector}.
307 @param name: The name (or index) of a service.
308 @type name: (int|str)
309 @return: A L{PortSelector} for the specified service.
310 @rtype: L{PortSelector}.
311 """
312 if len(self.__services) == 1:
313 port = self.__find(0)
314 return port[name]
315 default = self.__ds()
316 if default is not None:
317 port = default
318 return port[name]
319 return self.__find(name)
320
322 """
323 Find a I{service} by name (string) or index (integer).
324 @param name: The name (or index) of a service.
325 @type name: (int|str)
326 @return: A L{PortSelector} for the found service.
327 @rtype: L{PortSelector}.
328 """
329 service = None
330 if not len(self.__services):
331 raise Exception, 'No services defined'
332 if isinstance(name, int):
333 try:
334 service = self.__services[name]
335 name = service.name
336 except IndexError:
337 raise ServiceNotFound, 'at [%d]' % name
338 else:
339 for s in self.__services:
340 if name == s.name:
341 service = s
342 break
343 if service is None:
344 raise ServiceNotFound, name
345 return PortSelector(self.__client, service.ports, name)
346
348 """
349 Get the I{default} service if defined in the I{options}.
350 @return: A L{PortSelector} for the I{default} service.
351 @rtype: L{PortSelector}.
352 """
353 ds = self.__client.options.service
354 if ds is None:
355 return None
356 else:
357 return self.__find(ds)
358
361 """
362 The B{port} selector is used to select a I{web service} B{port}.
363 In cases where multiple ports have been defined and no default has been
364 specified, the port is found by name (or index) and a L{MethodSelector}
365 for the port is returned. In all cases, attribute access is
366 forwarded to the L{MethodSelector} for either the I{first} port or the
367 I{default} port (when specified).
368 @ivar __client: A suds client.
369 @type __client: L{Client}
370 @ivar __ports: A list of I{service} ports.
371 @type __ports: list
372 @ivar __qn: The I{qualified} name of the port (used for logging).
373 @type __qn: str
374 """
376 """
377 @param client: A suds client.
378 @type client: L{Client}
379 @param ports: A list of I{service} ports.
380 @type ports: list
381 @param qn: The name of the service.
382 @type qn: str
383 """
384 self.__client = client
385 self.__ports = ports
386 self.__qn = qn
387
389 """
390 Request to access an attribute is forwarded to the
391 L{MethodSelector} for either the I{first} port or the
392 I{default} port (when specified).
393 @param name: The name of a method.
394 @type name: str
395 @return: A L{MethodSelector}.
396 @rtype: L{MethodSelector}.
397 """
398 default = self.__dp()
399 if default is None:
400 m = self.__find(0)
401 else:
402 m = default
403 return getattr(m, name)
404
406 """
407 Provides selection of the I{port} by name (string) or
408 index (integer). In cases where only (1) port is defined
409 or a I{default} has been specified, the request is forwarded
410 to the L{MethodSelector}.
411 @param name: The name (or index) of a port.
412 @type name: (int|str)
413 @return: A L{MethodSelector} for the specified port.
414 @rtype: L{MethodSelector}.
415 """
416 default = self.__dp()
417 if default is None:
418 return self.__find(name)
419 else:
420 return default
421
423 """
424 Find a I{port} by name (string) or index (integer).
425 @param name: The name (or index) of a port.
426 @type name: (int|str)
427 @return: A L{MethodSelector} for the found port.
428 @rtype: L{MethodSelector}.
429 """
430 port = None
431 if not len(self.__ports):
432 raise Exception, 'No ports defined: %s' % self.__qn
433 if isinstance(name, int):
434 qn = '%s[%d]' % (self.__qn, name)
435 try:
436 port = self.__ports[name]
437 except IndexError:
438 raise PortNotFound, qn
439 else:
440 qn = '.'.join((self.__qn, name))
441 for p in self.__ports:
442 if name == p.name:
443 port = p
444 break
445 if port is None:
446 raise PortNotFound, qn
447 qn = '.'.join((self.__qn, port.name))
448 return MethodSelector(self.__client, port.methods, qn)
449
451 """
452 Get the I{default} port if defined in the I{options}.
453 @return: A L{MethodSelector} for the I{default} port.
454 @rtype: L{MethodSelector}.
455 """
456 dp = self.__client.options.port
457 if dp is None:
458 return None
459 else:
460 return self.__find(dp)
461
464 """
465 The B{method} selector is used to select a B{method} by name.
466 @ivar __client: A suds client.
467 @type __client: L{Client}
468 @ivar __methods: A dictionary of methods.
469 @type __methods: dict
470 @ivar __qn: The I{qualified} name of the method (used for logging).
471 @type __qn: str
472 """
473 - def __init__(self, client, methods, qn):
474 """
475 @param client: A suds client.
476 @type client: L{Client}
477 @param methods: A dictionary of methods.
478 @type methods: dict
479 @param qn: The I{qualified} name of the port.
480 @type qn: str
481 """
482 self.__client = client
483 self.__methods = methods
484 self.__qn = qn
485
487 """
488 Get a method by name and return it in an I{execution wrapper}.
489 @param name: The name of a method.
490 @type name: str
491 @return: An I{execution wrapper} for the specified method name.
492 @rtype: L{Method}
493 """
494 return self[name]
495
497 """
498 Get a method by name and return it in an I{execution wrapper}.
499 @param name: The name of a method.
500 @type name: str
501 @return: An I{execution wrapper} for the specified method name.
502 @rtype: L{Method}
503 """
504 m = self.__methods.get(name)
505 if m is None:
506 qn = '.'.join((self.__qn, name))
507 raise MethodNotFound, qn
508 return Method(self.__client, m)
509
512 """
513 The I{method} (namespace) object.
514 @ivar client: A client object.
515 @type client: L{Client}
516 @ivar method: A I{wsdl} method.
517 @type I{wsdl} Method.
518 """
519
521 """
522 @param client: A client object.
523 @type client: L{Client}
524 @param method: A I{raw} method.
525 @type I{raw} Method.
526 """
527 self.client = client
528 self.method = method
529
543
547
554
557 """
558 A lightweight soap based web client B{**not intended for external use}
559 @ivar service: The target method.
560 @type service: L{Service}
561 @ivar method: A target method.
562 @type method: L{Method}
563 @ivar options: A dictonary of options.
564 @type options: dict
565 @ivar cookiejar: A cookie jar.
566 @type cookiejar: libcookie.CookieJar
567 """
568
570 """
571 @param client: A suds client.
572 @type client: L{Client}
573 @param method: A target method.
574 @type method: L{Method}
575 """
576 self.client = client
577 self.method = method
578 self.options = client.options
579 self.cookiejar = CookieJar()
580
581 - def invoke(self, args, kwargs):
582 """
583 Send the required soap message to invoke the specified method
584 @param args: A list of args for the method invoked.
585 @type args: list
586 @param kwargs: Named (keyword) args for the method invoked.
587 @type kwargs: dict
588 @return: The result of the method invocation.
589 @rtype: I{builtin}|I{subclass of} L{Object}
590 """
591 timer = metrics.Timer()
592 timer.start()
593 result = None
594 binding = self.method.binding.input
595 soapenv = binding.get_message(self.method, args, kwargs)
596 timer.stop()
597 metrics.log.debug(
598 "message for '%s' created: %s",
599 self.method.name,
600 timer)
601 timer.start()
602 result = self.send(soapenv)
603 timer.stop()
604 metrics.log.debug(
605 "method '%s' invoked: %s",
606 self.method.name,
607 timer)
608 return result
609
610 - def send(self, soapenv):
611 """
612 Send soap message.
613 @param soapenv: A soap envelope to send.
614 @type soapenv: L{Document}
615 @return: The reply to the sent message.
616 @rtype: I{builtin} or I{subclass of} L{Object}
617 """
618 result = None
619 location = self.location()
620 binding = self.method.binding.input
621 transport = self.options.transport
622 retxml = self.options.retxml
623 nosend = self.options.nosend
624 prettyxml = self.options.prettyxml
625 timer = metrics.Timer()
626 log.debug('sending to (%s)\nmessage:\n%s', location, soapenv)
627 try:
628 self.last_sent(soapenv)
629 plugins = PluginContainer(self.options.plugins)
630 plugins.message.marshalled(envelope=soapenv.root())
631 if prettyxml:
632 soapenv = soapenv.str()
633 else:
634 soapenv = soapenv.plain()
635 soapenv = soapenv.encode('utf-8')
636 ctx = plugins.message.sending(envelope=soapenv)
637 soapenv = ctx.envelope
638 if nosend:
639 return RequestContext(self, binding, soapenv)
640 request = Request(location, soapenv)
641 request.headers = self.headers()
642 timer.start()
643 reply = transport.send(request)
644 timer.stop()
645 metrics.log.debug('waited %s on server reply', timer)
646 ctx = plugins.message.received(reply=reply.message)
647 reply.message = ctx.reply
648 if retxml:
649 result = reply.message
650 else:
651 result = self.succeeded(binding, reply.message)
652 except TransportError, e:
653 if e.httpcode in (202,204):
654 result = None
655 else:
656 log.error(self.last_sent())
657 result = self.failed(binding, e)
658 return result
659
661 """
662 Get http headers or the http/https request.
663 @return: A dictionary of header/values.
664 @rtype: dict
665 """
666 action = self.method.soap.action
667 if isinstance(action, unicode):
668 action = action.encode('utf-8')
669 stock = { 'Content-Type' : 'text/xml; charset=utf-8', 'SOAPAction': action }
670 result = dict(stock, **self.options.headers)
671 log.debug('headers = %s', result)
672 return result
673
675 """
676 Request succeeded, process the reply
677 @param binding: The binding to be used to process the reply.
678 @type binding: L{bindings.binding.Binding}
679 @param reply: The raw reply text.
680 @type reply: str
681 @return: The method result.
682 @rtype: I{builtin}, L{Object}
683 @raise WebFault: On server.
684 """
685 log.debug('http succeeded:\n%s', reply)
686 plugins = PluginContainer(self.options.plugins)
687 if len(reply) > 0:
688 reply, result = binding.get_reply(self.method, reply)
689 self.last_received(reply)
690 else:
691 result = None
692 ctx = plugins.message.unmarshalled(reply=result)
693 result = ctx.reply
694 if self.options.faults:
695 return result
696 else:
697 return (200, result)
698
699 - def failed(self, binding, error):
700 """
701 Request failed, process reply based on reason
702 @param binding: The binding to be used to process the reply.
703 @type binding: L{suds.bindings.binding.Binding}
704 @param error: The http error message
705 @type error: L{transport.TransportError}
706 """
707 status, reason = (error.httpcode, tostr(error))
708 reply = error.fp.read()
709 log.debug('http failed:\n%s', reply)
710 if status == 500:
711 if len(reply) > 0:
712 r, p = binding.get_fault(reply)
713 self.last_received(r)
714 return (status, p)
715 else:
716 return (status, None)
717 if self.options.faults:
718 raise Exception((status, reason))
719 else:
720 return (status, None)
721
725
727 key = 'tx'
728 messages = self.client.messages
729 if d is None:
730 return messages.get(key)
731 else:
732 messages[key] = d
733
735 key = 'rx'
736 messages = self.client.messages
737 if d is None:
738 return messages.get(key)
739 else:
740 messages[key] = d
741
744 """
745 Loopback client used for message/reply simulation.
746 """
747
748 injkey = '__inject'
749
750 @classmethod
752 """ get whether loopback has been specified in the I{kwargs}. """
753 return kwargs.has_key(SimClient.injkey)
754
755 - def invoke(self, args, kwargs):
756 """
757 Send the required soap message to invoke the specified method
758 @param args: A list of args for the method invoked.
759 @type args: list
760 @param kwargs: Named (keyword) args for the method invoked.
761 @type kwargs: dict
762 @return: The result of the method invocation.
763 @rtype: I{builtin} or I{subclass of} L{Object}
764 """
765 simulation = kwargs[self.injkey]
766 msg = simulation.get('msg')
767 reply = simulation.get('reply')
768 fault = simulation.get('fault')
769 if msg is None:
770 if reply is not None:
771 return self.__reply(reply, args, kwargs)
772 if fault is not None:
773 return self.__fault(fault)
774 raise Exception('(reply|fault) expected when msg=None')
775 sax = Parser()
776 msg = sax.parse(string=msg)
777 return self.send(msg)
778
779 - def __reply(self, reply, args, kwargs):
786
796
797
798 -class RequestContext:
799 """
800 A request context.
801 Returned when the ''nosend'' options is specified.
802 @ivar client: The suds client.
803 @type client: L{Client}
804 @ivar binding: The binding for this request.
805 @type binding: I{Binding}
806 @ivar envelope: The request soap envelope.
807 @type envelope: str
808 """
809
810 - def __init__(self, client, binding, envelope):
811 """
812 @param client: The suds client.
813 @type client: L{Client}
814 @param binding: The binding for this request.
815 @type binding: I{Binding}
816 @param envelope: The request soap envelope.
817 @type envelope: str
818 """
819 self.client = client
820 self.binding = binding
821 self.envelope = envelope
822
823 - def succeeded(self, reply):
824 """
825 Re-entry for processing a successful reply.
826 @param reply: The reply soap envelope.
827 @type reply: str
828 @return: The returned value for the invoked method.
829 @rtype: object
830 """
831 options = self.client.options
832 plugins = PluginContainer(options.plugins)
833 ctx = plugins.message.received(reply=reply)
834 reply = ctx.reply
835 return self.client.succeeded(self.binding, reply)
836
837 - def failed(self, error):
838 """
839 Re-entry for processing a failure reply.
840 @param error: The error returned by the transport.
841 @type error: A suds I{TransportError}.
842 """
843 return self.client.failed(self.binding, error)
844