Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Patch open, os and os.path builtins #322

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions cloudpathlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import os
import sys

from .anypath import AnyPath
from .azure.azblobclient import AzureBlobClient
from .azure.azblobpath import AzureBlobPath
from .cloudpath import CloudPath, implementation_registry
from .patches import patch_open, patch_os_functions, patch_glob
from .s3.s3client import S3Client
from .gs.gspath import GSPath
from .gs.gsclient import GSClient
Expand All @@ -27,6 +29,24 @@
"implementation_registry",
"GSClient",
"GSPath",
"patch_glob",
"patch_open",
"patch_os_functions",
"S3Client",
"S3Path",
]


if bool(os.environ.get("CLOUDPATHLIB_PATCH_OPEN", "")):
patch_open()

if bool(os.environ.get("CLOUDPATHLIB_PATCH_OS", "")):
patch_os_functions()

if bool(os.environ.get("CLOUDPATHLIB_PATCH_GLOB", "")):
patch_glob()

if bool(os.environ.get("CLOUDPATHLIB_PATCH_ALL", "")):
patch_open()
patch_os_functions()
patch_glob
4 changes: 2 additions & 2 deletions cloudpathlib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ def set_as_default_client(self) -> None:
instances for this cloud without a client specified."""
self.__class__._default_client = self

def CloudPath(self, cloud_path: Union[str, BoundedCloudPath]) -> BoundedCloudPath:
return self._cloud_meta.path_class(cloud_path=cloud_path, client=self) # type: ignore
def CloudPath(self, cloud_path: Union[str, BoundedCloudPath], *parts: str) -> BoundedCloudPath:
return self._cloud_meta.path_class(cloud_path, *parts, client=self) # type: ignore

def clear_cache(self):
"""Clears the contents of the cache folder.
Expand Down
22 changes: 19 additions & 3 deletions cloudpathlib/cloudpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def _make_selector(pattern_parts, _flavour, case_sensitive=True):
from .exceptions import (
ClientMismatchError,
CloudPathFileExistsError,
CloudPathFileNotFoundError,
CloudPathIsADirectoryError,
CloudPathNotADirectoryError,
CloudPathNotExistsError,
Expand Down Expand Up @@ -210,13 +211,21 @@ class CloudPath(metaclass=CloudPathMeta):
def __init__(
self,
cloud_path: Union[str, Self, "CloudPath"],
*parts: str,
client: Optional["Client"] = None,
) -> None:
# 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

if parts:
# ensure first part ends in "/"; (sometimes it is just prefix, sometimes a longer path)
if not str(cloud_path).endswith("/"):
cloud_path = str(cloud_path) + "/"

cloud_path = str(cloud_path) + "/".join(p.strip("/") for p in parts)

self.is_valid_cloudpath(cloud_path, raise_on_error=True)
self._cloud_meta.validate_completeness()

Expand Down Expand Up @@ -553,11 +562,18 @@ def open(
force_overwrite_to_cloud: bool = False, # extra kwarg not in pathlib
) -> IO[Any]:
# if trying to call open on a directory that exists
if self.exists() and not self.is_file():
exists_on_cloud = self.exists()

if exists_on_cloud and not self.is_file():
raise CloudPathIsADirectoryError(
f"Cannot open directory, only files. Tried to open ({self})"
)

if not exists_on_cloud and any(m in mode for m in ("r", "a")):
raise CloudPathFileNotFoundError(
f"File opened for read or append, but it does not exist on cloud: {self}"
)

if mode == "x" and self.exists():
raise CloudPathFileExistsError(f"Cannot open existing file ({self}) for creation.")

Expand Down Expand Up @@ -1094,7 +1110,7 @@ def _local(self) -> Path:
"""Cached local version of the file."""
return self.client._local_cache_dir / self._no_prefix

def _new_cloudpath(self, path: Union[str, os.PathLike]) -> Self:
def _new_cloudpath(self, path: Union[str, os.PathLike], *parts: str) -> Self:
"""Use the scheme, client, cache dir of this cloudpath to instantiate
a new cloudpath of the same type with the path passed.

Expand All @@ -1110,7 +1126,7 @@ def _new_cloudpath(self, path: Union[str, os.PathLike]) -> Self:
if not path.startswith(self.cloud_prefix):
path = f"{self.cloud_prefix}{path}"

return self.client.CloudPath(path)
return self.client.CloudPath(path, *parts)

def _refresh_cache(self, force_overwrite_from_cloud: bool = False) -> None:
try:
Expand Down
8 changes: 8 additions & 0 deletions cloudpathlib/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class CloudPathNotExistsError(CloudPathException):
pass


class CloudPathFileNotFoundError(CloudPathException, FileNotFoundError):
pass


class CloudPathIsADirectoryError(CloudPathException, IsADirectoryError):
pass

Expand Down Expand Up @@ -77,3 +81,7 @@ class OverwriteNewerCloudError(CloudPathException):

class OverwriteNewerLocalError(CloudPathException):
pass


class InvalidGlobArgumentsError(CloudPathException):
pass
Loading
Loading