Repository: wireservice/agate
Issue: parsedatetime parser instances can't be pickled and unpickled

Breaks proof. Need to use `__getstate__` and `__setstate__` to exclude them.


Changed files:
agate/data_types/date_time.py, agate/data_types/date.py, tests/test_data_types.py

diff --git a/agate/data_types/date.py b/agate/data_types/date.py
index 22d5f5b..9a837c2 100644
--- a/agate/data_types/date.py
+++ b/agate/data_types/date.py
@@ -22,6 +22,23 @@ class Date(DataType):
 
         self.date_format = date_format
         self.parser = parsedatetime.Calendar()
+        
+    def __getstate__(self):
+        """
+        Return state values to be pickled. Exclude _parser because parsedatetime 
+        cannot be pickled.
+        """
+        odict = self.__dict__.copy()
+        del odict['parser']
+        return odict
+
+    def __setstate__(self, dict):
+        """
+        Restore state from the unpickled state values. Set _parser to an instance 
+        of the parsedatetime Calendar class.
+        """
+        self.__dict__.update(dict)
+        self.parser = parsedatetime.Calendar()
 
     def cast(self, d):
         """
diff --git a/agate/data_types/date_time.py b/agate/data_types/date_time.py
index 9daecbe..ac4b799 100644
--- a/agate/data_types/date_time.py
+++ b/agate/data_types/date_time.py
@@ -31,7 +31,24 @@ class DateTime(DataType):
             now.year, now.month, now.day, 0, 0, 0, 0, None
         )
         self._parser = parsedatetime.Calendar()
+        
+    def __getstate__(self):
+        """
+        Return state values to be pickled. Exclude _parser because parsedatetime 
+        cannot be pickled.
+        """
+        odict = self.__dict__.copy()
+        del odict['_parser']
+        return odict
 
+    def __setstate__(self, dict):
+        """
+        Restore state from the unpickled state values. Set _parser to an instance 
+        of the parsedatetime Calendar class.
+        """
+        self.__dict__.update(dict)
+        self._parser = parsedatetime.Calendar()
+        
     def cast(self, d):
         """
         Cast a single value to a :class:`datetime.datetime`.
diff --git a/tests/test_data_types.py b/tests/test_data_types.py
index 8f93510..13da1fe 100644
--- a/tests/test_data_types.py
+++ b/tests/test_data_types.py
@@ -3,6 +3,8 @@
 
 import datetime
 from decimal import Decimal
+import pickle
+import parsedatetime
 
 try:
     import unittest2 as unittest
@@ -199,6 +201,11 @@ class TestDate(unittest.TestCase):
         with self.assertRaises(CastError):
             self.type.cast('quack')
 
+    def test_pickle_parser(self):
+        from_pickle = pickle.loads(pickle.dumps(self.type))
+        self.assertEqual(from_pickle.date_format, self.type.date_format)
+        self.assertIsInstance(from_pickle.parser, parsedatetime.Calendar)
+
 class TestDateTime(unittest.TestCase):
     def setUp(self):
         self.type = DateTime()
@@ -277,6 +284,13 @@ class TestDateTime(unittest.TestCase):
         with self.assertRaises(CastError):
             self.type.cast('quack')
 
+    def test_pickle_parser(self):
+        from_pickle = pickle.loads(pickle.dumps(self.type))
+        self.assertEqual(from_pickle.datetime_format, self.type.datetime_format)
+        self.assertEqual(from_pickle.timezone, self.type.timezone)
+        self.assertEqual(from_pickle._source_time, self.type._source_time)
+        self.assertIsInstance(from_pickle._parser, parsedatetime.Calendar)
+
 class TestTimeDelta(unittest.TestCase):
     def setUp(self):
         self.type = TimeDelta()