From 55bf2b60d53941768e3c04c30336616aa924e0d2 Mon Sep 17 00:00:00 2001 From: Raydel Miranda Date: Fri, 2 Jun 2017 16:57:20 -0400 Subject: [PATCH 1/3] Adding changes to match the library provided by transbank --- suds/__init__.py | 2 +- suds/bindings/binding.py | 43 +++++-- suds/builder.py | 19 +-- suds/cache.py | 72 +++++++++-- suds/client.py | 66 ++++++---- suds/mx/appender.py | 14 ++- suds/options.py | 16 ++- suds/plugin.py | 258 +++++++++++++++++++++++++++++++++++++++ suds/reader.py | 83 ++++++++----- suds/sax/__init__.py | 4 + suds/sax/date.py | 25 ++-- suds/sax/document.py | 24 ++-- suds/sax/element.py | 58 +++++++++ suds/soaparray.py | 5 +- suds/umx/core.py | 2 +- suds/xsd/doctor.py | 20 ++- suds/xsd/schema.py | 17 ++- suds/xsd/sxbase.py | 16 ++- suds/xsd/sxbasic.py | 14 ++- 19 files changed, 645 insertions(+), 113 deletions(-) create mode 100644 suds/plugin.py diff --git a/suds/__init__.py b/suds/__init__.py index c1d249a..166a206 100644 --- a/suds/__init__.py +++ b/suds/__init__.py @@ -27,7 +27,7 @@ # __version__ = '0.4' -__build__="(beta) R663-20100303" +__build__="GA R699-20100913" # # Exceptions diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py index a6d421e..4a7a996 100644 --- a/suds/bindings/binding.py +++ b/suds/bindings/binding.py @@ -22,6 +22,7 @@ from suds import * from suds.sax import Namespace from suds.sax.parser import Parser +from suds.sax.document import Document from suds.sax.element import Element from suds.sudsobject import Factory, Object from suds.mx import Content @@ -32,6 +33,7 @@ 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__) @@ -109,8 +111,8 @@ def get_message(self, method, args, kwargs): @type args: list @param kwargs: Named (keyword) args for the method invoked. @type kwargs: dict - @return: The soap message. - @rtype: str + @return: The soap envelope. + @rtype: L{Document} """ content = self.headercontent(method) @@ -123,7 +125,7 @@ def get_message(self, method, args, kwargs): env.promotePrefixes() else: env.refitPrefixes() - return env + return Document(env) def get_reply(self, method, reply): """ @@ -141,9 +143,12 @@ def get_reply(self, method, reply): 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) @@ -161,6 +166,23 @@ def get_reply(self, method, reply): return (replyroot, result) return (replyroot, None) + def detect_fault(self, body): + """ + Detect I{hidden} soapenv:Fault element in the soap body. + @param body: The soap envelope body. + @type body: L{Element} + @raise WebFault: When found. + """ + fault = body.getChild('Fault', envns) + if fault is None: + return + unmarshaller = self.unmarshaller(False) + p = unmarshaller.process(fault) + if self.options().faults: + raise WebFault(p, fault) + return self + + def replylist(self, rt, nodes): """ Construct a I{list} reply. This mehod is called when it has been detected @@ -206,14 +228,19 @@ def replycomposite(self, rtypes, nodes): continue resolved = rt.resolve(nobuiltin=True) sobject = unmarshaller.process(node, resolved) - if rt.unbounded(): - value = getattr(composite, tag, None) - if value is None: + value = getattr(composite, tag, None) + if value is None: + if rt.unbounded(): value = [] setattr(composite, tag, value) - value.append(sobject) + value.append(sobject) + else: + setattr(composite, tag, sobject) else: - setattr(composite, tag, sobject) + if not isinstance(value, list): + value = [value,] + setattr(composite, tag, value) + value.append(sobject) return composite def get_fault(self, reply): diff --git a/suds/builder.py b/suds/builder.py index 95f81c4..c2aad98 100644 --- a/suds/builder.py +++ b/suds/builder.py @@ -44,10 +44,10 @@ def build(self, name): else: type = name cls = type.name - if len(type): - data = Factory.object(cls) - else: + if type.mixed(): data = Factory.property(cls) + else: + data = Factory.object(cls) resolved = type.resolve() md = data.__metadata__ md.sxtype = resolved @@ -73,10 +73,15 @@ def process(self, data, type, history): value = [] else: if len(resolved) > 0: - value = Factory.object(resolved.name) - md = value.__metadata__ - md.sxtype = resolved - md.ordering = self.ordering(resolved) + if resolved.mixed(): + value = Factory.property(resolved.name) + md = value.__metadata__ + md.sxtype = resolved + else: + value = Factory.object(resolved.name) + md = value.__metadata__ + md.sxtype = resolved + md.ordering = self.ordering(resolved) setattr(data, type.name, value) if value is not None: data = value diff --git a/suds/cache.py b/suds/cache.py index 0e2b314..801c23c 100644 --- a/suds/cache.py +++ b/suds/cache.py @@ -19,8 +19,11 @@ """ import os +import suds from tempfile import gettempdir as tmp from suds.transport import * +from suds.sax.parser import Parser +from suds.sax.element import Element from datetime import datetime as dt from datetime import timedelta from cStringIO import StringIO @@ -115,8 +118,6 @@ class FileCache(Cache): """ A file-based URL cache. @cvar fnprefix: The file name prefix. - @type fnprefix: str - @ivar fnsuffix: The file name suffix. @type fnsuffix: str @ivar duration: The cached file duration which defines how long the file will be cached. @@ -125,7 +126,6 @@ class FileCache(Cache): @type location: str """ fnprefix = 'suds' - fnsuffix = 'gcf' units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds') def __init__(self, location=None, **duration): @@ -142,6 +142,15 @@ def __init__(self, location=None, **duration): self.location = location self.duration = (None, 0) self.setduration(**duration) + self.checkversion() + + def fnsuffix(self): + """ + Get the file name suffix + @return: The suffix + @rtype: str + """ + return 'gcf' def setduration(self, **duration): """ @@ -194,8 +203,9 @@ def putf(self, id, fp): fn = self.__fn(id) f = self.open(fn, 'w') f.write(fp.read()) + fp.close() f.close() - return fp + return open(fn) except: log.debug(id, exc_info=1) return fp @@ -226,7 +236,7 @@ def validate(self, fn): if self.duration[1] < 1: return created = dt.fromtimestamp(os.path.getctime(fn)) - d = {self.duration[0] : self.duration[1]} + d = { self.duration[0]:self.duration[1] } expired = created+timedelta(**d) if expired < dt.now(): log.debug('%s expired, deleted', fn) @@ -254,15 +264,50 @@ def open(self, fn, *args): self.mktmp() return open(fn, *args) + def checkversion(self): + path = os.path.join(self.location, 'version') + try: + + f = self.open(path) + version = f.read() + f.close() + if version != suds.__version__: + raise Exception() + except: + self.clear() + f = self.open(path, 'w') + f.write(suds.__version__) + f.close() + def __fn(self, id): - if hasattr(id, 'name') and hasattr(id, 'suffix'): - name = id.name - suffix = id.suffix - else: - name = id - suffix = self.fnsuffix - fn = '%s-%s.%s' % (self.fnprefix, abs(hash(name)), suffix) + name = id + suffix = self.fnsuffix() + fn = '%s-%s.%s' % (self.fnprefix, name, suffix) return os.path.join(self.location, fn) + + +class DocumentCache(FileCache): + """ + Provides xml document caching. + """ + + def fnsuffix(self): + return 'xml' + + def get(self, id): + try: + fp = FileCache.getf(self, id) + if fp is None: + return None + p = Parser() + return p.parse(fp) + except: + FileCache.purge(self, id) + + def put(self, id, object): + if isinstance(object, Element): + FileCache.put(self, id, str(object)) + return object class ObjectCache(FileCache): @@ -273,6 +318,9 @@ class ObjectCache(FileCache): """ protocol = 2 + def fnsuffix(self): + return 'px' + def get(self, id): try: fp = FileCache.getf(self, id) diff --git a/suds/client.py b/suds/client.py index b91a7da..a000b01 100644 --- a/suds/client.py +++ b/suds/client.py @@ -40,6 +40,7 @@ from suds.properties import Unskin from urlparse import urlparse from copy import deepcopy +from suds.plugin import PluginContainer from logging import getLogger log = getLogger(__name__) @@ -109,6 +110,8 @@ def __init__(self, url, **kwargs): 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 = [] @@ -589,24 +592,26 @@ def invoke(self, args, kwargs): timer.start() result = None binding = self.method.binding.input - msg = binding.get_message(self.method, args, kwargs) + soapenv = binding.get_message(self.method, args, kwargs) timer.stop() metrics.log.debug( "message for '%s' created: %s", - self.method.name, timer) + self.method.name, + timer) timer.start() - result = self.send(msg) + result = self.send(soapenv) timer.stop() metrics.log.debug( "method '%s' invoked: %s", - self.method.name, timer) + self.method.name, + timer) return result - def send(self, msg): + def send(self, soapenv): """ Send soap message. - @param msg: A soap message to send. - @type msg: basestring + @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} """ @@ -615,12 +620,29 @@ def send(self, msg): binding = self.method.binding.input transport = self.options.transport retxml = self.options.retxml - log.debug('sending to (%s)\nmessage:\n%s', location, msg) + prettyxml = self.options.prettyxml + log.debug('sending to (%s)\nmessage:\n%s', location, soapenv) try: - self.last_sent(Document(msg)) - request = Request(location, str(msg)) + 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') + result = plugins.message.sending(envelope=soapenv) + if (result): + #print("result:" + str(result)) + soapenv = result.envelope + #print("SENDING PLUGIN") + #print(soapenv) + #print(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: @@ -640,7 +662,7 @@ def headers(self): @rtype: dict """ action = self.method.soap.action - stock = { 'Content-Type' : 'text/xml', 'SOAPAction': action } + stock = { 'Content-Type' : 'text/xml; charset=utf-8', 'SOAPAction': action } result = dict(stock, **self.options.headers) log.debug('headers = %s', result) return result @@ -650,23 +672,25 @@ 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: - 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/mx/appender.py b/suds/mx/appender.py index 0501415..206abc0 100644 --- a/suds/mx/appender.py +++ b/suds/mx/appender.py @@ -191,7 +191,7 @@ def append(self, parent, content): if content.tag.startswith('_'): attr = content.tag[1:] value = tostr(content.value) - if value is not None and len(value): + if value: parent.set(attr, value) else: child = self.node(content) @@ -305,6 +305,12 @@ class TextAppender(Appender): """ def append(self, parent, content): - child = self.node(content) - child.setText(content.value) - parent.append(child) + if content.tag.startswith('_'): + attr = content.tag[1:] + value = tostr(content.value) + if value: + parent.set(attr, value) + else: + child = self.node(content) + child.setText(content.value) + parent.append(child) diff --git a/suds/options.py b/suds/options.py index 7e5c069..86ea245 100644 --- a/suds/options.py +++ b/suds/options.py @@ -84,10 +84,21 @@ class Options(Skin): of the python object graph. - type: I{bool} - default: False + - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when generating + the outbound soap envelope. + - type: I{bool} + - default: False - B{autoblend} - Flag that ensures that the schema(s) defined within the - WSDL import each other. B{**Experimental**}. + WSDL import each other. - type: I{bool} - default: False + - B{cachingpolicy} - The caching policy. + - type: I{int} + - 0 = Cache XML documents. + - 1 = Cache WSDL (pickled) object. + - default: 0 + - B{plugins} - A plugin container. + - type: I{list} """ def __init__(self, **kwargs): domain = __name__ @@ -104,6 +115,9 @@ def __init__(self, **kwargs): Definition('xstq', bool, True), Definition('prefixes', bool, True), Definition('retxml', bool, False), + Definition('prettyxml', bool, False), Definition('autoblend', bool, False), + Definition('cachingpolicy', int, 0), + Definition('plugins', (list, tuple), []), ] Skin.__init__(self, domain, definitions, kwargs) diff --git a/suds/plugin.py b/suds/plugin.py new file mode 100644 index 0000000..11fbcad --- /dev/null +++ b/suds/plugin.py @@ -0,0 +1,258 @@ +# 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 ) + +""" +The plugin module provides classes for implementation +of suds plugins. +""" + +from suds import * +from logging import getLogger + +log = getLogger(__name__) + + +class Context(object): + """ + Plugin context. + """ + pass + + +class InitContext(Context): + """ + Init Context. + @ivar wsdl: The wsdl. + @type wsdl: L{wsdl.Definitions} + """ + pass + + +class DocumentContext(Context): + """ + The XML document load context. + @ivar url: The URL. + @type url: str + @ivar document: Either the XML text or the B{parsed} document root. + @type document: (str|L{sax.element.Element}) + """ + pass + + +class MessageContext(Context): + """ + The context for sending the soap envelope. + @ivar envelope: The soap envelope to be sent. + @type envelope: (str|L{sax.element.Element}) + @ivar reply: The reply. + @type reply: (str|L{sax.element.Element}|object) + """ + pass + + +class Plugin: + """ + Plugin base. + """ + pass + + +class InitPlugin(Plugin): + """ + The base class for suds I{init} plugins. + """ + + def initialized(self, context): + """ + 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 loaded(self, context): + """ + Suds has loaded a WSDL/XSD document. Provides the plugin + with an opportunity to inspect/modify the unparsed document. + Called after each WSDL/XSD document is loaded. + @param context: The document context. + @type context: L{DocumentContext} + """ + pass + + 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{DocumentContext} + """ + pass + + +class MessagePlugin(Plugin): + """ + The base class for suds I{soap message} plugins. + """ + + def marshalled(self, context): + """ + 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 + + def sending(self, context): + """ + Suds will send the specified soap envelope. + Provides the plugin with the opportunity to inspect/modify + the message text it is sent. + @param context: The send context. + The I{envelope} is the envelope text. + @type context: L{MessageContext} + """ + pass + + def received(self, context): + """ + Suds has received the specified reply. + Provides the plugin with the opportunity to inspect/modify + 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 before it is unmarshalled. + @param context: The reply context. + The I{reply} is DOM tree. + @type context: L{MessageContext} + """ + pass + + def unmarshalled(self, context): + """ + Suds has unmarshalled the received reply. + Provides the plugin with the opportunity to inspect/modify + the unmarshalled reply object before it is returned. + @param context: The reply context. + The I{reply} is unmarshalled suds object. + @type context: L{MessageContext} + """ + pass + + +class PluginContainer: + """ + Plugin container provides easy method invocation. + @ivar plugins: A list of plugin objects. + @type plugins: [L{Plugin},] + @cvar ctxclass: A dict of plugin method / context classes. + @type ctxclass: dict + """ + + domains = {\ + 'init': (InitContext, InitPlugin), + 'document': (DocumentContext, DocumentPlugin), + 'message': (MessageContext, MessagePlugin ), + } + + def __init__(self, plugins): + """ + @param plugins: A list of plugin objects. + @type plugins: [L{Plugin},] + """ + self.plugins = plugins + + def __getattr__(self, name): + domain = self.domains.get(name) + if domain: + plugins = [] + ctx, pclass = domain + for p in self.plugins: + if isinstance(p, pclass): + plugins.append(p) + return PluginDomain(ctx, plugins) + else: + 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: + """ + Plugin method. + @ivar name: The method name. + @type name: str + @ivar domain: The plugin domain. + @type domain: L{PluginDomain} + """ + + def __init__(self, name, domain): + """ + @param name: The method name. + @type name: str + @param domain: A plugin domain. + @type domain: L{PluginDomain} + """ + self.name = name + self.domain = domain + + def __call__(self, **kwargs): + ctx = self.domain.ctx() + ctx.__dict__.update(kwargs) + for plugin in self.domain.plugins: + try: + method = getattr(plugin, self.name, None) + if method and callable(method): + method(ctx) + except Exception, pe: + log.exception(pe) + raise + return ctx diff --git a/suds/reader.py b/suds/reader.py index 84e6822..1184f12 100644 --- a/suds/reader.py +++ b/suds/reader.py @@ -21,38 +21,44 @@ from suds.sax.parser import Parser from suds.transport import Request +from suds.cache import Cache, NoCache from suds.store import DocumentStore +from suds.plugin import PluginContainer from logging import getLogger log = getLogger(__name__) -class ObjectId(object): - - def __init__(self, name, suffix): - self.name = name - self.suffix = suffix - - -class DocumentReader: +class Reader: """ - The XML document reader provides an integration - between the SAX L{Parser} and the document cache. - @cvar suffix: The cache file suffix. - @type suffix: str + The reader provides integration with cache. @ivar options: An options object. @type options: I{Options} """ - - suffix = 'pxd' - + def __init__(self, options): """ @param options: An options object. @type options: I{Options} """ self.options = options + self.plugins = PluginContainer(options.plugins) + + def mangle(self, name, x): + """ + Mangle the name by hashing the I{name} and appending I{x}. + @return: the mangled name. + """ + h = abs(hash(name)) + return '%s-%s' % (h, x) + + +class DocumentReader(Reader): + """ + The XML document reader provides an integration + between the SAX L{Parser} and the document cache. + """ def open(self, url): """ @@ -66,12 +72,13 @@ def open(self, url): @return: The specified XML document. @rtype: I{Document} """ - id = ObjectId(url, self.suffix) - cache = self.options.cache + cache = self.cache() + id = self.mangle(url, 'document') d = cache.get(id) if d is None: d = self.download(url) cache.put(id, d) + self.plugins.document.parsed(url=url, document=d.root()) return d def download(self, url): @@ -86,25 +93,34 @@ def download(self, url): fp = store.open(url) if fp is None: fp = self.options.transport.open(Request(url)) + content = fp.read() + fp.close() + ctx = self.plugins.document.loaded(url=url, document=content) + content = ctx.document sax = Parser() - return sax.parse(file=fp) + return sax.parse(string=content) + + def cache(self): + """ + Get the cache. + @return: The I{options} when I{cachingpolicy} = B{0}. + @rtype: L{Cache} + """ + if self.options.cachingpolicy == 0: + return self.options.cache + else: + return NoCache() -class DefinitionsReader: +class DefinitionsReader(Reader): """ The WSDL definitions reader provides an integration between the Definitions and the object cache. - @cvar suffix: The cache file suffix. - @type suffix: str - @ivar options: An options object. - @type options: I{Options} @ivar fn: A factory function (constructor) used to create the object not found in the cache. @type fn: I{Constructor} """ - suffix = 'pw' - def __init__(self, options, fn): """ @param options: An options object. @@ -113,7 +129,7 @@ def __init__(self, options, fn): create the object not found in the cache. @type fn: I{Constructor} """ - self.options = options + Reader.__init__(self, options) self.fn = fn def open(self, url): @@ -129,8 +145,8 @@ def open(self, url): @return: The WSDL object. @rtype: I{Definitions} """ - id = ObjectId(url, self.suffix) - cache = self.options.cache + cache = self.cache() + id = self.mangle(url, 'wsdl') d = cache.get(id) if d is None: d = self.fn(url, self.options) @@ -140,3 +156,14 @@ def open(self, url): for imp in d.imports: imp.imported.options = self.options return d + + def cache(self): + """ + Get the cache. + @return: The I{options} when I{cachingpolicy} = B{1}. + @rtype: L{Cache} + """ + if self.options.cachingpolicy == 1: + return self.options.cache + else: + return NoCache() \ No newline at end of file diff --git a/suds/sax/__init__.py b/suds/sax/__init__.py index b4d343e..3d71432 100644 --- a/suds/sax/__init__.py +++ b/suds/sax/__init__.py @@ -68,6 +68,10 @@ class Namespace: def create(cls, p=None, u=None): return (p, u) + @classmethod + def none(cls, ns): + return ( ns == cls.default ) + @classmethod def xsd(cls, ns): try: diff --git a/suds/sax/date.py b/suds/sax/date.py index 81a7a10..6e31c4c 100644 --- a/suds/sax/date.py +++ b/suds/sax/date.py @@ -120,6 +120,8 @@ class Time: - HH:MI:SS.ms(z|Z) - HH:MI:SS(+|-)06:00 - HH:MI:SS.ms(+|-)06:00 + @ivar tz: The timezone + @type tz: L{Timezone} @ivar date: The object value. @type date: B{datetime}.I{time} """ @@ -181,8 +183,7 @@ def __adjust(self): """ if hasattr(self, 'offset'): today = dt.date.today() - tz = Timezone() - delta = Timezone.adjustment(self.offset) + delta = self.tz.adjustment(self.offset) d = dt.datetime.combine(today, self.time) d = ( d + delta ) self.time = d.time() @@ -303,8 +304,7 @@ def __adjust(self): """ if not hasattr(self, 'offset'): return - tz = Timezone() - delta = Timezone.adjustment(self.offset) + delta = self.tz.adjustment(self.offset) try: d = ( self.datetime + delta ) self.datetime = d @@ -341,13 +341,17 @@ class Timezone: @cvar local: The (A) local TZ offset. @type local: int @cvar patten: The regex patten to match TZ. - @type patten: L{re.RegexObject} + @type patten: re.Pattern """ pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})') + + LOCAL = ( 0-time.timezone/60/60 ) - def __init__(self): - self.local = ( 0-time.timezone/60/60 ) + def __init__(self, offset=None): + if offset is None: + offset = self.LOCAL + self.local = offset @classmethod def split(cls, s): @@ -363,13 +367,12 @@ def split(cls, s): return (s,) x = m.start(0) return (s[:x], s[x:]) - - @classmethod - def adjustment(cls, offset): + + def adjustment(self, offset): """ Get the adjustment to the I{local} TZ. @return: The delta between I{offset} and local TZ. @rtype: B{datetime}.I{timedelta} """ - delta = ( cls.local - offset ) + delta = ( self.local - offset ) return dt.timedelta(hours=delta) diff --git a/suds/sax/document.py b/suds/sax/document.py index c7129cb..5a004eb 100644 --- a/suds/sax/document.py +++ b/suds/sax/document.py @@ -27,6 +27,8 @@ class Document(Element): """ simple document """ + + DECL = '' def __init__(self, root=None): Element.__init__(self, 'document') @@ -34,18 +36,26 @@ def __init__(self, root=None): self.append(root) def root(self): - if len(self.children) > 0: + if len(self.children): return self.children[0] else: return None + def str(self): + s = [] + s.append(self.DECL) + s.append('\n') + s.append(self.root().str()) + return ''.join(s) + + def plain(self): + s = [] + s.append(self.DECL) + s.append(self.root().plain()) + return ''.join(s) + def __str__(self): return unicode(self).encode('utf-8') def __unicode__(self): - result = '' - root = self.root() - if root is not None: - result += '\n' - result += root.str() - return unicode(result) + return self.str() \ No newline at end of file diff --git a/suds/sax/element.py b/suds/sax/element.py index ca44801..9dec1f9 100644 --- a/suds/sax/element.py +++ b/suds/sax/element.py @@ -767,6 +767,29 @@ def str(self, indent=0): result.append('' % self.qname()) result = ''.join(result) return result + + def plain(self): + """ + Get a string representation of this XML fragment. + @return: A I{plain} string. + @rtype: basestring + """ + result = [] + result.append('<%s' % self.qname()) + result.append(self.nsdeclarations()) + for a in [unicode(a) for a in self.attributes]: + result.append(' %s' % a) + if self.isempty(): + result.append('/>') + return ''.join(result) + result.append('>') + if self.hasText(): + result.append(self.text.escape()) + for c in self.children: + result.append(c.plain()) + result.append('' % self.qname()) + result = ''.join(result) + return result def nsdeclarations(self): """ @@ -923,6 +946,41 @@ def __str__(self): def __unicode__(self): return self.str() + + def __iter__(self): + return NodeIterator(self) + + +class NodeIterator: + """ + The L{Element} child node iterator. + @ivar pos: The current position + @type pos: int + @ivar children: A list of a child nodes. + @type children: [L{Element},..] + """ + + def __init__(self, parent): + """ + @param parent: An element to iterate. + @type parent: L{Element} + """ + self.pos = 0 + self.children = parent.children + + def next(self): + """ + Get the next child. + @return: The next child. + @rtype: L{Element} + @raise StopIterator: At the end. + """ + try: + child = self.children[self.pos] + self.pos += 1 + return child + except: + raise StopIteration() class PrefixNormalizer: diff --git a/suds/soaparray.py b/suds/soaparray.py index c7b85e5..04847d5 100644 --- a/suds/soaparray.py +++ b/suds/soaparray.py @@ -39,7 +39,10 @@ def __init__(self, schema, root, aty): @type aty: The value of wsdl:arrayType. """ SXAttribute.__init__(self, schema, root) - self.aty = aty[:-2] + if aty.endswith('[]'): + self.aty = aty[:-2] + else: + self.aty = aty def autoqualified(self): aqs = SXAttribute.autoqualified(self) diff --git a/suds/umx/core.py b/suds/umx/core.py index 7fb4ac4..07d33c4 100644 --- a/suds/umx/core.py +++ b/suds/umx/core.py @@ -135,7 +135,7 @@ def append_children(self, content): @param content: The current content being unmarshalled. @type content: L{Content} """ - for child in content.node.children: + for child in content.node: cont = Content(child) cval = self.append(cont) key = reserved.get(child.name, child.name) diff --git a/suds/xsd/doctor.py b/suds/xsd/doctor.py index 84c9151..d7bbc14 100644 --- a/suds/xsd/doctor.py +++ b/suds/xsd/doctor.py @@ -22,6 +22,7 @@ from logging import getLogger from suds.sax import splitPrefix, Namespace from suds.sax.element import Element +from suds.plugin import DocumentPlugin, DocumentContext log = getLogger(__name__) @@ -186,7 +187,7 @@ def exists(self, root): return 0 -class ImportDoctor(Doctor): +class ImportDoctor(Doctor, DocumentPlugin): """ Doctor used to fix missing imports. @ivar imports: A list of imports to apply. @@ -207,6 +208,19 @@ def add(self, *imports): """ self.imports += imports - def examine(self, root): + def examine(self, node): for imp in self.imports: - imp.apply(root) + imp.apply(node) + + def parsed(self, context): + node = context.document + # xsd root + if node.name == 'schema' and Namespace.xsd(node.namespace()): + self.examine(node) + return + # look deeper + context = DocumentContext() + for child in node: + context.document = child + self.parsed(context) + \ No newline at end of file diff --git a/suds/xsd/schema.py b/suds/xsd/schema.py index a22f1c5..cb7d678 100644 --- a/suds/xsd/schema.py +++ b/suds/xsd/schema.py @@ -22,7 +22,7 @@ tranparent referenced type resolution and targeted denormalization. """ -from logging import getLogger + import suds.metrics from suds import * from suds.xsd import * @@ -33,6 +33,7 @@ from suds.xsd.deplist import DepList from suds.sax.element import Element from suds.sax import splitPrefix, Namespace +from logging import getLogger log = getLogger(__name__) @@ -166,16 +167,22 @@ class Schema: @type baseurl: str @ivar container: A schema collection containing this schema. @type container: L{SchemaCollection} - @ivar types: A schema types cache. - @type types: {name:L{SchemaObject}} - @ivar groups: A schema groups cache. - @type groups: {name:L{SchemaObject}} @ivar children: A list of direct top level children. @type children: [L{SchemaObject},...] @ivar all: A list of all (includes imported) top level children. @type all: [L{SchemaObject},...] + @ivar types: A schema types cache. + @type types: {name:L{SchemaObject}} @ivar imports: A list of import objects. @type imports: [L{SchemaObject},...] + @ivar elements: A list of objects. + @type elements: [L{SchemaObject},...] + @ivar attributes: A list of objects. + @type attributes: [L{SchemaObject},...] + @ivar groups: A list of group objects. + @type groups: [L{SchemaObject},...] + @ivar agrps: A list of attribute group objects. + @type agrps: [L{SchemaObject},...] @ivar form_qualified: The flag indicating: (@elementFormDefault). @type form_qualified: bool diff --git a/suds/xsd/sxbase.py b/suds/xsd/sxbase.py index 9c2cb1f..2577ffd 100644 --- a/suds/xsd/sxbase.py +++ b/suds/xsd/sxbase.py @@ -23,6 +23,7 @@ from suds import * from suds.xsd import * from suds.sax.element import Element +from suds.sax import Namespace log = getLogger(__name__) @@ -220,7 +221,7 @@ def resolve(self, nobuiltin=False): def sequence(self): """ Get whether this is an - @return: True if any, else False + @return: True if , else False @rtype: boolean """ return False @@ -296,6 +297,12 @@ def restriction(self): @rtype: boolean """ return False + + def mixed(self): + """ + Get whether this I{mixed} content. + """ + return False def find(self, qref, classes=()): """ @@ -354,9 +361,14 @@ def autoqualified(self): def qualify(self): """ Convert attribute values, that are references to other - objects, into I{qref}. + objects, into I{qref}. Qualfied using default document namespace. + Since many wsdls are written improperly: when the document does + not define a default namespace, the schema target namespace is used + to qualify references. """ defns = self.root.defaultNamespace() + if Namespace.none(defns): + defns = self.schema.tns for a in self.autoqualified(): ref = getattr(self, a) if ref is None: diff --git a/suds/xsd/sxbasic.py b/suds/xsd/sxbasic.py index 0b536d2..2506e04 100644 --- a/suds/xsd/sxbasic.py +++ b/suds/xsd/sxbasic.py @@ -118,6 +118,12 @@ def extension(self): if c.extension(): return True return False + + def mixed(self): + for c in self.rawchildren: + if isinstance(c, SimpleContent) and c.mixed(): + return True + return False class Group(SchemaObject): @@ -195,6 +201,9 @@ def enum(self): if isinstance(child, Enumeration): return True return False + + def mixed(self): + return len(self) def description(self): return ('name',) @@ -340,6 +349,9 @@ def restriction(self): if c.restriction(): return True return False + + def mixed(self): + return len(self) class Enumeration(Content): @@ -470,7 +482,7 @@ def extension(self): def description(self): return ('ref',) - + class Import(SchemaObject): """ From b80d6cc3bbb344b20ffd190ac3bf728eadfbbb1e Mon Sep 17 00:00:00 2001 From: Raydel Miranda Date: Mon, 5 Jun 2017 11:06:09 -0400 Subject: [PATCH 2/3] Adding files --- suds_transbank_chile.egg-info/PKG-INFO | 10 +++ suds_transbank_chile.egg-info/SOURCES.txt | 61 +++++++++++++++++++ .../dependency_links.txt | 1 + suds_transbank_chile.egg-info/top_level.txt | 1 + 4 files changed, 73 insertions(+) create mode 100644 suds_transbank_chile.egg-info/PKG-INFO create mode 100644 suds_transbank_chile.egg-info/SOURCES.txt create mode 100644 suds_transbank_chile.egg-info/dependency_links.txt create mode 100644 suds_transbank_chile.egg-info/top_level.txt diff --git a/suds_transbank_chile.egg-info/PKG-INFO b/suds_transbank_chile.egg-info/PKG-INFO new file mode 100644 index 0000000..848a962 --- /dev/null +++ b/suds_transbank_chile.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: suds-transbank-chile +Version: 0.4 +Summary: Lightweight SOAP client (Adapted For working with Transbank, Chile) +Home-page: https://fedorahosted.org/suds +Author: Jeff Ortel +Author-email: jortel@redhat.com +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/suds_transbank_chile.egg-info/SOURCES.txt b/suds_transbank_chile.egg-info/SOURCES.txt new file mode 100644 index 0000000..bdb2d32 --- /dev/null +++ b/suds_transbank_chile.egg-info/SOURCES.txt @@ -0,0 +1,61 @@ +README +setup.cfg +suds/__init__.py +suds/builder.py +suds/cache.py +suds/client.py +suds/metrics.py +suds/options.py +suds/plugin.py +suds/properties.py +suds/reader.py +suds/resolver.py +suds/servicedefinition.py +suds/serviceproxy.py +suds/soaparray.py +suds/store.py +suds/sudsobject.py +suds/wsdl.py +suds/wsse.py +suds/bindings/__init__.py +suds/bindings/binding.py +suds/bindings/document.py +suds/bindings/multiref.py +suds/bindings/rpc.py +suds/mx/__init__.py +suds/mx/appender.py +suds/mx/basic.py +suds/mx/core.py +suds/mx/encoded.py +suds/mx/literal.py +suds/mx/typer.py +suds/sax/__init__.py +suds/sax/attribute.py +suds/sax/date.py +suds/sax/document.py +suds/sax/element.py +suds/sax/enc.py +suds/sax/parser.py +suds/sax/text.py +suds/transport/__init__.py +suds/transport/http.py +suds/transport/https.py +suds/transport/options.py +suds/umx/__init__.py +suds/umx/attrlist.py +suds/umx/basic.py +suds/umx/core.py +suds/umx/encoded.py +suds/umx/typed.py +suds/xsd/__init__.py +suds/xsd/deplist.py +suds/xsd/doctor.py +suds/xsd/query.py +suds/xsd/schema.py +suds/xsd/sxbase.py +suds/xsd/sxbasic.py +suds/xsd/sxbuiltin.py +suds_transbank_chile.egg-info/PKG-INFO +suds_transbank_chile.egg-info/SOURCES.txt +suds_transbank_chile.egg-info/dependency_links.txt +suds_transbank_chile.egg-info/top_level.txt \ No newline at end of file diff --git a/suds_transbank_chile.egg-info/dependency_links.txt b/suds_transbank_chile.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/suds_transbank_chile.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/suds_transbank_chile.egg-info/top_level.txt b/suds_transbank_chile.egg-info/top_level.txt new file mode 100644 index 0000000..55a2df3 --- /dev/null +++ b/suds_transbank_chile.egg-info/top_level.txt @@ -0,0 +1 @@ +suds From ce4fd08885a47ba7c6d9a6ca8928af09a9d55d67 Mon Sep 17 00:00:00 2001 From: Raydel Miranda Date: Mon, 5 Jun 2017 11:06:59 -0400 Subject: [PATCH 3/3] Changing name. We change the name here for descriptive purposes. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 659e123..51f8236 100644 --- a/setup.py +++ b/setup.py @@ -21,9 +21,9 @@ from setuptools import setup, find_packages setup( - name="suds", + name="suds-transbank-chile", version=suds.__version__, - description="Lightweight SOAP client", + description="Lightweight SOAP client (Adapted For working with Transbank, Chile)", author="Jeff Ortel", author_email="jortel@redhat.com", maintainer="Jeff Ortel",