|
""" |
|
Support pre-0.12 series pickle compatibility. |
|
""" |
|
from __future__ import annotations |
|
|
|
import contextlib |
|
import copy |
|
import io |
|
import pickle as pkl |
|
from typing import TYPE_CHECKING |
|
|
|
import numpy as np |
|
|
|
from pandas._libs.arrays import NDArrayBacked |
|
from pandas._libs.tslibs import BaseOffset |
|
|
|
from pandas import Index |
|
from pandas.core.arrays import ( |
|
DatetimeArray, |
|
PeriodArray, |
|
TimedeltaArray, |
|
) |
|
from pandas.core.internals import BlockManager |
|
|
|
if TYPE_CHECKING: |
|
from collections.abc import Generator |
|
|
|
|
|
def load_reduce(self) -> None: |
|
stack = self.stack |
|
args = stack.pop() |
|
func = stack[-1] |
|
|
|
try: |
|
stack[-1] = func(*args) |
|
return |
|
except TypeError as err: |
|
|
|
|
|
|
|
msg = "_reconstruct: First argument must be a sub-type of ndarray" |
|
|
|
if msg in str(err): |
|
try: |
|
cls = args[0] |
|
stack[-1] = object.__new__(cls) |
|
return |
|
except TypeError: |
|
pass |
|
elif args and isinstance(args[0], type) and issubclass(args[0], BaseOffset): |
|
|
|
cls = args[0] |
|
stack[-1] = cls.__new__(*args) |
|
return |
|
elif args and issubclass(args[0], PeriodArray): |
|
cls = args[0] |
|
stack[-1] = NDArrayBacked.__new__(*args) |
|
return |
|
|
|
raise |
|
|
|
|
|
|
|
_class_locations_map = { |
|
("pandas.core.sparse.array", "SparseArray"): ("pandas.core.arrays", "SparseArray"), |
|
|
|
("pandas.core.base", "FrozenNDArray"): ("numpy", "ndarray"), |
|
|
|
|
|
("pandas.core.internals.blocks", "new_block"): ( |
|
"pandas._libs.internals", |
|
"_unpickle_block", |
|
), |
|
("pandas.core.indexes.frozen", "FrozenNDArray"): ("numpy", "ndarray"), |
|
("pandas.core.base", "FrozenList"): ("pandas.core.indexes.frozen", "FrozenList"), |
|
|
|
("pandas.core.series", "TimeSeries"): ("pandas.core.series", "Series"), |
|
("pandas.sparse.series", "SparseTimeSeries"): ( |
|
"pandas.core.sparse.series", |
|
"SparseSeries", |
|
), |
|
|
|
("pandas._sparse", "BlockIndex"): ("pandas._libs.sparse", "BlockIndex"), |
|
("pandas.tslib", "Timestamp"): ("pandas._libs.tslib", "Timestamp"), |
|
|
|
("pandas._period", "Period"): ("pandas._libs.tslibs.period", "Period"), |
|
("pandas._libs.period", "Period"): ("pandas._libs.tslibs.period", "Period"), |
|
|
|
("pandas.tslib", "__nat_unpickle"): ( |
|
"pandas._libs.tslibs.nattype", |
|
"__nat_unpickle", |
|
), |
|
("pandas._libs.tslib", "__nat_unpickle"): ( |
|
"pandas._libs.tslibs.nattype", |
|
"__nat_unpickle", |
|
), |
|
|
|
("pandas.sparse.array", "SparseArray"): ( |
|
"pandas.core.arrays.sparse", |
|
"SparseArray", |
|
), |
|
("pandas.indexes.base", "_new_Index"): ("pandas.core.indexes.base", "_new_Index"), |
|
("pandas.indexes.base", "Index"): ("pandas.core.indexes.base", "Index"), |
|
("pandas.indexes.numeric", "Int64Index"): ( |
|
"pandas.core.indexes.base", |
|
"Index", |
|
), |
|
("pandas.indexes.range", "RangeIndex"): ("pandas.core.indexes.range", "RangeIndex"), |
|
("pandas.indexes.multi", "MultiIndex"): ("pandas.core.indexes.multi", "MultiIndex"), |
|
("pandas.tseries.index", "_new_DatetimeIndex"): ( |
|
"pandas.core.indexes.datetimes", |
|
"_new_DatetimeIndex", |
|
), |
|
("pandas.tseries.index", "DatetimeIndex"): ( |
|
"pandas.core.indexes.datetimes", |
|
"DatetimeIndex", |
|
), |
|
("pandas.tseries.period", "PeriodIndex"): ( |
|
"pandas.core.indexes.period", |
|
"PeriodIndex", |
|
), |
|
|
|
("pandas.core.categorical", "Categorical"): ("pandas.core.arrays", "Categorical"), |
|
|
|
("pandas.tseries.tdi", "TimedeltaIndex"): ( |
|
"pandas.core.indexes.timedeltas", |
|
"TimedeltaIndex", |
|
), |
|
("pandas.indexes.numeric", "Float64Index"): ( |
|
"pandas.core.indexes.base", |
|
"Index", |
|
), |
|
|
|
("pandas.core.indexes.numeric", "Int64Index"): ( |
|
"pandas.core.indexes.base", |
|
"Index", |
|
), |
|
("pandas.core.indexes.numeric", "UInt64Index"): ( |
|
"pandas.core.indexes.base", |
|
"Index", |
|
), |
|
("pandas.core.indexes.numeric", "Float64Index"): ( |
|
"pandas.core.indexes.base", |
|
"Index", |
|
), |
|
("pandas.core.arrays.sparse.dtype", "SparseDtype"): ( |
|
"pandas.core.dtypes.dtypes", |
|
"SparseDtype", |
|
), |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
class Unpickler(pkl._Unpickler): |
|
def find_class(self, module, name): |
|
|
|
key = (module, name) |
|
module, name = _class_locations_map.get(key, key) |
|
return super().find_class(module, name) |
|
|
|
|
|
Unpickler.dispatch = copy.copy(Unpickler.dispatch) |
|
Unpickler.dispatch[pkl.REDUCE[0]] = load_reduce |
|
|
|
|
|
def load_newobj(self) -> None: |
|
args = self.stack.pop() |
|
cls = self.stack[-1] |
|
|
|
|
|
if issubclass(cls, Index): |
|
obj = object.__new__(cls) |
|
elif issubclass(cls, DatetimeArray) and not args: |
|
arr = np.array([], dtype="M8[ns]") |
|
obj = cls.__new__(cls, arr, arr.dtype) |
|
elif issubclass(cls, TimedeltaArray) and not args: |
|
arr = np.array([], dtype="m8[ns]") |
|
obj = cls.__new__(cls, arr, arr.dtype) |
|
elif cls is BlockManager and not args: |
|
obj = cls.__new__(cls, (), [], False) |
|
else: |
|
obj = cls.__new__(cls, *args) |
|
|
|
self.stack[-1] = obj |
|
|
|
|
|
Unpickler.dispatch[pkl.NEWOBJ[0]] = load_newobj |
|
|
|
|
|
def load_newobj_ex(self) -> None: |
|
kwargs = self.stack.pop() |
|
args = self.stack.pop() |
|
cls = self.stack.pop() |
|
|
|
|
|
if issubclass(cls, Index): |
|
obj = object.__new__(cls) |
|
else: |
|
obj = cls.__new__(cls, *args, **kwargs) |
|
self.append(obj) |
|
|
|
|
|
try: |
|
Unpickler.dispatch[pkl.NEWOBJ_EX[0]] = load_newobj_ex |
|
except (AttributeError, KeyError): |
|
pass |
|
|
|
|
|
def load(fh, encoding: str | None = None, is_verbose: bool = False): |
|
""" |
|
Load a pickle, with a provided encoding, |
|
|
|
Parameters |
|
---------- |
|
fh : a filelike object |
|
encoding : an optional encoding |
|
is_verbose : show exception output |
|
""" |
|
try: |
|
fh.seek(0) |
|
if encoding is not None: |
|
up = Unpickler(fh, encoding=encoding) |
|
else: |
|
up = Unpickler(fh) |
|
|
|
up.is_verbose = is_verbose |
|
|
|
return up.load() |
|
except (ValueError, TypeError): |
|
raise |
|
|
|
|
|
def loads( |
|
bytes_object: bytes, |
|
*, |
|
fix_imports: bool = True, |
|
encoding: str = "ASCII", |
|
errors: str = "strict", |
|
): |
|
""" |
|
Analogous to pickle._loads. |
|
""" |
|
fd = io.BytesIO(bytes_object) |
|
return Unpickler( |
|
fd, fix_imports=fix_imports, encoding=encoding, errors=errors |
|
).load() |
|
|
|
|
|
@contextlib.contextmanager |
|
def patch_pickle() -> Generator[None, None, None]: |
|
""" |
|
Temporarily patch pickle to use our unpickler. |
|
""" |
|
orig_loads = pkl.loads |
|
try: |
|
setattr(pkl, "loads", loads) |
|
yield |
|
finally: |
|
setattr(pkl, "loads", orig_loads) |
|
|