From 88f60edf280cba5f21a23484554d31e4629b43b3 Mon Sep 17 00:00:00 2001 From: Peter Bull Date: Thu, 2 May 2024 20:30:53 -0600 Subject: [PATCH 1/4] lazy default client --- cloudpathlib/cloudpath.py | 24 +++++++++++++++--------- tests/test_cloudpath_instantiation.py | 7 +++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cloudpathlib/cloudpath.py b/cloudpathlib/cloudpath.py index 0c17a4e2..c2872aa9 100644 --- a/cloudpathlib/cloudpath.py +++ b/cloudpathlib/cloudpath.py @@ -214,6 +214,7 @@ def __init__( # handle if local file gets opened. must be set at the top of the method in case any code # below raises an exception, this prevents __del__ from raising an AttributeError self._handle: Optional[IO] = None + self._client: Optional["Client"] = None self.is_valid_cloudpath(cloud_path, raise_on_error=True) @@ -225,27 +226,34 @@ def __init__( # setup client if client is None: if isinstance(cloud_path, CloudPath): - client = cloud_path.client - else: - client = self._cloud_meta.client_class.get_default_client() - if not isinstance(client, self._cloud_meta.client_class): + self._client = cloud_path.client + else: + self._client = client + + if client is not None and not isinstance(client, self._cloud_meta.client_class): raise ClientMismatchError( f"Client of type [{client.__class__}] is not valid for cloud path of type " f"[{self.__class__}]; must be instance of [{self._cloud_meta.client_class}], or " f"None to use default client for this cloud path class." ) - self.client: Client = client # track if local has been written to, if so it may need to be uploaded self._dirty = False + @property + def client(self): + if getattr(self, "_client", None) is None: + self._client = self._cloud_meta.client_class.get_default_client() + + return self._client + def __del__(self) -> None: # make sure that file handle to local path is closed if self._handle is not None: self._handle.close() # ensure file removed from cache when cloudpath object deleted - client = getattr(self, "client", None) + client = getattr(self, "_client", None) if getattr(client, "file_cache_mode", None) == FileCacheMode.cloudpath_object: self.clear_cache() @@ -253,13 +261,11 @@ def __getstate__(self) -> Dict[str, Any]: state = self.__dict__.copy() # don't pickle client - del state["client"] + del state["_client"] return state def __setstate__(self, state: Dict[str, Any]) -> None: - client = self._cloud_meta.client_class.get_default_client() - state["client"] = client self.__dict__.update(state) @property diff --git a/tests/test_cloudpath_instantiation.py b/tests/test_cloudpath_instantiation.py index fe0ca3fa..b625a47b 100644 --- a/tests/test_cloudpath_instantiation.py +++ b/tests/test_cloudpath_instantiation.py @@ -59,6 +59,13 @@ def test_instantiation(rig, path): assert str(p._path) == expected.split(":/", 1)[-1] +def test_default_client_lazy(rig): + cp = rig.path_class(rig.cloud_prefix + "testing/file.txt") + assert cp._client is None + assert cp.client is not None + assert cp._client is not None + + def test_instantiation_errors(rig): with pytest.raises(TypeError): rig.path_class() From 7b626f5ad4723d27d4865b7f0ac75c0de0b23911 Mon Sep 17 00:00:00 2001 From: Peter Bull Date: Thu, 2 May 2024 20:33:36 -0600 Subject: [PATCH 2/4] Add note to changelog --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index c56f815c..a0177797 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,7 @@ - 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)) - Drop support for Python 3.7; pin minimal `boto3` version to Python 3.8+ versions. (PR [#407](https://github.com/drivendataorg/cloudpathlib/pull/407)) - fix: use native `exists()` method in `GSClient`. (PR [#420](https://github.com/drivendataorg/cloudpathlib/pull/420)) +- Enhancement: lazy instantiation of default client (PR [#431](https://github.com/drivendataorg/cloudpathlib/issues/431), Issue [#428](https://github.com/drivendataorg/cloudpathlib/issues/428)) ## v0.18.1 (2024-02-26) From 166e676b5c53519760a00069e1ef0cd6f99946b1 Mon Sep 17 00:00:00 2001 From: Peter Bull Date: Fri, 3 May 2024 09:21:45 -0700 Subject: [PATCH 3/4] pr number in history --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index a0177797..a21d8215 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,7 +6,7 @@ - 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)) - Drop support for Python 3.7; pin minimal `boto3` version to Python 3.8+ versions. (PR [#407](https://github.com/drivendataorg/cloudpathlib/pull/407)) - fix: use native `exists()` method in `GSClient`. (PR [#420](https://github.com/drivendataorg/cloudpathlib/pull/420)) -- Enhancement: lazy instantiation of default client (PR [#431](https://github.com/drivendataorg/cloudpathlib/issues/431), Issue [#428](https://github.com/drivendataorg/cloudpathlib/issues/428)) +- Enhancement: lazy instantiation of default client (PR [#432](https://github.com/drivendataorg/cloudpathlib/issues/432), Issue [#428](https://github.com/drivendataorg/cloudpathlib/issues/428)) ## v0.18.1 (2024-02-26) From c11d1d70f6114a29628d77703533d22c3a131262 Mon Sep 17 00:00:00 2001 From: Peter Bull Date: Fri, 3 May 2024 09:39:44 -0700 Subject: [PATCH 4/4] Validate in cloudpath --- cloudpathlib/cloudpath.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudpathlib/cloudpath.py b/cloudpathlib/cloudpath.py index c2872aa9..afca5cb1 100644 --- a/cloudpathlib/cloudpath.py +++ b/cloudpathlib/cloudpath.py @@ -217,6 +217,7 @@ def __init__( self._client: Optional["Client"] = None self.is_valid_cloudpath(cloud_path, raise_on_error=True) + self._cloud_meta.validate_completeness() # versions of the raw string that provide useful methods self._str = str(cloud_path)