1# This program is free software; you can redistribute it and/or modify it under
2# the terms of the (LGPL) GNU Lesser General Public License as published by the
3# Free Software Foundation; either version 3 of the License, or (at your
4# option) any later version.
5#
6# This program is distributed in the hope that it will be useful, but WITHOUT
7# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
8# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
9# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
10#
11# You should have received a copy of the GNU Lesser General Public License
12# along with this program; if not, write to the Free Software Foundation, Inc.,
13# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14# written by: Jeff Ortel ( jortel@redhat.com )
15
16"""
17Provides a collection of suds objects primarily used for highly dynamic
18interactions with WSDL/XSD defined types.
19
20"""
21
22from suds import *
23
24from logging import getLogger
25log = getLogger(__name__)
26
27
28def items(sobject):
29    """
30    Extract the I{items} from a suds object.
31
32    Much like the items() method works on I{dict}.
33
34    @param sobject: A suds object
35    @type sobject: L{Object}
36    @return: A list of items contained in I{sobject}.
37    @rtype: [(key, value),...]
38
39    """
40    for item in sobject:
41        yield item
42
43
44def asdict(sobject):
45    """
46    Convert a sudsobject into a dictionary.
47
48    @param sobject: A suds object
49    @type sobject: L{Object}
50    @return: A python dictionary containing the items contained in I{sobject}.
51    @rtype: dict
52
53    """
54    return dict(items(sobject))
55
56def merge(a, b):
57    """
58    Merge all attributes and metadata from I{a} to I{b}.
59
60    @param a: A I{source} object
61    @type a: L{Object}
62    @param b: A I{destination} object
63    @type b: L{Object}
64
65    """
66    for item in a:
67        setattr(b, item[0], item[1])
68        b.__metadata__ = b.__metadata__
69    return b
70
71def footprint(sobject):
72    """
73    Get the I{virtual footprint} of the object.
74
75    This is really a count of all the significant value attributes in the
76    branch.
77
78    @param sobject: A suds object.
79    @type sobject: L{Object}
80    @return: The branch footprint.
81    @rtype: int
82
83    """
84    n = 0
85    for a in sobject.__keylist__:
86        v = getattr(sobject, a)
87        if v is None:
88            continue
89        if isinstance(v, Object):
90            n += footprint(v)
91            continue
92        if hasattr(v, "__len__"):
93            if len(v):
94                n += 1
95            continue
96        n += 1
97    return n
98
99
100class Factory:
101
102    cache = {}
103
104    @classmethod
105    def subclass(cls, name, bases, dict={}):
106        if not isinstance(bases, tuple):
107            bases = (bases,)
108        # name is of type unicode in python 2 -> not accepted by type()
109        name = str(name)
110        key = ".".join((name, str(bases)))
111        subclass = cls.cache.get(key)
112        if subclass is None:
113            subclass = type(name, bases, dict)
114            cls.cache[key] = subclass
115        return subclass
116
117    @classmethod
118    def object(cls, classname=None, dict={}):
119        if classname is not None:
120            subclass = cls.subclass(classname, Object)
121            inst = subclass()
122        else:
123            inst = Object()
124        for a in list(dict.items()):
125            setattr(inst, a[0], a[1])
126        return inst
127
128    @classmethod
129    def metadata(cls):
130        return Metadata()
131
132    @classmethod
133    def property(cls, name, value=None):
134        subclass = cls.subclass(name, Property)
135        return subclass(value)
136
137
138class Object(UnicodeMixin):
139
140    def __init__(self):
141        self.__keylist__ = []
142        self.__printer__ = Printer()
143        self.__metadata__ = Metadata()
144
145    def __setattr__(self, name, value):
146        builtin = name.startswith("__") and name.endswith("__")
147        if not builtin and name not in self.__keylist__:
148            self.__keylist__.append(name)
149        self.__dict__[name] = value
150
151    def __delattr__(self, name):
152        try:
153            del self.__dict__[name]
154            builtin = name.startswith("__") and name.endswith("__")
155            if not builtin:
156                self.__keylist__.remove(name)
157        except Exception:
158            cls = self.__class__.__name__
159            raise AttributeError("%s has no attribute '%s'" % (cls, name))
160
161    def __getitem__(self, name):
162        if isinstance(name, int):
163            name = self.__keylist__[int(name)]
164        return getattr(self, name)
165
166    def __setitem__(self, name, value):
167        setattr(self, name, value)
168
169    def __iter__(self):
170        return Iter(self)
171
172    def __len__(self):
173        return len(self.__keylist__)
174
175    def __contains__(self, name):
176        return name in self.__keylist__
177
178    def __repr__(self):
179        return str(self)
180
181    def __unicode__(self):
182        return self.__printer__.tostr(self)
183
184
185class Iter:
186
187    def __init__(self, sobject):
188        self.sobject = sobject
189        self.keylist = self.__keylist(sobject)
190        self.index = 0
191
192    def __next__(self):
193        keylist = self.keylist
194        nkeys = len(self.keylist)
195        while self.index < nkeys:
196            k = keylist[self.index]
197            self.index += 1
198            if hasattr(self.sobject, k):
199                v = getattr(self.sobject, k)
200                return (k, v)
201        raise StopIteration()
202
203    def __keylist(self, sobject):
204        keylist = sobject.__keylist__
205        try:
206            keyset = set(keylist)
207            ordering = sobject.__metadata__.ordering
208            ordered = set(ordering)
209            if not ordered.issuperset(keyset):
210                log.debug("%s must be superset of %s, ordering ignored",
211                    keylist, ordering)
212                raise KeyError()
213            return ordering
214        except Exception:
215            return keylist
216
217    def __iter__(self):
218        return self
219
220
221class Metadata(Object):
222    def __init__(self):
223        self.__keylist__ = []
224        self.__printer__ = Printer()
225
226
227class Facade(Object):
228    def __init__(self, name):
229        Object.__init__(self)
230        md = self.__metadata__
231        md.facade = name
232
233
234class Property(Object):
235
236    def __init__(self, value):
237        Object.__init__(self)
238        self.value = value
239
240    def items(self):
241        for item in self:
242            if item[0] != "value":
243                yield item
244
245    def get(self):
246        return self.value
247
248    def set(self, value):
249        self.value = value
250        return self
251
252
253class Printer:
254    """Pretty printing of a Object object."""
255
256    @classmethod
257    def indent(cls, n):
258        return "%*s" % (n * 3, " ")
259
260    def tostr(self, object, indent=-2):
261        """Get s string representation of object."""
262        history = []
263        return self.process(object, history, indent)
264
265    def process(self, object, h, n=0, nl=False):
266        """Print object using the specified indent (n) and newline (nl)."""
267        if object is None:
268            return "None"
269        if isinstance(object, Object):
270            if len(object) == 0:
271                return "<empty>"
272            return self.print_object(object, h, n + 2, nl)
273        if isinstance(object, dict):
274            if len(object) == 0:
275                return "<empty>"
276            return self.print_dictionary(object, h, n + 2, nl)
277        if isinstance(object, (list, tuple)):
278            if len(object) == 0:
279                return "<empty>"
280            return self.print_collection(object, h, n + 2)
281        if isinstance(object, str):
282            return '"%s"' % (tostr(object),)
283        return "%s" % (tostr(object),)
284
285    def print_object(self, d, h, n, nl=False):
286        """Print complex using the specified indent (n) and newline (nl)."""
287        s = []
288        cls = d.__class__
289        if d in h:
290            s.append("(")
291            s.append(cls.__name__)
292            s.append(")")
293            s.append("...")
294            return "".join(s)
295        h.append(d)
296        if nl:
297            s.append("\n")
298            s.append(self.indent(n))
299        if cls != Object:
300            s.append("(")
301            if isinstance(d, Facade):
302                s.append(d.__metadata__.facade)
303            else:
304                s.append(cls.__name__)
305            s.append(")")
306        s.append("{")
307        for item in d:
308            if self.exclude(d, item):
309                continue
310            item = self.unwrap(d, item)
311            s.append("\n")
312            s.append(self.indent(n+1))
313            if isinstance(item[1], (list,tuple)):
314                s.append(item[0])
315                s.append("[]")
316            else:
317                s.append(item[0])
318            s.append(" = ")
319            s.append(self.process(item[1], h, n, True))
320        s.append("\n")
321        s.append(self.indent(n))
322        s.append("}")
323        h.pop()
324        return "".join(s)
325
326    def print_dictionary(self, d, h, n, nl=False):
327        """Print complex using the specified indent (n) and newline (nl)."""
328        if d in h:
329            return "{}..."
330        h.append(d)
331        s = []
332        if nl:
333            s.append("\n")
334            s.append(self.indent(n))
335        s.append("{")
336        for item in list(d.items()):
337            s.append("\n")
338            s.append(self.indent(n+1))
339            if isinstance(item[1], (list,tuple)):
340                s.append(tostr(item[0]))
341                s.append("[]")
342            else:
343                s.append(tostr(item[0]))
344            s.append(" = ")
345            s.append(self.process(item[1], h, n, True))
346        s.append("\n")
347        s.append(self.indent(n))
348        s.append("}")
349        h.pop()
350        return "".join(s)
351
352    def print_collection(self, c, h, n):
353        """Print collection using the specified indent (n) and newline (nl)."""
354        if c in h:
355            return "[]..."
356        h.append(c)
357        s = []
358        for item in c:
359            s.append("\n")
360            s.append(self.indent(n))
361            s.append(self.process(item, h, n - 2))
362            s.append(",")
363        h.pop()
364        return "".join(s)
365
366    def unwrap(self, d, item):
367        """Translate (unwrap) using an optional wrapper function."""
368        try:
369            md = d.__metadata__
370            pmd = getattr(md, "__print__", None)
371            if pmd is None:
372                return item
373            wrappers = getattr(pmd, "wrappers", {})
374            fn = wrappers.get(item[0], lambda x: x)
375            return (item[0], fn(item[1]))
376        except Exception:
377            pass
378        return item
379
380    def exclude(self, d, item):
381        """Check metadata for excluded items."""
382        try:
383            md = d.__metadata__
384            pmd = getattr(md, "__print__", None)
385            if pmd is None:
386                return False
387            excludes = getattr(pmd, "excludes", [])
388            return item[0] in excludes
389        except Exception:
390            pass
391        return False
392