Repository: pandas-dev/pandas

Issue:
Pandas 15.0:  TypeError: can't pickle busdaycalendar objects
Using the documentation's own CustomBusinessDay example, you can no longer save objects with a CustomBusinessDay frequency to an HDF store in Pandas 15.0 Final (but worked in 14.1):

``` python
import pandas as pd
import numpy as np
from pandas.tseries.offsets import CustomBusinessDay
from datetime import datetime
weekmask_egypt = 'Sun Mon Tue Wed Thu'
holidays = ['2012-05-01', datetime(2013, 5, 1), np.datetime64('2014-05-01')]
bday_egypt = CustomBusinessDay(holidays=holidays, weekmask=weekmask_egypt)
dt = datetime(2013, 4, 30)
dts = pd.date_range(dt, periods=5, freq=bday_egypt)
s = (pd.Series(dts.weekday, dts).map(pd.Series('Mon Tue Wed Thu Fri Sat Sun'.split())))
store = pd.HDFStore('test.hdf')
store.put('test',s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/pandas/io/pytables.py", line 831, in put
    self._write_to_group(key, value, append=append, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/pandas/io/pytables.py", line 1280, in _write_to_group
    s.write(obj=value, append=append, complib=complib, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/pandas/io/pytables.py", line 2557, in write
    self.write_index('index', obj.index)
  File "/usr/local/lib/python2.7/site-packages/pandas/io/pytables.py", line 2316, in write_index
    node._v_attrs.freq = index.freq
  File "/usr/local/lib/python2.7/site-packages/tables/attributeset.py", line 455, in __setattr__
    self._g__setattr(name, value)
  File "/usr/local/lib/python2.7/site-packages/tables/attributeset.py", line 397, in _g__setattr
    self._g_setattr(self._v_node, name, stvalue)
  File "hdf5extension.pyx", line 700, in tables.hdf5extension.AttributeSet._g_setattr (tables/hdf5extension.c:6623)
  File "/usr/local/Cellar/python/2.7.8/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy_reg.py", line 70, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle busdaycalendar objects


>>> pd.show_versions()

INSTALLED VERSIONS
------------------
commit: None
python: 2.7.8.final.0
python-bits: 64
OS: Darwin
OS-release: 13.4.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8

pandas: 0.15.0
nose: 1.3.1
Cython: 0.20.2
numpy: 1.8.1
scipy: 0.14.0
statsmodels: None
IPython: 2.2.0
sphinx: None
patsy: None
dateutil: 2.2
pytz: 2014.4
bottleneck: 0.8.0
tables: 3.1.1
numexpr: 2.4
matplotlib: 1.3.1
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
httplib2: None
apiclient: None
rpy2: None
sqlalchemy: None
pymysql: None
psycopg2: None
```



Discussion:
cc @bjonen

this pickles/unpickles just fine, but in this case the `__getstate__` in the CustomeBusinessDay offset delets the calendard, but not from the `kwds` (so the pickle fails). can you do a pr to fix (you can use this test case), and maybe run thru all of the offsets just in case, they are prob not directly tested for pickliblity.

@bazeli thanks for reporting

Right now I do not have the time to look into this. It will be a couple of weeks but I'll do it unless someone else wants to take it earlier. 


Resolving patch:
diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py
--- a/pandas/tseries/offsets.py
+++ b/pandas/tseries/offsets.py
@@ -614,6 +614,14 @@ def __getstate__(self):
         """Return a pickleable state"""
         state = self.__dict__.copy()
         del state['calendar']
+
+        # we don't want to actually pickle the calendar object
+        # as its a np.busyday; we recreate on deserilization
+        try:
+            state['kwds'].pop('calendar')
+        except:
+            pass
+
         return state
 
     def __setstate__(self, state):


Test patch:
diff --git a/pandas/io/tests/test_pytables.py b/pandas/io/tests/test_pytables.py
--- a/pandas/io/tests/test_pytables.py
+++ b/pandas/io/tests/test_pytables.py
@@ -2047,6 +2047,28 @@ def compare(a,b):
             result = store.select('df')
             assert_frame_equal(result,df)
 
+    def test_calendar_roundtrip_issue(self):
+
+        # 8591
+        # doc example from tseries holiday section
+        weekmask_egypt = 'Sun Mon Tue Wed Thu'
+        holidays = ['2012-05-01', datetime.datetime(2013, 5, 1), np.datetime64('2014-05-01')]
+        bday_egypt = pandas.offsets.CustomBusinessDay(holidays=holidays, weekmask=weekmask_egypt)
+        dt = datetime.datetime(2013, 4, 30)
+        dts = date_range(dt, periods=5, freq=bday_egypt)
+
+        s = (Series(dts.weekday, dts).map(Series('Mon Tue Wed Thu Fri Sat Sun'.split())))
+
+        with ensure_clean_store(self.path) as store:
+
+            store.put('fixed',s)
+            result = store.select('fixed')
+            assert_series_equal(result, s)
+
+            store.append('table',s)
+            result = store.select('table')
+            assert_series_equal(result, s)
+
     def test_append_with_timezones_dateutil(self):
 
         from datetime import timedelta
diff --git a/pandas/tseries/tests/test_offsets.py b/pandas/tseries/tests/test_offsets.py
--- a/pandas/tseries/tests/test_offsets.py
+++ b/pandas/tseries/tests/test_offsets.py
@@ -12,12 +12,13 @@
 from pandas.core.datetools import (
     bday, BDay, CDay, BQuarterEnd, BMonthEnd,
     CBMonthEnd, CBMonthBegin,
-    BYearEnd, MonthEnd, MonthBegin, BYearBegin,
+    BYearEnd, MonthEnd, MonthBegin, BYearBegin, CustomBusinessDay,
     QuarterBegin, BQuarterBegin, BMonthBegin, DateOffset, Week,
     YearBegin, YearEnd, Hour, Minute, Second, Day, Micro, Milli, Nano, Easter,
     WeekOfMonth, format, ole2datetime, QuarterEnd, to_datetime, normalize_date,
     get_offset, get_offset_name, get_standard_freq)
 
+from pandas import Series
 from pandas.tseries.frequencies import _offset_map
 from pandas.tseries.index import _to_m8, DatetimeIndex, _daterange_cache, date_range
 from pandas.tseries.tools import parse_time_string
@@ -867,7 +868,6 @@ def test_pickle_compat_0_14_1(self):
         cday = CDay(holidays=hdays)
         self.assertEqual(cday, cday0_14_1)
 
-
 class CustomBusinessMonthBase(object):
     _multiprocess_can_split_ = True