1
2#
3# spyne - Copyright (C) Spyne contributors.
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
18#
19
20"""
21Some code dump from some time ago.
22
23If you're using this for anything serious, you're insane.
24"""
25
26from __future__ import absolute_import
27
28import logging
29logger = logging.getLogger(__name__)
30
31from inspect import isclass
32
33from spyne import rpc, Any, AnyDict, NATIVE_MAP, M, Array, ComplexModelBase, \
34    UnsignedInteger32, PushBase, Iterable, ModelBase, File, Service, \
35    ResourceNotFoundError, Unicode
36
37from spyne.const import MAX_ARRAY_ELEMENT_NUM, MAX_DICT_ELEMENT_NUM, \
38    MAX_STRING_FIELD_LENGTH, MAX_FIELD_NUM
39
40try:
41    from spyne.store.relational.document import FileData
42    from sqlalchemy.orm.exc import DetachedInstanceError
43except ImportError:
44    # these are used just for isinstance checks. so we just set it to an
45    # anonymous value
46    FileData = type('__hidden', (object, ), {})
47    DetachedInstanceError = type('__hidden', (Exception, ), {})
48
49from spyne.util import memoize, six
50
51EXCEPTION_ADDRESS = None
52
53
54try:
55    from colorama.ansi import Fore
56    from colorama.ansi import Style
57    RED = Fore.RED + Style.BRIGHT
58    GREEN = Fore.GREEN + Style.BRIGHT
59    RESET = Style.RESET_ALL
60
61except ImportError:
62    RED = ""
63    GREEN = ""
64    RESET = ""
65
66
67class ReaderService(Service):
68    pass
69
70
71class WriterService(Service):
72    pass
73
74
75def log_repr(obj, cls=None, given_len=None, parent=None, from_array=False,
76                                                          tags=None, prot=None):
77    """Use this function if you want to serialize a ComplexModelBase instance to
78    logs. It will:
79
80        * Limit size of the String types
81        * Limit size of Array types
82        * Not try to iterate on iterators, push data, etc.
83    """
84
85    if tags is None:
86        tags = set()
87
88    if obj is None:
89        return 'None'
90
91    objcls = None
92    if hasattr(obj, '__class__'):
93        objcls = obj.__class__
94
95    if objcls in (list, tuple):
96        objcls = Array(Any)
97
98    elif objcls is dict:
99        objcls = AnyDict
100
101    elif objcls in NATIVE_MAP:
102        objcls = NATIVE_MAP[objcls]
103
104    if objcls is not None and (cls is None or issubclass(objcls, cls)):
105        cls = objcls
106
107    cls_attrs = None
108    logged = None
109
110    if hasattr(cls, 'Attributes'):
111        if prot is None:
112            cls_attrs = cls.Attributes
113        else:
114            cls_attrs = prot.get_cls_attrs(cls)
115
116        logged = cls_attrs.logged
117        if not logged:
118            return "%s(...)" % cls.get_type_name()
119
120        if logged == '...':
121            return "(...)"
122
123    if issubclass(cls, File) and isinstance(obj, File.Value):
124        cls = obj.__class__
125
126    if cls_attrs.logged == 'len':
127        l = '?'
128        try:
129            if isinstance(obj, (list, tuple)):
130                l = str(sum([len(o) for o in obj]))
131
132            else:
133                l = str(len(obj))
134        except (TypeError, ValueError):
135            if given_len is not None:
136                l = str(given_len)
137
138        return "<len=%s>" % l
139
140    if callable(cls_attrs.logged):
141        try:
142            return cls_attrs.logged(obj)
143        except Exception as e:
144            logger.error("Exception %r in log_repr transformer ignored", e)
145            logger.exception(e)
146            pass
147
148    if issubclass(cls, AnyDict):
149        retval = []
150
151        if isinstance(obj, dict):
152            if logged == 'full':
153                for i, (k, v) in enumerate(obj.items()):
154                    retval.append('%r: %r' % (k, v))
155
156            elif logged == 'keys':
157                for i, k in enumerate(obj.keys()):
158                    if i >= MAX_DICT_ELEMENT_NUM:
159                        retval.append("(...)")
160                        break
161
162                    retval.append('%r: (...)' % (k,))
163
164            elif logged == 'values':
165                for i, v in enumerate(obj.values()):
166                    if i >= MAX_DICT_ELEMENT_NUM:
167                        retval.append("(...)")
168                        break
169
170                    retval.append('(...): %s' % (log_repr(v, tags=tags),))
171
172            elif logged == 'keys-full':
173                for k in obj.keys():
174                    retval.append('%r: (...)' % (k,))
175
176            elif logged == 'values-full':
177                for v in obj.values():
178                    retval.append('(...): %r' % (v,))
179
180            elif logged is True:  # default behaviour
181                for i, (k, v) in enumerate(obj.items()):
182                    if i >= MAX_DICT_ELEMENT_NUM:
183                        retval.append("(...)")
184                        break
185
186                    retval.append('%r: %s' % (k,
187                                              log_repr(v, parent=k, tags=tags)))
188            else:
189                raise ValueError("Invalid value logged=%r", logged)
190
191            return "{%s}" % ', '.join(retval)
192
193        else:
194            if logged in ('full', 'keys-full', 'values-full'):
195                retval = [repr(s) for s in obj]
196
197            else:
198                for i, v in enumerate(obj):
199                    if i >= MAX_DICT_ELEMENT_NUM:
200                        retval.append("(...)")
201                        break
202
203                    retval.append(log_repr(v, tags=tags))
204
205            return "[%s]" % ', '.join(retval)
206
207    if (issubclass(cls, Array) or (cls_attrs.max_occurs > 1)) and not from_array:
208        if id(obj) in tags:
209            return "%s(...)" % obj.__class__.__name__
210
211        tags.add(id(obj))
212
213        retval = []
214
215        subcls = cls
216        if issubclass(cls, Array):
217            subcls, = cls._type_info.values()
218
219        if isinstance(obj, PushBase):
220            return '[<PushData>]'
221
222        if logged is None:
223            logged = cls_attrs.logged
224
225        for i, o in enumerate(obj):
226            if logged != 'full' and i >= MAX_ARRAY_ELEMENT_NUM:
227                retval.append("(...)")
228                break
229
230            retval.append(log_repr(o, subcls, from_array=True, tags=tags))
231
232        return "[%s]" % (', '.join(retval))
233
234    if issubclass(cls, ComplexModelBase):
235        if id(obj) in tags:
236            return "%s(...)" % obj.__class__.__name__
237
238        tags.add(id(obj))
239
240        retval = []
241        i = 0
242
243        for k, t in cls.get_flat_type_info(cls).items():
244            if i >= MAX_FIELD_NUM:
245                retval.append("(...)")
246                break
247
248            if not t.Attributes.logged:
249                continue
250
251            if logged == '...':
252                retval.append("%s=(...)" % k)
253                continue
254
255            try:
256                v = getattr(obj, k, None)
257            except (AttributeError, KeyError, DetachedInstanceError):
258                v = None
259
260            # HACK!: sometimes non-db attributes restored from database don't
261            # get properly reinitialized.
262            if isclass(v) and issubclass(v, ModelBase):
263                continue
264
265            polymap = t.Attributes.polymap
266            if polymap is not None:
267                t = polymap.get(v.__class__, t)
268
269            if v is not None:
270                retval.append("%s=%s" % (k, log_repr(v, t, parent=k, tags=tags)))
271                i += 1
272
273        return "%s(%s)" % (cls.get_type_name(), ', '.join(retval))
274
275    if issubclass(cls, Unicode) and isinstance(obj, six.string_types):
276        if len(obj) > MAX_STRING_FIELD_LENGTH:
277            return '%r(...)' % obj[:MAX_STRING_FIELD_LENGTH]
278
279        return repr(obj)
280
281    if issubclass(cls, File) and isinstance(obj, FileData):
282        return log_repr(obj, FileData, tags=tags)
283
284    retval = repr(obj)
285
286    if len(retval) > MAX_STRING_FIELD_LENGTH:
287        retval = retval[:MAX_STRING_FIELD_LENGTH] + "(...)"
288
289    return retval
290
291
292def TReaderService(T, T_name):
293    class ReaderService(ReaderService):
294        @rpc(M(UnsignedInteger32), _returns=T,
295                    _in_message_name='get_%s' % T_name,
296                    _in_variable_names={'obj_id': "%s_id" % T_name})
297        def get(ctx, obj_id):
298            return ctx.udc.session.query(T).filter_by(id=obj_id).one()
299
300        @rpc(_returns=Iterable(T),
301                    _in_message_name='get_all_%s' % T_name)
302        def get_all(ctx):
303            return ctx.udc.session.query(T).order_by(T.id)
304
305    return ReaderService
306
307
308def TWriterService(T, T_name, put_not_found='raise'):
309    assert put_not_found in ('raise', 'fix')
310
311    if put_not_found == 'raise':
312        def put_not_found(obj):
313            raise ResourceNotFoundError('%s.id=%d' % (T_name, obj.id))
314
315    elif put_not_found == 'fix':
316        def put_not_found(obj):
317            obj.id = None
318
319    class WriterService(WriterService):
320        @rpc(M(T), _returns=UnsignedInteger32,
321                    _in_message_name='put_%s' % T_name,
322                    _in_variable_names={'obj': T_name})
323        def put(ctx, obj):
324            if obj.id is None:
325                ctx.udc.session.add(obj)
326                ctx.udc.session.flush() # so that we get the obj.id value
327
328            else:
329                if ctx.udc.session.query(T).get(obj.id) is None:
330                    # this is to prevent the client from setting the primary key
331                    # of a new object instead of the database's own primary-key
332                    # generator.
333                    # Instead of raising an exception, you can also choose to
334                    # ignore the primary key set by the client by silently doing
335                    # obj.id = None in order to have the database assign the
336                    # primary key the traditional way.
337                    put_not_found(obj.id)
338
339                else:
340                    ctx.udc.session.merge(obj)
341
342            return obj.id
343
344        @rpc(M(UnsignedInteger32),
345                    _in_message_name='del_%s' % T_name,
346                    _in_variable_names={'obj_id': '%s_id' % T_name})
347        def del_(ctx, obj_id):
348            count = ctx.udc.session.query(T).filter_by(id=obj_id).count()
349            if count == 0:
350                raise ResourceNotFoundError(obj_id)
351
352            ctx.udc.session.query(T).filter_by(id=obj_id).delete()
353
354    return WriterService
355