Skip to content

Commit 0cd2397

Browse files
committed
Working implementation
1 parent 1f1ff0e commit 0cd2397

File tree

3 files changed

+164
-58
lines changed

3 files changed

+164
-58
lines changed

cloudpathlib/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .azure.azblobclient import AzureBlobClient
66
from .azure.azblobpath import AzureBlobPath
77
from .cloudpath import CloudPath, implementation_registry
8-
from .patches import patch_open
8+
from .patches import patch_open, patch_os_functions
99
from .s3.s3client import S3Client
1010
from .gs.gspath import GSPath
1111
from .gs.gsclient import GSClient
@@ -29,7 +29,8 @@
2929
"implementation_registry",
3030
"GSClient",
3131
"GSPath",
32-
"patch_open"
32+
"patch_open",
33+
"patch_os_functions",
3334
"S3Client",
3435
"S3Path",
3536
]

cloudpathlib/cloudpath.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,20 @@ class CloudPath(metaclass=CloudPathMeta):
177177
def __init__(
178178
self: DerivedCloudPath,
179179
cloud_path: Union[str, DerivedCloudPath],
180+
*parts: str,
180181
client: Optional["Client"] = None,
181182
) -> None:
182183
# handle if local file gets opened. must be set at the top of the method in case any code
183184
# below raises an exception, this prevents __del__ from raising an AttributeError
184185
self._handle: Optional[IO] = None
185186

187+
if parts:
188+
# ensure first part ends in "/"; (sometimes it is just prefix, sometimes a longer path)
189+
if not str(cloud_path).endswith("/"):
190+
cloud_path = str(cloud_path) + "/"
191+
192+
cloud_path = str(cloud_path) + "/".join(p.strip("/") for p in parts)
193+
186194
self.is_valid_cloudpath(cloud_path, raise_on_error=True)
187195

188196
# versions of the raw string that provide useful methods

cloudpathlib/patches.py

Lines changed: 153 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,209 @@
1+
import builtins
12
import os
3+
import os.path
24

35
from .cloudpath import CloudPath
46

57

6-
def _cloudpath_open(*args, **kwargs):
7-
if isinstance(args[0], CloudPath):
8-
return args[0].open(*args[1:], **kwargs)
9-
else:
10-
return open(*args, **kwargs)
8+
def _check_first_arg(*args, **kwargs):
9+
return isinstance(args[0], CloudPath)
1110

1211

13-
def patch_open():
14-
open = _cloudpath_open
12+
def _check_first_arg_first_index(*args, **kwargs):
13+
return isinstance(args[0][0], CloudPath)
14+
1515

16+
def _patch_factory(original_version, cpl_version, cpl_check=_check_first_arg):
17+
_original = original_version
1618

17-
def _dispatch_to_pathlib(path, pathlib_func, os_func, pathlib_args=None, pathlib_kwargs=None, *args, **kwargs):
18-
if pathlib_args is None:
19-
pathlib_args = args
19+
def _patched_version(*args, **kwargs):
20+
if cpl_check(*args, **kwargs):
21+
return cpl_version(*args, **kwargs)
22+
else:
23+
return _original(*args, **kwargs)
2024

21-
if pathlib_kwargs is None:
22-
pathlib_kwargs = kwargs
25+
original_version = _patched_version
26+
return _patched_version
2327

24-
if isinstance(path, CloudPath):
25-
return pathlib_func(path, *pathlib_args, **pathlib_kwargs)
26-
else:
27-
return os_func(*args, **kwargs)
28+
29+
def patch_open():
30+
patched = _patch_factory(
31+
builtins.open,
32+
CloudPath.open,
33+
)
34+
builtins.open = patched
35+
return patched
2836

2937

3038
def _cloudpath_os_listdir(path="."):
31-
return _dispatch_to_pathlib(path, lambda path: list(path.iterdir()), os.listdir, path=path)
39+
return list(path.iterdir())
40+
3241

42+
def _cloudpath_lstat(path, *, dir_fd=None):
43+
return path.stat()
3344

34-
def _cloudpath_os_lstat(path, *, dir_fd=None):
35-
return _dispatch_to_pathlib(path, CloudPath.stat, os.lstat, path, dir_fd=dir_fd)
3645

37-
def _cloudpath_os_mkdir(path, mode=0o777, *, dir_fd=None):
38-
return _dispatch_to_pathlib(path, CloudPath.mkdir, os.mkdir, path, dir_fd=dir_fd)
46+
def _cloudpath_mkdir(path, *, dir_fd=None):
47+
return path.mkdir()
48+
3949

4050
def _cloudpath_os_makedirs(name, mode=0o777, exist_ok=False):
41-
pass
51+
return CloudPath.mkdir(name, parents=True, exist_ok=exist_ok)
52+
4253

4354
def _cloudpath_os_remove(path, *, dir_fd=None):
44-
pass
55+
return path.unlink()
56+
4557

4658
def _cloudpath_os_removedirs(name):
47-
pass
59+
for d in name.parents:
60+
d.rmdir()
61+
4862

4963
def _cloudpath_os_rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None):
50-
pass
64+
return src.rename(dst)
65+
5166

5267
def _cloudpath_os_renames(old, new):
53-
pass
68+
old.rename(new) # move file
69+
_cloudpath_os_removedirs(old) # remove previous directories if empty
70+
5471

5572
def _cloudpath_os_replace(src, dst, *, src_dir_fd=None, dst_dir_fd=None):
56-
pass
73+
return src.rename(dst)
74+
5775

5876
def _cloudpath_os_rmdir(path, *, dir_fd=None):
59-
pass
77+
return path.rmdir()
78+
79+
80+
def _cloudpath_os_scandir(path="."):
81+
return path.iterdir()
6082

61-
def _cloudpath_os_scandir(path='.'):
62-
pass
6383

6484
def _cloudpath_os_stat(path, *, dir_fd=None, follow_symlinks=True):
65-
if isinstance(path, CloudPath):
66-
return path.stat()
67-
else:
68-
return os.stat(path, dir_fd=dir_fd, follow_symlinks=follow_symlinks)
85+
return path.stat()
86+
6987

7088
def _cloudpath_os_unlink(path, *, dir_fd=None):
71-
pass
89+
return path.unlink()
90+
7291

7392
def _cloudpath_os_walk(top, topdown=True, onerror=None, followlinks=False):
74-
pass
93+
try:
94+
dirs, files = [], []
95+
for p in top.iterdir():
96+
dirs.append(p) if p.is_dir() else files.append(p)
97+
98+
if topdown:
99+
yield (top, files, dirs)
100+
101+
for d in dirs:
102+
yield from _cloudpath_os_walk(d, topdown=topdown, onerror=onerror)
103+
104+
if not topdown:
105+
yield (top, files, dirs)
106+
107+
except Exception as e:
108+
if onerror is not None:
109+
onerror(e)
110+
else:
111+
raise
112+
75113

76114
def _cloudpath_os_path_basename(path):
77-
pass
115+
return path.name
116+
117+
118+
def __common(parts):
119+
i = 0
120+
121+
try:
122+
while all(item[i] == parts[0][i] for item in parts[1:]):
123+
i += 1
124+
except IndexError:
125+
pass
126+
127+
return parts[0][:i]
128+
129+
130+
def _cloudpath_os_path_commonpath(paths):
131+
common = __common([p.parts for p in paths])
132+
return paths[0].client.CloudPath(*common)
133+
134+
135+
def _cloudpath_os_path_commonprefix(list):
136+
common = __common([str(p) for p in list])
137+
return common
138+
139+
140+
def _cloudpath_os_path_dirname(path):
141+
return path.parent
78142

79-
def _cloudpath_os_path_exists(path):
80-
pass
81143

82144
def _cloudpath_os_path_getatime(path):
83-
pass
145+
return (path.stat().st_atime,)
146+
84147

85148
def _cloudpath_os_path_getmtime(path):
86-
pass
149+
return (path.stat().st_mtime,)
150+
87151

88152
def _cloudpath_os_path_getctime(path):
89-
pass
153+
return (path.stat().st_ctime,)
90154

91-
def _cloudpath_os_path_getsize(path):
92-
pass
93155

94-
def _cloudpath_os_path_isfile(path):
95-
pass
156+
def _cloudpath_os_path_getsize(path):
157+
return (path.stat().st_size,)
96158

97-
def _cloudpath_os_path_isdir(path):
98-
pass
99159

100160
def _cloudpath_os_path_join(path, *paths):
101-
pass
161+
for p in paths:
162+
path /= p
163+
return path
102164

103-
def _cloudpath_os_path_split(path):
104-
pass
105-
106-
def _cloudpath_os_path_splitext(path):
107-
pass
108165

166+
def _cloudpath_os_path_split(path):
167+
return path.parent, path.name
109168

110-
def patch_os_function():
111-
os.listdir = _cloudpath_os_listdir
112169

170+
def _cloudpath_os_path_splitext(path):
171+
return str(path)[: -len(path.suffix)], path.suffix
172+
173+
174+
def patch_os_functions():
175+
os.listdir = _patch_factory(os.listdir, _cloudpath_os_listdir)
176+
os.lstat = _patch_factory(os.lstat, _cloudpath_lstat)
177+
os.mkdir = _patch_factory(os.mkdir, _cloudpath_mkdir)
178+
os.makedirs = _patch_factory(os.makedirs, _cloudpath_os_makedirs)
179+
os.remove = _patch_factory(os.remove, _cloudpath_os_remove)
180+
os.removedirs = _patch_factory(os.removedirs, _cloudpath_os_removedirs)
181+
os.rename = _patch_factory(os.rename, _cloudpath_os_rename)
182+
os.renames = _patch_factory(os.renames, _cloudpath_os_renames)
183+
os.replace = _patch_factory(os.replace, _cloudpath_os_replace)
184+
os.rmdir = _patch_factory(os.rmdir, _cloudpath_os_rmdir)
185+
os.scandir = _patch_factory(os.scandir, _cloudpath_os_scandir)
186+
os.stat = _patch_factory(os.stat, _cloudpath_os_stat)
187+
os.unlink = _patch_factory(os.unlink, _cloudpath_os_unlink)
188+
os.walk = _patch_factory(os.walk, _cloudpath_os_walk)
189+
190+
os.path.basename = _patch_factory(os.path.basename, _cloudpath_os_path_basename)
191+
os.path.commonpath = _patch_factory(
192+
os.path.commonpath, _cloudpath_os_path_commonpath, cpl_check=_check_first_arg_first_index
193+
)
194+
os.path.commonprefix = _patch_factory(
195+
os.path.commonprefix,
196+
_cloudpath_os_path_commonprefix,
197+
cpl_check=_check_first_arg_first_index,
198+
)
199+
os.path.dirname = _patch_factory(os.path.dirname, _cloudpath_os_path_dirname)
200+
os.path.exists = _patch_factory(os.path.exists, CloudPath.exists)
201+
os.path.getatime = _patch_factory(os.path.getatime, _cloudpath_os_path_getatime)
202+
os.path.getmtime = _patch_factory(os.path.getmtime, _cloudpath_os_path_getmtime)
203+
os.path.getctime = _patch_factory(os.path.getctime, _cloudpath_os_path_getctime)
204+
os.path.getsize = _patch_factory(os.path.getsize, _cloudpath_os_path_getsize)
205+
os.path.isfile = _patch_factory(os.path.isfile, CloudPath.is_file)
206+
os.path.isdir = _patch_factory(os.path.isdir, CloudPath.is_dir)
207+
os.path.join = _patch_factory(os.path.join, _cloudpath_os_path_join)
208+
os.path.split = _patch_factory(os.path.split, _cloudpath_os_path_split)
209+
os.path.splitext = _patch_factory(os.path.splitext, _cloudpath_os_path_splitext)

0 commit comments

Comments
 (0)