Skip to content

Commit eecc23d

Browse files
authored
Merge pull request #373 from enthought/fix/dynamictraits
Resolve pickling and deepcopying bug with dynamically added traits
2 parents 6208f63 + cd5c9ea commit eecc23d

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

traits/has_traits.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,9 @@ def __getstate__(self):
13721372
for name in self.trait_names( type = 'delegate',
13731373
transient = False )
13741374
if name in dic ] ) )
1375+
# Add all instance traits
1376+
inst_traits = self._instance_traits()
1377+
result['__instance_traits__'] = inst_traits
13751378

13761379
# If this object implements ISerializable, make sure that all
13771380
# contained HasTraits objects in its persisted state also implement
@@ -1411,7 +1414,10 @@ def __setstate__ ( self, state, trait_change_notify = True ):
14111414
else:
14121415
# Otherwise, apply the Traits 3.0 restore logic:
14131416
self._init_trait_listeners()
1417+
inst_traits = state.pop('__instance_traits__', {})
14141418
self.trait_set( trait_change_notify = trait_change_notify, **state )
1419+
for attr in inst_traits:
1420+
self.add_trait(attr, inst_traits[attr])
14151421
self._post_init_trait_listeners()
14161422
self.traits_init()
14171423

@@ -1779,6 +1785,9 @@ def clone_traits ( self, traits = None, memo = None, copy = None,
17791785
new = self.__new__( self.__class__ )
17801786
memo[ id( self ) ] = new
17811787
new._init_trait_listeners()
1788+
inst_traits = self._instance_traits()
1789+
for attr in inst_traits:
1790+
new.add_trait(attr, inst_traits[attr])
17821791
new.copy_traits( self, traits, memo, copy, **metadata )
17831792
new._post_init_trait_listeners()
17841793
new.traits_init()

traits/tests/test_regression.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from traits.trait_numeric import Array
1414

1515
from traits.has_traits import HasTraits, Property, on_trait_change
16+
from traits.trait_errors import TraitError
1617
from traits.trait_types import Bool, DelegatesTo, Either, Instance, Int, List
1718
from traits.testing.unittest_tools import unittest
1819

@@ -211,6 +212,31 @@ def test_delegation_refleak(self):
211212
# All the counts should be the same.
212213
self.assertEqual(counts[warmup:-1], counts[warmup+1:])
213214

215+
def test_hastraits_deepcopy(self):
216+
# Regression test for enthought/traits#2 and enthought/traits#16
217+
from copy import deepcopy
218+
a = HasTraits()
219+
a.add_trait('foo', Int)
220+
a.foo = 1
221+
with self.assertRaises(TraitError):
222+
a.foo = 'a'
223+
copied_a = deepcopy(a)
224+
with self.assertRaises(TraitError):
225+
copied_a.foo = 'a'
226+
227+
def test_hastraits_pickle(self):
228+
# Regression test for enthought/traits#2 and enthought/traits#16
229+
from pickle import dumps, loads
230+
a = HasTraits()
231+
a.add_trait('foo', Int)
232+
a.foo = 1
233+
with self.assertRaises(TraitError):
234+
a.foo = 'a'
235+
pickled_a = dumps(a)
236+
unpickled_a = loads(pickled_a)
237+
with self.assertRaises(TraitError):
238+
unpickled_a.foo = 'a'
239+
214240
@unittest.skipUnless(numpy_available, "test requires NumPy")
215241
def test_exception_from_numpy_comparison_ignored(self):
216242
# Regression test for enthought/traits#376.

0 commit comments

Comments
 (0)