1import doctest
2import unittest
3
4
5doctests = """
6
7Basic class construction.
8
9    >>> class C:
10    ...     def meth(self): print("Hello")
11    ...
12    >>> C.__class__ is type
13    True
14    >>> a = C()
15    >>> a.__class__ is C
16    True
17    >>> a.meth()
18    Hello
19    >>>
20
21Use *args notation for the bases.
22
23    >>> class A: pass
24    >>> class B: pass
25    >>> bases = (A, B)
26    >>> class C(*bases): pass
27    >>> C.__bases__ == bases
28    True
29    >>>
30
31Use a trivial metaclass.
32
33    >>> class M(type):
34    ...     pass
35    ...
36    >>> class C(metaclass=M):
37    ...    def meth(self): print("Hello")
38    ...
39    >>> C.__class__ is M
40    True
41    >>> a = C()
42    >>> a.__class__ is C
43    True
44    >>> a.meth()
45    Hello
46    >>>
47
48Use **kwds notation for the metaclass keyword.
49
50    >>> kwds = {'metaclass': M}
51    >>> class C(**kwds): pass
52    ...
53    >>> C.__class__ is M
54    True
55    >>> a = C()
56    >>> a.__class__ is C
57    True
58    >>>
59
60Use a metaclass with a __prepare__ static method.
61
62    >>> class M(type):
63    ...    @staticmethod
64    ...    def __prepare__(*args, **kwds):
65    ...        print("Prepare called:", args, kwds)
66    ...        return dict()
67    ...    def __new__(cls, name, bases, namespace, **kwds):
68    ...        print("New called:", kwds)
69    ...        return type.__new__(cls, name, bases, namespace)
70    ...    def __init__(cls, *args, **kwds):
71    ...        pass
72    ...
73    >>> class C(metaclass=M):
74    ...     def meth(self): print("Hello")
75    ...
76    Prepare called: ('C', ()) {}
77    New called: {}
78    >>>
79
80Also pass another keyword.
81
82    >>> class C(object, metaclass=M, other="haha"):
83    ...     pass
84    ...
85    Prepare called: ('C', (<class 'object'>,)) {'other': 'haha'}
86    New called: {'other': 'haha'}
87    >>> C.__class__ is M
88    True
89    >>> C.__bases__ == (object,)
90    True
91    >>> a = C()
92    >>> a.__class__ is C
93    True
94    >>>
95
96Check that build_class doesn't mutate the kwds dict.
97
98    >>> kwds = {'metaclass': type}
99    >>> class C(**kwds): pass
100    ...
101    >>> kwds == {'metaclass': type}
102    True
103    >>>
104
105Use various combinations of explicit keywords and **kwds.
106
107    >>> bases = (object,)
108    >>> kwds = {'metaclass': M, 'other': 'haha'}
109    >>> class C(*bases, **kwds): pass
110    ...
111    Prepare called: ('C', (<class 'object'>,)) {'other': 'haha'}
112    New called: {'other': 'haha'}
113    >>> C.__class__ is M
114    True
115    >>> C.__bases__ == (object,)
116    True
117    >>> class B: pass
118    >>> kwds = {'other': 'haha'}
119    >>> class C(B, metaclass=M, *bases, **kwds): pass
120    ...
121    Prepare called: ('C', (<class 'test.test_metaclass.B'>, <class 'object'>)) {'other': 'haha'}
122    New called: {'other': 'haha'}
123    >>> C.__class__ is M
124    True
125    >>> C.__bases__ == (B, object)
126    True
127    >>>
128
129Check for duplicate keywords.
130
131    >>> class C(metaclass=type, metaclass=type): pass
132    ...
133    Traceback (most recent call last):
134    [...]
135    SyntaxError: keyword argument repeated: metaclass
136    >>>
137
138Another way.
139
140    >>> kwds = {'metaclass': type}
141    >>> class C(metaclass=type, **kwds): pass
142    ...
143    Traceback (most recent call last):
144    [...]
145    TypeError: __build_class__() got multiple values for keyword argument 'metaclass'
146    >>>
147
148Use a __prepare__ method that returns an instrumented dict.
149
150    >>> class LoggingDict(dict):
151    ...     def __setitem__(self, key, value):
152    ...         print("d[%r] = %r" % (key, value))
153    ...         dict.__setitem__(self, key, value)
154    ...
155    >>> class Meta(type):
156    ...    @staticmethod
157    ...    def __prepare__(name, bases):
158    ...        return LoggingDict()
159    ...
160    >>> class C(metaclass=Meta):
161    ...     foo = 2+2
162    ...     foo = 42
163    ...     bar = 123
164    ...
165    d['__module__'] = 'test.test_metaclass'
166    d['__qualname__'] = 'C'
167    d['foo'] = 4
168    d['foo'] = 42
169    d['bar'] = 123
170    >>>
171
172Use a metaclass that doesn't derive from type.
173
174    >>> def meta(name, bases, namespace, **kwds):
175    ...     print("meta:", name, bases)
176    ...     print("ns:", sorted(namespace.items()))
177    ...     print("kw:", sorted(kwds.items()))
178    ...     return namespace
179    ...
180    >>> class C(metaclass=meta):
181    ...     a = 42
182    ...     b = 24
183    ...
184    meta: C ()
185    ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
186    kw: []
187    >>> type(C) is dict
188    True
189    >>> print(sorted(C.items()))
190    [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
191    >>>
192
193And again, with a __prepare__ attribute.
194
195    >>> def prepare(name, bases, **kwds):
196    ...     print("prepare:", name, bases, sorted(kwds.items()))
197    ...     return LoggingDict()
198    ...
199    >>> meta.__prepare__ = prepare
200    >>> class C(metaclass=meta, other="booh"):
201    ...    a = 1
202    ...    a = 2
203    ...    b = 3
204    ...
205    prepare: C () [('other', 'booh')]
206    d['__module__'] = 'test.test_metaclass'
207    d['__qualname__'] = 'C'
208    d['a'] = 1
209    d['a'] = 2
210    d['b'] = 3
211    meta: C ()
212    ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 2), ('b', 3)]
213    kw: [('other', 'booh')]
214    >>>
215
216The default metaclass must define a __prepare__() method.
217
218    >>> type.__prepare__()
219    {}
220    >>>
221
222Make sure it works with subclassing.
223
224    >>> class M(type):
225    ...     @classmethod
226    ...     def __prepare__(cls, *args, **kwds):
227    ...         d = super().__prepare__(*args, **kwds)
228    ...         d["hello"] = 42
229    ...         return d
230    ...
231    >>> class C(metaclass=M):
232    ...     print(hello)
233    ...
234    42
235    >>> print(C.hello)
236    42
237    >>>
238
239Test failures in looking up the __prepare__ method work.
240    >>> class ObscureException(Exception):
241    ...     pass
242    >>> class FailDescr:
243    ...     def __get__(self, instance, owner):
244    ...        raise ObscureException
245    >>> class Meta(type):
246    ...     __prepare__ = FailDescr()
247    >>> class X(metaclass=Meta):
248    ...     pass
249    Traceback (most recent call last):
250    [...]
251    test.test_metaclass.ObscureException
252
253"""
254
255import sys
256
257# Trace function introduces __locals__ which causes various tests to fail.
258if hasattr(sys, 'gettrace') and sys.gettrace():
259    __test__ = {}
260else:
261    __test__ = {'doctests' : doctests}
262
263def load_tests(loader, tests, pattern):
264    tests.addTest(doctest.DocTestSuite())
265    return tests
266
267
268if __name__ == "__main__":
269    unittest.main()
270