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