Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Include/cpython/immutability.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
# error "this header file must not be included directly"
#endif

PyAPI_DATA(PyTypeObject) _PyNotFreezable_Type;
typedef enum {
_Py_FREEZABLE_YES = 0,
_Py_FREEZABLE_NO = 1,
_Py_FREEZABLE_EXPLICIT = 2,
_Py_FREEZABLE_PROXY = 3,
} _Py_freezable_status;

PyAPI_FUNC(int) _PyImmutability_Freeze(PyObject*);
PyAPI_FUNC(int) _PyImmutability_FreezeMany(PyObject *const *, Py_ssize_t);
PyAPI_FUNC(int) _PyImmutability_RegisterFreezable(PyTypeObject*);
PyAPI_FUNC(int) _PyImmutability_RegisterShallowImmutable(PyTypeObject*);
PyAPI_FUNC(int) _PyImmutability_CanViewAsImmutable(PyObject*);
PyAPI_FUNC(int) _PyImmutability_SetFreezable(PyObject *, int);
PyAPI_FUNC(int) _PyImmutability_SetFreezable(PyObject *, _Py_freezable_status);
PyAPI_FUNC(int) _PyImmutability_GetFreezable(PyObject *);
9 changes: 0 additions & 9 deletions Include/internal/pycore_immutability.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,9 @@ extern "C" {

typedef struct _Py_hashtable_t _Py_hashtable_t;

typedef enum {
_Py_FREEZABLE_YES = 0,
_Py_FREEZABLE_NO = 1,
_Py_FREEZABLE_EXPLICIT = 2,
_Py_FREEZABLE_PROXY = 3,
} _Py_freezable_status;

struct _Py_immutability_state {
int late_init_done;
PyObject *freezable_types;
_Py_hashtable_t *shallow_immutable_types;
PyObject *destroy_cb;
_Py_hashtable_t *warned_types;
Comment on lines 13 to 16
// With the pre-freeze hook it can happen that freeze calls are
// nested. This is stack of the enclosing freeze states.
Expand Down
10 changes: 6 additions & 4 deletions Lib/immutable.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@
import _immutable as _c

freeze = _c.freeze
isfrozen = _c.isfrozen
is_frozen = _c.is_frozen
set_freezable = _c.set_freezable
NotFreezable = getattr(_c, "NotFreezable", None)
NotFreezableError = _c.NotFreezableError
ImmutableModule = _c.ImmutableModule
FREEZABLE_YES = _c.FREEZABLE_YES
FREEZABLE_NO = _c.FREEZABLE_NO
FREEZABLE_EXPLICIT = _c.FREEZABLE_EXPLICIT
FREEZABLE_PROXY = _c.FREEZABLE_PROXY

# FIXME(immutable): For the longest time we used the name `isfrozen`
# without the underscore. This keeps the function name for now, but
# aliases it to `is_frozen`
isfrozen = is_frozen

def freezable(cls):
"""Class decorator: mark a class as always freezable."""
Expand Down Expand Up @@ -47,9 +50,8 @@ def frozen(cls):

__all__ = [
"freeze",
"isfrozen",
"is_frozen",
"set_freezable",
"NotFreezable",
"NotFreezableError",
"ImmutableModule",
"FREEZABLE_YES",
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_freeze/test_bz2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@


class TestBZ2Compressor(BaseNotFreezableTest):
def __init__(self, *args, **kwargs):
super().__init__(*args, obj=BZ2Compressor(), **kwargs)
def test_not_freezable(self):
self.check_not_freezable(BZ2Compressor())


class TestBZ2Decompressor(BaseNotFreezableTest):
def __init__(self, *args, **kwargs):
super().__init__(*args, obj=BZ2Decompressor(), **kwargs)
def test_not_freezable(self):
self.check_not_freezable(BZ2Decompressor())


if __name__ == '__main__':
Expand Down
18 changes: 10 additions & 8 deletions Lib/test/test_freeze/test_common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from immutable import freeze, isfrozen, NotFreezable
from immutable import freeze, is_frozen


class BaseObjectTest(unittest.TestCase):
Expand All @@ -14,27 +14,29 @@ def setUp(self):
freeze(self.obj)

def test_immutable(self):
self.assertTrue(isfrozen(self.obj))
self.assertTrue(is_frozen(self.obj))

def test_add_attribute(self):
with self.assertRaises(TypeError):
self.obj.new_attribute = 'value'

def test_type_immutable(self):
self.assertTrue(isfrozen(self.obj))
self.assertTrue(isfrozen(type(self.obj)), "Type should be frozen when instance is frozen: {}".format(type(self.obj)))
self.assertTrue(is_frozen(self.obj))
self.assertTrue(is_frozen(type(self.obj)), "Type should be frozen when instance is frozen: {}".format(type(self.obj)))


class BaseNotFreezableTest(unittest.TestCase):
def __init__(self, *args, obj=NotFreezable(), **kwargs):
def __init__(self, *args, obj=None, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.obj = obj

def test_not_freezable(self):
def check_not_freezable(self, obj):
self.assertIsNotNone(obj)

with self.assertRaises(TypeError):
freeze(self.obj)
freeze(obj)

self.assertFalse(isfrozen(self.obj))
self.assertFalse(is_frozen(obj))


if __name__ == '__main__':
Expand Down
105 changes: 48 additions & 57 deletions Lib/test/test_freeze/test_core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest
from immutable import freeze, NotFreezable, isfrozen
from immutable import freeze, is_frozen

from .test_common import BaseNotFreezableTest, BaseObjectTest
from .test_common import BaseObjectTest


# This is a canary to check that global variables are not made immutable
Expand All @@ -19,7 +19,7 @@ def global1_inc():
class MutableGlobalTest(unittest.TestCase):
# Add initial test to confirm that global_canary is mutable
def test_global_mutable(self):
self.assertTrue(not isfrozen(global_canary))
self.assertTrue(not is_frozen(global_canary))


class TestBasicObject(BaseObjectTest):
Expand All @@ -34,13 +34,13 @@ class TestFloat(unittest.TestCase):
def test_freeze_float(self):
obj = 0.0
freeze(obj)
self.assertTrue(isfrozen(obj))
self.assertTrue(is_frozen(obj))

class TestFloatType(unittest.TestCase):
def test_float_type_immutable(self):
obj = 0.0
c = obj.__class__
self.assertTrue(isfrozen(c))
self.assertTrue(is_frozen(c))

class TestList(BaseObjectTest):
class C:
Expand Down Expand Up @@ -190,25 +190,25 @@ class C:
freeze(self.obj)

def test_immutable(self):
self.assertTrue(isfrozen(self.obj))
self.assertTrue(isfrozen(self.obj.a))
self.assertTrue(isfrozen(self.obj.a.b))
self.assertTrue(isfrozen(self.obj.d))
self.assertTrue(isfrozen(self.obj.d[0]))
self.assertTrue(isfrozen(self.obj.d[0].e))
self.assertTrue(isfrozen(self.obj.g))
self.assertTrue(isfrozen(self.obj.g[1]))
self.assertTrue(isfrozen(self.obj.g[1].h))
self.assertTrue(isfrozen(self.obj.g["two"]))
self.assertTrue(isfrozen(self.obj.g["two"].i))
self.assertTrue(is_frozen(self.obj))
self.assertTrue(is_frozen(self.obj.a))
self.assertTrue(is_frozen(self.obj.a.b))
self.assertTrue(is_frozen(self.obj.d))
self.assertTrue(is_frozen(self.obj.d[0]))
self.assertTrue(is_frozen(self.obj.d[0].e))
self.assertTrue(is_frozen(self.obj.g))
self.assertTrue(is_frozen(self.obj.g[1]))
self.assertTrue(is_frozen(self.obj.g[1].h))
self.assertTrue(is_frozen(self.obj.g["two"]))
self.assertTrue(is_frozen(self.obj.g["two"].i))

def test_set_const(self):
with self.assertRaises(TypeError):
self.obj.const = 1

def test_type_immutable(self):
self.assertTrue(isfrozen(type(self.obj)))
self.assertTrue(isfrozen(type(self.obj).const))
self.assertTrue(is_frozen(type(self.obj)))
self.assertTrue(is_frozen(type(self.obj).const))


class TestFunctions(unittest.TestCase):
Expand Down Expand Up @@ -269,8 +269,8 @@ def d():

self.assertEqual(d(), 1)
freeze(d)
self.assertTrue(isfrozen(global0))
self.assertFalse(isfrozen(global_canary))
self.assertTrue(is_frozen(global0))
self.assertFalse(is_frozen(global_canary))
self.assertRaises(TypeError, d)

def test_hidden_global(self):
Expand All @@ -292,9 +292,9 @@ def e():
return sum(test)

freeze(e)
self.assertTrue(isfrozen(list))
self.assertTrue(isfrozen(range))
self.assertTrue(isfrozen(sum))
self.assertTrue(is_frozen(list))
self.assertTrue(is_frozen(range))
self.assertTrue(is_frozen(sum))

def test_builtins_nested(self):
def g():
Expand All @@ -305,18 +305,18 @@ def nested_test():
return nested_test()

freeze(g)
self.assertTrue(isfrozen(list))
self.assertTrue(isfrozen(range))
self.assertTrue(isfrozen(sum))
self.assertTrue(is_frozen(list))
self.assertTrue(is_frozen(range))
self.assertTrue(is_frozen(sum))

def test_global_fun(self):
def d():
return global1_inc()

freeze(d)
self.assertTrue(isfrozen(global1))
self.assertTrue(isfrozen(global1_inc))
self.assertFalse(isfrozen(global_canary))
self.assertTrue(is_frozen(global1))
self.assertTrue(is_frozen(global1_inc))
self.assertFalse(is_frozen(global_canary))
self.assertRaises(TypeError, d)

def test_globals_copy(self):
Expand Down Expand Up @@ -348,18 +348,18 @@ def test_lambda(self):
obj = TestMethods.C()
obj.c = lambda x: pow(x, 2)
freeze(obj)
self.assertTrue(isfrozen(TestMethods.C))
self.assertTrue(isfrozen(pow))
self.assertTrue(is_frozen(TestMethods.C))
self.assertTrue(is_frozen(pow))
self.assertRaises(TypeError, obj.b, 1)
self.assertEqual(obj.c(2), 4)

def test_method(self):
obj = TestMethods.C()
freeze(obj)
self.assertEqual(obj.a(), 1)
self.assertTrue(isfrozen(obj))
self.assertTrue(isfrozen(abs))
self.assertTrue(isfrozen(obj.val))
self.assertTrue(is_frozen(obj))
self.assertTrue(is_frozen(abs))
self.assertTrue(is_frozen(obj.val))
self.assertRaises(TypeError, obj.b, 1)
# Second test as the byte code can be changed by the first call
self.assertRaises(TypeError, obj.b, 1)
Expand All @@ -383,9 +383,9 @@ def inner():
freeze(obj)
return obj, obj2, obj3
obj, obj2, obj3 = inner()
self.assertTrue(isfrozen(obj))
self.assertTrue(isfrozen(obj2))
self.assertFalse(isfrozen(obj3))
self.assertTrue(is_frozen(obj))
self.assertTrue(is_frozen(obj2))
self.assertFalse(is_frozen(obj3))

class TestDictMutation(unittest.TestCase):
class C:
Expand All @@ -402,7 +402,7 @@ def set(self, x):
def test_dict_mutation(self):
obj = TestDictMutation.C()
freeze(obj)
self.assertTrue(isfrozen(obj))
self.assertTrue(is_frozen(obj))
self.assertRaises(TypeError, obj.set, 1)
self.assertEqual(obj.get(), 0)

Expand All @@ -412,15 +412,15 @@ def test_dict_mutation2(self):
self.assertEqual(obj.get(), 1)
freeze(obj)
self.assertEqual(obj.get(), 1)
self.assertTrue(isfrozen(obj))
self.assertTrue(is_frozen(obj))
self.assertRaises(TypeError, obj.set, 1)

def test_dict_mutation3(self):
obj = TestDictMutation.C()
d = obj.__dict__
freeze(d)
# Should obj be frozen?
# self.assertTrue(isfrozen(obj))
# self.assertTrue(is_frozen(obj))
# The following line should raise an exception, as we are trying to mutate the dict
with self.assertRaises(TypeError):
obj.f = 1
Expand Down Expand Up @@ -454,9 +454,9 @@ def test_weakref(self):
obj = TestWeakRef.B()
c = TestWeakRef.C(obj)
freeze(c)
self.assertTrue(isfrozen(c))
self.assertTrue(is_frozen(c))
self.assertTrue(c.val() is obj)
self.assertTrue(isfrozen(c.val()))
self.assertTrue(is_frozen(c.val()))
obj = None
# The reference should remain as it was reachable through a frozen weakref.
self.assertTrue(c.val() is not None)
Expand All @@ -469,8 +469,8 @@ def test_stack_capture(self):
x = {}
x["frame"] = sys._getframe()
freeze(x)
self.assertTrue(isfrozen(x))
self.assertTrue(isfrozen(x["frame"]))
self.assertTrue(is_frozen(x))
self.assertTrue(is_frozen(x["frame"]))


class TestSubclass(unittest.TestCase):
Expand All @@ -487,8 +487,8 @@ def b(self, val):

c_obj = C(1)
freeze(c_obj)
self.assertTrue(isfrozen(c_obj))
self.assertTrue(isfrozen(C))
self.assertTrue(is_frozen(c_obj))
self.assertTrue(is_frozen(C))
class D(C):
def __init__(self, val):
super().__init__(val)
Expand Down Expand Up @@ -585,7 +585,7 @@ def f(b=bdef):

freeze(f)

self.assertTrue(isfrozen(bdef))
self.assertTrue(is_frozen(bdef))

def test_function_kwdefaults(self):
bdef = {}
Expand All @@ -595,16 +595,7 @@ def f(a, **b):

freeze(f)

self.assertTrue(isfrozen(bdef))


class TestNotFreezable(BaseNotFreezableTest):
class C(NotFreezable):
pass

def __init__(self, *args, **kwargs):
obj = self.C()
super().__init__(*args, obj=obj, **kwargs)
self.assertTrue(is_frozen(bdef))


class TestInheritFromCType(unittest.TestCase):
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_freeze/test_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@


class TestCSVReader(BaseNotFreezableTest):
def __init__(self, *args, **kwargs):
super().__init__(*args, obj=csv.reader([]), **kwargs)
def test_not_freezable(self):
self.check_not_freezable(csv.reader([]))


class TestCSVWriter(BaseNotFreezableTest):
def __init__(self, *args, **kwargs):
def test_not_freezable(self):
self.buffer = BytesIO()
super().__init__(*args, obj=csv.writer(self.buffer), **kwargs)
self.check_not_freezable(csv.writer(self.buffer))
Loading
Loading