Skip to content

Commit

Permalink
Replace use of sweet_pickle in naming, and delete sweet_pickle subpac…
Browse files Browse the repository at this point in the history
…kage (#199)

* delete sweet_pickle subpackage and references to it.  Update its use in apptools.naming to use apptools.persistence or pickle from the standard library instead

* adding news fragment

* remove @classmethod on load_build

* finish merge

* add roundtrip test for object serializer

* rewrite test_two_stage_unpickler to use persistence instead of sweet pickle and remove test_global registry

* flake8

* convert test_class_mapping to use persistence

* flake8

* update test_state_function to use persistence

* delete apptools.sweet_pickle

* flake8

* remove/update any references to sweet_pickle in the comments

* apply suggestions from code review
  • Loading branch information
aaronayres35 committed Nov 25, 2020
1 parent 632a4bf commit 4d71eb5
Show file tree
Hide file tree
Showing 21 changed files with 196 additions and 1,879 deletions.
3 changes: 0 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ that is commonly needed by many applications
listener of selected items in an application.
- **apptools.scripting**: A framework for automatic recording of Python
scripts.
- **apptools.sweet_pickle**: Handles class-level versioning, to support
loading of saved data that exist over several generations of internal class
structures.
- **apptools.undo**: Supports undoing and scripting application commands.

Prerequisites
Expand Down
14 changes: 4 additions & 10 deletions apptools/naming/object_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@
import logging
from traceback import print_exc
from os.path import splitext

# import cPickle
# import pickle
import pickle

# Enthought library imports.
import apptools.sweet_pickle as sweet_pickle
from apptools.persistence.versioned_unpickler import VersionedUnpickler
from traits.api import HasTraits, Str


Expand Down Expand Up @@ -57,9 +55,7 @@ def load(self, path):
f = open(path, "rb")
try:
try:
obj = sweet_pickle.load(f)
# obj = cPickle.load(f)
# obj = pickle.load(f)
obj = VersionedUnpickler(f).load()
except Exception as ex:
print_exc()
logger.exception(
Expand Down Expand Up @@ -89,9 +85,7 @@ def save(self, path, obj):
# Pickle the object.
f = open(actual_path, "wb")
try:
sweet_pickle.dump(obj, f, 1)
# cPickle.dump(obj, f, 1)
# pickle.dump(obj, f, 1)
pickle.dump(obj, f, 1)
except Exception as ex:
logger.exception(
"Failed to pickle into file: %s, %s, object:%s"
Expand Down
52 changes: 52 additions & 0 deletions apptools/naming/tests/test_object_serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
import os
import shutil
import tempfile
import unittest

from traits.api import cached_property, HasTraits, Property, Str, Event

from apptools.naming.api import ObjectSerializer


class FooWithTraits(HasTraits):
"""Dummy HasTraits class for testing ObjectSerizalizer."""

full_name = Str()

last_name = Property(depends_on="full_name")

event = Event()

@cached_property
def _get_last_name(self):
return self.full_name.split(" ")[-1]

class TestObjectSerializer(unittest.TestCase):

def setUp(self):
self.tmpdir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.tmpdir)
self.tmp_file = os.path.join(self.tmpdir, "tmp.pickle")

def test_save_load_roundtrip(self):
# Test HasTraits objects can be serialized and deserialized as expected
obj = FooWithTraits(full_name="John Doe")

serializer = ObjectSerializer()
serializer.save(self.tmp_file, obj)

self.assertTrue(serializer.can_load(self.tmp_file))
deserialized = serializer.load(self.tmp_file)

self.assertIsInstance(deserialized, FooWithTraits)
self.assertEqual(deserialized.full_name, "John Doe")
self.assertEqual(deserialized.last_name, "Doe")
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import logging

# Enthought library imports
import apptools.sweet_pickle as sweet_pickle
from apptools.sweet_pickle.global_registry import _clear_global_registry
from traits.api import Bool, Float, HasTraits, Int, Str


Expand Down
81 changes: 81 additions & 0 deletions apptools/persistence/tests/test_class_mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# -----------------------------------------------------------------------------
#
# Copyright (c) 2006 by Enthought, Inc.
# All rights reserved.
#
# Author: Dave Peterson <dpeterson@enthought.com>
#
# -----------------------------------------------------------------------------

""" Tests the class mapping functionality of the enthought.pickle
framework.
"""

# Standard library imports.
import io
import pickle
import unittest

# Enthought library imports
from apptools.persistence.versioned_unpickler import VersionedUnpickler
from apptools.persistence.updater import Updater


##############################################################################
# Classes to use within the tests
##############################################################################


class Foo:
pass


class Bar:
pass


class Baz:
pass


##############################################################################
# class 'ClassMappingTestCase'
##############################################################################


class ClassMappingTestCase(unittest.TestCase):
"""Originally tests for the class mapping functionality of the now deleted
apptools.sweet_pickle framework, converted to use apptools.persistence.
"""

##########################################################################
# 'TestCase' interface
##########################################################################

### public interface #####################################################

def test_unpickled_class_mapping(self):

class TestUpdater(Updater):
def __init__(self):
self.refactorings = {
(Foo.__module__, Foo.__name__):
(Bar.__module__, Bar.__name__),
(Bar.__module__, Bar.__name__):
(Baz.__module__, Baz.__name__),
}
self.setstates = {}

# Validate that unpickling the first class gives us an instance of
# the second class.
start = Foo()
test_file = io.BytesIO(pickle.dumps(start, 2))
end = VersionedUnpickler(test_file, updater=TestUpdater()).load()
self.assertIsInstance(end, Bar)

# Validate that unpickling the second class gives us an instance of
# the third class.
start = Bar()
test_file = io.BytesIO(pickle.dumps(start, 2))
end = VersionedUnpickler(test_file, updater=TestUpdater()).load()
self.assertIsInstance(end, Baz)
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,35 @@
#
# -----------------------------------------------------------------------------

""" Tests the state function functionality of the apptools.sweet_pickle
framework.
""" These tests were originally for the the state function functionality of the
now deleted apptools.sweet_pickle framework. They have been modified here to
use apptools.persistence instead.
"""

# Standard library imports.
import io
import pickle
import unittest
import logging

# Enthought library imports
import apptools.sweet_pickle as sweet_pickle
from apptools.sweet_pickle.global_registry import _clear_global_registry
from traits.api import Bool, Float, HasTraits, Int, Str
from apptools.persistence.tests.state_function_classes import Foo, Bar, Baz
from apptools.persistence.versioned_unpickler import VersionedUnpickler
from apptools.persistence.updater import Updater


logger = logging.getLogger(__name__)


##############################################################################
# Classes to use within the tests
##############################################################################

# Need complete package name so that mapping matches correctly.
# The problem here is the Python loader that will load the same module with
# multiple names in sys.modules due to relative naming. Nice.
from apptools.sweet_pickle.tests.state_function_classes import Foo, Bar, Baz

##############################################################################
# State functions to use within the tests
##############################################################################


def bar_state_function(state):
for old, new in [("b1", "b2"), ("f1", "f2"), ("i1", "i2"), ("s1", "s2")]:
state[new] = state[old]
del state[old]
state["_enthought_pickle_version"] = 2
return state
class TestUpdater(Updater):
def __init__(self):
self.refactorings = {
(Foo.__module__, Foo.__name__):
(Bar.__module__, Bar.__name__),
(Bar.__module__, Bar.__name__):
(Baz.__module__, Baz.__name__),
}
self.setstates = {}


##############################################################################
Expand All @@ -52,8 +44,8 @@ def bar_state_function(state):


class StateFunctionTestCase(unittest.TestCase):
"""Tests the state function functionality of the apptools.sweet_pickle
framework.
"""Originally tests for the state function functionality of the now deleted
apptools.sweet_pickle framework, converted to use apptools.persistence.
"""

##########################################################################
Expand All @@ -68,15 +60,8 @@ def setUp(self):
Overridden here to ensure each test starts with an empty global
registry.
"""
# Clear the global registry
_clear_global_registry()

# Cache a reference to the new global registry
self.registry = sweet_pickle.get_global_registry()

# Add the class mappings to the registry
self.registry.add_mapping_to_class(Foo.__module__, Foo.__name__, Bar)
self.registry.add_mapping_to_class(Bar.__module__, Bar.__name__, Baz)
self.updater = TestUpdater()

##########################################################################
# 'StateFunctionTestCase' interface
Expand All @@ -89,47 +74,25 @@ def test_normal_setstate(self):
there are no registered state functions in the class chain.
"""
# Validate that unpickling the first class gives us an instance of
# the third class with the appropriate attribute values. It will have
# the second class with the appropriate attribute values. It will have
# the default Foo values (because there is no state function to move
# them) and also the default Baz values (since they inherit the
# them) and also the default Bar values (since they inherit the
# trait defaults because nothing overwrote the values.)
start = Foo()
end = sweet_pickle.loads(sweet_pickle.dumps(start))
self.assertIsInstance(end, Baz)
test_file = io.BytesIO(pickle.dumps(start, 2))
end = VersionedUnpickler(test_file, updater=TestUpdater()).load()
self.assertIsInstance(end, Bar)
self._assertAttributes(end, 1, (False, 1, 1, "foo"))
self._assertAttributes(end, 2, None)
self._assertAttributes(end, 3, (False, 3, 3, "baz"))
self._assertAttributes(end, 2, (True, 2, 2, "bar"))
self._assertAttributes(end, 3, None)

# Validate that unpickling the second class gives us an instance of
# the third class with the appropriate attribute values. It will have
# only the Baz attributes with the Bar values (since the __setstate__
# on Baz converted the Bar attributes to Baz attributes.)
start = Bar()
end = sweet_pickle.loads(sweet_pickle.dumps(start))
self.assertIsInstance(end, Baz)
self._assertAttributes(end, 2, None)
self._assertAttributes(end, 3, (True, 2, 2, "bar"))

def test_unpickled_chain_functionality(self):
"""Validates that the registered state functions are used when
unpickling.
"""
# Add the state function to the registry
self.registry.add_state_function_for_class(Bar, 2, bar_state_function)

# Validate that unpickling the first class gives us an instance of
# the third class with the appropriate attribute values.
start = Foo()
end = sweet_pickle.loads(sweet_pickle.dumps(start))
self.assertIsInstance(end, Baz)
self._assertAttributes(end, 1, None)
self._assertAttributes(end, 2, None)
self._assertAttributes(end, 3, (False, 1, 1, "foo"))

# Validate that unpickling the second class gives us an instance of
# the third class.
start = Bar()
end = sweet_pickle.loads(sweet_pickle.dumps(start))
test_file = io.BytesIO(pickle.dumps(start, 2))
end = VersionedUnpickler(test_file, updater=TestUpdater()).load()
self.assertIsInstance(end, Baz)
self._assertAttributes(end, 2, None)
self._assertAttributes(end, 3, (True, 2, 2, "bar"))
Expand Down
Loading

0 comments on commit 4d71eb5

Please sign in to comment.