123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- import json
- from contextlib import suppress
- from pathlib import PurePath
- from typing import (
- Any,
- Callable,
- ClassVar,
- Dict,
- List,
- Mapping,
- Optional,
- Sequence,
- Tuple,
- )
- from .registry import _import_class, get_filesystem_class
- from .spec import AbstractFileSystem
- class FilesystemJSONEncoder(json.JSONEncoder):
- include_password: ClassVar[bool] = True
- def default(self, o: Any) -> Any:
- if isinstance(o, AbstractFileSystem):
- return o.to_dict(include_password=self.include_password)
- if isinstance(o, PurePath):
- cls = type(o)
- return {"cls": f"{cls.__module__}.{cls.__name__}", "str": str(o)}
- return super().default(o)
- def make_serializable(self, obj: Any) -> Any:
- """
- Recursively converts an object so that it can be JSON serialized via
- :func:`json.dumps` and :func:`json.dump`, without actually calling
- said functions.
- """
- if isinstance(obj, (str, int, float, bool)):
- return obj
- if isinstance(obj, Mapping):
- return {k: self.make_serializable(v) for k, v in obj.items()}
- if isinstance(obj, Sequence):
- return [self.make_serializable(v) for v in obj]
- return self.default(obj)
- class FilesystemJSONDecoder(json.JSONDecoder):
- def __init__(
- self,
- *,
- object_hook: Optional[Callable[[Dict[str, Any]], Any]] = None,
- parse_float: Optional[Callable[[str], Any]] = None,
- parse_int: Optional[Callable[[str], Any]] = None,
- parse_constant: Optional[Callable[[str], Any]] = None,
- strict: bool = True,
- object_pairs_hook: Optional[Callable[[List[Tuple[str, Any]]], Any]] = None,
- ) -> None:
- self.original_object_hook = object_hook
- super().__init__(
- object_hook=self.custom_object_hook,
- parse_float=parse_float,
- parse_int=parse_int,
- parse_constant=parse_constant,
- strict=strict,
- object_pairs_hook=object_pairs_hook,
- )
- @classmethod
- def try_resolve_path_cls(cls, dct: Dict[str, Any]):
- with suppress(Exception):
- fqp = dct["cls"]
- path_cls = _import_class(fqp)
- if issubclass(path_cls, PurePath):
- return path_cls
- return None
- @classmethod
- def try_resolve_fs_cls(cls, dct: Dict[str, Any]):
- with suppress(Exception):
- if "cls" in dct:
- try:
- fs_cls = _import_class(dct["cls"])
- if issubclass(fs_cls, AbstractFileSystem):
- return fs_cls
- except Exception:
- if "protocol" in dct: # Fallback if cls cannot be imported
- return get_filesystem_class(dct["protocol"])
- raise
- return None
- def custom_object_hook(self, dct: Dict[str, Any]):
- if "cls" in dct:
- if (obj_cls := self.try_resolve_fs_cls(dct)) is not None:
- return AbstractFileSystem.from_dict(dct)
- if (obj_cls := self.try_resolve_path_cls(dct)) is not None:
- return obj_cls(dct["str"])
- if self.original_object_hook is not None:
- return self.original_object_hook(dct)
- return dct
- def unmake_serializable(self, obj: Any) -> Any:
- """
- Inverse function of :meth:`FilesystemJSONEncoder.make_serializable`.
- """
- if isinstance(obj, dict):
- obj = self.custom_object_hook(obj)
- if isinstance(obj, dict):
- return {k: self.unmake_serializable(v) for k, v in obj.items()}
- if isinstance(obj, (list, tuple)):
- return [self.unmake_serializable(v) for v in obj]
- return obj
|