Skip to content

Commit

Permalink
Instantiate default client lazily (#432)
Browse files Browse the repository at this point in the history
* lazy default client

* Add note to changelog

* pr number in history

* Validate in cloudpath
  • Loading branch information
pjbull authored May 3, 2024
1 parent fabb0e4 commit 71331d3
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 [#432](https://github.com/drivendataorg/cloudpathlib/issues/432), Issue [#428](https://github.com/drivendataorg/cloudpathlib/issues/428))

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

Expand Down
25 changes: 16 additions & 9 deletions cloudpathlib/cloudpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,10 @@ 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)
self._cloud_meta.validate_completeness()

# versions of the raw string that provide useful methods
self._str = str(cloud_path)
Expand All @@ -225,41 +227,46 @@ 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()

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
Expand Down
7 changes: 7 additions & 0 deletions tests/test_cloudpath_instantiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 71331d3

Please sign in to comment.