1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
2
3# This implementation of the immutable decorator requires python >=
4# 3.7, and is significantly more storage efficient when making classes
5# with slots immutable. It's also faster.
6
7import contextvars
8import inspect
9
10_in__init__ = contextvars.ContextVar("_immutable_in__init__", default=False)
11
12
13class _Immutable:
14 """Immutable mixin class"""
15
16 # We set slots to the empty list to say "we don't have any attributes".
17 # We do this so that if we're mixed in with a class with __slots__, we
18 # don't cause a __dict__ to be added which would waste space.
19
20 __slots__ = ()
21
22 def __setattr__(self, name, value):
23 if _in__init__.get() is not self:
24 raise TypeError("object doesn't support attribute assignment")
25 else:
26 super().__setattr__(name, value)
27
28 def __delattr__(self, name):
29 if _in__init__.get() is not self:
30 raise TypeError("object doesn't support attribute assignment")
31 else:
32 super().__delattr__(name)
33
34
35def _immutable_init(f):
36 def nf(*args, **kwargs):
37 previous = _in__init__.set(args[0])
38 try:
39 # call the actual __init__
40 f(*args, **kwargs)
41 finally:
42 _in__init__.reset(previous)
43
44 nf.__signature__ = inspect.signature(f) # pyright: ignore
45 return nf
46
47
48def immutable(cls):
49 if _Immutable in cls.__mro__:
50 # Some ancestor already has the mixin, so just make sure we keep
51 # following the __init__ protocol.
52 cls.__init__ = _immutable_init(cls.__init__)
53 if hasattr(cls, "__setstate__"):
54 cls.__setstate__ = _immutable_init(cls.__setstate__)
55 ncls = cls
56 else:
57 # Mixin the Immutable class and follow the __init__ protocol.
58 class ncls(_Immutable, cls):
59 # We have to do the __slots__ declaration here too!
60 __slots__ = ()
61
62 @_immutable_init
63 def __init__(self, *args, **kwargs):
64 super().__init__(*args, **kwargs)
65
66 if hasattr(cls, "__setstate__"):
67
68 @_immutable_init
69 def __setstate__(self, *args, **kwargs):
70 super().__setstate__(*args, **kwargs)
71
72 # make ncls have the same name and module as cls
73 ncls.__name__ = cls.__name__
74 ncls.__qualname__ = cls.__qualname__
75 ncls.__module__ = cls.__module__
76 return ncls