File: noconflict.py

package info (click to toggle)
glueviz 0.9.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 17,180 kB
  • ctags: 6,728
  • sloc: python: 37,111; makefile: 134; sh: 60
file content (73 lines) | stat: -rw-r--r-- 2,317 bytes parent folder | download
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
# Code adapted from:
#
# http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/
#
# The code at the above URL was released under the PSF license.

import inspect
import types

from glue.external import six

if six.PY2:
    import __builtin__
    CLASS_TYPE = types.ClassType
else:
    import builtins
    CLASS_TYPE = type

__all__ = ['classmaker']

def skip_redundant(iterable, skipset=None):
    """
    Redundant items are repeated items or items in the original skipset.
    """
    if skipset is None:
        skipset = set()
    for item in iterable:
        if item not in skipset:
            skipset.add(item)
            yield item


def remove_redundant(metaclasses):
    skipset = set([CLASS_TYPE])
    for meta in metaclasses:  # determines the metaclasses to be skipped
        skipset.update(inspect.getmro(meta)[1:])
    return tuple(skip_redundant(metaclasses, skipset))

memoized_metaclasses_map = {}


def get_noconflict_metaclass(bases, left_metas, right_metas):
    """
    Not intended to be used outside of this module, unless you know
    what you are doing.
    """
    # 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
    elif not needed_metas:         # wee, a trivial case, happy us
        meta = type
    elif len(needed_metas) == 1:  # another trivial case
        meta = needed_metas[0]
    # check for recursion, can happen i.e. for Zope ExtensionClasses
    elif needed_metas == bases:
        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