# Buggy code
def decode(obj):
    """
    Decoder for deserializing numpy data types.
    """

    typ = obj.get("typ")
    if typ is None:
        return obj
    elif typ == "timestamp":
        freq = obj["freq"] if "freq" in obj else obj["offset"]
        return Timestamp(obj["value"], tz=obj["tz"], freq=freq)
    elif typ == "nat":
        return NaT
    elif typ == "period":
        return Period(ordinal=obj["ordinal"], freq=obj["freq"])
    elif typ == "index":
        dtype = dtype_for(obj["dtype"])
        data = unconvert(obj["data"], dtype, obj.get("compress"))
        return globals()[obj["klass"]](data, dtype=dtype, name=obj["name"])
    elif typ == "range_index":
        return globals()[obj["klass"]](
            obj["start"], obj["stop"], obj["step"], name=obj["name"]
        )
    elif typ == "multi_index":
        dtype = dtype_for(obj["dtype"])
        data = unconvert(obj["data"], dtype, obj.get("compress"))
        data = [tuple(x) for x in data]
        return globals()[obj["klass"]].from_tuples(data, names=obj["names"])
    elif typ == "period_index":
        data = unconvert(obj["data"], np.int64, obj.get("compress"))
        d = dict(name=obj["name"], freq=obj["freq"])
        freq = d.pop("freq", None)
        return globals()[obj["klass"]](PeriodArray(data, freq), **d)

    elif typ == "datetime_index":
        data = unconvert(obj["data"], np.int64, obj.get("compress"))
        d = dict(name=obj["name"], freq=obj["freq"])
        result = DatetimeIndex._simple_new(data, **d)
        tz = obj["tz"]

        # reverse tz conversion
        if tz is not None:
            result = result.tz_localize("UTC").tz_convert(tz)
        return result

    elif typ in ("interval_index", "interval_array"):
        return globals()[obj["klass"]].from_arrays(
            obj["left"], obj["right"], obj["closed"], name=obj["name"]
        )
    elif typ == "category":
        from_codes = globals()[obj["klass"]].from_codes
        return from_codes(
            codes=obj["codes"], categories=obj["categories"], ordered=obj["ordered"]
        )

    elif typ == "interval":
        return Interval(obj["left"], obj["right"], obj["closed"])
    elif typ == "series":
        dtype = dtype_for(obj["dtype"])
        pd_dtype = pandas_dtype(dtype)

        index = obj["index"]
        result = globals()[obj["klass"]](
            unconvert(obj["data"], dtype, obj["compress"]),
            index=index,
            dtype=pd_dtype,
            name=obj["name"],
        )
        return result

    elif typ == "block_manager":
        axes = obj["axes"]

        def create_block(b):
            values = _safe_reshape(
                unconvert(b["values"], dtype_for(b["dtype"]), b["compress"]), b["shape"]
            )

            # locs handles duplicate column names, and should be used instead
            # of items; see GH 9618
            if "locs" in b:
                placement = b["locs"]
            else:
                placement = axes[0].get_indexer(b["items"])
            return make_block(
                values=values,
                klass=getattr(internals, b["klass"]),
                placement=placement,
                dtype=b["dtype"],
            )

        blocks = [create_block(b) for b in obj["blocks"]]
        return globals()[obj["klass"]](BlockManager(blocks, axes))
    elif typ == "datetime":
        return parse(obj["data"])
    elif typ == "datetime64":
        return np.datetime64(parse(obj["data"]))
    elif typ == "date":
        return parse(obj["data"]).date()
    elif typ == "timedelta":
        return timedelta(*obj["data"])
    elif typ == "timedelta64":
        return np.timedelta64(int(obj["data"]))
    # elif typ == 'sparse_series':
    #    dtype = dtype_for(obj['dtype'])
    #    return globals()[obj['klass']](
    #        unconvert(obj['sp_values'], dtype, obj['compress']),
    #        sparse_index=obj['sp_index'], index=obj['index'],
    #        fill_value=obj['fill_value'], kind=obj['kind'], name=obj['name'])
    # elif typ == 'sparse_dataframe':
    #    return globals()[obj['klass']](
    #        obj['data'], columns=obj['columns'],
    #        default_fill_value=obj['default_fill_value'],
    #        default_kind=obj['default_kind']
    #    )
    # elif typ == 'sparse_panel':
    #    return globals()[obj['klass']](
    #        obj['data'], items=obj['items'],
    #        default_fill_value=obj['default_fill_value'],
    #        default_kind=obj['default_kind'])
    elif typ == "block_index":
        return globals()[obj["klass"]](obj["length"], obj["blocs"], obj["blengths"])
    elif typ == "int_index":
        return globals()[obj["klass"]](obj["length"], obj["indices"])
    elif typ == "ndarray":
        return unconvert(
            obj["data"], np.typeDict[obj["dtype"]], obj.get("compress")
        ).reshape(obj["shape"])
    elif typ == "np_scalar":
        if obj.get("sub_typ") == "np_complex":
            return c2f(obj["real"], obj["imag"], obj["dtype"])
        else:
            dtype = dtype_for(obj["dtype"])
            try:
                return dtype(obj["data"])
            except (ValueError, TypeError):
                return dtype.type(obj["data"])
    elif typ == "np_complex":
        return complex(obj["real"] + "+" + obj["imag"] + "j")
    elif isinstance(obj, (dict, list, set)):
        return obj
    else:
        return obj


# Traceback (AttributeError)
In [18]: s = pd.Series(np.random.rand(5), index=pd.date_range('20130101', periods=5))

In [19]: s
Out[19]:
2013-01-01    0.130706
2013-01-02    0.232104
2013-01-03    0.506547
2013-01-04    0.155568
2013-01-05    0.873604
Freq: D, dtype: float64

In [20]: s.to_msgpack('test.msg')

In [22]: s2 = pd.read_msgpack('test.msg')

In [23]: s2
Out[23]: ---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/miniconda3/envs/dev/lib/python3.5/site-packages/IPython/core/formatters.py in __call__(self, obj)
700                 type_pprinters=self.type_printers,
701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
703             printer.flush()
704             return stream.getvalue()

~/miniconda3/envs/dev/lib/python3.5/site-packages/IPython/lib/pretty.py in pretty(self, obj)
400                         if cls is not object \
401                                 and callable(cls.__dict__.get('__repr__')):
--> 402                             return _repr_pprint(obj, self, cycle)
403
404             return _default_pprint(obj, self, cycle)

~/miniconda3/envs/dev/lib/python3.5/site-packages/IPython/lib/pretty.py in _repr_pprint(obj, p, cycle)
695     """A pprint that just redirects to the normal repr function."""
696     # Find newlines and replace them with p.break_()
--> 697     output = repr(obj)
698     for idx,output_line in enumerate(output.splitlines()):
699         if idx:

~/scipy/pandas/pandas/core/base.py in __repr__(self)
75         Yields Bytestring in Py2, Unicode String in py3.
76         """
---> 77         return str(self)
78
79

~/scipy/pandas/pandas/core/base.py in __str__(self)
54
55         if compat.PY3:
---> 56             return self.__unicode__()
57         return self.__bytes__()
58

~/scipy/pandas/pandas/core/series.py in __unicode__(self)
1272
1273         self.to_string(buf=buf, name=self.name, dtype=self.dtype,
-> 1274                        max_rows=max_rows, length=show_dimensions)
1275         result = buf.getvalue()
1276

~/scipy/pandas/pandas/core/series.py in to_string(self, buf, na_rep, float_format, header, index, length, dtype, name, max_rows)
1316                                         float_format=float_format,
1317                                         max_rows=max_rows)
-> 1318         result = formatter.to_string()
1319
1320         # catch contract violations

~/scipy/pandas/pandas/io/formats/format.py in to_string(self)
258     def to_string(self):
259         series = self.tr_series
--> 260         footer = self._get_footer()
261
262         if len(series) == 0:

~/scipy/pandas/pandas/io/formats/format.py in _get_footer(self)
205
206         if getattr(self.series.index, 'freq', None) is not None:
--> 207             footer += 'Freq: {freq}'.format(freq=self.series.index.freqstr)
208
209         if self.name is not False and name is not None:

~/scipy/pandas/pandas/core/arrays/datetimelike.py in freqstr(self)
502         if self.freq is None:
503             return None
--> 504         return self.freq.freqstr
505
506     @property  # NB: override with cache_readonly in immutable subclasses

AttributeError: 'str' object has no attribute 'freqstr'

# Fixed code
def decode(obj):
    """
    Decoder for deserializing numpy data types.
    """

    typ = obj.get("typ")
    if typ is None:
        return obj
    elif typ == "timestamp":
        freq = obj["freq"] if "freq" in obj else obj["offset"]
        return Timestamp(obj["value"], tz=obj["tz"], freq=freq)
    elif typ == "nat":
        return NaT
    elif typ == "period":
        return Period(ordinal=obj["ordinal"], freq=obj["freq"])
    elif typ == "index":
        dtype = dtype_for(obj["dtype"])
        data = unconvert(obj["data"], dtype, obj.get("compress"))
        return globals()[obj["klass"]](data, dtype=dtype, name=obj["name"])
    elif typ == "range_index":
        return globals()[obj["klass"]](
            obj["start"], obj["stop"], obj["step"], name=obj["name"]
        )
    elif typ == "multi_index":
        dtype = dtype_for(obj["dtype"])
        data = unconvert(obj["data"], dtype, obj.get("compress"))
        data = [tuple(x) for x in data]
        return globals()[obj["klass"]].from_tuples(data, names=obj["names"])
    elif typ == "period_index":
        data = unconvert(obj["data"], np.int64, obj.get("compress"))
        d = dict(name=obj["name"], freq=obj["freq"])
        freq = d.pop("freq", None)
        return globals()[obj["klass"]](PeriodArray(data, freq), **d)

    elif typ == "datetime_index":
        data = unconvert(obj["data"], np.int64, obj.get("compress"))
        d = dict(name=obj["name"], freq=obj["freq"])
        result = DatetimeIndex(data, **d)
        tz = obj["tz"]

        # reverse tz conversion
        if tz is not None:
            result = result.tz_localize("UTC").tz_convert(tz)
        return result

    elif typ in ("interval_index", "interval_array"):
        return globals()[obj["klass"]].from_arrays(
            obj["left"], obj["right"], obj["closed"], name=obj["name"]
        )
    elif typ == "category":
        from_codes = globals()[obj["klass"]].from_codes
        return from_codes(
            codes=obj["codes"], categories=obj["categories"], ordered=obj["ordered"]
        )

    elif typ == "interval":
        return Interval(obj["left"], obj["right"], obj["closed"])
    elif typ == "series":
        dtype = dtype_for(obj["dtype"])
        pd_dtype = pandas_dtype(dtype)

        index = obj["index"]
        result = globals()[obj["klass"]](
            unconvert(obj["data"], dtype, obj["compress"]),
            index=index,
            dtype=pd_dtype,
            name=obj["name"],
        )
        return result

    elif typ == "block_manager":
        axes = obj["axes"]

        def create_block(b):
            values = _safe_reshape(
                unconvert(b["values"], dtype_for(b["dtype"]), b["compress"]), b["shape"]
            )

            # locs handles duplicate column names, and should be used instead
            # of items; see GH 9618
            if "locs" in b:
                placement = b["locs"]
            else:
                placement = axes[0].get_indexer(b["items"])
            return make_block(
                values=values,
                klass=getattr(internals, b["klass"]),
                placement=placement,
                dtype=b["dtype"],
            )

        blocks = [create_block(b) for b in obj["blocks"]]
        return globals()[obj["klass"]](BlockManager(blocks, axes))
    elif typ == "datetime":
        return parse(obj["data"])
    elif typ == "datetime64":
        return np.datetime64(parse(obj["data"]))
    elif typ == "date":
        return parse(obj["data"]).date()
    elif typ == "timedelta":
        return timedelta(*obj["data"])
    elif typ == "timedelta64":
        return np.timedelta64(int(obj["data"]))
    # elif typ == 'sparse_series':
    #    dtype = dtype_for(obj['dtype'])
    #    return globals()[obj['klass']](
    #        unconvert(obj['sp_values'], dtype, obj['compress']),
    #        sparse_index=obj['sp_index'], index=obj['index'],
    #        fill_value=obj['fill_value'], kind=obj['kind'], name=obj['name'])
    # elif typ == 'sparse_dataframe':
    #    return globals()[obj['klass']](
    #        obj['data'], columns=obj['columns'],
    #        default_fill_value=obj['default_fill_value'],
    #        default_kind=obj['default_kind']
    #    )
    # elif typ == 'sparse_panel':
    #    return globals()[obj['klass']](
    #        obj['data'], items=obj['items'],
    #        default_fill_value=obj['default_fill_value'],
    #        default_kind=obj['default_kind'])
    elif typ == "block_index":
        return globals()[obj["klass"]](obj["length"], obj["blocs"], obj["blengths"])
    elif typ == "int_index":
        return globals()[obj["klass"]](obj["length"], obj["indices"])
    elif typ == "ndarray":
        return unconvert(
            obj["data"], np.typeDict[obj["dtype"]], obj.get("compress")
        ).reshape(obj["shape"])
    elif typ == "np_scalar":
        if obj.get("sub_typ") == "np_complex":
            return c2f(obj["real"], obj["imag"], obj["dtype"])
        else:
            dtype = dtype_for(obj["dtype"])
            try:
                return dtype(obj["data"])
            except (ValueError, TypeError):
                return dtype.type(obj["data"])
    elif typ == "np_complex":
        return complex(obj["real"] + "+" + obj["imag"] + "j")
    elif isinstance(obj, (dict, list, set)):
        return obj
    else:
        return obj