Skip to content

Commit 71331d3

Browse files
authored
Instantiate default client lazily (#432)
* lazy default client * Add note to changelog * pr number in history * Validate in cloudpath
1 parent fabb0e4 commit 71331d3

File tree

3 files changed

+24
-9
lines changed

3 files changed

+24
-9
lines changed

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- 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))
77
- Drop support for Python 3.7; pin minimal `boto3` version to Python 3.8+ versions. (PR [#407](https://github.com/drivendataorg/cloudpathlib/pull/407))
88
- fix: use native `exists()` method in `GSClient`. (PR [#420](https://github.com/drivendataorg/cloudpathlib/pull/420))
9+
- Enhancement: lazy instantiation of default client (PR [#432](https://github.com/drivendataorg/cloudpathlib/issues/432), Issue [#428](https://github.com/drivendataorg/cloudpathlib/issues/428))
910

1011
## v0.18.1 (2024-02-26)
1112

cloudpathlib/cloudpath.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,10 @@ def __init__(
214214
# handle if local file gets opened. must be set at the top of the method in case any code
215215
# below raises an exception, this prevents __del__ from raising an AttributeError
216216
self._handle: Optional[IO] = None
217+
self._client: Optional["Client"] = None
217218

218219
self.is_valid_cloudpath(cloud_path, raise_on_error=True)
220+
self._cloud_meta.validate_completeness()
219221

220222
# versions of the raw string that provide useful methods
221223
self._str = str(cloud_path)
@@ -225,41 +227,46 @@ def __init__(
225227
# setup client
226228
if client is None:
227229
if isinstance(cloud_path, CloudPath):
228-
client = cloud_path.client
229-
else:
230-
client = self._cloud_meta.client_class.get_default_client()
231-
if not isinstance(client, self._cloud_meta.client_class):
230+
self._client = cloud_path.client
231+
else:
232+
self._client = client
233+
234+
if client is not None and not isinstance(client, self._cloud_meta.client_class):
232235
raise ClientMismatchError(
233236
f"Client of type [{client.__class__}] is not valid for cloud path of type "
234237
f"[{self.__class__}]; must be instance of [{self._cloud_meta.client_class}], or "
235238
f"None to use default client for this cloud path class."
236239
)
237-
self.client: Client = client
238240

239241
# track if local has been written to, if so it may need to be uploaded
240242
self._dirty = False
241243

244+
@property
245+
def client(self):
246+
if getattr(self, "_client", None) is None:
247+
self._client = self._cloud_meta.client_class.get_default_client()
248+
249+
return self._client
250+
242251
def __del__(self) -> None:
243252
# make sure that file handle to local path is closed
244253
if self._handle is not None:
245254
self._handle.close()
246255

247256
# ensure file removed from cache when cloudpath object deleted
248-
client = getattr(self, "client", None)
257+
client = getattr(self, "_client", None)
249258
if getattr(client, "file_cache_mode", None) == FileCacheMode.cloudpath_object:
250259
self.clear_cache()
251260

252261
def __getstate__(self) -> Dict[str, Any]:
253262
state = self.__dict__.copy()
254263

255264
# don't pickle client
256-
del state["client"]
265+
del state["_client"]
257266

258267
return state
259268

260269
def __setstate__(self, state: Dict[str, Any]) -> None:
261-
client = self._cloud_meta.client_class.get_default_client()
262-
state["client"] = client
263270
self.__dict__.update(state)
264271

265272
@property

tests/test_cloudpath_instantiation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ def test_instantiation(rig, path):
5959
assert str(p._path) == expected.split(":/", 1)[-1]
6060

6161

62+
def test_default_client_lazy(rig):
63+
cp = rig.path_class(rig.cloud_prefix + "testing/file.txt")
64+
assert cp._client is None
65+
assert cp.client is not None
66+
assert cp._client is not None
67+
68+
6269
def test_instantiation_errors(rig):
6370
with pytest.raises(TypeError):
6471
rig.path_class()

0 commit comments

Comments
 (0)