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"""This module contains the ModelBase class and other building blocks for
21defining models.
22"""
23
24from __future__ import print_function
25
26import logging
27logger = logging.getLogger(__name__)
28
29import re
30import decimal
31import threading
32
33import spyne.const.xml
34
35from copy import deepcopy
36from collections import OrderedDict
37
38from spyne import const
39from spyne.util import Break, six
40from spyne.util.cdict import cdict
41from spyne.util.odict import odict
42
43from spyne.const.xml import DEFAULT_NS
44
45
46def _decode_pa_dict(d):
47    """Decodes dict passed to prot_attrs.
48
49    >>> _decode_pa_dict({})
50    cdict({})
51    >>> _decode_pa_dict({1: 2)})
52    cdict({1: 2})
53    >>> _decode_pa_dict({(1,2): 3)})
54    cdict({1: 3, 2: 3})
55    """
56
57    retval = cdict()
58    for k, v in d.items():
59        if isinstance(k, (frozenset, tuple)):
60            for subk in k:
61                retval[subk] = v
62
63    for k, v in d.items():
64        if not isinstance(k, (frozenset, tuple)):
65            retval[k] = v
66
67    return retval
68
69
70class AttributesMeta(type(object)):
71    NULLABLE_DEFAULT = True
72
73    def __new__(cls, cls_name, cls_bases, cls_dict):
74        # Mapper args should not be inherited.
75        if not 'sqla_mapper_args' in cls_dict:
76            cls_dict['sqla_mapper_args'] = None
77
78        rd = {}
79        for k in list(cls_dict.keys()):
80            if k in ('parser', 'cast'):
81                rd['parser'] = cls_dict.pop(k)
82                continue
83
84            if k in ('sanitize', 'sanitizer'):
85                rd['sanitizer'] = cls_dict.pop(k)
86                continue
87
88            if k == 'logged':
89                rd['logged'] = cls_dict.pop(k)
90                continue
91
92        retval = super(AttributesMeta, cls).__new__(cls, cls_name, cls_bases,
93                                                                       cls_dict)
94
95        for k, v in rd.items():
96            if v is None:
97                setattr(retval, k, None)
98            else:
99                setattr(retval, k, staticmethod(v))
100
101        return retval
102
103    def __init__(self, cls_name, cls_bases, cls_dict):
104        # you will probably want to look at ModelBase._s_customize as well.
105        if not hasattr(self, '_method_config_do'):
106            self._method_config_do = None
107
108        nullable = cls_dict.get('nullable', None)
109        nillable = cls_dict.get('nillable', None)
110        if nullable is not None:
111            assert nillable is None or nullable == nillable
112            self._nullable = nullable
113
114        elif nillable is not None:
115            assert nullable is None or nullable == nillable
116            self._nullable = nillable
117
118        if not hasattr(self, '_nullable'):
119            self._nullable = None
120
121        if not hasattr(self, '_default_factory'):
122            self._default_factory = None
123
124        if not hasattr(self, '_html_cloth'):
125            self._html_cloth = None
126        if not hasattr(self, '_html_root_cloth'):
127            self._html_root_cloth = None
128
129        if 'html_cloth' in cls_dict:
130            self.set_html_cloth(cls_dict.pop('html_cloth'))
131        if 'html_root_cloth' in cls_dict:
132            self.set_html_cloth(cls_dict.pop('html_root_cloth'))
133
134        if not hasattr(self, '_xml_cloth'):
135            self._xml_cloth = None
136        if not hasattr(self, '_xml_root_cloth'):
137            self._xml_root_cloth = None
138
139        if 'xml_cloth' in cls_dict:
140            self.set_xml_cloth(cls_dict.pop('xml_cloth'))
141
142        if 'xml_root_cloth' in cls_dict:
143            self.set_xml_cloth(cls_dict.pop('xml_root_cloth'))
144
145        if 'method_config_do' in cls_dict and \
146                                       cls_dict['method_config_do'] is not None:
147            cls_dict['method_config_do'] = \
148                                      staticmethod(cls_dict['method_config_do'])
149
150        super(AttributesMeta, self).__init__(cls_name, cls_bases, cls_dict)
151
152    def get_nullable(self):
153        return (self._nullable if self._nullable is not None else
154                                                          self.NULLABLE_DEFAULT)
155
156    def set_nullable(self, what):
157        self._nullable = what
158
159    nullable = property(get_nullable, set_nullable)
160
161    def get_nillable(self):
162        return self.nullable
163
164    def set_nillable(self, what):
165        self.nullable = what
166
167    nillable = property(get_nillable, set_nillable)
168
169    def get_default_factory(self):
170        return self._default_factory
171
172    def set_default_factory(self, what):
173        self._default_factory = staticmethod(what)
174
175    default_factory = property(get_default_factory, set_default_factory)
176
177    def get_html_cloth(self):
178        return self._html_cloth
179    def set_html_cloth(self, what):
180        from spyne.protocol.cloth.to_cloth import ClothParserMixin
181        cm = ClothParserMixin.from_html_cloth(what)
182        if cm._root_cloth is not None:
183            self._html_root_cloth = cm._root_cloth
184        elif cm._cloth is not None:
185            self._html_cloth = cm._cloth
186        else:
187            raise Exception("%r is not a suitable cloth", what)
188    html_cloth = property(get_html_cloth, set_html_cloth)
189
190    def get_html_root_cloth(self):
191        return self._html_root_cloth
192    html_root_cloth = property(get_html_root_cloth)
193
194    def get_xml_cloth(self):
195        return self._xml_cloth
196    def set_xml_cloth(self, what):
197        from spyne.protocol.cloth.to_cloth import ClothParserMixin
198        cm = ClothParserMixin.from_xml_cloth(what)
199        if cm._root_cloth is not None:
200            self._xml_root_cloth = cm._root_cloth
201        elif cm._cloth is not None:
202            self._xml_cloth = cm._cloth
203        else:
204            raise Exception("%r is not a suitable cloth", what)
205    xml_cloth = property(get_xml_cloth, set_xml_cloth)
206
207    def get_xml_root_cloth(self):
208        return self._xml_root_cloth
209    xml_root_cloth = property(get_xml_root_cloth)
210
211    def get_method_config_do(self):
212        return self._method_config_do
213    def set_method_config_do(self, what):
214        if what is None:
215            self._method_config_do = None
216        else:
217            self._method_config_do = staticmethod(what)
218    method_config_do = property(get_method_config_do, set_method_config_do)
219
220
221class ModelBaseMeta(type(object)):
222    def __getitem__(self, item):
223        return self.customize(**item)
224
225    def customize(self, **kwargs):
226        """Duplicates cls and overwrites the values in ``cls.Attributes`` with
227        ``**kwargs`` and returns the new class."""
228
229        cls_name, cls_bases, cls_dict = self._s_customize(**kwargs)
230
231        return type(cls_name, cls_bases, cls_dict)
232
233
234@six.add_metaclass(ModelBaseMeta)
235class ModelBase(object):
236    """The base class for type markers. It defines the model interface for the
237    interface generators to use and also manages class customizations that are
238    mainly used for defining constraints on input values.
239    """
240
241    __orig__ = None
242    """This holds the original class the class .customize()d from. Ie if this is
243    None, the class is not a customize()d one."""
244
245    __extends__ = None
246    """This holds the original class the class inherited or .customize()d from.
247    This is different from __orig__ because it's only set when
248    ``cls.is_default(cls) == False``"""
249
250    __namespace__ = None
251    """The public namespace of this class. Use ``get_namespace()`` instead of
252    accessing it directly."""
253
254    __type_name__ = None
255    """The public type name of the class. Use ``get_type_name()`` instead of
256    accessing it directly."""
257
258    Value = type(None)
259    """The value of this type is an instance of this class"""
260
261    # These are not the xml schema defaults. The xml schema defaults are
262    # considered in XmlSchema's add() method. the defaults here are to reflect
263    # what people seem to want most.
264    #
265    # Please note that min_occurs and max_occurs must be validated in the
266    # ComplexModelBase deserializer.
267    @six.add_metaclass(AttributesMeta)
268    class Attributes(object):
269        """The class that holds the constraints for the given type."""
270
271        _wrapper = False
272        # when skip_wrappers=True is passed to a protocol, these objects
273        # are skipped. just for internal use.
274
275        _explicit_type_name = False
276        # set to true when type_name is passed to customize() call.
277
278        out_type = None
279        """Override serialization type. Usually, this designates the return type
280        of the callable in the `sanitizer` attribute. If this is a two-way type,
281        it may be a good idea to also use the `parser` attribute to perform
282        reverse conversion."""
283
284        default = None
285        """The default value if the input is None.
286
287        Please note that this default is UNCONDITIONALLY applied in class
288        initializer. It's recommended to at least make an effort to use this
289        only in customized classes and not in original models.
290        """
291
292        default_factory = None
293        """The callable that produces a default value if the value is None.
294
295        The warnings in ``default`` apply here as well."""
296
297        db_default = None
298        """The default value used only when persisting the value if it is
299        ``None``.
300
301        Only works for primitives. Unlike ``default`` this can also be set to a
302        callable that takes no arguments according to SQLAlchemy docs."""
303
304        nillable = None
305        """Set this to false to reject null values. Synonyms with
306        ``nullable``. True by default. The default value can be changed by
307         setting ``AttributesMeta.NULLABLE_DEFAULT``."""
308
309        min_occurs = 0
310        """Set this to 1 to make this object mandatory. Can be set to any
311        positive integer. Note that an object can still be null or empty, even
312        if it's there."""
313
314        max_occurs = 1
315        """Can be set to any strictly positive integer. Values greater than 1
316        will imply an iterable of objects as native python type. Can be set to
317        ``decimal.Decimal("inf")`` for arbitrary number of arguments."""
318
319        schema_tag = spyne.const.xml.XSD('element')
320        """The tag used to add a primitives as child to a complex type in the
321        xml schema."""
322
323        translations = None
324        """A dict that contains locale codes as keys and translations of field
325        names to that language as values.
326        """
327
328        sub_ns = None
329        """An Xml-specific attribute that specifies which namespace should be
330        used for field names in classes.
331        """
332
333        sub_name = None
334        """This specifies which string should be used as field name when this
335        type is seriazed under a ComplexModel.
336        """
337
338        wsdl_part_name = None
339        """This specifies which string should be used as wsdl message part name when this
340            type is serialized under a ComplexModel ie."parameters".
341        """
342
343        sqla_column_args = None
344        """A dict that will be passed to SQLAlchemy's ``Column`` constructor as
345        ``**kwargs``.
346        """
347
348        exc_mapper = False
349        """If true, this field will be excluded from the table mapper of the
350        parent class.
351        """
352
353        exc_table = False
354        """DEPRECATED !!! Use ``exc_db`` instead."""
355
356        exc_db = False
357        """If ``True``, this field will not be persisted to the database. This
358        attribute only makes sense in a subfield of a ``ComplexModel`` subclass.
359        """
360
361        exc_interface = False
362        """If `True`, this field will be excluded from the interface
363        document."""
364
365        exc = False
366        """If `True`, this field will be excluded from all serialization or
367         deserialization operations. See `prot_attrs` to make this only apply to
368         a specific protocol class or instance."""
369
370        logged = True
371        """If `False`, this object will be ignored in ``log_repr``, mostly used
372        for logging purposes.
373
374        * Primitives can have logger=``'...'`` which will
375        always log the value as ``(...)``.
376
377        * ``AnyDict`` can have one of
378        ``('keys', 'keys-full', 'values', 'values-full, 'full')`` as logger
379        value where for ``'keys'`` and ``'values'`` the output of ``keys()``
380        and ``values()`` will be logged up to MAX_DICT_ELEMENT_NUM number of
381        elements and for ``'full'`` variants, all of the contents of the dict
382        will be logged will be logged
383
384        * ``Array`` can also have ``logger='full'`` where all of the value
385        will be logged where as for simple ``logger=True`` only
386        MAX_ARRAY_ELEMENT_NUM elements will be logged.
387
388        * For ``ComplexModel`` subclasses sent as first value to log_repr,
389        ``logger=False`` means a string of form ``ClassName(...)`` will  be
390        logged.
391        """
392
393        sanitizer = None
394        """A callable that takes the associated native type and returns the
395        parsed value. Only called during serialization."""
396
397        parser = None
398        """A callable that takes the associated native type and returns the
399        parsed value. Only called during deserialization."""
400
401        unique = None
402        """If True, this object will be set as unique in the database schema
403        with default indexing options. If the value is a string, it will be
404        used as the indexing method to create the unique index. See sqlalchemy
405        documentation on how to create multi-column unique constraints.
406        """
407
408        db_type = None
409        """When not None, it overrides Spyne's own mapping from Spyne types to
410        SQLAlchemy types. It's a standard SQLAlchemy type marker, e.g.
411        ``sqlalchemy.Integer``.
412        """
413
414        table_name = None
415        """Database table name."""
416
417        xml_choice_group = None
418        """When not None, shares the same <choice> tag with fields with the same
419        xml_choice_group value.
420        """
421
422        index = None
423        """Can be ``True``, a string, or a tuple of two strings.
424
425        * If True, this object will be set as indexed in the database schema
426          with default options.
427
428        * If the value is a string, the value will denote the indexing method
429          used by the database. Should be one of:
430
431            ('btree', 'gin', 'gist', 'hash', 'spgist')
432
433          See: http://www.postgresql.org/docs/9.2/static/indexes-types.html
434
435        * If the value is a tuple of two strings, the first value will denote
436          the index name and the second value will denote the indexing method as
437          above.
438        """
439
440        read_only = False
441        """If True, the attribute won't be initialized from outside values.
442        Set this to ``True`` for e.g. read-only properties."""
443
444        prot_attrs = None
445        """Customize child attributes for protocols. It's a dict of dicts.
446        The key is either a ProtocolBase subclass or a ProtocolBase instance.
447        Instances override classes."""
448
449        pa = None
450        """Alias for prot_attrs."""
451
452        empty_is_none = False
453        """When the incoming object is empty (e.g. '' for strings) treat it as
454        None. No effect (yet) for outgoing values."""
455
456        order = None
457        """An integer that's passed to ``_type_info.insert()`` as first argument
458         when not None. ``.append()`` is used otherwise."""
459
460        validate_on_assignment = False
461        """Perform validation on assignment (i.e. all the time) instead of on
462        just serialization"""
463
464        polymap = {}
465        """A dict of classes that override polymorphic substitions for classes
466        given as keys to classes given as values."""
467
468
469    class Annotations(object):
470        """The class that holds the annotations for the given type."""
471
472        __use_parent_doc__ = False
473        """If equal to True and doc is empty, Annotations will use __doc__
474        from parent. Set it to False to avoid this mechanism. This is a
475        convenience option"""
476
477        doc = ""
478        """The public documentation for the given type."""
479
480        appinfo = None
481        """Any object that carries app-specific info."""
482
483    class Empty(object):
484        pass
485
486    _force_own_namespace = None
487
488    @classmethod
489    def ancestors(cls):
490        """Returns a list of parent classes in child-to-parent order."""
491
492        retval = []
493
494        extends = cls.__extends__
495        while extends is not None:
496            retval.append(extends)
497            extends = extends.__extends__
498
499        return retval
500
501    @staticmethod
502    def is_default(cls):
503        return True
504
505    @classmethod
506    def get_namespace_prefix(cls, interface):
507        """Returns the namespace prefix for the given interface. The
508        get_namespace_prefix of the interface class generates a prefix if none
509        is defined.
510        """
511
512        ns = cls.get_namespace()
513
514        retval = interface.get_namespace_prefix(ns)
515
516        return retval
517
518    @classmethod
519    def get_namespace(cls):
520        """Returns the namespace of the class. Defaults to the python module
521        name."""
522
523        return cls.__namespace__
524
525    @classmethod
526    def _fill_empty_type_name(cls, parent_ns, parent_tn, k):
527        cls.__namespace__ = parent_ns
528
529        cls.__type_name__ = "%s_%s%s" % (parent_tn, k, const.TYPE_SUFFIX)
530        extends = cls.__extends__
531        while extends is not None and extends.__type_name__ is ModelBase.Empty:
532            cls.__extends__._fill_empty_type_name(cls.get_namespace(),
533                               cls.get_type_name(), k + const.PARENT_SUFFIX)
534            extends = extends.__extends__
535
536    # TODO: rename to "resolve_identifier"
537    @staticmethod
538    def resolve_namespace(cls, default_ns, tags=None):
539        """This call finalizes the namespace assignment. The default namespace
540        is not available until the application calls populate_interface method
541        of the interface generator.
542        """
543
544        if tags is None:
545            tags = set()
546        elif cls in tags:
547            return False
548        tags.add(cls)
549
550        if cls.__namespace__ is spyne.const.xml.DEFAULT_NS:
551            cls.__namespace__ = default_ns
552
553        if (cls.__namespace__ in spyne.const.xml.PREFMAP and
554                                                       not cls.is_default(cls)):
555            cls.__namespace__ = default_ns
556
557        if cls.__namespace__ is None:
558            ret = []
559            for f in cls.__module__.split('.'):
560                if f.startswith('_'):
561                    break
562                else:
563                    ret.append(f)
564
565            cls.__namespace__ = '.'.join(ret)
566
567        if cls.__namespace__ is None or len(cls.__namespace__) == 0:
568            cls.__namespace__ = default_ns
569
570        if cls.__namespace__ is None or len(cls.__namespace__) == 0:
571            raise ValueError("You need to explicitly set %r.__namespace__" % cls)
572
573        # print("    resolve ns for %r to %r" % (cls, cls.__namespace__))
574
575        if getattr(cls, '__extends__', None) != None:
576            cls.__extends__.resolve_namespace(cls.__extends__, default_ns, tags)
577
578        return True
579
580    @classmethod
581    def get_type_name(cls):
582        """Returns the class name unless the __type_name__ attribute is defined.
583        """
584
585        retval = cls.__type_name__
586        if retval is None:
587            retval = cls.__name__
588
589        return retval
590
591    # FIXME: Rename this to get_type_name_with_ns_pref
592    @classmethod
593    def get_type_name_ns(cls, interface):
594        """Returns the type name with a namespace prefix, separated by a column.
595        """
596
597        if cls.get_namespace() != None:
598            return "%s:%s" % (cls.get_namespace_prefix(interface),
599                                                            cls.get_type_name())
600
601    @classmethod
602    def get_element_name(cls):
603        return cls.Attributes.sub_name or cls.get_type_name()
604
605    @classmethod
606    def get_wsdl_part_name(cls):
607        return cls.Attributes.wsdl_part_name or cls.get_element_name()
608
609    @classmethod
610    def get_element_name_ns(cls, interface):
611        ns = cls.Attributes.sub_ns or cls.get_namespace()
612        if ns is DEFAULT_NS:
613            ns = interface.get_tns()
614        if ns is not None:
615            pref = interface.get_namespace_prefix(ns)
616            return "%s:%s" % (pref, cls.get_element_name())
617
618    @classmethod
619    def to_bytes(cls, value):
620        """
621        Returns str(value). This should be overridden if this is not enough.
622        """
623        return six.binary_type(value)
624
625    @classmethod
626    def to_unicode(cls, value):
627        """
628        Returns unicode(value). This should be overridden if this is not enough.
629        """
630        return six.text_type(value)
631
632    @classmethod
633    def get_documentation(cls):
634        if cls.Annotations.doc:
635            return cls.Annotations.doc
636        elif cls.Annotations.__use_parent_doc__:
637            return cls.__doc__
638        else:
639            return ''
640
641    @classmethod
642    def _s_customize(cls, **kwargs):
643        """Sanitizes customization parameters of the class it belongs to.
644        Doesn't perform any actual customization.
645        """
646
647        def _log_debug(s, *args):
648            logger.debug("\t%s: %s" % (cls.get_type_name(), s), *args)
649
650        cls_dict = odict({'__module__': cls.__module__, '__doc__': cls.__doc__})
651
652        if getattr(cls, '__orig__', None) is None:
653            cls_dict['__orig__'] = cls
654        else:
655            cls_dict['__orig__'] = cls.__orig__
656
657        class Attributes(cls.Attributes):
658            _explicit_type_name = False
659
660        if cls.Attributes.translations is None:
661            Attributes.translations = {}
662
663        if cls.Attributes.sqla_column_args is None:
664            Attributes.sqla_column_args = (), {}
665        else:
666            Attributes.sqla_column_args = deepcopy(
667                                                cls.Attributes.sqla_column_args)
668
669        cls_dict['Attributes'] = Attributes
670
671        # properties get reset every time a new class is defined. So we need
672        # to reinitialize them explicitly.
673        for k in ('nillable', '_xml_cloth', '_xml_root_cloth', '_html_cloth',
674                                                            '_html_root_cloth'):
675            v = getattr(cls.Attributes, k)
676            if v is not None:
677                setattr(Attributes, k, v)
678
679        class Annotations(cls.Annotations):
680            pass
681        cls_dict['Annotations'] = Annotations
682
683        # get protocol attrs
684        prot = kwargs.get('protocol', None)
685        if prot is None:
686            prot = kwargs.get('prot', None)
687
688        if prot is None:
689            prot = kwargs.get('p', None)
690
691        if prot is not None and len(prot.type_attrs) > 0:
692            # if there is a class customization from protocol, do it
693
694            type_attrs = prot.type_attrs.copy()
695            type_attrs.update(kwargs)
696            _log_debug("kwargs %r => %r from prot typeattr %r",
697                                            kwargs, type_attrs, prot.type_attrs)
698            kwargs = type_attrs
699
700        # the ones that wrap values in staticmethod() should be added to
701        # AttributesMeta initializer
702        for k, v in kwargs.items():
703            if k.startswith('_'):
704                _log_debug("ignoring '%s' because of leading underscore", k)
705                continue
706
707            if k in ('protocol', 'prot', 'p'):
708                Attributes.prot = v
709                _log_debug("setting prot=%r", v)
710
711            elif k in ('voa', 'validate_on_assignment'):
712                Attributes.validate_on_assignment = v
713                _log_debug("setting voa=%r", v)
714
715            elif k in ('parser', 'in_cast'):
716                setattr(Attributes, 'parser', staticmethod(v))
717                _log_debug("setting %s=%r", k, v)
718
719            elif k in ('sanitize', 'sanitizer', 'out_cast'):
720                setattr(Attributes, 'sanitizer', staticmethod(v))
721                _log_debug("setting %s=%r as sanitizer", k, v)
722
723            elif k == 'logged':
724                setattr(Attributes, 'logged', staticmethod(v))
725                _log_debug("setting %s=%r as log sanitizer", k, v)
726
727            elif k in ("doc", "appinfo"):
728                setattr(Annotations, k, v)
729                _log_debug("setting Annotations.%s=%r", k, v)
730
731            elif k in ('primary_key', 'pk'):
732                setattr(Attributes, 'primary_key', v)
733                Attributes.sqla_column_args[-1]['primary_key'] = v
734                _log_debug("setting primary_key=%r", v)
735
736            elif k in ('protocol_attrs', 'prot_attrs', 'pa'):
737                setattr(Attributes, 'prot_attrs', _decode_pa_dict(v))
738                _log_debug("setting prot_attrs=%r", v)
739
740            elif k in ('foreign_key', 'fk'):
741                from sqlalchemy.schema import ForeignKey
742                t, d = Attributes.sqla_column_args
743                fkt = (ForeignKey(v),)
744                new_v = (t + fkt, d)
745                Attributes.sqla_column_args = new_v
746                _log_debug("setting sqla_column_args=%r", new_v)
747
748            elif k in ('autoincrement', 'onupdate', 'server_default'):
749                Attributes.sqla_column_args[-1][k] = v
750                _log_debug("adding %s=%r to Attributes.sqla_column_args", k, v)
751
752            elif k == 'values_dict':
753                assert not 'values' in v, "`values` and `values_dict` can't be" \
754                                          "specified at the same time"
755
756                if not isinstance(v, dict):
757                    # our odict has one nasty implicit behaviour: setitem on
758                    # int keys is treated as array indexes, not dict keys. so
759                    # dicts with int indexes can't work with odict. so we use
760                    # the one from stdlib
761                    v = OrderedDict(v)
762
763                Attributes.values = list(v.keys())
764                Attributes.values_dict = v
765                _log_debug("setting values=%r, values_dict=%r",
766                                      Attributes.values, Attributes.values_dict)
767
768            elif k == 'exc_table':
769                Attributes.exc_table = v
770                Attributes.exc_db = v
771                _log_debug("setting exc_table=%r, exc_db=%r", v, v)
772
773            elif k == 'max_occurs' and v in ('unbounded', 'inf', float('inf')):
774                new_v = decimal.Decimal('inf')
775                setattr(Attributes, k, new_v)
776                _log_debug("setting max_occurs=%r", new_v)
777
778            elif k == 'type_name':
779                Attributes._explicit_type_name = True
780                _log_debug("setting _explicit_type_name=True because "
781                                                          "we have 'type_name'")
782
783            else:
784                setattr(Attributes, k, v)
785                _log_debug("setting %s=%r", k, v)
786
787        return (cls.__name__, (cls,), cls_dict)
788
789    @staticmethod
790    def validate_string(cls, value):
791        """Override this method to do your own input validation on the input
792        string. This is called before converting the incoming string to the
793        native python value."""
794
795        return (cls.Attributes.nillable or value is not None)
796
797    @staticmethod
798    def validate_native(cls, value):
799        """Override this method to do your own input validation on the native
800        value. This is called after converting the incoming string to the
801        native python value."""
802
803        return (cls.Attributes.nullable or value is not None)
804
805
806class Null(ModelBase):
807    pass
808
809
810class SimpleModelAttributesMeta(AttributesMeta):
811    def __init__(self, cls_name, cls_bases, cls_dict):
812        super(SimpleModelAttributesMeta, self).__init__(cls_name, cls_bases,
813                                                                       cls_dict)
814        if getattr(self, '_pattern', None) is None:
815            self._pattern = None
816
817    def get_pattern(self):
818        return self._pattern
819
820    def set_pattern(self, pattern):
821        self._pattern = pattern
822        if pattern is not None:
823            self._pattern_re = re.compile(pattern)
824
825    pattern = property(get_pattern, set_pattern)
826
827    def get_unicode_pattern(self):
828        return self._pattern
829
830    def set_unicode_pattern(self, pattern):
831        self._pattern = pattern
832        if pattern is not None:
833            self._pattern_re = re.compile(pattern, re.UNICODE)
834
835    unicode_pattern = property(get_unicode_pattern, set_unicode_pattern)
836    upattern = property(get_unicode_pattern, set_unicode_pattern)
837
838
839class SimpleModel(ModelBase):
840    """The base class for primitives."""
841
842    __namespace__ = "http://www.w3.org/2001/XMLSchema"
843
844    @six.add_metaclass(SimpleModelAttributesMeta)
845    class Attributes(ModelBase.Attributes):
846        """The class that holds the constraints for the given type."""
847
848        values = set()
849        """The set of possible values for this type."""
850
851        # some hacks are done in _s_customize to make `values_dict`
852        # behave like `values`
853        values_dict = dict()
854        """The dict of possible values for this type. Dict keys are values and
855        dict values are either a single string or a translation dict."""
856
857        _pattern_re = None
858
859    def __new__(cls, **kwargs):
860        """Overriden so that any attempt to instantiate a primitive will return
861        a customized class instead of an instance.
862
863        See spyne.model.base.ModelBase for more information.
864        """
865
866        return cls.customize(**kwargs)
867
868    @classmethod
869    def customize(cls, **kwargs):
870        """Duplicates cls and overwrites the values in ``cls.Attributes`` with
871        ``**kwargs`` and returns the new class."""
872
873        cls_name, cls_bases, cls_dict = cls._s_customize(**kwargs)
874
875        retval = type(cls_name, cls_bases, cls_dict)
876
877        if not retval.is_default(retval):
878            retval.__extends__ = cls
879            retval.__type_name__ = kwargs.get("type_name", ModelBase.Empty)
880            if 'type_name' in kwargs:
881                logger.debug("Type name for %r was overridden as '%s'",
882                                                   retval, retval.__type_name__)
883
884        retval.resolve_namespace(retval, kwargs.get('__namespace__'))
885
886        return retval
887
888    @staticmethod
889    def is_default(cls):
890        return (cls.Attributes.values == SimpleModel.Attributes.values)
891
892    @staticmethod
893    def validate_native(cls, value):
894        return (ModelBase.validate_native(cls, value)
895                and (
896                    cls.Attributes.values is None or
897                    len(cls.Attributes.values) == 0 or (
898                        (value is None     and cls.Attributes.nillable) or
899                        (value is not None and value in cls.Attributes.values)
900                    )
901                )
902            )
903
904
905class PushBase(object):
906    def __init__(self, callback=None, errback=None):
907        self.orig_thread = threading.current_thread()
908
909        self._cb = callback
910        self._eb = errback
911
912        self.length = 0
913        self.ctx = None
914        self.app = None
915        self.gen = None
916        self._cb_finish = None
917        self._eb_finish = None
918        self.interim = False
919
920    def _init(self, ctx, gen, _cb_finish, _eb_finish, interim):
921        self.length = 0
922
923        self.ctx = ctx
924        self.app = ctx.app
925
926        self.gen = gen
927
928        self._cb_finish = _cb_finish
929        self._eb_finish = _eb_finish
930
931        self.interim = interim
932
933    def init(self, ctx, gen, _cb_finish, _eb_finish, interim):
934        self._init(ctx, gen, _cb_finish, _eb_finish, interim)
935        if self._cb is not None:
936            return self._cb(self)
937
938    def __len__(self):
939        return self.length
940
941    def append(self, inst):
942        self.gen.send(inst)
943        self.length += 1
944
945    def extend(self, insts):
946        for inst in insts:
947            self.gen.send(inst)
948            self.length += 1
949
950    def close(self):
951        try:
952            self.gen.throw(Break())
953        except (Break, StopIteration, GeneratorExit):
954            pass
955        self._cb_finish()
956
957
958class xml:
959    """Compound option object for xml serialization. It's meant to be passed to
960    :func:`ComplexModelBase.Attributes.store_as`.
961
962    :param root_tag: Root tag of the xml element that contains the field values.
963    :param no_ns: When true, the xml document is stripped from namespace
964        information. This is generally a stupid thing to do. Use with caution.
965    """
966
967    def __init__(self, root_tag=None, no_ns=False, pretty_print=False):
968        self.root_tag = root_tag
969        self.no_ns = no_ns
970        self.pretty_print = pretty_print
971
972
973class table:
974    """Compound option object for for storing the class instance as in row in a
975    table in a relational database. It's meant to be passed to
976    :func:`ComplexModelBase.Attributes.store_as`.
977
978    :param multi: When False, configures a one-to-many relationship where the
979        child table has a foreign key to the parent. When not ``False``,
980        configures a many-to-many relationship by creating an intermediate
981        relation table that has foreign keys to both parent and child classes
982        and generates a table name automatically. When ``True``, the table name
983        is generated automatically. Otherwise, it should be a string, as the
984        value is used as the name of the intermediate table.
985    :param left: Name of the left join column.
986    :param right: Name of the right join column.
987    :param backref: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.backref
988    :param cascade: https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.cascade
989    :param lazy: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.lazy
990    :param back_populates: See https://docs.sqlalchemy.org/en/13/orm/relationship_api.html?highlight=lazy#sqlalchemy.orm.relationship.params.back_populates
991    """
992
993    def __init__(self, multi=False, left=None, right=None, backref=None,
994            id_backref=None, cascade=False, lazy='select', back_populates=None,
995                       fk_left_deferrable=None, fk_left_initially=None,
996                       fk_right_deferrable=None, fk_right_initially=None,
997                       fk_left_ondelete=None, fk_left_onupdate=None,
998                       fk_right_ondelete=None, fk_right_onupdate=None,
999                       explicit_join=False, order_by=False, single_parent=None):
1000        self.multi = multi
1001        self.left = left
1002        self.right = right
1003        self.backref = backref
1004        self.id_backref = id_backref
1005        self.cascade = cascade
1006        self.lazy = lazy
1007        self.back_populates = back_populates
1008        self.fk_left_deferrable = fk_left_deferrable
1009        self.fk_left_initially = fk_left_initially
1010        self.fk_right_deferrable = fk_right_deferrable
1011        self.fk_right_initially = fk_right_initially
1012        self.fk_left_ondelete = fk_left_ondelete
1013        self.fk_left_onupdate = fk_left_onupdate
1014        self.fk_right_ondelete = fk_right_ondelete
1015        self.fk_right_onupdate = fk_right_onupdate
1016        self.explicit_join = explicit_join
1017        self.order_by = order_by
1018        self.single_parent = single_parent
1019
1020
1021class json:
1022    """Compound option object for json serialization. It's meant to be passed to
1023    :func:`ComplexModelBase.Attributes.store_as`.
1024
1025    Make sure you don't mix this with the json package when importing.
1026    """
1027
1028    def __init__(self, ignore_wrappers=True, complex_as=dict):
1029        if ignore_wrappers != True:
1030            raise NotImplementedError("ignore_wrappers != True")
1031        self.ignore_wrappers = ignore_wrappers
1032        self.complex_as = complex_as
1033
1034
1035class jsonb:
1036    """Compound option object for jsonb serialization. It's meant to be passed
1037    to :func:`ComplexModelBase.Attributes.store_as`.
1038    """
1039
1040    def __init__(self, ignore_wrappers=True, complex_as=dict):
1041        if ignore_wrappers != True:
1042            raise NotImplementedError("ignore_wrappers != True")
1043        self.ignore_wrappers = ignore_wrappers
1044        self.complex_as = complex_as
1045
1046
1047class msgpack:
1048    """Compound option object for msgpack serialization. It's meant to be passed
1049    to :func:`ComplexModelBase.Attributes.store_as`.
1050
1051    Make sure you don't mix this with the msgpack package when importing.
1052    """
1053    def __init__(self):
1054        pass
1055
1056
1057PSSM_VALUES = {'json': json, 'jsonb': jsonb, 'xml': xml,
1058                                             'msgpack': msgpack, 'table': table}
1059
1060
1061def apply_pssm(val):
1062    if val is not None:
1063        val_c = PSSM_VALUES.get(val, None)
1064        if val_c is None:
1065            assert isinstance(val, tuple(PSSM_VALUES.values())), \
1066             "'store_as' should be one of: %r or an instance of %r not %r" \
1067             % (tuple(PSSM_VALUES.keys()), tuple(PSSM_VALUES.values()), val)
1068
1069            return val
1070        return val_c()
1071