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
|
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 f"{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(
f"cls {kls!r}; slots is {slots!r} (should be a tuple or list)"
)
slots = (slots,)
if slots is None:
assert not raw_slottings
if not isinstance(slots, tuple):
if self.err_if_slots_is_mutable:
pytest.fail(f"cls {kls!r}; slots is {slots!r}- - should be a tuple")
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(
f"cls {kls!r}; slots is {slots!r}, seemingly inherited from "
f"{raw_slottings[slots]!r}; the derivative class should be __slots__ = ()"
)
for slot in slots:
if slot in slotting:
pytest.fail(
f"cls {kls!r}; slot {slot!r} was already defined at {slotting[slot]!r}"
)
|