|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
3 | 3 | import io
|
| 4 | +import json |
4 | 5 | import logging
|
5 | 6 | import os
|
6 | 7 | import threading
|
|
9 | 10 | from errno import ESPIPE
|
10 | 11 | from glob import has_magic
|
11 | 12 | from hashlib import sha256
|
12 |
| -from typing import ClassVar |
| 13 | +from typing import Any, ClassVar, Dict, Tuple |
13 | 14 |
|
14 | 15 | from .callbacks import DEFAULT_CALLBACK
|
15 | 16 | from .config import apply_config, conf
|
@@ -115,6 +116,10 @@ class AbstractFileSystem(metaclass=_Cached):
|
115 | 116 | #: Extra *class attributes* that should be considered when hashing.
|
116 | 117 | _extra_tokenize_attributes = ()
|
117 | 118 |
|
| 119 | + # Set by _Cached metaclass |
| 120 | + storage_args: Tuple[Any, ...] |
| 121 | + storage_options: Dict[str, Any] |
| 122 | + |
118 | 123 | def __init__(self, *args, **storage_options):
|
119 | 124 | """Create and configure file-system instance
|
120 | 125 |
|
@@ -1381,61 +1386,98 @@ def read_block(self, fn, offset, length, delimiter=None):
|
1381 | 1386 | length = size - offset
|
1382 | 1387 | return read_block(f, offset, length, delimiter)
|
1383 | 1388 |
|
1384 |
| - def to_json(self): |
| 1389 | + def to_json(self) -> str: |
1385 | 1390 | """
|
1386 |
| - JSON representation of this filesystem instance |
| 1391 | + JSON representation of this filesystem instance. |
1387 | 1392 |
|
1388 | 1393 | Returns
|
1389 | 1394 | -------
|
1390 |
| - str: JSON structure with keys cls (the python location of this class), |
1391 |
| - protocol (text name of this class's protocol, first one in case of |
1392 |
| - multiple), args (positional args, usually empty), and all other |
1393 |
| - kwargs as their own keys. |
| 1395 | + JSON string with keys ``cls`` (the python location of this class), |
| 1396 | + protocol (text name of this class's protocol, first one in case of |
| 1397 | + multiple), ``args`` (positional args, usually empty), and all other |
| 1398 | + keyword arguments as their own keys. |
| 1399 | + """ |
| 1400 | + from .json import FilesystemJSONEncoder |
| 1401 | + |
| 1402 | + return json.dumps(self, cls=FilesystemJSONEncoder) |
| 1403 | + |
| 1404 | + @staticmethod |
| 1405 | + def from_json(blob: str) -> AbstractFileSystem: |
1394 | 1406 | """
|
1395 |
| - import json |
| 1407 | + Recreate a filesystem instance from JSON representation. |
| 1408 | +
|
| 1409 | + See ``.to_json()`` for the expected structure of the input. |
| 1410 | +
|
| 1411 | + Parameters |
| 1412 | + ---------- |
| 1413 | + blob: str |
| 1414 | +
|
| 1415 | + Returns |
| 1416 | + ------- |
| 1417 | + file system instance, not necessarily of this particular class. |
1396 | 1418 |
|
| 1419 | + Warnings |
| 1420 | + -------- |
| 1421 | + This can import arbitrary modules (as determined by the ``cls`` key). |
| 1422 | + Make sure you haven't installed any modules that may execute malicious code |
| 1423 | + at import time. |
| 1424 | + """ |
| 1425 | + from .json import FilesystemJSONDecoder |
| 1426 | + |
| 1427 | + return json.loads(blob, cls=FilesystemJSONDecoder) |
| 1428 | + |
| 1429 | + def to_dict(self) -> Dict[str, Any]: |
| 1430 | + """ |
| 1431 | + JSON-serializable dictionary representation of this filesystem instance. |
| 1432 | +
|
| 1433 | + Returns |
| 1434 | + ------- |
| 1435 | + Dictionary with keys ``cls`` (the python location of this class), |
| 1436 | + protocol (text name of this class's protocol, first one in case of |
| 1437 | + multiple), ``args`` (positional args, usually empty), and all other |
| 1438 | + keyword arguments as their own keys. |
| 1439 | + """ |
1397 | 1440 | cls = type(self)
|
1398 |
| - cls = ".".join((cls.__module__, cls.__name__)) |
1399 |
| - proto = ( |
1400 |
| - self.protocol[0] |
1401 |
| - if isinstance(self.protocol, (tuple, list)) |
1402 |
| - else self.protocol |
1403 |
| - ) |
1404 |
| - return json.dumps( |
1405 |
| - dict( |
1406 |
| - cls=cls, |
1407 |
| - protocol=proto, |
1408 |
| - args=self.storage_args, |
1409 |
| - **self.storage_options, |
1410 |
| - ) |
| 1441 | + proto = self.protocol |
| 1442 | + |
| 1443 | + return dict( |
| 1444 | + cls=f"{cls.__module__}:{cls.__name__}", |
| 1445 | + protocol=proto[0] if isinstance(proto, (tuple, list)) else proto, |
| 1446 | + args=self.storage_args, |
| 1447 | + **self.storage_options, |
1411 | 1448 | )
|
1412 | 1449 |
|
1413 | 1450 | @staticmethod
|
1414 |
| - def from_json(blob): |
| 1451 | + def from_dict(dct: Dict[str, Any]) -> AbstractFileSystem: |
1415 | 1452 | """
|
1416 |
| - Recreate a filesystem instance from JSON representation |
| 1453 | + Recreate a filesystem instance from dictionary representation. |
1417 | 1454 |
|
1418 |
| - See ``.to_json()`` for the expected structure of the input |
| 1455 | + See ``.to_dict()`` for the expected structure of the input. |
1419 | 1456 |
|
1420 | 1457 | Parameters
|
1421 | 1458 | ----------
|
1422 |
| - blob: str |
| 1459 | + dct: Dict[str, Any] |
1423 | 1460 |
|
1424 | 1461 | Returns
|
1425 | 1462 | -------
|
1426 | 1463 | file system instance, not necessarily of this particular class.
|
| 1464 | +
|
| 1465 | + Warnings |
| 1466 | + -------- |
| 1467 | + This can import arbitrary modules (as determined by the ``cls`` key). |
| 1468 | + Make sure you haven't installed any modules that may execute malicious code |
| 1469 | + at import time. |
1427 | 1470 | """
|
1428 |
| - import json |
| 1471 | + from .json import FilesystemJSONDecoder |
1429 | 1472 |
|
1430 |
| - from .registry import _import_class, get_filesystem_class |
| 1473 | + cls = FilesystemJSONDecoder.try_resolve_fs_cls(dct) |
| 1474 | + if cls is None: |
| 1475 | + raise ValueError("Not a serialized AbstractFileSystem") |
1431 | 1476 |
|
1432 |
| - dic = json.loads(blob) |
1433 |
| - protocol = dic.pop("protocol") |
1434 |
| - try: |
1435 |
| - cls = _import_class(dic.pop("cls")) |
1436 |
| - except (ImportError, ValueError, RuntimeError, KeyError): |
1437 |
| - cls = get_filesystem_class(protocol) |
1438 |
| - return cls(*dic.pop("args", ()), **dic) |
| 1477 | + dct.pop("cls", None) |
| 1478 | + dct.pop("protocol", None) |
| 1479 | + |
| 1480 | + return cls(*dct.pop("args", ()), **dct) |
1439 | 1481 |
|
1440 | 1482 | def _get_pyarrow_filesystem(self):
|
1441 | 1483 | """
|
|
0 commit comments