summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjortel <devnull@localhost>2010-01-12 23:15:00 +0000
committerjortel <devnull@localhost>2010-01-12 23:15:00 +0000
commite1f87183cd08a0e3d76129ff5429e2452585bdb2 (patch)
tree41cd12e82347f8b0c0274d3d91d8cedab4facf10
parent8f4faa9ecb07f6bab61bb4e7210d67d96897c1df (diff)
downloadsuds-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__.py8
-rw-r--r--suds/bindings/binding.py11
-rw-r--r--suds/cache.py (renamed from suds/transport/cache.py)170
-rw-r--r--suds/client.py7
-rw-r--r--suds/options.py10
-rw-r--r--suds/reader.py37
-rw-r--r--suds/sax/__init__.py2
-rw-r--r--suds/transport/__init__.py68
-rw-r--r--suds/transport/http.py11
-rw-r--r--suds/transport/https.py3
-rw-r--r--suds/transport/options.py4
-rw-r--r--suds/wsdl.py8
-rw-r--r--suds/xsd/sxbasic.py16
-rw-r--r--tests/axis2.py2
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)