Package suds :: Module client
[hide private]
[frames] | no frames]

Source Code for Module suds.client

  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  """ 
 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__) 
47 48 49 -class Client(object):
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
65 - def items(cls, sobject):
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
89 - def metadata(cls, sobject):
90 """ 91 Extract the metadata from a suds object. 92 @param sobject: A suds object 93 @type sobject: L{Object} 94 @return: The object's metadata 95 @rtype: L{sudsobject.Metadata} 96 """ 97 return sobject.__metadata__
98
99 - def __init__(self, url, **kwargs):
100 """ 101 @param url: The URL for the WSDL. 102 @type url: str 103 @param kwargs: keyword arguments. 104 @see: L{Options} 105 """ 106 options = Options() 107 options.transport = HttpAuthenticated() 108 self.options = options 109 # Removed because of bug https://fedorahosted.org/suds/ticket/376 (also MXSUP-1489) Olof Svensson 2013-04-02 110 #options.cache = ObjectCache(days=1) 111 self.set_options(**kwargs) 112 reader = DefinitionsReader(options, Definitions) 113 self.wsdl = reader.open(url) 114 plugins = PluginContainer(options.plugins) 115 plugins.init.initialized(wsdl=self.wsdl) 116 self.factory = Factory(self.wsdl) 117 self.service = ServiceSelector(self, self.wsdl.services) 118 self.sd = [] 119 for s in self.wsdl.services: 120 sd = ServiceDefinition(self.wsdl, s) 121 self.sd.append(sd) 122 self.messages = dict(tx=None, rx=None)
123
124 - def set_options(self, **kwargs):
125 """ 126 Set options. 127 @param kwargs: keyword arguments. 128 @see: L{Options} 129 """ 130 p = Unskin(self.options) 131 p.update(kwargs)
132
133 - def add_prefix(self, prefix, uri):
134 """ 135 Add I{static} mapping of an XML namespace prefix to a namespace. 136 This is useful for cases when a wsdl and referenced schemas make heavy 137 use of namespaces and those namespaces are subject to changed. 138 @param prefix: An XML namespace prefix. 139 @type prefix: str 140 @param uri: An XML namespace URI. 141 @type uri: str 142 @raise Exception: when prefix is already mapped. 143 """ 144 root = self.wsdl.root 145 mapped = root.resolvePrefix(prefix, None) 146 if mapped is None: 147 root.addPrefix(prefix, uri) 148 return 149 if mapped[1] != uri: 150 raise Exception('"%s" already mapped as "%s"' % (prefix, mapped))
151
152 - def last_sent(self):
153 """ 154 Get last sent I{soap} message. 155 @return: The last sent I{soap} message. 156 @rtype: L{Document} 157 """ 158 return self.messages.get('tx')
159
160 - def last_received(self):
161 """ 162 Get last received I{soap} message. 163 @return: The last received I{soap} message. 164 @rtype: L{Document} 165 """ 166 return self.messages.get('rx')
167
168 - def clone(self):
169 """ 170 Get a shallow clone of this object. 171 The clone only shares the WSDL. All other attributes are 172 unique to the cloned object including options. 173 @return: A shallow clone. 174 @rtype: L{Client} 175 """ 176 class Uninitialized(Client): 177 def __init__(self): 178 pass
179 clone = Uninitialized() 180 clone.options = Options() 181 cp = Unskin(clone.options) 182 mp = Unskin(self.options) 183 cp.update(deepcopy(mp)) 184 clone.wsdl = self.wsdl 185 clone.factory = self.factory 186 clone.service = ServiceSelector(clone, self.wsdl.services) 187 clone.sd = self.sd 188 clone.messages = dict(tx=None, rx=None) 189 return clone 190
191 - def __str__(self):
192 return unicode(self)
193
194 - def __unicode__(self):
195 s = ['\n'] 196 build = suds.__build__.split() 197 s.append('Suds ( https://fedorahosted.org/suds/ )') 198 s.append(' version: %s' % suds.__version__) 199 s.append(' %s build: %s' % (build[0], build[1])) 200 for sd in self.sd: 201 s.append('\n\n%s' % unicode(sd)) 202 return ''.join(s)
203
204 205 -class Factory:
206 """ 207 A factory for instantiating types defined in the wsdl 208 @ivar resolver: A schema type resolver. 209 @type resolver: L{PathResolver} 210 @ivar builder: A schema object builder. 211 @type builder: L{Builder} 212 """ 213
214 - def __init__(self, wsdl):
215 """ 216 @param wsdl: A schema object. 217 @type wsdl: L{wsdl.Definitions} 218 """ 219 self.wsdl = wsdl 220 self.resolver = PathResolver(wsdl) 221 self.builder = Builder(self.resolver)
222
223 - def create(self, name):
224 """ 225 create a WSDL type by name 226 @param name: The name of a type defined in the WSDL. 227 @type name: str 228 @return: The requested object. 229 @rtype: L{Object} 230 """ 231 timer = metrics.Timer() 232 timer.start() 233 type = self.resolver.find(name) 234 if type is None: 235 raise TypeNotFound(name) 236 if type.enum(): 237 result = InstFactory.object(name) 238 for e, a in type.children(): 239 setattr(result, e.name, e.name) 240 else: 241 try: 242 result = self.builder.build(type) 243 except Exception, e: 244 log.error("create '%s' failed", name, exc_info=True) 245 raise BuildError(name, e) 246 timer.stop() 247 metrics.log.debug('%s created: %s', name, timer) 248 return result
249
250 - def separator(self, ps):
251 """ 252 Set the path separator. 253 @param ps: The new path separator. 254 @type ps: char 255 """ 256 self.resolver = PathResolver(self.wsdl, ps)
257
258 259 -class ServiceSelector:
260 """ 261 The B{service} selector is used to select a web service. 262 In most cases, the wsdl only defines (1) service in which access 263 by subscript is passed through to a L{PortSelector}. This is also the 264 behavior when a I{default} service has been specified. In cases 265 where multiple services have been defined and no default has been 266 specified, the service is found by name (or index) and a L{PortSelector} 267 for the service is returned. In all cases, attribute access is 268 forwarded to the L{PortSelector} for either the I{first} service or the 269 I{default} service (when specified). 270 @ivar __client: A suds client. 271 @type __client: L{Client} 272 @ivar __services: A list of I{wsdl} services. 273 @type __services: list 274 """
275 - def __init__(self, client, services):
276 """ 277 @param client: A suds client. 278 @type client: L{Client} 279 @param services: A list of I{wsdl} services. 280 @type services: list 281 """ 282 self.__client = client 283 self.__services = services
284
285 - def __getattr__(self, name):
286 """ 287 Request to access an attribute is forwarded to the 288 L{PortSelector} for either the I{first} service or the 289 I{default} service (when specified). 290 @param name: The name of a method. 291 @type name: str 292 @return: A L{PortSelector}. 293 @rtype: L{PortSelector}. 294 """ 295 default = self.__ds() 296 if default is None: 297 port = self.__find(0) 298 else: 299 port = default 300 return getattr(port, name)
301
302 - def __getitem__(self, name):
303 """ 304 Provides selection of the I{service} by name (string) or 305 index (integer). In cases where only (1) service is defined 306 or a I{default} has been specified, the request is forwarded 307 to the L{PortSelector}. 308 @param name: The name (or index) of a service. 309 @type name: (int|str) 310 @return: A L{PortSelector} for the specified service. 311 @rtype: L{PortSelector}. 312 """ 313 if len(self.__services) == 1: 314 port = self.__find(0) 315 return port[name] 316 default = self.__ds() 317 if default is not None: 318 port = default 319 return port[name] 320 return self.__find(name)
321
322 - def __find(self, name):
323 """ 324 Find a I{service} by name (string) or index (integer). 325 @param name: The name (or index) of a service. 326 @type name: (int|str) 327 @return: A L{PortSelector} for the found service. 328 @rtype: L{PortSelector}. 329 """ 330 service = None 331 if not len(self.__services): 332 raise Exception, 'No services defined' 333 if isinstance(name, int): 334 try: 335 service = self.__services[name] 336 name = service.name 337 except IndexError: 338 raise ServiceNotFound, 'at [%d]' % name 339 else: 340 for s in self.__services: 341 if name == s.name: 342 service = s 343 break 344 if service is None: 345 raise ServiceNotFound, name 346 return PortSelector(self.__client, service.ports, name)
347
348 - def __ds(self):
349 """ 350 Get the I{default} service if defined in the I{options}. 351 @return: A L{PortSelector} for the I{default} service. 352 @rtype: L{PortSelector}. 353 """ 354 ds = self.__client.options.service 355 if ds is None: 356 return None 357 else: 358 return self.__find(ds)
359
360 361 -class PortSelector:
362 """ 363 The B{port} selector is used to select a I{web service} B{port}. 364 In cases where multiple ports have been defined and no default has been 365 specified, the port is found by name (or index) and a L{MethodSelector} 366 for the port is returned. In all cases, attribute access is 367 forwarded to the L{MethodSelector} for either the I{first} port or the 368 I{default} port (when specified). 369 @ivar __client: A suds client. 370 @type __client: L{Client} 371 @ivar __ports: A list of I{service} ports. 372 @type __ports: list 373 @ivar __qn: The I{qualified} name of the port (used for logging). 374 @type __qn: str 375 """
376 - def __init__(self, client, ports, qn):
377 """ 378 @param client: A suds client. 379 @type client: L{Client} 380 @param ports: A list of I{service} ports. 381 @type ports: list 382 @param qn: The name of the service. 383 @type qn: str 384 """ 385 self.__client = client 386 self.__ports = ports 387 self.__qn = qn
388
389 - def __getattr__(self, name):
390 """ 391 Request to access an attribute is forwarded to the 392 L{MethodSelector} for either the I{first} port or the 393 I{default} port (when specified). 394 @param name: The name of a method. 395 @type name: str 396 @return: A L{MethodSelector}. 397 @rtype: L{MethodSelector}. 398 """ 399 default = self.__dp() 400 if default is None: 401 m = self.__find(0) 402 else: 403 m = default 404 return getattr(m, name)
405
406 - def __getitem__(self, name):
407 """ 408 Provides selection of the I{port} by name (string) or 409 index (integer). In cases where only (1) port is defined 410 or a I{default} has been specified, the request is forwarded 411 to the L{MethodSelector}. 412 @param name: The name (or index) of a port. 413 @type name: (int|str) 414 @return: A L{MethodSelector} for the specified port. 415 @rtype: L{MethodSelector}. 416 """ 417 default = self.__dp() 418 if default is None: 419 return self.__find(name) 420 else: 421 return default
422
423 - def __find(self, name):
424 """ 425 Find a I{port} by name (string) or index (integer). 426 @param name: The name (or index) of a port. 427 @type name: (int|str) 428 @return: A L{MethodSelector} for the found port. 429 @rtype: L{MethodSelector}. 430 """ 431 port = None 432 if not len(self.__ports): 433 raise Exception, 'No ports defined: %s' % self.__qn 434 if isinstance(name, int): 435 qn = '%s[%d]' % (self.__qn, name) 436 try: 437 port = self.__ports[name] 438 except IndexError: 439 raise PortNotFound, qn 440 else: 441 qn = '.'.join((self.__qn, name)) 442 for p in self.__ports: 443 if name == p.name: 444 port = p 445 break 446 if port is None: 447 raise PortNotFound, qn 448 qn = '.'.join((self.__qn, port.name)) 449 return MethodSelector(self.__client, port.methods, qn)
450
451 - def __dp(self):
452 """ 453 Get the I{default} port if defined in the I{options}. 454 @return: A L{MethodSelector} for the I{default} port. 455 @rtype: L{MethodSelector}. 456 """ 457 dp = self.__client.options.port 458 if dp is None: 459 return None 460 else: 461 return self.__find(dp)
462
463 464 -class MethodSelector:
465 """ 466 The B{method} selector is used to select a B{method} by name. 467 @ivar __client: A suds client. 468 @type __client: L{Client} 469 @ivar __methods: A dictionary of methods. 470 @type __methods: dict 471 @ivar __qn: The I{qualified} name of the method (used for logging). 472 @type __qn: str 473 """
474 - def __init__(self, client, methods, qn):
475 """ 476 @param client: A suds client. 477 @type client: L{Client} 478 @param methods: A dictionary of methods. 479 @type methods: dict 480 @param qn: The I{qualified} name of the port. 481 @type qn: str 482 """ 483 self.__client = client 484 self.__methods = methods 485 self.__qn = qn
486
487 - def __getattr__(self, name):
488 """ 489 Get a method by name and return it in an I{execution wrapper}. 490 @param name: The name of a method. 491 @type name: str 492 @return: An I{execution wrapper} for the specified method name. 493 @rtype: L{Method} 494 """ 495 return self[name]
496
497 - def __getitem__(self, name):
498 """ 499 Get a method by name and return it in an I{execution wrapper}. 500 @param name: The name of a method. 501 @type name: str 502 @return: An I{execution wrapper} for the specified method name. 503 @rtype: L{Method} 504 """ 505 m = self.__methods.get(name) 506 if m is None: 507 qn = '.'.join((self.__qn, name)) 508 raise MethodNotFound, qn 509 return Method(self.__client, m)
510
511 512 -class Method:
513 """ 514 The I{method} (namespace) object. 515 @ivar client: A client object. 516 @type client: L{Client} 517 @ivar method: A I{wsdl} method. 518 @type I{wsdl} Method. 519 """ 520
521 - def __init__(self, client, method):
522 """ 523 @param client: A client object. 524 @type client: L{Client} 525 @param method: A I{raw} method. 526 @type I{raw} Method. 527 """ 528 self.client = client 529 self.method = method
530
531 - def __call__(self, *args, **kwargs):
532 """ 533 Invoke the method. 534 """ 535 clientclass = self.clientclass(kwargs) 536 client = clientclass(self.client, self.method) 537 if not self.faults(): 538 try: 539 return client.invoke(args, kwargs) 540 except WebFault, e: 541 return (500, e) 542 else: 543 return client.invoke(args, kwargs)
544
545 - def faults(self):
546 """ get faults option """ 547 return self.client.options.faults
548
549 - def clientclass(self, kwargs):
550 """ get soap client class """ 551 if SimClient.simulation(kwargs): 552 return SimClient 553 else: 554 return SoapClient
555
556 557 -class SoapClient:
558 """ 559 A lightweight soap based web client B{**not intended for external use} 560 @ivar service: The target method. 561 @type service: L{Service} 562 @ivar method: A target method. 563 @type method: L{Method} 564 @ivar options: A dictonary of options. 565 @type options: dict 566 @ivar cookiejar: A cookie jar. 567 @type cookiejar: libcookie.CookieJar 568 """ 569
570 - def __init__(self, client, method):
571 """ 572 @param client: A suds client. 573 @type client: L{Client} 574 @param method: A target method. 575 @type method: L{Method} 576 """ 577 self.client = client 578 self.method = method 579 self.options = client.options 580 self.cookiejar = CookieJar()
581
582 - def invoke(self, args, kwargs):
583 """ 584 Send the required soap message to invoke the specified method 585 @param args: A list of args for the method invoked. 586 @type args: list 587 @param kwargs: Named (keyword) args for the method invoked. 588 @type kwargs: dict 589 @return: The result of the method invocation. 590 @rtype: I{builtin}|I{subclass of} L{Object} 591 """ 592 timer = metrics.Timer() 593 timer.start() 594 result = None 595 binding = self.method.binding.input 596 soapenv = binding.get_message(self.method, args, kwargs) 597 timer.stop() 598 metrics.log.debug( 599 "message for '%s' created: %s", 600 self.method.name, 601 timer) 602 timer.start() 603 result = self.send(soapenv) 604 timer.stop() 605 metrics.log.debug( 606 "method '%s' invoked: %s", 607 self.method.name, 608 timer) 609 return result
610
611 - def send(self, soapenv):
612 """ 613 Send soap message. 614 @param soapenv: A soap envelope to send. 615 @type soapenv: L{Document} 616 @return: The reply to the sent message. 617 @rtype: I{builtin} or I{subclass of} L{Object} 618 """ 619 result = None 620 location = self.location() 621 binding = self.method.binding.input 622 transport = self.options.transport 623 retxml = self.options.retxml 624 prettyxml = self.options.prettyxml 625 log.debug('sending to (%s)\nmessage:\n%s', location, soapenv) 626 try: 627 self.last_sent(soapenv) 628 plugins = PluginContainer(self.options.plugins) 629 plugins.message.marshalled(envelope=soapenv.root()) 630 if prettyxml: 631 soapenv = soapenv.str() 632 else: 633 soapenv = soapenv.plain() 634 soapenv = soapenv.encode('utf-8') 635 plugins.message.sending(envelope=soapenv) 636 request = Request(location, soapenv) 637 request.headers = self.headers() 638 reply = transport.send(request) 639 ctx = plugins.message.received(reply=reply.message) 640 reply.message = ctx.reply 641 if retxml: 642 result = reply.message 643 else: 644 result = self.succeeded(binding, reply.message) 645 except TransportError, e: 646 if e.httpcode in (202,204): 647 result = None 648 else: 649 log.error(self.last_sent()) 650 result = self.failed(binding, e) 651 return result
652
653 - def headers(self):
654 """ 655 Get http headers or the http/https request. 656 @return: A dictionary of header/values. 657 @rtype: dict 658 """ 659 action = self.method.soap.action 660 stock = { 'Content-Type' : 'text/xml; charset=utf-8', 'SOAPAction': action } 661 result = dict(stock, **self.options.headers) 662 log.debug('headers = %s', result) 663 return result
664
665 - def succeeded(self, binding, reply):
666 """ 667 Request succeeded, process the reply 668 @param binding: The binding to be used to process the reply. 669 @type binding: L{bindings.binding.Binding} 670 @param reply: The raw reply text. 671 @type reply: str 672 @return: The method result. 673 @rtype: I{builtin}, L{Object} 674 @raise WebFault: On server. 675 """ 676 log.debug('http succeeded:\n%s', reply) 677 plugins = PluginContainer(self.options.plugins) 678 if len(reply) > 0: 679 reply, result = binding.get_reply(self.method, reply) 680 self.last_received(reply) 681 else: 682 result = None 683 ctx = plugins.message.unmarshalled(reply=result) 684 result = ctx.reply 685 if self.options.faults: 686 return result 687 else: 688 return (200, result)
689
690 - def failed(self, binding, error):
691 """ 692 Request failed, process reply based on reason 693 @param binding: The binding to be used to process the reply. 694 @type binding: L{suds.bindings.binding.Binding} 695 @param error: The http error message 696 @type error: L{transport.TransportError} 697 """ 698 status, reason = (error.httpcode, tostr(error)) 699 reply = error.fp.read() 700 log.debug('http failed:\n%s', reply) 701 if status == 500: 702 if len(reply) > 0: 703 r, p = binding.get_fault(reply) 704 self.last_received(r) 705 return (status, p) 706 else: 707 return (status, None) 708 if self.options.faults: 709 raise Exception((status, reason)) 710 else: 711 return (status, None)
712
713 - def location(self):
714 p = Unskin(self.options) 715 return p.get('location', self.method.location)
716
717 - def last_sent(self, d=None):
718 key = 'tx' 719 messages = self.client.messages 720 if d is None: 721 return messages.get(key) 722 else: 723 messages[key] = d
724
725 - def last_received(self, d=None):
726 key = 'rx' 727 messages = self.client.messages 728 if d is None: 729 return messages.get(key) 730 else: 731 messages[key] = d
732
733 734 -class SimClient(SoapClient):
735 """ 736 Loopback client used for message/reply simulation. 737 """ 738 739 injkey = '__inject' 740 741 @classmethod
742 - def simulation(cls, kwargs):
743 """ get whether loopback has been specified in the I{kwargs}. """ 744 return kwargs.has_key(SimClient.injkey)
745
746 - def invoke(self, args, kwargs):
747 """ 748 Send the required soap message to invoke the specified method 749 @param args: A list of args for the method invoked. 750 @type args: list 751 @param kwargs: Named (keyword) args for the method invoked. 752 @type kwargs: dict 753 @return: The result of the method invocation. 754 @rtype: I{builtin} or I{subclass of} L{Object} 755 """ 756 simulation = kwargs[self.injkey] 757 msg = simulation.get('msg') 758 reply = simulation.get('reply') 759 fault = simulation.get('fault') 760 if msg is None: 761 if reply is not None: 762 return self.__reply(reply, args, kwargs) 763 if fault is not None: 764 return self.__fault(fault) 765 raise Exception('(reply|fault) expected when msg=None') 766 sax = Parser() 767 msg = sax.parse(string=msg) 768 return self.send(msg)
769
770 - def __reply(self, reply, args, kwargs):
771 """ simulate the reply """ 772 binding = self.method.binding.input 773 msg = binding.get_message(self.method, args, kwargs) 774 log.debug('inject (simulated) send message:\n%s', msg) 775 binding = self.method.binding.output 776 return self.succeeded(binding, reply)
777
778 - def __fault(self, reply):
779 """ simulate the (fault) reply """ 780 binding = self.method.binding.output 781 if self.options.faults: 782 r, p = binding.get_fault(reply) 783 self.last_received(r) 784 return (500, p) 785 else: 786 return (500, None)
787