1# encoding: utf-8 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"""Metaclass utilities for 21:attr:`spyne.model.complex.ComplexModelBase.Attributes.declare_order` 22""" 23 24import sys 25import inspect 26 27from functools import wraps 28from itertools import chain 29from warnings import warn 30 31from spyne.util.odict import odict 32 33 34class ClassNotFoundException(Exception): 35 """Raise when class declaration is not found in frame stack.""" 36 37 38class AttributeNotFoundException(Exception): 39 """Raise when attribute is not found in class declaration.""" 40 41 42class Prepareable(type): 43 """Implement __prepare__ for Python 2. 44 45 This class is used in Python 2 and Python 3 to support `six.add_metaclass` 46 decorator that populates attributes of resulting class from plain unordered 47 attributes dict of decorated class. 48 49 Based on https://gist.github.com/DasIch/5562625 50 """ 51 52 def __new__(cls, name, bases, attributes): 53 try: 54 constructor = attributes["__new__"] 55 except KeyError: 56 return type.__new__(cls, name, bases, attributes) 57 58 def preparing_constructor(cls, name, bases, attributes): 59 # Don't bother with this shit unless the user *explicitly* asked for 60 # it 61 for c in chain(bases, [cls]): 62 if hasattr(c,'Attributes') and not \ 63 (c.Attributes.declare_order in (None, 'random')): 64 break 65 else: 66 return constructor(cls, name, bases, attributes) 67 68 try: 69 cls.__prepare__ 70 except AttributeError: 71 return constructor(cls, name, bases, attributes) 72 73 if isinstance(attributes, odict): 74 # we create class dynamically with passed odict 75 return constructor(cls, name, bases, attributes) 76 77 current_frame = sys._getframe() 78 class_declaration = None 79 80 while class_declaration is None: 81 literals = list(reversed(current_frame.f_code.co_consts)) 82 83 for literal in literals: 84 if inspect.iscode(literal) and literal.co_name == name: 85 class_declaration = literal 86 break 87 88 else: 89 if current_frame.f_back: 90 current_frame = current_frame.f_back 91 else: 92 raise ClassNotFoundException( 93 "Can't find class declaration in any frame") 94 95 def get_index(attribute_name, 96 _names=class_declaration.co_names): 97 try: 98 return _names.index(attribute_name) 99 except ValueError: 100 if attribute_name.startswith('_'): 101 # we don't care about the order of magic and non 102 # public attributes 103 return 0 104 else: 105 msg = ("Can't find {0} in {1} class declaration. " 106 .format(attribute_name, 107 class_declaration.co_name)) 108 msg += ("HINT: use spyne.util.odict.odict for " 109 "class attributes if you populate them" 110 " dynamically.") 111 raise AttributeNotFoundException(msg) 112 113 by_appearance = sorted( 114 attributes.items(), key=lambda item: get_index(item[0]) 115 ) 116 117 namespace = cls.__prepare__(name, bases) 118 for key, value in by_appearance: 119 namespace[key] = value 120 121 new_cls = constructor(cls, name, bases, namespace) 122 123 found_module = inspect.getmodule(class_declaration) 124 assert found_module is not None, ( 125 'Module is not found for class_declaration {0}, name {1}' 126 .format(class_declaration, name)) 127 assert found_module.__name__ == new_cls.__module__, ( 128 'Found wrong class declaration of {0}: {1} != {2}.' 129 .format(name, found_module.__name__, new_cls.__module__)) 130 131 return new_cls 132 133 try: 134 attributes["__new__"] = wraps(constructor)(preparing_constructor) 135 except: 136 warn("Wrapping class initializer failed. This is normal " 137 "when running under Nuitka") 138 139 return type.__new__(cls, name, bases, attributes) 140