diff --git a/ibis/common/grounds.py b/ibis/common/grounds.py index 8fba74a1b25a..394bc4ccffdd 100644 --- a/ibis/common/grounds.py +++ b/ibis/common/grounds.py @@ -155,8 +155,14 @@ def copy(self, **overrides: Any) -> Annotable: class Immutable(Base): + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + def __setattr__(self, name: str, _: Any) -> None: - raise TypeError( + raise AttributeError( f"Attribute {name!r} cannot be assigned to immutable instance of " f"type {type(self)}" ) diff --git a/ibis/common/tests/test_grounds.py b/ibis/common/tests/test_grounds.py index e35200693434..ab44f7651449 100644 --- a/ibis/common/tests/test_grounds.py +++ b/ibis/common/tests/test_grounds.py @@ -182,6 +182,26 @@ class MyExpr(Concrete): c: Map[str, Integer] +def test_immutable(): + class Foo(Immutable): + __slots__ = ("a", "b") + + def __init__(self, a, b): + object.__setattr__(self, "a", a) + object.__setattr__(self, "b", b) + + foo = Foo(1, 2) + assert foo.a == 1 + assert foo.b == 2 + with pytest.raises(AttributeError): + foo.a = 2 + with pytest.raises(AttributeError): + foo.b = 3 + + assert copy.copy(foo) is foo + assert copy.deepcopy(foo) is foo + + def test_annotable(): class InBetween(BetweenSimple): pass @@ -307,11 +327,11 @@ class ImmAnn(Immutable, Annotable): upper = optional(is_int, default=None) obj = AnnImm(3, lower=0, upper=4) - with pytest.raises(TypeError): + with pytest.raises(AttributeError): obj.value = 1 obj = ImmAnn(3, lower=0, upper=4) - with pytest.raises(TypeError): + with pytest.raises(AttributeError): obj.value = 1 @@ -715,15 +735,6 @@ class Between(Value, ConditionalOp): ) -def test_immutability(): - class Value(Annotable, Immutable): - a = is_int - - op = Value(1) - with pytest.raises(TypeError): - op.a = 3 - - class Value(Annotable): i = is_int j = attribute(is_int) @@ -1071,7 +1082,7 @@ def test_concrete(): assert obj.argnames == ("value", "lower", "upper") # immutable - with pytest.raises(TypeError): + with pytest.raises(AttributeError): obj.value = 11 # hashable