Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace use of sweet_pickle in naming, and delete sweet_pickle subpackage #199

Merged
merged 18 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from 17 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
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)
kitchoi marked this conversation as resolved.
Show resolved Hide resolved
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,53 +7,53 @@
#
# -----------------------------------------------------------------------------

""" 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):
def bar_state_function(self, state):
aaronayres35 marked this conversation as resolved.
Show resolved Hide resolved
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 = {}


##############################################################################
# class 'StateFunctionTestCase'
##############################################################################


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 +68,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 +82,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.
"""
Comment on lines -113 to -116
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it may be possible to modify this test, to get a similar flavor of test for apptools.persistence by using the setstates attribute on an Updater instance that is passed to a VersionedUnpickler. Unfortunately I was unable to get it to work atm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this briefly... IMO, it looks evil! I opened #238.

# 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