| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- """
- Pickle compatibility to pandas version 1.0
- """
- from __future__ import annotations
- import contextlib
- import io
- import pickle
- from typing import (
- TYPE_CHECKING,
- Any,
- )
- import numpy as np
- from pandas._libs.arrays import NDArrayBacked
- from pandas._libs.tslibs import BaseOffset
- from pandas.core.arrays import (
- DatetimeArray,
- PeriodArray,
- TimedeltaArray,
- )
- from pandas.core.internals import BlockManager
- if TYPE_CHECKING:
- from collections.abc import Generator
- # If classes are moved, provide compat here.
- _class_locations_map = {
- # Re-routing unpickle block logic to go through _unpickle_block instead
- # for pandas <= 1.3.5
- ("pandas.core.internals.blocks", "new_block"): (
- "pandas._libs.internals",
- "_unpickle_block",
- ),
- # Avoid Cython's warning "contradiction to Python 'class private name' rules"
- ("pandas._libs.tslibs.nattype", "__nat_unpickle"): (
- "pandas._libs.tslibs.nattype",
- "_nat_unpickle",
- ),
- # 50775, remove Int64Index, UInt64Index & Float64Index from codebase
- ("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",
- ),
- }
- # our Unpickler sub-class to override methods and some dispatcher
- # functions for compat and uses a non-public class of the pickle module.
- class Unpickler(pickle._Unpickler):
- def find_class(self, module: str, name: str) -> Any:
- key = (module, name)
- module, name = _class_locations_map.get(key, key)
- return super().find_class(module, name)
- dispatch = pickle._Unpickler.dispatch.copy()
- def load_reduce(self) -> None:
- stack = self.stack # type: ignore[attr-defined]
- args = stack.pop()
- func = stack[-1]
- try:
- stack[-1] = func(*args)
- except TypeError:
- # If we have a deprecated function,
- # try to replace and try again.
- if args and isinstance(args[0], type) and issubclass(args[0], BaseOffset):
- # TypeError: object.__new__(Day) is not safe, use Day.__new__()
- 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
- dispatch[pickle.REDUCE[0]] = load_reduce # type: ignore[assignment]
- def load_newobj(self) -> None:
- args = self.stack.pop() # type: ignore[attr-defined]
- cls = self.stack.pop() # type: ignore[attr-defined]
- # compat
- if 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.append(obj) # type: ignore[attr-defined]
- dispatch[pickle.NEWOBJ[0]] = load_newobj # type: ignore[assignment]
- def loads(
- bytes_object: bytes,
- *,
- fix_imports: bool = True,
- encoding: str = "ASCII",
- errors: str = "strict",
- ) -> Any:
- """
- 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]:
- """
- Temporarily patch pickle to use our unpickler.
- """
- orig_loads = pickle.loads
- try:
- setattr(pickle, "loads", loads)
- yield
- finally:
- setattr(pickle, "loads", orig_loads)
|