Skip to content

Commit 527728d

Browse files
Add option to skip password when serializing filesystem (#1625)
1 parent 5a03271 commit 527728d

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

fsspec/json.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import json
22
from contextlib import suppress
33
from pathlib import PurePath
4-
from typing import Any, Callable, Dict, List, Optional, Tuple
4+
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
55

66
from .registry import _import_class, get_filesystem_class
77
from .spec import AbstractFileSystem
88

99

1010
class FilesystemJSONEncoder(json.JSONEncoder):
11+
include_password: ClassVar[bool] = True
12+
1113
def default(self, o: Any) -> Any:
1214
if isinstance(o, AbstractFileSystem):
13-
return o.to_dict()
15+
return o.to_dict(include_password=self.include_password)
1416
if isinstance(o, PurePath):
1517
cls = type(o)
1618
return {"cls": f"{cls.__module__}.{cls.__name__}", "str": str(o)}

fsspec/spec.py

+37-4
Original file line numberDiff line numberDiff line change
@@ -1386,20 +1386,38 @@ def read_block(self, fn, offset, length, delimiter=None):
13861386
length = size - offset
13871387
return read_block(f, offset, length, delimiter)
13881388

1389-
def to_json(self) -> str:
1389+
def to_json(self, *, include_password: bool = True) -> str:
13901390
"""
13911391
JSON representation of this filesystem instance.
13921392
1393+
Parameters
1394+
----------
1395+
include_password: bool, default True
1396+
Whether to include the password (if any) in the output.
1397+
13931398
Returns
13941399
-------
13951400
JSON string with keys ``cls`` (the python location of this class),
13961401
protocol (text name of this class's protocol, first one in case of
13971402
multiple), ``args`` (positional args, usually empty), and all other
13981403
keyword arguments as their own keys.
1404+
1405+
Warnings
1406+
--------
1407+
Serialized filesystems may contain sensitive information which have been
1408+
passed to the constructor, such as passwords and tokens. Make sure you
1409+
store and send them in a secure environment!
13991410
"""
14001411
from .json import FilesystemJSONEncoder
14011412

1402-
return json.dumps(self, cls=FilesystemJSONEncoder)
1413+
return json.dumps(
1414+
self,
1415+
cls=type(
1416+
"_FilesystemJSONEncoder",
1417+
(FilesystemJSONEncoder,),
1418+
{"include_password": include_password},
1419+
),
1420+
)
14031421

14041422
@staticmethod
14051423
def from_json(blob: str) -> AbstractFileSystem:
@@ -1426,25 +1444,40 @@ def from_json(blob: str) -> AbstractFileSystem:
14261444

14271445
return json.loads(blob, cls=FilesystemJSONDecoder)
14281446

1429-
def to_dict(self) -> Dict[str, Any]:
1447+
def to_dict(self, *, include_password: bool = True) -> Dict[str, Any]:
14301448
"""
14311449
JSON-serializable dictionary representation of this filesystem instance.
14321450
1451+
Parameters
1452+
----------
1453+
include_password: bool, default True
1454+
Whether to include the password (if any) in the output.
1455+
14331456
Returns
14341457
-------
14351458
Dictionary with keys ``cls`` (the python location of this class),
14361459
protocol (text name of this class's protocol, first one in case of
14371460
multiple), ``args`` (positional args, usually empty), and all other
14381461
keyword arguments as their own keys.
1462+
1463+
Warnings
1464+
--------
1465+
Serialized filesystems may contain sensitive information which have been
1466+
passed to the constructor, such as passwords and tokens. Make sure you
1467+
store and send them in a secure environment!
14391468
"""
14401469
cls = type(self)
14411470
proto = self.protocol
14421471

1472+
storage_options = dict(self.storage_options)
1473+
if not include_password:
1474+
storage_options.pop("password", None)
1475+
14431476
return dict(
14441477
cls=f"{cls.__module__}:{cls.__name__}",
14451478
protocol=proto[0] if isinstance(proto, (tuple, list)) else proto,
14461479
args=self.storage_args,
1447-
**self.storage_options,
1480+
**storage_options,
14481481
)
14491482

14501483
@staticmethod

fsspec/tests/test_spec.py

+14
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,20 @@ def test_dict_idempotent():
912912
assert DummyTestFS.from_dict(outa) is a
913913

914914

915+
def test_serialize_no_password():
916+
fs = DummyTestFS(1, password="admin")
917+
918+
assert "password" not in fs.to_json(include_password=False)
919+
assert "password" not in fs.to_dict(include_password=False)
920+
921+
922+
def test_serialize_with_password():
923+
fs = DummyTestFS(1, password="admin")
924+
925+
assert "password" in fs.to_json(include_password=True)
926+
assert "password" in fs.to_dict(include_password=True)
927+
928+
915929
def test_from_dict_valid():
916930
fs = DummyTestFS.from_dict({"cls": "fsspec.tests.test_spec.DummyTestFS"})
917931
assert isinstance(fs, DummyTestFS)

0 commit comments

Comments
 (0)