Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • toejough/din
1 result
Show changes
Commits on Source (2)
......@@ -20,3 +20,11 @@
## Patch Updates
* restricts the callable exclusion so that attributes set with callables still
show up(resolves #5)
# 0.4.0 - frozen speedup
## Major Updates
* sped up frozen attribute setting (resolves #6)
* requires FrozenMixin users to use the new `self._thaw()` context manager
in `__init__` in order to set attributes, where before you could just set
them. If this change isn't made, AttributeErrors will start to be raised at
init time for FrozenMixin classes.
......@@ -2,6 +2,7 @@
# [ Imports:Python ]
import contextlib
import copy
import inspect
import typing
......@@ -304,12 +305,21 @@ class FrozenMixin:
say foo.my_string = 'new thing', but you can do foo.my_list.append('new thing').
"""
def __setattr__(self, name: str, value: typing.Any) -> None:
# if the caller is self.__init__, make the update
@contextlib.contextmanager
def _thawed(self) -> typing.Iterator:
stack = inspect.stack()
caller = stack[1].frame.f_locals.get('self', None)
func = stack[1].function
if caller is self and func == '__init__':
caller = stack[2].frame.f_locals.get('self', None)
func = stack[2].function
if not (caller is self and func == '__init__'):
raise RuntimeError("Can only thaw from __init__!")
object.__setattr__(self, '__is_thawed', True)
try:
yield
finally:
object.__setattr__(self, '__is_thawed', False)
def __setattr__(self, name: str, value: typing.Any) -> None:
if getattr(self, '__is_thawed', False):
object.__setattr__(self, name, value)
return
# else, don't
......
......@@ -797,7 +797,8 @@ def test_independent_reprs() -> None:
class _FrozenTestObject(din.FrozenMixin):
def __init__(self) -> None:
super().__init__()
self.my_string = 'original'
with self._thawed():
self.my_string = 'original'
def test_frozen() -> None:
......@@ -835,3 +836,58 @@ def test_copy_frozen() -> None:
assert frozen.my_string == 'original'
# the new object has the new value
assert frozen_2.my_string == 'changed!'
def test_frozen_inheritance() -> None:
# Given
# a frozen object
class _Inherited(_FrozenTestObject):
def __init__(self) -> None:
super().__init__()
with self._thawed():
self.additional_thing = "set in init"
frozen = _Inherited()
# an expected error
expected_message = "Can't set additional_thing, because _Inherited is frozen."
# When
# an attribute is set
try:
frozen.additional_thing = 'changed!'
except AttributeError as error:
actual_message = str(error)
# Then
# an attribute error is raised
assert expected_message == actual_message
# the value didn't change
assert frozen.my_string == 'original'
# vulture
frozen.additional_thing # pylint: disable=pointless-statement
def test_frozen_bad_thaw() -> None:
# Given
# a frozen object
class _Inherited(_FrozenTestObject):
def update(self) -> None:
with self._thawed():
self.my_string = "updated"
frozen = _Inherited()
# an expected error
expected_message = "Can only thaw from __init__!"
# When
# an attribute is set
try:
frozen.update()
except RuntimeError as error:
actual_message = str(error)
# Then
# an attribute error is raised
assert expected_message == actual_message
# the value didn't change
assert frozen.my_string == 'original'
......@@ -2,7 +2,7 @@
# [ API ]
VERSION = '0.3.2'
VERSION = '0.4.0'
# [ Vulture ]
......