diff options
author | jortel <devnull@localhost> | 2010-01-12 23:15:00 +0000 |
---|---|---|
committer | jortel <devnull@localhost> | 2010-01-12 23:15:00 +0000 |
commit | e1f87183cd08a0e3d76129ff5429e2452585bdb2 (patch) | |
tree | 41cd12e82347f8b0c0274d3d91d8cedab4facf10 | |
parent | 8f4faa9ecb07f6bab61bb4e7210d67d96897c1df (diff) | |
download | suds-e1f87183cd08a0e3d76129ff5429e2452585bdb2.tar.gz |
Move transport caching to document-based caching. This approach leverages the performance of pickling/unpickling of sax DOM object graphs. Also, ensures that only valid/complete XML documents are cached. The NoCache class moved as well so users disabling the cache as Client(url, cache=NoCache) should switch to the preferred: Client(url, cache=None).
-rw-r--r-- | suds/__init__.py | 8 | ||||
-rw-r--r-- | suds/bindings/binding.py | 11 | ||||
-rw-r--r-- | suds/cache.py (renamed from suds/transport/cache.py) | 170 | ||||
-rw-r--r-- | suds/client.py | 7 | ||||
-rw-r--r-- | suds/options.py | 10 | ||||
-rw-r--r-- | suds/reader.py | 37 | ||||
-rw-r--r-- | suds/sax/__init__.py | 2 | ||||
-rw-r--r-- | suds/transport/__init__.py | 68 | ||||
-rw-r--r-- | suds/transport/http.py | 11 | ||||
-rw-r--r-- | suds/transport/https.py | 3 | ||||
-rw-r--r-- | suds/transport/options.py | 4 | ||||
-rw-r--r-- | suds/wsdl.py | 8 | ||||
-rw-r--r-- | suds/xsd/sxbasic.py | 16 | ||||
-rw-r--r-- | tests/axis2.py | 2 |
14 files changed, 221 insertions, 136 deletions
diff --git a/suds/__init__.py b/suds/__init__.py index ec16846..e3f2db4 100644 --- a/suds/__init__.py +++ b/suds/__init__.py @@ -15,10 +15,8 @@ # written by: Jeff Ortel ( jortel@redhat.com ) """ -Suds is a lightweight SOAP python client that provides a service proxy -for Web Services. -@var properties: Library properties. -@type properties: dict +Suds is a lightweight SOAP python client that provides a +service proxy for Web Services. """ import os @@ -29,7 +27,7 @@ import sys # __version__ = '0.3.9' -__build__="(beta) R634-20100105" +__build__="(beta) R637-20100112" # # Exceptions diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py index 1b828d1..c2e7810 100644 --- a/suds/bindings/binding.py +++ b/suds/bindings/binding.py @@ -51,8 +51,6 @@ class Binding: @type schema: L{xsd.schema.Schema} @ivar options: A dictionary options. @type options: L{Options} - @ivar parser: A sax parser. - @type parser: L{suds.sax.parser.Parser} """ replyfilter = (lambda s,r: r) @@ -64,8 +62,7 @@ class Binding: """ self.wsdl = wsdl self.schema = wsdl.schema - self.options = Options() - self.parser = Parser() + self.options = wsdl.options self.multiref = MultiRef() def unmarshaller(self, typed=True): @@ -138,7 +135,8 @@ class Binding: @rtype: tuple ( L{Element}, L{Object} ) """ reply = self.replyfilter(reply) - replyroot = self.parser.parse(string=reply) + sax = Parser() + replyroot = sax.parse(string=reply) soapenv = replyroot.getChild('Envelope') soapenv.promotePrefixes() soapbody = soapenv.getChild('Body') @@ -225,7 +223,8 @@ class Binding: @rtype: tuple ( L{Element}, L{Object} ) """ reply = self.replyfilter(reply) - faultroot = self.parser.parse(string=reply) + sax = Parser() + faultroot = sax.parse(string=reply) soapenv = faultroot.getChild('Envelope') soapbody = soapenv.getChild('Body') fault = soapbody.getChild('Fault') diff --git a/suds/transport/cache.py b/suds/cache.py index e96d41e..02f7e55 100644 --- a/suds/transport/cache.py +++ b/suds/cache.py @@ -15,26 +15,73 @@ # written by: Jeff Ortel ( jortel@redhat.com ) """ -Contains transport interface (classes) and reference implementation. +Contains basic caching classes. """ import os from tempfile import gettempdir as tmp -from urlparse import urlparse from suds.transport import * from datetime import datetime as dt from datetime import timedelta +from cStringIO import StringIO from logging import getLogger +try: + import cPickle as pickle +except: + import pickle log = getLogger(__name__) -class FileCache(Cache): +class ByteCache: + """ + The URL caching object. + """ + + def put(self, id, fp): + """ + Put an item into the cache. + @param id: A file ID. + @type id: str + @param fp: A file stream. + @type fp: stream + @return: The stream. + @rtype: stream + """ + raise Exception('not-implemented') + + def get(self, id): + """ + Get an item from the cache by id. + @param id: A file ID. + @type id: str + @return: A stream when found, else None. + @rtype: stream + """ + raise Exception('not-implemented') + + def purge(self, id): + """ + Purge a file from the cache by id. + @param id: A file ID. + @type id: str + """ + raise Exception('not-implemented') + + def clear(self): + """ + @param id: A file ID. + @type id: str + """ + raise Exception('not-implemented') + + +class FileCache(ByteCache): """ A file-based URL cache. @cvar fnprefix: The file name prefix. @type fnprefix: str - @cvar fnsuffix: The file name suffix. + @ivar fnsuffix: The file name suffix. @type fnsuffix: str @ivar duration: The cached file duration which defines how long the file will be cached. @@ -42,9 +89,7 @@ class FileCache(Cache): @ivar location: The directory for the cached files. @type location: str """ - fnprefix = 'suds' - fnsuffix = 'http' units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds') def __init__(self, location=None, **duration): @@ -56,6 +101,7 @@ class FileCache(Cache): The duration may be: (months|weeks|days|hours|minutes|seconds). @type duration: {unit:value} """ + self.fnsuffix = 'xml' if location is None: location = os.path.join(tmp(), 'suds') self.location = location @@ -97,20 +143,20 @@ class FileCache(Cache): log.debug(self.location, exc_info=1) return self - def put(self, url, fp): + def put(self, id, fp): try: - fn = self.__fn(url) + fn = self.__fn(id) f = self.open(fn, 'w') f.write(fp.read()) f.close() return open(fn) except: - log.debug(url, exc_info=1) + log.debug(id, exc_info=1) return fp - def get(self, url): + def get(self, id): try: - fn = self.__fn(url) + fn = self.__fn(id) self.validate(fn) return self.open(fn) except: @@ -139,6 +185,13 @@ class FileCache(Cache): log.debug('deleted: %s', fn) os.remove(os.path.join(self.location, fn)) + def purge(self, id): + fn = self.__fn(id) + try: + os.remove(fn) + except: + pass + def open(self, fn, *args): """ Open the cache file making sure the directory is created. @@ -146,13 +199,92 @@ class FileCache(Cache): self.mktmp() return open(fn, *args) - def __fn(self, url): - if self.__ignored(url): - raise Exception('URL %s, ignored') - fn = '%s-%s.%s' % (self.fnprefix, abs(hash(url)), self.fnsuffix) + def __fn(self, id): + fn = '%s-%s.%s' % (self.fnprefix, abs(hash(id)), self.fnsuffix) return os.path.join(self.location, fn) + + +class Cache: + """ + The XML document cache. + """ + + def get(self, id): + """ + Get a document from the store by ID. + @param id: The document ID. + @type id: str + @return: The document, else None + @rtype: I{Document} + """ + raise Exception('not-implemented') + + def put(self, id, document): + """ + Put a document into the store. + @param id: The document ID. + @type id: str + @param document: The document to add. + @type document: I{Document} + """ + raise Exception('not-implemented') + + def purge(self, id): + """ + Purge a document from the cache by id. + @param id: A document ID. + @type id: str + """ + raise Exception('not-implemented') + + def clear(self): + """ + Clear all documents from the cache. + """ + raise Exception('not-implemented') + + +class NoCache(Cache): + """ + The passthru document cache. + """ + + def get(self, id): + return None + + def put(self, id, document): + pass + + +class DocumentStore(Cache): + + def __init__(self, location=None, **duration): + """ + @param location: The directory for the cached documents. + @type location: str + @param duration: The cached file duration which defines how + long the document will be cached. A duration=0 means forever. + The duration may be: (months|weeks|days|hours|minutes|seconds). + @type duration: {unit:value} + """ + cache = FileCache(location, **duration) + cache.fnsuffix = 'pxd' + self.cache = cache + + def get(self, id): + try: + fp = self.cache.get(id) + if fp is None: + return None + else: + return pickle.load(fp) + except: + self.cache.purge(id) - def __ignored(self, url): - """ ignore urls based on protocol """ - protocol = urlparse(url)[0] - return protocol in ('file',)
\ No newline at end of file + def put(self, id, document): + ostr = StringIO() + pickle.dump(document, ostr) + istr = StringIO(ostr.getvalue()) + fp = self.cache.put(id, istr) + fp.close() + return document diff --git a/suds/client.py b/suds/client.py index 22d5cfc..0d7c7f3 100644 --- a/suds/client.py +++ b/suds/client.py @@ -25,7 +25,6 @@ from cookielib import CookieJar from suds import * from suds.transport import TransportError, Request from suds.transport.https import HttpAuthenticated -from suds.transport.cache import FileCache from suds.servicedefinition import ServiceDefinition from suds import sudsobject from sudsobject import Factory as InstFactory @@ -33,6 +32,7 @@ from sudsobject import Object from suds.resolver import PathResolver from suds.builder import Builder from suds.wsdl import Definitions +from suds.cache import DocumentStore from suds.sax.document import Document from suds.sax.parser import Parser from suds.options import Options @@ -104,7 +104,7 @@ class Client(object): options = Options() options.transport = HttpAuthenticated() self.options = options - options.cache = FileCache(days=1) + options.cache = DocumentStore(days=1) self.set_options(**kwargs) self.wsdl = Definitions(url, options) self.factory = Factory(self.wsdl) @@ -743,7 +743,8 @@ class SimClient(SoapClient): if fault is not None: return self.__fault(fault) raise Exception('(reply|fault) expected when msg=None') - msg = Parser().parse(string=msg) + sax = Parser() + msg = sax.parse(string=msg) return self.send(msg) def __reply(self, reply, args, kwargs): diff --git a/suds/options.py b/suds/options.py index 27f87f4..d7a711e 100644 --- a/suds/options.py +++ b/suds/options.py @@ -15,13 +15,14 @@ # written by: Jeff Ortel ( jortel@redhat.com ) """ -Suds base pptions classes. +Suds basic options classes. """ from suds.properties import * from suds.wsse import Security from suds.xsd.doctor import Doctor from suds.transport import Transport +from suds.cache import Cache, NoCache class TpLinker(AutoLinker): @@ -42,6 +43,9 @@ class TpLinker(AutoLinker): class Options(Skin): """ Options: + - B{cache} - The XML document cache. May be set (None) for no caching. + - type: L{Cache} + - default: L{NoCache} - B{faults} - Raise faults raised by server, else return tuple from service method invocation as (httpcode, object). - type: I{bool} @@ -84,10 +88,14 @@ class Options(Skin): WSDL import each other. B{**Experimental**}. - type: I{bool} - default: False + - B{store} - The SAX document store. + - type: L{DocumentStore} + - default: None """ def __init__(self, **kwargs): domain = __name__ definitions = [ + Definition('cache', Cache, NoCache()), Definition('faults', bool, True), Definition('transport', Transport, None, TpLinker()), Definition('service', (int, basestring), None), diff --git a/suds/reader.py b/suds/reader.py new file mode 100644 index 0000000..0925012 --- /dev/null +++ b/suds/reader.py @@ -0,0 +1,37 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains xml document reader classes. +""" + + +from suds.sax.parser import Parser +from suds.transport import Request + +class DocumentReader: + + def __init__(self, options): + self.options = options + + def open(self, url): + d = self.options.cache.get(url) + if d is None: + fp = self.options.transport.open(Request(url)) + sax = Parser() + d = sax.parse(file=fp) + self.options.cache.put(url, d) + return d diff --git a/suds/sax/__init__.py b/suds/sax/__init__.py index c50cbae..b4d343e 100644 --- a/suds/sax/__init__.py +++ b/suds/sax/__init__.py @@ -102,4 +102,4 @@ class Namespace: return isinstance(ns, tuple) and len(ns) == len(cls.default) except: pass - return False
\ No newline at end of file + return False diff --git a/suds/transport/__init__.py b/suds/transport/__init__.py index 9bf5674..e1e00d7 100644 --- a/suds/transport/__init__.py +++ b/suds/transport/__init__.py @@ -128,71 +128,3 @@ class Transport: @raise TransportError: On all transport errors. """ raise Exception('not-implemented') - - -class Cache: - """ - The URL caching object. - """ - - def put(self, url, fp): - """ - Put an item into the cache. - @param url: A url. - @type url: str - @param fp: A file stream. - @type fp: stream - @return: The stream. - @rtype: stream - """ - raise Exception('not-implemented') - - def get(self, url): - """ - Get an item from the cache by url. - @param url: A url. - @type url: str - @return: A stream when found, else None. - @rtype: stream - """ - raise Exception('not-implemented') - - def clear(self): - """ - Clear the cached items. - """ - raise Exception('not-implemented') - - -class NoCache(Cache): - """ - The NO caching implementation. - """ - - def put(self, url, fp): - """ - Put an item into the cache. - @param url: A url. - @type url: str - @param fp: A file stream. - @type fp: stream - @return: The stream. - @rtype: stream - """ - return fp - - def get(self, url): - """ - Get an item from the cache by url. - @param url: A url. - @type url: str - @return: A stream when found, else None. - @rtype: stream - """ - return None - - def clear(self): - """ - Clear the cached items. - """ - pass diff --git a/suds/transport/http.py b/suds/transport/http.py index b2650ba..cf00615 100644 --- a/suds/transport/http.py +++ b/suds/transport/http.py @@ -46,9 +46,6 @@ class HttpTransport(Transport): - B{timeout} - Set the url open timeout (seconds). - type: I{float} - default: 90 - - B{cache} - The http I{transport} cache. May be set (None) for no caching. - - type: L{Cache} - - default: L{NoCache} """ Transport.__init__(self) Unskin(self.options).update(kwargs) @@ -58,16 +55,10 @@ class HttpTransport(Transport): def open(self, request): try: url = request.url - cache = self.options.cache - fp = cache.get(url) - if fp is not None: - log.debug('opening (%s), cached', url) - return fp log.debug('opening (%s)', url) u2request = u2.Request(url) self.setproxy(url, u2request) - fp = self.u2open(u2request) - return cache.put(url, fp) + return self.u2open(u2request) except u2.HTTPError, e: raise TransportError(str(e), e.code, e.fp) diff --git a/suds/transport/https.py b/suds/transport/https.py index 3c3a4a2..52b463f 100644 --- a/suds/transport/https.py +++ b/suds/transport/https.py @@ -45,9 +45,6 @@ class HttpAuthenticated(HttpTransport): - B{timeout} - Set the url open timeout (seconds). - type: I{float} - default: 90 - - B{cache} - The http I{transport} cache. May be set (None) for no caching. - - type: L{Cache} - - default: L{NoCache} - B{username} - The username used for http authentication. - type: I{str} - default: None diff --git a/suds/transport/options.py b/suds/transport/options.py index e920ca3..8b0d194 100644 --- a/suds/transport/options.py +++ b/suds/transport/options.py @@ -26,9 +26,6 @@ from suds.properties import * class Options(Skin): """ Options: - - B{cache} - The http I{transport} cache. May be set (None) for no caching. - - type: L{Cache} - - default: L{NoCache} - B{proxy} - An http proxy to be specified on requests. The proxy is defined as {protocol:proxy,} - type: I{dict} @@ -51,7 +48,6 @@ class Options(Skin): def __init__(self, **kwargs): domain = __name__ definitions = [ - Definition('cache', Cache, NoCache()), Definition('proxy', dict, {}), Definition('timeout', (int,float), 90), Definition('headers', dict, {}), diff --git a/suds/wsdl.py b/suds/wsdl.py index f9708fd..558f8be 100644 --- a/suds/wsdl.py +++ b/suds/wsdl.py @@ -23,8 +23,6 @@ found in the document. from logging import getLogger from suds import * from suds.sax import splitPrefix -from suds.transport import Request -from suds.sax.parser import Parser from suds.sax.element import Element from suds.bindings.document import Document from suds.bindings.rpc import RPC, Encoded @@ -33,6 +31,7 @@ from suds.xsd.schema import Schema, SchemaCollection from suds.xsd.query import ElementQuery from suds.sudsobject import Object from suds.sudsobject import Factory as SFactory +from suds.reader import DocumentReader from urlparse import urljoin import re, soaparray @@ -169,9 +168,8 @@ class Definitions(WObject): @type options: L{options.Options} """ log.debug('reading wsdl at: %s ...', url) - fp = options.transport.open(Request(url)) - p = Parser() - d = p.parse(file=fp) + reader = DocumentReader(options) + d = reader.open(url) root = d.root() WObject.__init__(self, root) self.id = objid(self) diff --git a/suds/xsd/sxbasic.py b/suds/xsd/sxbasic.py index 15ef40e..d58dfff 100644 --- a/suds/xsd/sxbasic.py +++ b/suds/xsd/sxbasic.py @@ -25,8 +25,8 @@ from suds.xsd import * from suds.xsd.sxbase import * from suds.xsd.query import * from suds.sax import splitPrefix, Namespace -from suds.sax.parser import Parser -from suds.transport import Request, TransportError +from suds.transport import TransportError +from suds.reader import DocumentReader from urlparse import urljoin @@ -527,10 +527,8 @@ class Import(SchemaObject): try: if '://' not in url: url = urljoin(self.schema.baseurl, url) - transport = self.schema.options.transport - p = Parser() - fp = transport.open(Request(url)) - d = p.parse(file=fp) + reader = DocumentReader(self.schema.options) + d = reader.open(url) root = d.root() root.set('url', url) return self.schema.instance(root, url) @@ -581,10 +579,8 @@ class Include(SchemaObject): try: if '://' not in url: url = urljoin(self.schema.baseurl, url) - transport = self.schema.options.transport - p = Parser() - fp = transport.open(Request(url)) - d = p.parse(file=fp) + reader = DocumentReader(self.schema.options) + d = reader.open(url) root = d.root() root.set('url', url) self.__applytns(root) diff --git a/tests/axis2.py b/tests/axis2.py index 966cb7e..8ed0bdd 100644 --- a/tests/axis2.py +++ b/tests/axis2.py @@ -38,7 +38,7 @@ print 'url=%s' % url # # create a service client using the wsdl. # -client = Client(url, cache=None) +client = Client(url) # # print the service (introspection) |