Skip to content

Commit 7cbff39

Browse files
pjbullkujenga
andauthored
454 Multiple pickle support (#460)
* allow for roundtrips of cloudpaths through pickle serialization (#454) This avoids an exception thrown because the _client is not serialized into the pickled object, and thus when __getstate__ is called the second time, there is no _client field to delete. Closes #450 * pickle tests --------- Co-authored-by: Aaron Taylor <[email protected]>
1 parent a23a38c commit 7cbff39

File tree

4 files changed

+35
-20
lines changed

4 files changed

+35
-20
lines changed

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## UNRELEASED
44

5+
- Allow `CloudPath` objects to be loaded/dumped through pickle format repeatedly. (Issue [#450](https://github.com/drivendataorg/cloudpathlib/issues/450))
56
- Fixes typo in `FileCacheMode` where values were being filled by envvar `CLOUPATHLIB_FILE_CACHE_MODE` instead of `CLOUDPATHLIB_FILE_CACHE_MODE`. (PR [#424](https://github.com/drivendataorg/cloudpathlib/pull/424)
67
- Fix `CloudPath` cleanup via `CloudPath.__del__` when `Client` encounters an exception during initialization and does not create a `file_cache_mode` attribute. (Issue [#372](https://github.com/drivendataorg/cloudpathlib/issues/372), thanks to [@bryanwweber](https://github.com/bryanwweber))
78
- Drop support for Python 3.7; pin minimal `boto3` version to Python 3.8+ versions. (PR [#407](https://github.com/drivendataorg/cloudpathlib/pull/407))

cloudpathlib/cloudpath.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ def __getstate__(self) -> Dict[str, Any]:
263263
state = self.__dict__.copy()
264264

265265
# don't pickle client
266-
del state["_client"]
266+
if "_client" in state:
267+
del state["_client"]
267268

268269
return state
269270

tests/test_cloudpath_file_io.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from datetime import datetime
22
import os
33
from pathlib import Path, PurePosixPath
4-
import pickle
54
from shutil import rmtree
65
import sys
76
from time import sleep
@@ -454,24 +453,6 @@ def test_os_open(rig):
454453
assert f.readable()
455454

456455

457-
def test_pickle(rig, tmpdir):
458-
p = rig.create_cloud_path("dir_0/file0_0.txt")
459-
460-
with (tmpdir / "test.pkl").open("wb") as f:
461-
pickle.dump(p, f)
462-
463-
with (tmpdir / "test.pkl").open("rb") as f:
464-
pickled = pickle.load(f)
465-
466-
# test a call to the network
467-
assert pickled.exists()
468-
469-
# check we unpickled, and that client is the default client
470-
assert str(pickled) == str(p)
471-
assert pickled.client == p.client
472-
assert rig.client_class._default_client == pickled.client
473-
474-
475456
def test_drive_exists(rig):
476457
"""Tests the exists call for top level bucket/container"""
477458
p = rig.create_cloud_path("dir_0/file0_0.txt")

tests/test_cloudpath_serialize.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import pickle
2+
3+
from cloudpathlib import CloudPath
4+
5+
6+
def test_pickle(rig, tmpdir):
7+
p = rig.create_cloud_path("dir_0/file0_0.txt")
8+
9+
with (tmpdir / "test.pkl").open("wb") as f:
10+
pickle.dump(p, f)
11+
12+
with (tmpdir / "test.pkl").open("rb") as f:
13+
pickled = pickle.load(f)
14+
15+
# test a call to the network
16+
assert pickled.exists()
17+
18+
# check we unpickled, and that client is the default client
19+
assert str(pickled) == str(p)
20+
assert pickled.client == p.client
21+
assert rig.client_class._default_client == pickled.client
22+
23+
24+
def test_pickle_roundtrip():
25+
path1 = CloudPath("s3://bucket/key")
26+
pkl1 = pickle.dumps(path1)
27+
28+
path2 = pickle.loads(pkl1)
29+
pkl2 = pickle.dumps(path2)
30+
31+
assert path1 == path2
32+
assert pkl1 == pkl2

0 commit comments

Comments
 (0)