summaryrefslogtreecommitdiff
path: root/pypers/recipes/noconflict_alex.py
blob: 542e08675bc1a9a85880c308e342b0186637c776 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# noconflict_alex.py

import inspect, types
try: set
except NameError:
    from sets import Set as set

memoized_metaclasses_map = {}

def uniques(sequence, skipset):
    for item in sequence:
        if item not in skipset:
            skipset.add(item)
            yield item# noconflict.py

def remove_redundant(classes):
    redundant = set([types.ClassType])
    for c in classes:
        redundant.update(inspect.getmro(c)[1:])
    return tuple(uniques(classes, redundant))

def offending_metaclass_in(metas):
    for m in metas:
        if not issubclass(m, type): return True
    return False

def _get_noconflict_metaclass(bases, left_metas, right_metas):
     # make tuple of needed metaclasses in specified priority order
     metas = left_metas + tuple(map(type, bases)) + right_metas
     needed_metas = remove_redundant(metas)

     # return existing confict-solving meta, if any
     if needed_metas in memoized_metaclasses_map:
         return memoized_metaclasses_map[needed_metas]

     # nope: compute, memoize and return needed conflict-solving meta
     if not needed_metas:         # wee, a trivial case, happy us
         meta = type
     elif len(needed_metas) == 1: # another trivial case
         meta = needed_metas[0]
     elif offending_metaclass_in(needed_metas): # es. Zope Extension Classes
         raise TypeError("Incompatible root metatypes", needed_metas)
     else: # gotta work ...          
         metaname = '_' + ''.join([m.__name__ for m in needed_metas])
         meta = classmaker()(metaname, needed_metas, {})
     memoized_metaclasses_map[needed_metas] = meta
     return meta

def classmaker(left_metas=(), right_metas=()):
     def make_class(name, bases, adict):
         metaclass = _get_noconflict_metaclass(bases, left_metas, right_metas)
         return metaclass(name, bases, adict)
     return make_class

__test__ = dict(
    ex1 = """
>>> class Meta_A(type): pass
... 
>>> class Meta_B(type): pass
... 
>>> class A: __metaclass__ = Meta_A
... 
>>> class B: __metaclass__ = Meta_B
... 
>>> class C(A, B): pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

>>> import __main__ as noconflict
>>> class Meta_C(Meta_A, Meta_B): pass
...
>>> class C(A, B): __metaclass__ = Meta_C
...

>>> class C(A, B): __metaclass__ = noconflict.classmaker()
...
>>> class D(A):
...     __metaclass__ = noconflict.classmaker((Meta_B,))
""",
    remove_redundant="""
    >>> class C1(object): pass
    ...
    
    >>> class C2(object): pass
    ...
    >>> from __main__ import remove_redundant
    >>> remove_redundant((C1, C2, C1))
    (<class '__main__.C1'>, <class '__main__.C2'>)
""")

if __name__ == "__main__":
    import doctest,__main__
    doctest.testmod(__main__)