Spaces:
Paused
Paused
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, | |
) | |
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 | |
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 | |