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