aboutsummaryrefslogtreecommitdiff
blob: 429fb7771032912c81a7439f9ba940b7a6ccbc31 (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
import pytest

from . import mixins


class SlotShadowing(mixins.TargetedNamespaceWalker, mixins.SubclassWalker):

    target_namespace = 'snakeoil'
    err_if_slots_is_str = True
    err_if_slots_is_mutable = True

    def recurse_parents(self, kls, seen=None):
        if not seen:
            seen = set()
        for subcls in kls.__bases__:
            if subcls in seen:
                continue
            seen.add(subcls)
            for grand_dad in self.recurse_parents(subcls, seen=seen):
                yield grand_dad
            yield subcls

    @staticmethod
    def mk_name(kls):
        return '%s.%s' % (kls.__module__, kls.__name__)

    def _should_ignore(self, kls):
        return self.mk_name(kls).split(".")[0] != self.target_namespace

    def run_check(self, kls):
        if getattr(kls, '__slotting_intentionally_disabled__', False):
            return

        slotting = {}
        raw_slottings = {}

        for parent in self.recurse_parents(kls):
            slots = getattr(parent, '__slots__', None)

            if slots is None:
                continue

            if isinstance(slots, str):
                slots = (slots,)
            elif isinstance(slots, (dict, list)):
                slots = tuple(slots)

            raw_slottings[slots] = parent
            for slot in slots:
                slotting.setdefault(slot, parent)

        slots = getattr(kls, '__slots__', None)
        if slots is None and not slotting:
            return

        if isinstance(slots, str):
            if self.err_if_slots_is_str:
                pytest.fail(
                    "cls %r; slots is %r (should be a tuple or list)" %
                    (kls, slots))
            slots = (slots,)

        if slots is None:
            assert not raw_slottings

        if not isinstance(slots, tuple):
            if self.err_if_slots_is_mutable:
                pytest.fail(
                    "cls %r; slots is %r- - should be a tuple" % (kls, slots))
            slots = tuple(slots)

        if slots is None or (slots and slots in raw_slottings):
            # we do a bool on slots to ignore (); that's completely valid
            # this means that the child either didn't define __slots__, or
            # daftly copied the parents... thus defeating the purpose.
            pytest.fail(
                "cls %r; slots is %r, seemingly inherited from %r; the "
                "derivative class should be __slots__ = ()" %
                (kls, slots, raw_slottings[slots]))

        for slot in slots:
            if slot in slotting:
                pytest.fail(
                    "cls %r; slot %r was already defined at %r" %
                    (kls, slot, slotting[slot]))