def test_collecting_plugins_and_context_per_empty_domain(domain, expected_context): container = PluginContainer([]) result = getattr(container, domain) assert result.__class__ is PluginDomain assert result.ctx is expected_context assert result.plugins == []
def __init__(self, username, password, source='', wsdl_url=None, **kwargs): if not wsdl_url: wsdl_url = docmail.client.DOCMAIL_WSDL self.source = source self.username = username self.password = password self.return_format = 'XML' self.failure_return_format = 'XML' options = Options() options.transport = HttpAuthenticated() self.options = options options.cache = MemCache() self.set_options(**kwargs) reader = DefinitionsReader(options, Definitions) self.wsdl = reader.open(wsdl_url) plugins = PluginContainer(options.plugins) plugins.init.initialized(wsdl=self.wsdl) self.factory = Factory(self.wsdl) self.service = ServiceSelector(self, self.wsdl.services) self.sd = [] for s in self.wsdl.services: sd = ServiceDefinition(self.wsdl, s) self.sd.append(sd) self.messages = dict(tx=None, rx=None)
def __init__(self, options): """ @param options: An options object. @type options: I{Options} """ self.options = options self.plugins = PluginContainer(options.plugins)
def test_exception_passing(): class FailingPluginException(Exception): pass class FailingPlugin(MessagePlugin): def marshalled(self, context): raise FailingPluginException container = PluginContainer([FailingPlugin()]) pytest.raises(FailingPluginException, container.message.marshalled)
def test_invalid_plugin_domain(): container = PluginContainer([]) domain = "invalid_domain_name" e = pytest.raises(Exception, getattr, container, domain) try: e = e.value assert e.__class__ is Exception assert str(e) == "plugin domain (%s), invalid" % (domain,) finally: del e # explicitly break circular reference chain in Python 3
def succeeded(self, reply): """ Re-entry for processing a successful reply. @param reply: The reply soap envelope. @type reply: str @return: The returned value for the invoked method. @rtype: object """ options = self.client.options plugins = PluginContainer(options.plugins) ctx = plugins.message.received(reply=reply) reply = ctx.reply return self.client.succeeded(self.binding, reply)
def __init__(self, url, options): """ @param url: A URL to the WSDL. @type url: str @param options: An options dictionary. @type options: L{options.Options} """ log.debug('reading wsdl at: %s ...', url) reader = DocumentReader(options) d = reader.open(url) root = d.root() plugins = PluginContainer(options.plugins) plugins.loaded(root=root) WObject.__init__(self, root) self.id = objid(self) self.options = options self.url = url self.tns = self.mktns(root) self.types = [] self.schema = None self.children = [] self.imports = [] self.messages = {} self.port_types = {} self.bindings = {} self.services = [] self.add_children(self.root) self.children.sort() pmd = self.__metadata__.__print__ pmd.excludes.append('children') pmd.excludes.append('wsdl') pmd.wrappers['schema'] = repr self.open_imports() self.resolve() self.build_schema() self.set_wrapped() for s in self.services: self.add_methods(s) log.debug("wsdl at '%s' loaded:\n%s", url, self)
def __init__(self, root, baseurl, options, container=None): """ @param root: The xml root. @type root: L{sax.element.Element} @param baseurl: The base url used for importing. @type baseurl: basestring @param options: An options dictionary. @type options: L{options.Options} @param container: An optional container. @type container: L{SchemaCollection} """ self.root = root self.id = objid(self) self.tns = self.mktns() self.baseurl = baseurl self.container = container self.children = [] self.all = [] self.types = {} self.imports = [] self.elements = {} self.attributes = {} self.groups = {} self.agrps = {} plugins = PluginContainer(options.plugins) plugins.loaded(root=root) if options.doctor is not None: options.doctor.examine(root) form = self.root.get('elementFormDefault') if form is None: self.form_qualified = False else: self.form_qualified = ( form == 'qualified' ) if container is None: self.build() self.open_imports(options) log.debug('built:\n%s', self) self.dereference() log.debug('dereferenced:\n%s', self)
def send(self, soapenv): """ Send soap message. @param soapenv: A soap envelope to send. @type soapenv: L{Document} @return: The reply to the sent message. @rtype: I{builtin} or I{subclass of} L{Object} """ result = None location = self.location() binding = self.method.binding.input transport = self.options.transport retxml = self.options.retxml nosend = self.options.nosend prettyxml = self.options.prettyxml timer = metrics.Timer() log.debug('sending to (%s)\nmessage:\n%s', location, soapenv) try: self.last_sent(soapenv) plugins = PluginContainer(self.options.plugins) plugins.message.marshalled(envelope=soapenv.root()) if prettyxml: soapenv = soapenv.str() else: soapenv = soapenv.plain() soapenv = soapenv.encode('utf-8') ctx = plugins.message.sending(envelope=soapenv) soapenv = ctx.envelope if nosend: return RequestContext(self, binding, soapenv) request = Request(location, soapenv) request.headers = self.headers() timer.start() reply = transport.send(request) timer.stop() metrics.log.debug('waited %s on server reply', timer) ctx = plugins.message.received(reply=reply.message) reply.message = ctx.reply if retxml: result = reply.message else: result = self.succeeded(binding, reply.message) except TransportError as e: if e.httpcode in (202, 204): result = None else: log.error(self.last_sent()) result = self.failed(binding, e) return result
def _failed(self, binding, error): status, reason = (error.httpcode, str(error)) reply = error.fp.read() if status == 500: if len(reply) > 0: reply, result = binding.get_reply(self.method, reply) self.last_received(reply) plugins = PluginContainer(self.options.plugins) ctx = plugins.message.unmarshalled(reply=result) result = ctx.reply return (status, result) else: return (status, None) if self.options.faults: raise Exception((status, reason)) else: return (status, None)
def send(self, soapenv): """ Send SOAP message. Depending on how the ``nosend`` & ``retxml`` options are set, may do one of the following: * Return a constructed web service operation request without sending it to the web service. * Invoke the web service operation and return its SOAP reply XML. * Invoke the web service operation, process its results and return the Python object representing the returned value. @param soapenv: A SOAP envelope to send. @type soapenv: L{Document} @return: SOAP request, SOAP reply or a web service return value. @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| I{None} """ location = self.__location() log.debug("sending to (%s)\nmessage:\n%s", location, soapenv) plugins = PluginContainer(self.options.plugins) plugins.message.marshalled(envelope=soapenv.root()) if self.options.prettyxml: soapenv = soapenv.str() else: soapenv = soapenv.plain() soapenv = soapenv.encode("utf-8") ctx = plugins.message.sending(envelope=soapenv) soapenv = ctx.envelope if self.options.nosend: return RequestContext(self.process_reply, soapenv) request = suds.transport.Request(location, soapenv) request.headers = self.__headers() try: timer = metrics.Timer() timer.start() reply = self.options.transport.send(request) timer.stop() metrics.log.debug("waited %s on server reply", timer) except suds.transport.TransportError as e: content = e.fp and e.fp.read() or "" return self.process_reply(content, e.httpcode, tostr(e)) return self.process_reply(reply.message, None, None)
def test_collecting_plugins_per_domain(domain, plugin_class): plugins = [ MyDocumentPlugin(), MyDocumentPlugin(), MyMessagePlugin(), MyDocumentPlugin(), MyInitPlugin(), MyInitPlugin(), MyMessagePlugin(), InitPlugin(), MyMessagePlugin(), MyMessagePlugin(), None, MessagePlugin(), DocumentPlugin(), MyMessagePlugin(), MyDocumentPlugin(), InitPlugin(), InitPlugin(), MyInitPlugin(), MyInitPlugin(), None, MyDocumentPlugin(), DocumentPlugin(), MessagePlugin(), DocumentPlugin(), MessagePlugin(), DocumentPlugin(), InitPlugin(), MessagePlugin(), object(), DocumentPlugin(), MessagePlugin(), object(), InitPlugin(), Plugin(), Plugin(), MyInitPlugin()] container = PluginContainer(plugins) expected_plugins = [p for p in plugins if isinstance(p, plugin_class)] result = getattr(container, domain).plugins assert result == expected_plugins
def get_reply(self, method, reply): """ Process the I{reply} for the specified I{method} by sax parsing the I{reply} and then unmarshalling into Python object(s). @param method: The name of the invoked method. @type method: str @param reply: The reply XML received after invoking the specified method. @type reply: str @return: The unmarshalled reply. The returned value is an L{Object} for a I{list} depending on whether the service returns a single object or a collection. @rtype: tuple ( L{Element}, L{Object} ) """ reply = self.replyfilter(reply) sax = Parser() replyroot = sax.parse(string=reply) plugins = PluginContainer(self.options().plugins) plugins.message.parsed(reply=replyroot) soapenv = replyroot.getChild('Envelope') soapenv.promotePrefixes() soapbody = soapenv.getChild('Body') self.detect_fault(soapbody) soapbody = self.multiref.process(soapbody) nodes = self.replycontent(method, soapbody) rtypes = self.returned_types(method) if len(rtypes) > 1: result = self.replycomposite(rtypes, nodes) return (replyroot, result) if len(rtypes) == 1: if rtypes[0].multi_occurrence(): result = self.replylist(rtypes[0], nodes) return (replyroot, result) if len(nodes): unmarshaller = self.unmarshaller() resolved = rtypes[0].resolve(nobuiltin=True) result = unmarshaller.process(nodes[0], resolved) return (replyroot, result) return (replyroot, None)
def send(self, soapenv): """ Send soap message. @param soapenv: A soap envelope to send. @type soapenv: L{Document} @return: The reply to the sent message. @rtype: I{builtin} or I{subclass of} L{Object} """ location = self.location() log.debug('sending to (%s)\nmessage:\n%s', location, soapenv) original_soapenv = soapenv plugins = PluginContainer(self.options.plugins) plugins.message.marshalled(envelope=soapenv.root()) if self.options.prettyxml: soapenv = soapenv.str() else: soapenv = soapenv.plain() soapenv = soapenv.encode('utf-8') ctx = plugins.message.sending(envelope=soapenv) soapenv = ctx.envelope if self.options.nosend: return RequestContext(self, soapenv, original_soapenv) request = Request(location, soapenv) request.headers = self.headers() try: timer = metrics.Timer() timer.start() reply = self.options.transport.send(request) timer.stop() metrics.log.debug('waited %s on server reply', timer) except TransportError as e: content = e.fp and e.fp.read() or '' return self.process_reply(reply=content, status=e.httpcode, description=tostr(e), original_soapenv=original_soapenv) return self.process_reply(reply=reply.message, original_soapenv=original_soapenv)
def __init__(self, url, **kwargs): """ @param url: The URL for the WSDL. @type url: str @param kwargs: keyword arguments. @see: L{Options} """ options = Options() options.transport = HttpAuthenticated() self.options = options options.cache = ObjectCache(days=1) self.set_options(**kwargs) reader = DefinitionsReader(options, Definitions) self.wsdl = reader.open(url) plugins = PluginContainer(options.plugins) plugins.init.initialized(wsdl=self.wsdl) self.factory = Factory(self.wsdl) self.service = ServiceSelector(self, self.wsdl.services) self.sd = [] for s in self.wsdl.services: sd = ServiceDefinition(self.wsdl, s) self.sd.append(sd) self.messages = dict(tx=None, rx=None)
def succeeded(self, binding, reply): """ Request succeeded, process the reply @param binding: The binding to be used to process the reply. @type binding: L{bindings.binding.Binding} @param reply: The raw reply text. @type reply: str @return: The method result. @rtype: I{builtin}, L{Object} @raise WebFault: On server. """ log.debug('http succeeded:\n%s', reply) plugins = PluginContainer(self.options.plugins) if len(reply) > 0: reply, result = binding.get_reply(self.method, reply) self.last_received(reply) else: result = None ctx = plugins.message.unmarshalled(reply=result) result = ctx.reply if self.options.faults: return result else: return (200, result)
def process_reply(self, reply, status=None, description=None, original_soapenv=None): if status is None: status = http.client.OK if status in (http.client.ACCEPTED, http.client.NO_CONTENT): return failed = True try: if status == http.client.OK: log.debug('HTTP succeeded:\n%s', reply) else: log.debug('HTTP failed - %d - %s:\n%s', status, description, reply) # (todo) # Consider whether and how to allow plugins to handle error, # httplib.ACCEPTED & httplib.NO_CONTENT replies as well as # successful ones. # (todo) (27.03.2013.) (Jurko) plugins = PluginContainer(self.options.plugins) ctx = plugins.message.received(reply=reply) reply = ctx.reply # SOAP standard states that SOAP errors must be accompanied by HTTP # status code 500 - internal server error: # # From SOAP 1.1 Specification: # In case of a SOAP error while processing the request, the SOAP # HTTP server MUST issue an HTTP 500 "Internal Server Error" # response and include a SOAP message in the response containing a # SOAP Fault element (see section 4.4) indicating the SOAP # processing error. # # From WS-I Basic profile: # An INSTANCE MUST use a "500 Internal Server Error" HTTP status # code if the response message is a SOAP Fault. replyroot = None if status in (http.client.OK, http.client.INTERNAL_SERVER_ERROR): replyroot = _parse(reply) plugins.message.parsed(reply=replyroot) fault = self.get_fault(replyroot) if fault: if status != http.client.INTERNAL_SERVER_ERROR: log.warn( "Web service reported a SOAP processing " "fault using an unexpected HTTP status code %d. " "Reporting as an internal server error.", status) if self.options.faults: raise WebFault(fault, replyroot) return (http.client.INTERNAL_SERVER_ERROR, fault) if status != http.client.OK: if self.options.faults: # (todo) # Use a more specific exception class here. # (27.03.2013.) (Jurko) raise Exception((status, description)) return (status, description) if self.options.retxml: failed = False return reply result = replyroot and self.method.binding.output.get_reply( self.method, replyroot) ctx = plugins.message.unmarshalled(reply=result) result = ctx.reply failed = False if self.options.faults: return result return (http.client.OK, result) finally: if failed and original_soapenv: log.error(original_soapenv)
def process_reply(self, reply, status, description): """ Process a web service operation SOAP reply. Depending on how the ``retxml`` option is set, may return the SOAP reply XML or process it and return the Python object representing the returned value. @param reply: The SOAP reply envelope. @type reply: I{bytes} @param status: The HTTP status code (None indicates httplib.OK). @type status: int|I{None} @param description: Additional status description. @type description: str @return: The invoked web service operation return value. @rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None} """ if status is None: status = http.client.OK debug_message = "Reply HTTP status - %d" % (status, ) if status in (http.client.ACCEPTED, http.client.NO_CONTENT): log.debug(debug_message) return # TODO: Consider whether and how to allow plugins to handle error, # httplib.ACCEPTED & httplib.NO_CONTENT replies as well as successful # ones. if status == http.client.OK: log.debug("%s\n%s", debug_message, reply) else: log.debug("%s - %s\n%s", debug_message, description, reply) plugins = PluginContainer(self.options.plugins) ctx = plugins.message.received(reply=reply) reply = ctx.reply # SOAP standard states that SOAP errors must be accompanied by HTTP # status code 500 - internal server error: # # From SOAP 1.1 specification: # In case of a SOAP error while processing the request, the SOAP HTTP # server MUST issue an HTTP 500 "Internal Server Error" response and # include a SOAP message in the response containing a SOAP Fault # element (see section 4.4) indicating the SOAP processing error. # # From WS-I Basic profile: # An INSTANCE MUST use a "500 Internal Server Error" HTTP status code # if the response message is a SOAP Fault. replyroot = None if status in (http.client.OK, http.client.INTERNAL_SERVER_ERROR): replyroot = _parse(reply) plugins.message.parsed(reply=replyroot) fault = self.__get_fault(replyroot) if fault: if status != http.client.INTERNAL_SERVER_ERROR: log.warn( "Web service reported a SOAP processing fault " "using an unexpected HTTP status code %d. Reporting " "as an internal server error.", status) if self.options.faults: raise WebFault(fault, replyroot) return http.client.INTERNAL_SERVER_ERROR, fault if status != http.client.OK: if self.options.faults: # TODO: Use a more specific exception class here. raise Exception((status, description)) return status, description if self.options.retxml: return reply result = replyroot and self.method.binding.output.get_reply( self.method, replyroot) ctx = plugins.message.unmarshalled(reply=result) result = ctx.reply if self.options.faults: return result return http.client.OK, result