From 02882710fa81da1ed1c4d380f3be7c9fdf68a789 Mon Sep 17 00:00:00 2001 From: jortel Date: Tue, 24 Aug 2010 14:42:24 +0000 Subject: plugin API - version 2. --- suds/bindings/binding.py | 3 + suds/client.py | 33 +++++----- suds/options.py | 2 +- suds/plugin.py | 160 ++++++++++++++++++++++++++++++++++------------- suds/wsdl.py | 2 +- suds/xsd/doctor.py | 6 +- suds/xsd/schema.py | 2 +- tests/axis1.py | 36 ++++++++--- 8 files changed, 169 insertions(+), 75 deletions(-) diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py index ef857df..4a7a996 100644 --- a/suds/bindings/binding.py +++ b/suds/bindings/binding.py @@ -33,6 +33,7 @@ from suds.bindings.multiref import MultiRef from suds.xsd.query import TypeQuery, ElementQuery from suds.xsd.sxbasic import Element as SchemaElement from suds.options import Options +from suds.plugin import PluginContainer from copy import deepcopy log = getLogger(__name__) @@ -142,6 +143,8 @@ class Binding: 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') diff --git a/suds/client.py b/suds/client.py index 0bdde6f..d56cb99 100644 --- a/suds/client.py +++ b/suds/client.py @@ -111,7 +111,7 @@ class Client(object): reader = DefinitionsReader(options, Definitions) self.wsdl = reader.open(url) plugins = PluginContainer(options.plugins) - plugins.initialized(wsdl=self.wsdl) + plugins.init.initialized(wsdl=self.wsdl) self.factory = Factory(self.wsdl) self.service = ServiceSelector(self, self.wsdl.services) self.sd = [] @@ -624,10 +624,14 @@ class SoapClient: try: self.last_sent(Document(msg)) plugins = PluginContainer(self.options.plugins) - plugins.sending(envelope=msg.root()) - request = Request(location, str(msg)) + plugins.message.marshalled(envelope=msg.root()) + soapenv = str(msg) + plugins.message.sending(envelope=soapenv) + request = Request(location, soapenv) request.headers = self.headers() reply = transport.send(request) + ctx = plugins.message.received(reply=reply.message) + reply.message = ctx.reply if retxml: result = reply.message else: @@ -657,26 +661,25 @@ class SoapClient: 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) - ctx = plugins.received(reply=reply) - reply = ctx.reply if len(reply) > 0: - r, p = binding.get_reply(self.method, reply) - self.last_received(r) - if self.options.faults: - return p - else: - return (200, p) + reply, result = binding.get_reply(self.method, reply) + self.last_received(reply) else: - if self.options.faults: - return None - else: - return (200, None) + result = None + ctx = plugins.message.unmarshalled(reply=result) + result = ctx.reply + if self.options.faults: + return result + else: + return (200, result) def failed(self, binding, error): """ diff --git a/suds/options.py b/suds/options.py index 36f1732..b4876d3 100644 --- a/suds/options.py +++ b/suds/options.py @@ -113,6 +113,6 @@ class Options(Skin): Definition('retxml', bool, False), Definition('autoblend', bool, False), Definition('cachingpolicy', int, 0), - Definition('plugins', [], (list, tuple)), + Definition('plugins', (list, tuple), []), ] Skin.__init__(self, domain, definitions, kwargs) diff --git a/suds/plugin.py b/suds/plugin.py index ca0826f..4cdb610 100644 --- a/suds/plugin.py +++ b/suds/plugin.py @@ -41,58 +41,90 @@ class InitContext(Context): pass -class LoadContext(Context): +class DocumentContext(Context): """ - The XSD load context. + The XML document load context. @ivar root: The loaded xsd document root. @type root: L{sax.Element} """ pass + + +class InvokeContext(Context): + """ + The method invocaton context. + @ivar method: The name of the method. + @type method: str + @ivar params: The parameters passed. + @type params: list + @ivar params: The return object. + @type params: object + """ -class SendContext(Context): +class MessageContext(InvokeContext): """ The context for sending the soap envelope. @ivar envelope: The soap envelope I{root} element to be sent. @type envelope: L{sax.Element} + @ivar reply: The reply. + @type reply: (str|L{Element}|object) """ pass - - -class ReplyContext(Context): + + +class Plugin: """ - The context for the text received as a reply - to method invocation. - @ivar reply: The received text. - @type reply: unicode + Plugin base. """ pass - -class Plugin: +class InitPlugin(Plugin): """ - The base class for suds plugins. - All plugins should implement this interface. + The base class for suds I{init} plugins. """ def initialized(self, context): """ - Suds initialization. + Suds client initialization. Called after wsdl the has been loaded. Provides the plugin with the opportunity to inspect/modify the WSDL. @param context: The init context. @type context: L{InitContext} """ pass + + +class DocumentPlugin(Plugin): + """ + The base class for suds I{document} plugins. + """ + + def parsed(self, context): + """ + Suds has parsed a WSDL/XSD document. Provides the plugin + with an opportunity to inspect/modify the parsed document. + Called after each WSDL/XSD document is parsed. + @param context: The document context. + @type context: L{LDocumentContext} + """ + pass + + +class MessagePlugin(Plugin): + """ + The base class for suds I{soap message} plugins. + """ - def loaded(self, context): + def marshalled(self, context): """ - Suds has loaded an XSD document. Provides the plugin - with an opportunity to inspect/modify the loaded XSD. - Called after each XSD document is loaded. - @param context: The XSD load context. - @type context: L{LoadContext} + Suds will send the specified soap envelope. + Provides the plugin with the opportunity to inspect/modify + the envelope Document before it is sent. + @param context: The send context. + The I{envelope} is the envelope docuemnt. + @type context: L{MessageContext} """ pass @@ -100,9 +132,10 @@ class Plugin: """ Suds will send the specified soap envelope. Provides the plugin with the opportunity to inspect/modify - the message before it is sent. + the message text it is sent. @param context: The send context. - @type context: L{SendContext} + The I{envelope} is the envelope text. + @type context: L{MessageContext} """ pass @@ -110,12 +143,35 @@ class Plugin: """ Suds has received the specified reply. Provides the plugin with the opportunity to inspect/modify - the received XML. + the received XML text before it is SAX parsed. + @param context: The reply context. + The I{reply} is the raw text. + @type context: L{MessageContext} + """ + pass + + def parsed(self, context): + """ + Suds has sax parsed the received reply. + Provides the plugin with the opportunity to inspect/modify + the sax parsed DOM tree for the reply. + @param context: The reply context. + The I{reply} is DOM tree. + @type context: L{MessageContext} + """ + pass + + def unmarshalled(self, context): + """ + Suds has sax parsed the received reply. + Provides the plugin with the opportunity to inspect/modify + the unmarshalled reply. @param context: The reply context. - @type context: L{ReplyContext} + The I{reply} is DOM tree. + @type context: L{MessageContext} """ pass - + class PluginContainer: """ @@ -126,11 +182,10 @@ class PluginContainer: @type ctxclass: dict """ - ctxclass = {\ - 'initialized':InitContext, - 'loaded':LoadContext, - 'sending':SendContext, - 'received':ReplyContext, + domain = {\ + 'init':InitContext, + 'document':DocumentContext, + 'message':MessageContext, } def __init__(self, plugins): @@ -141,11 +196,28 @@ class PluginContainer: self.plugins = plugins def __getattr__(self, name): - ctx = self.ctxclass.get(name) + ctx = self.domain.get(name) if ctx: - return Method(name, ctx, self.plugins) + return PluginDomain(ctx, self.plugins) else: - raise AttributeError(name) + raise Exception, 'plugin domain (%s), invalid' % name + + +class PluginDomain: + """ + The plugin domain. + @ivar ctx: A context. + @type ctx: L{Context} + @ivar plugins: A list of plugins (targets). + @type plugins: list + """ + + def __init__(self, ctx, plugins): + self.ctx = ctx + self.plugins = plugins + + def __getattr__(self, name): + return Method(name, self) class Method: @@ -153,13 +225,11 @@ class Method: Plugin method. @ivar name: The method name. @type name: str - @ivar ctx: A context. - @type ctx: L{Context} - @ivar plugins: A list of plugins (targets). - @type plugins: list + @ivar domain: The plugin domain. + @type domain: L{PluginDomain} """ - def __init__(self, name, ctx, plugins): + def __init__(self, name, domain): """ @param name: The method name. @type name: str @@ -169,16 +239,16 @@ class Method: @type plugins: list """ self.name = name - self.ctx = ctx() - self.plugins = plugins + self.domain = domain def __call__(self, **kwargs): - self.ctx.__dict__.update(kwargs) - for plugin in self.plugins: + ctx = self.domain.ctx() + ctx.__dict__.update(kwargs) + for plugin in self.domain.plugins: try: method = getattr(plugin, self.name, None) if method: - method(self.ctx) + method(ctx) except Exception, pe: log.exception(pe) - return self.ctx + return ctx diff --git a/suds/wsdl.py b/suds/wsdl.py index 0400e9b..5115f4e 100644 --- a/suds/wsdl.py +++ b/suds/wsdl.py @@ -137,7 +137,7 @@ class Definitions(WObject): d = reader.open(url) root = d.root() plugins = PluginContainer(options.plugins) - plugins.loaded(root=root) + plugins.document.parsed(root=root) WObject.__init__(self, root) self.id = objid(self) self.options = options diff --git a/suds/xsd/doctor.py b/suds/xsd/doctor.py index 63271dc..700919c 100644 --- a/suds/xsd/doctor.py +++ b/suds/xsd/doctor.py @@ -22,7 +22,7 @@ schema(s). from logging import getLogger from suds.sax import splitPrefix, Namespace from suds.sax.element import Element -from suds.plugin import Plugin +from suds.plugin import MessagePlugin log = getLogger(__name__) @@ -187,7 +187,7 @@ class Import: return 0 -class ImportDoctor(Doctor, Plugin): +class ImportDoctor(Doctor, MessagePlugin): """ Doctor used to fix missing imports. @ivar imports: A list of imports to apply. @@ -212,7 +212,7 @@ class ImportDoctor(Doctor, Plugin): for imp in self.imports: imp.apply(root) - def loaded(self, context): + def parsed(self, context): root = context.root if Namespace.xsd(root.namespace()): self.examine(root) diff --git a/suds/xsd/schema.py b/suds/xsd/schema.py index 912fb03..4d0ccdb 100644 --- a/suds/xsd/schema.py +++ b/suds/xsd/schema.py @@ -215,7 +215,7 @@ class Schema: self.groups = {} self.agrps = {} plugins = PluginContainer(options.plugins) - plugins.loaded(root=root) + plugins.document.parsed(root=root) if options.doctor is not None: options.doctor.examine(root) form = self.root.get('elementFormDefault') diff --git a/tests/axis1.py b/tests/axis1.py index 2206d02..3a0ebd4 100644 --- a/tests/axis1.py +++ b/tests/axis1.py @@ -29,7 +29,7 @@ from suds import WebFault from suds.client import Client from suds.sudsobject import Object from suds.transport.https import HttpAuthenticated -from suds.plugin import Plugin +from suds.plugin import * errors = 0 @@ -38,19 +38,37 @@ credentials = dict(username='jortel', password='abc123') setup_logging() -class TestPlugin(Plugin): - +class MyInitPlugin(InitPlugin): + def initialized(self, context): - print 'initialized: ctx=%s' % context.__dict__ - + print 'PLUGIN (init): initialized: ctx=%s' % context.__dict__ + + +class MyDocumentPlugin(DocumentPlugin): + def loaded(self, context): - print 'loaded: ctx=%s' % context.__dict__ + print 'PLUGIN (document): loaded: ctx=%s' % context.__dict__ + + +class MyMessagePlugin(MessagePlugin): + + def marshalled(self, context): + print 'PLUGIN (message): marshalled: ctx=%s' % context.__dict__ def sending(self, context): - print 'sending: ctx=%s' % context.__dict__ + print 'PLUGIN (message): sending: ctx=%s' % context.__dict__ def received(self, context): - print 'received: ctx=%s' % context.__dict__ + print 'PLUGIN (message): received: ctx=%s' % context.__dict__ + + def parsed(self, context): + print 'PLUGIN (message): parsed: ctx=%s' % context.__dict__ + + def unmarshalled(self, context): + print 'PLUGIN: (massage): unmarshalled: ctx=%s' % context.__dict__ + + +myplugins = (MyInitPlugin(), MyDocumentPlugin(), MyMessagePlugin(),) #logging.getLogger('suds.client').setLevel(logging.DEBUG) @@ -64,7 +82,7 @@ try: url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl' start(url) t = HttpAuthenticated(**credentials) - client = Client(url, transport=t, cache=None, plugins=[TestPlugin()]) + client = Client(url, transport=t, cache=None, plugins=myplugins) print client # # create a name object using the wsdl -- cgit v1.2.1