megfile 3.0.5__py3-none-any.whl → 3.0.6.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- megfile/cli.py +21 -10
- megfile/errors.py +26 -18
- megfile/fs.py +9 -5
- megfile/fs_path.py +72 -15
- megfile/hdfs.py +3 -2
- megfile/hdfs_path.py +16 -6
- megfile/http_path.py +30 -11
- megfile/lib/http_prefetch_reader.py +26 -10
- megfile/pathlike.py +10 -3
- megfile/s3.py +14 -7
- megfile/s3_path.py +55 -25
- megfile/sftp.py +18 -9
- megfile/sftp_path.py +61 -30
- megfile/smart.py +60 -16
- megfile/version.py +1 -1
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/METADATA +5 -1
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/RECORD +22 -22
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/LICENSE +0 -0
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/LICENSE.pyre +0 -0
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/WHEEL +0 -0
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/entry_points.txt +0 -0
- {megfile-3.0.5.dist-info → megfile-3.0.6.post1.dist-info}/top_level.txt +0 -0
megfile/s3.py
CHANGED
|
@@ -169,14 +169,16 @@ def s3_hasbucket(path: PathLike) -> bool:
|
|
|
169
169
|
return S3Path(path).hasbucket()
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
def s3_move(
|
|
172
|
+
def s3_move(
|
|
173
|
+
src_url: PathLike, dst_url: PathLike, overwrite: bool = True) -> None:
|
|
173
174
|
'''
|
|
174
175
|
Move file/directory path from src_url to dst_url
|
|
175
176
|
|
|
176
177
|
:param src_url: Given path
|
|
177
178
|
:param dst_url: Given destination path
|
|
179
|
+
:param overwrite: whether or not overwrite file when exists
|
|
178
180
|
'''
|
|
179
|
-
return S3Path(src_url).move(dst_url)
|
|
181
|
+
return S3Path(src_url).move(dst_url, overwrite)
|
|
180
182
|
|
|
181
183
|
|
|
182
184
|
def s3_remove(path: PathLike, missing_ok: bool = False) -> None:
|
|
@@ -305,8 +307,9 @@ def s3_getmd5(
|
|
|
305
307
|
def s3_copy(
|
|
306
308
|
src_url: PathLike,
|
|
307
309
|
dst_url: PathLike,
|
|
310
|
+
callback: Optional[Callable[[int], None]] = None,
|
|
308
311
|
followlinks: bool = False,
|
|
309
|
-
|
|
312
|
+
overwrite: bool = True) -> None:
|
|
310
313
|
''' File copy on S3
|
|
311
314
|
Copy content of file on `src_path` to `dst_path`.
|
|
312
315
|
It's caller's responsebility to ensure the s3_isfile(src_url) == True
|
|
@@ -314,24 +317,28 @@ def s3_copy(
|
|
|
314
317
|
:param src_url: Given path
|
|
315
318
|
:param dst_path: Target file path
|
|
316
319
|
:param callback: Called periodically during copy, and the input parameter is the data size (in bytes) of copy since the last call
|
|
320
|
+
:param followlinks: False if regard symlink as file, else True
|
|
321
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
317
322
|
'''
|
|
318
|
-
return S3Path(src_url).copy(dst_url, followlinks,
|
|
323
|
+
return S3Path(src_url).copy(dst_url, callback, followlinks, overwrite)
|
|
319
324
|
|
|
320
325
|
|
|
321
326
|
def s3_sync(
|
|
322
327
|
src_url: PathLike,
|
|
323
328
|
dst_url: PathLike,
|
|
324
329
|
followlinks: bool = False,
|
|
325
|
-
force: bool = False
|
|
330
|
+
force: bool = False,
|
|
331
|
+
overwrite: bool = True) -> None:
|
|
326
332
|
'''
|
|
327
333
|
Copy file/directory on src_url to dst_url
|
|
328
334
|
|
|
329
335
|
:param src_url: Given path
|
|
330
336
|
:param dst_url: Given destination path
|
|
331
337
|
:param followlinks: False if regard symlink as file, else True
|
|
332
|
-
:param force: Sync file forcely, do not ignore same files
|
|
338
|
+
:param force: Sync file forcely, do not ignore same files, priority is higher than 'overwrite', default is False
|
|
339
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
333
340
|
'''
|
|
334
|
-
return S3Path(src_url).sync(dst_url, followlinks, force)
|
|
341
|
+
return S3Path(src_url).sync(dst_url, followlinks, force, overwrite)
|
|
335
342
|
|
|
336
343
|
|
|
337
344
|
def s3_symlink(src_path: PathLike, dst_path: PathLike) -> None:
|
megfile/s3_path.py
CHANGED
|
@@ -930,16 +930,29 @@ def s3_download(
|
|
|
930
930
|
src_url: PathLike,
|
|
931
931
|
dst_url: PathLike,
|
|
932
932
|
followlinks: bool = False,
|
|
933
|
-
callback: Optional[Callable[[int], None]] = None
|
|
933
|
+
callback: Optional[Callable[[int], None]] = None,
|
|
934
|
+
overwrite: bool = True) -> None:
|
|
934
935
|
'''
|
|
935
936
|
Downloads a file from s3 to local filesystem.
|
|
936
937
|
:param src_url: source s3 path
|
|
937
938
|
:param dst_url: target fs path
|
|
938
939
|
:param callback: Called periodically during copy, and the input parameter is the data size (in bytes) of copy since the last call
|
|
940
|
+
:param followlinks: False if regard symlink as file, else True
|
|
941
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
939
942
|
'''
|
|
940
943
|
from megfile.fs import is_fs
|
|
941
944
|
from megfile.fs_path import FSPath
|
|
942
945
|
|
|
946
|
+
dst_url = fspath(dst_url)
|
|
947
|
+
if not is_fs(dst_url):
|
|
948
|
+
raise OSError(f'dst_url is not fs path: {dst_url}')
|
|
949
|
+
if not dst_url or dst_url.endswith('/'):
|
|
950
|
+
raise S3IsADirectoryError('Is a directory: %r' % dst_url)
|
|
951
|
+
|
|
952
|
+
dst_path = FSPath(dst_url)
|
|
953
|
+
if not overwrite and dst_path.exists():
|
|
954
|
+
return
|
|
955
|
+
|
|
943
956
|
if not isinstance(src_url, S3Path):
|
|
944
957
|
src_url = S3Path(src_url)
|
|
945
958
|
if followlinks:
|
|
@@ -960,13 +973,6 @@ def s3_download(
|
|
|
960
973
|
raise S3IsADirectoryError(
|
|
961
974
|
'Is a directory: %r' % src_url.path_with_protocol)
|
|
962
975
|
|
|
963
|
-
dst_url = fspath(dst_url)
|
|
964
|
-
if not is_fs(dst_url):
|
|
965
|
-
raise OSError(f'dst_url is not fs path: {dst_url}')
|
|
966
|
-
if not dst_url or dst_url.endswith('/'):
|
|
967
|
-
raise S3IsADirectoryError('Is a directory: %r' % dst_url)
|
|
968
|
-
|
|
969
|
-
dst_path = FSPath(dst_url)
|
|
970
976
|
dst_directory = os.path.dirname(dst_path.path_without_protocol)
|
|
971
977
|
if dst_directory != '':
|
|
972
978
|
os.makedirs(dst_directory, exist_ok=True)
|
|
@@ -997,12 +1003,14 @@ def s3_upload(
|
|
|
997
1003
|
src_url: PathLike,
|
|
998
1004
|
dst_url: PathLike,
|
|
999
1005
|
callback: Optional[Callable[[int], None]] = None,
|
|
1006
|
+
overwrite: bool = True,
|
|
1000
1007
|
**kwargs) -> None:
|
|
1001
1008
|
'''
|
|
1002
1009
|
Uploads a file from local filesystem to s3.
|
|
1003
1010
|
:param src_url: source fs path
|
|
1004
1011
|
:param dst_url: target s3 path
|
|
1005
1012
|
:param callback: Called periodically during copy, and the input parameter is the data size (in bytes) of copy since the last call
|
|
1013
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
1006
1014
|
'''
|
|
1007
1015
|
from megfile.fs import is_fs
|
|
1008
1016
|
from megfile.fs_path import FSPath
|
|
@@ -1017,6 +1025,9 @@ def s3_upload(
|
|
|
1017
1025
|
if not dst_key or dst_key.endswith('/'):
|
|
1018
1026
|
raise S3IsADirectoryError('Is a directory: %r' % dst_url)
|
|
1019
1027
|
|
|
1028
|
+
if not overwrite and S3Path(dst_url).is_file():
|
|
1029
|
+
return
|
|
1030
|
+
|
|
1020
1031
|
client = get_s3_client_with_cache(
|
|
1021
1032
|
profile_name=S3Path(dst_url)._profile_name)
|
|
1022
1033
|
upload_fileobj = patch_method(
|
|
@@ -1086,18 +1097,15 @@ def s3_readlink(path) -> str:
|
|
|
1086
1097
|
return S3Path(path).readlink().path_with_protocol
|
|
1087
1098
|
|
|
1088
1099
|
|
|
1089
|
-
def s3_rename(
|
|
1100
|
+
def s3_rename(
|
|
1101
|
+
src_url: PathLike, dst_url: PathLike, overwrite: bool = True) -> None:
|
|
1090
1102
|
'''
|
|
1091
1103
|
Move s3 file path from src_url to dst_url
|
|
1092
1104
|
|
|
1093
1105
|
:param dst_url: Given destination path
|
|
1106
|
+
:param overwrite: whether or not overwrite file when exists
|
|
1094
1107
|
'''
|
|
1095
|
-
|
|
1096
|
-
if src_path_ins.is_file():
|
|
1097
|
-
src_path_ins.copy(dst_url)
|
|
1098
|
-
else:
|
|
1099
|
-
src_path_ins.sync(dst_url)
|
|
1100
|
-
src_path_ins.remove()
|
|
1108
|
+
S3Path(src_url).rename(dst_url, overwrite)
|
|
1101
1109
|
|
|
1102
1110
|
|
|
1103
1111
|
class S3Cacher(FileCacher):
|
|
@@ -1692,15 +1700,16 @@ class S3Path(URIPath):
|
|
|
1692
1700
|
if self.exists():
|
|
1693
1701
|
raise S3FileExistsError('File exists: %r' % self.path_with_protocol)
|
|
1694
1702
|
|
|
1695
|
-
def move(self, dst_url: PathLike) -> None:
|
|
1703
|
+
def move(self, dst_url: PathLike, overwrite: bool = True) -> None:
|
|
1696
1704
|
'''
|
|
1697
1705
|
Move file/directory path from src_url to dst_url
|
|
1698
1706
|
|
|
1699
1707
|
:param dst_url: Given destination path
|
|
1708
|
+
:param overwrite: whether or not overwrite file when exists
|
|
1700
1709
|
'''
|
|
1701
1710
|
for src_file_path, dst_file_path in _s3_scan_pairs(
|
|
1702
1711
|
self.path_with_protocol, dst_url):
|
|
1703
|
-
S3Path(src_file_path).rename(dst_file_path)
|
|
1712
|
+
S3Path(src_file_path).rename(dst_file_path, overwrite)
|
|
1704
1713
|
|
|
1705
1714
|
def remove(self, missing_ok: bool = False) -> None:
|
|
1706
1715
|
'''
|
|
@@ -1774,13 +1783,18 @@ class S3Path(URIPath):
|
|
|
1774
1783
|
raise S3UnknownError(
|
|
1775
1784
|
Exception(error_msg), self.path_with_protocol)
|
|
1776
1785
|
|
|
1777
|
-
def rename(self, dst_path: PathLike) -> 'S3Path':
|
|
1786
|
+
def rename(self, dst_path: PathLike, overwrite: bool = True) -> 'S3Path':
|
|
1778
1787
|
'''
|
|
1779
1788
|
Move s3 file path from src_url to dst_url
|
|
1780
1789
|
|
|
1781
1790
|
:param dst_path: Given destination path
|
|
1791
|
+
:param overwrite: whether or not overwrite file when exists
|
|
1782
1792
|
'''
|
|
1783
|
-
|
|
1793
|
+
if self.is_file():
|
|
1794
|
+
self.copy(dst_path, overwrite=overwrite)
|
|
1795
|
+
else:
|
|
1796
|
+
self.sync(dst_path, overwrite=overwrite)
|
|
1797
|
+
self.remove(missing_ok=True)
|
|
1784
1798
|
return self.from_path(dst_path)
|
|
1785
1799
|
|
|
1786
1800
|
def scan(self, missing_ok: bool = True,
|
|
@@ -2111,15 +2125,21 @@ class S3Path(URIPath):
|
|
|
2111
2125
|
def copy(
|
|
2112
2126
|
self,
|
|
2113
2127
|
dst_url: PathLike,
|
|
2128
|
+
callback: Optional[Callable[[int], None]] = None,
|
|
2114
2129
|
followlinks: bool = False,
|
|
2115
|
-
|
|
2130
|
+
overwrite: bool = True) -> None:
|
|
2116
2131
|
''' File copy on S3
|
|
2117
2132
|
Copy content of file on `src_path` to `dst_path`.
|
|
2118
2133
|
It's caller's responsebility to ensure the s3_isfile(src_url) == True
|
|
2119
2134
|
|
|
2120
2135
|
:param dst_path: Target file path
|
|
2121
2136
|
:param callback: Called periodically during copy, and the input parameter is the data size (in bytes) of copy since the last call
|
|
2137
|
+
:param followlinks: False if regard symlink as file, else True
|
|
2138
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
2122
2139
|
'''
|
|
2140
|
+
if not overwrite and self.from_path(dst_url).is_file():
|
|
2141
|
+
return
|
|
2142
|
+
|
|
2123
2143
|
src_url = self.path_with_protocol
|
|
2124
2144
|
src_bucket, src_key = parse_s3_url(src_url)
|
|
2125
2145
|
dst_bucket, dst_key = parse_s3_url(dst_url)
|
|
@@ -2156,22 +2176,32 @@ class S3Path(URIPath):
|
|
|
2156
2176
|
Callback=callback)
|
|
2157
2177
|
|
|
2158
2178
|
def sync(
|
|
2159
|
-
self,
|
|
2160
|
-
|
|
2179
|
+
self,
|
|
2180
|
+
dst_url: PathLike,
|
|
2181
|
+
followlinks: bool = False,
|
|
2182
|
+
force: bool = False,
|
|
2183
|
+
overwrite: bool = True) -> None:
|
|
2161
2184
|
'''
|
|
2162
2185
|
Copy file/directory on src_url to dst_url
|
|
2163
2186
|
|
|
2164
2187
|
:param dst_url: Given destination path
|
|
2165
2188
|
:param followlinks: False if regard symlink as file, else True
|
|
2166
|
-
:param force: Sync file forcely, do not ignore same files
|
|
2189
|
+
:param force: Sync file forcely, do not ignore same files, priority is higher than 'overwrite', default is False
|
|
2190
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
2167
2191
|
'''
|
|
2168
2192
|
for src_file_path, dst_file_path in _s3_scan_pairs(
|
|
2169
2193
|
self.path_with_protocol, dst_url):
|
|
2170
2194
|
src_file_path = self.from_path(src_file_path)
|
|
2171
2195
|
dst_file_path = self.from_path(dst_file_path)
|
|
2172
|
-
|
|
2196
|
+
|
|
2197
|
+
if force:
|
|
2198
|
+
pass
|
|
2199
|
+
elif not overwrite and dst_file_path.exists():
|
|
2200
|
+
continue
|
|
2201
|
+
elif dst_file_path.exists() and is_same_file(
|
|
2173
2202
|
src_file_path.stat(), dst_file_path.stat(), 'copy'):
|
|
2174
2203
|
continue
|
|
2204
|
+
|
|
2175
2205
|
src_file_path.copy(dst_file_path, followlinks=followlinks)
|
|
2176
2206
|
|
|
2177
2207
|
def symlink(self, dst_path: PathLike) -> None:
|
|
@@ -2181,7 +2211,7 @@ class S3Path(URIPath):
|
|
|
2181
2211
|
:param dst_path: Desination path
|
|
2182
2212
|
:raises: S3NameTooLongError, S3BucketNotFoundError, S3IsADirectoryError
|
|
2183
2213
|
'''
|
|
2184
|
-
if len(
|
|
2214
|
+
if len(fspath(self._s3_path).encode()) > 1024:
|
|
2185
2215
|
raise S3NameTooLongError('File name too long: %r' % dst_path)
|
|
2186
2216
|
src_bucket, src_key = parse_s3_url(self.path_with_protocol)
|
|
2187
2217
|
dst_bucket, dst_key = parse_s3_url(dst_path)
|
megfile/sftp.py
CHANGED
|
@@ -162,24 +162,30 @@ def sftp_realpath(path: PathLike) -> str:
|
|
|
162
162
|
return SftpPath(path).realpath()
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
def sftp_rename(
|
|
165
|
+
def sftp_rename(
|
|
166
|
+
src_path: PathLike, dst_path: PathLike,
|
|
167
|
+
overwrite: bool = True) -> 'SftpPath':
|
|
166
168
|
'''
|
|
167
169
|
rename file on sftp
|
|
168
170
|
|
|
169
171
|
:param src_path: Given path
|
|
170
172
|
:param dst_path: Given destination path
|
|
173
|
+
:param overwrite: whether or not overwrite file when exists
|
|
171
174
|
'''
|
|
172
|
-
return SftpPath(src_path).rename(dst_path)
|
|
175
|
+
return SftpPath(src_path).rename(dst_path, overwrite)
|
|
173
176
|
|
|
174
177
|
|
|
175
|
-
def sftp_move(
|
|
178
|
+
def sftp_move(
|
|
179
|
+
src_path: PathLike, dst_path: PathLike,
|
|
180
|
+
overwrite: bool = True) -> 'SftpPath':
|
|
176
181
|
'''
|
|
177
182
|
move file on sftp
|
|
178
183
|
|
|
179
184
|
:param src_path: Given path
|
|
180
185
|
:param dst_path: Given destination path
|
|
186
|
+
:param overwrite: whether or not overwrite file when exists
|
|
181
187
|
'''
|
|
182
|
-
return SftpPath(src_path).replace(dst_path)
|
|
188
|
+
return SftpPath(src_path).replace(dst_path, overwrite)
|
|
183
189
|
|
|
184
190
|
|
|
185
191
|
def sftp_remove(path: PathLike, missing_ok: bool = False) -> None:
|
|
@@ -369,7 +375,8 @@ def sftp_copy(
|
|
|
369
375
|
src_path: PathLike,
|
|
370
376
|
dst_path: PathLike,
|
|
371
377
|
callback: Optional[Callable[[int], None]] = None,
|
|
372
|
-
followlinks: bool = False
|
|
378
|
+
followlinks: bool = False,
|
|
379
|
+
overwrite: bool = True):
|
|
373
380
|
"""
|
|
374
381
|
Copy the file to the given destination path.
|
|
375
382
|
|
|
@@ -381,19 +388,21 @@ def sftp_copy(
|
|
|
381
388
|
:raises IsADirectoryError: If the source is a directory.
|
|
382
389
|
:raises OSError: If there is an error copying the file.
|
|
383
390
|
"""
|
|
384
|
-
return SftpPath(src_path).copy(dst_path, callback, followlinks)
|
|
391
|
+
return SftpPath(src_path).copy(dst_path, callback, followlinks, overwrite)
|
|
385
392
|
|
|
386
393
|
|
|
387
394
|
def sftp_sync(
|
|
388
395
|
src_path: PathLike,
|
|
389
396
|
dst_path: PathLike,
|
|
390
397
|
followlinks: bool = False,
|
|
391
|
-
force: bool = False
|
|
398
|
+
force: bool = False,
|
|
399
|
+
overwrite: bool = True):
|
|
392
400
|
'''Copy file/directory on src_url to dst_url
|
|
393
401
|
|
|
394
402
|
:param src_path: Given path
|
|
395
403
|
:param dst_url: Given destination path
|
|
396
404
|
:param followlinks: False if regard symlink as file, else True
|
|
397
|
-
:param force: Sync file forcely, do not ignore same files
|
|
405
|
+
:param force: Sync file forcely, do not ignore same files, priority is higher than 'overwrite', default is False
|
|
406
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
398
407
|
'''
|
|
399
|
-
return SftpPath(src_path).sync(dst_path, followlinks, force)
|
|
408
|
+
return SftpPath(src_path).sync(dst_path, followlinks, force, overwrite)
|
megfile/sftp_path.py
CHANGED
|
@@ -417,9 +417,15 @@ def sftp_download(
|
|
|
417
417
|
src_url: PathLike,
|
|
418
418
|
dst_url: PathLike,
|
|
419
419
|
callback: Optional[Callable[[int], None]] = None,
|
|
420
|
-
followlinks: bool = False
|
|
420
|
+
followlinks: bool = False,
|
|
421
|
+
overwrite: bool = True):
|
|
421
422
|
'''
|
|
422
|
-
|
|
423
|
+
Downloads a file from sftp to local filesystem.
|
|
424
|
+
:param src_url: source sftp path
|
|
425
|
+
:param dst_url: target fs path
|
|
426
|
+
:param callback: Called periodically during copy, and the input parameter is the data size (in bytes) of copy since the last call
|
|
427
|
+
:param followlinks: False if regard symlink as file, else True
|
|
428
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
423
429
|
'''
|
|
424
430
|
from megfile.fs import is_fs
|
|
425
431
|
from megfile.fs_path import FSPath
|
|
@@ -429,6 +435,10 @@ def sftp_download(
|
|
|
429
435
|
if not is_sftp(src_url) and not isinstance(src_url, SftpPath):
|
|
430
436
|
raise OSError(f'src_url is not sftp path: {src_url}')
|
|
431
437
|
|
|
438
|
+
dst_path = FSPath(dst_url)
|
|
439
|
+
if not overwrite and dst_path.exists():
|
|
440
|
+
return
|
|
441
|
+
|
|
432
442
|
if isinstance(src_url, SftpPath):
|
|
433
443
|
src_path = src_url
|
|
434
444
|
else:
|
|
@@ -441,7 +451,6 @@ def sftp_download(
|
|
|
441
451
|
if str(dst_url).endswith('/'):
|
|
442
452
|
raise IsADirectoryError('Is a directory: %r' % dst_url)
|
|
443
453
|
|
|
444
|
-
dst_path = FSPath(dst_url)
|
|
445
454
|
dst_path.parent.makedirs(exist_ok=True)
|
|
446
455
|
|
|
447
456
|
sftp_callback = None
|
|
@@ -467,9 +476,14 @@ def sftp_upload(
|
|
|
467
476
|
src_url: PathLike,
|
|
468
477
|
dst_url: PathLike,
|
|
469
478
|
callback: Optional[Callable[[int], None]] = None,
|
|
470
|
-
followlinks: bool = False
|
|
479
|
+
followlinks: bool = False,
|
|
480
|
+
overwrite: bool = True):
|
|
471
481
|
'''
|
|
472
|
-
|
|
482
|
+
Uploads a file from local filesystem to sftp server.
|
|
483
|
+
:param src_url: source fs path
|
|
484
|
+
:param dst_url: target sftp path
|
|
485
|
+
:param callback: Called periodically during copy, and the input parameter is the data size (in bytes) of copy since the last call
|
|
486
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
473
487
|
'''
|
|
474
488
|
from megfile.fs import is_fs
|
|
475
489
|
from megfile.fs_path import FSPath
|
|
@@ -491,6 +505,9 @@ def sftp_upload(
|
|
|
491
505
|
dst_path = dst_url
|
|
492
506
|
else:
|
|
493
507
|
dst_path = SftpPath(dst_url)
|
|
508
|
+
if not overwrite and dst_path.exists():
|
|
509
|
+
return
|
|
510
|
+
|
|
494
511
|
dst_path.parent.makedirs(exist_ok=True)
|
|
495
512
|
|
|
496
513
|
sftp_callback = None
|
|
@@ -859,53 +876,57 @@ class SftpPath(URIPath):
|
|
|
859
876
|
def _is_same_protocol(self, path):
|
|
860
877
|
return is_sftp(path)
|
|
861
878
|
|
|
862
|
-
def rename(self, dst_path: PathLike) -> 'SftpPath':
|
|
879
|
+
def rename(self, dst_path: PathLike, overwrite: bool = True) -> 'SftpPath':
|
|
863
880
|
'''
|
|
864
881
|
rename file on sftp
|
|
865
882
|
|
|
866
883
|
:param dst_path: Given destination path
|
|
884
|
+
:param overwrite: whether or not overwrite file when exists
|
|
867
885
|
'''
|
|
868
886
|
if not self._is_same_protocol(dst_path):
|
|
869
887
|
raise OSError('Not a %s path: %r' % (self.protocol, dst_path))
|
|
870
|
-
if str(dst_path).endswith('/'):
|
|
871
|
-
raise IsADirectoryError('Is a directory: %r' % dst_path)
|
|
872
888
|
|
|
873
|
-
dst_path = self.from_path(dst_path)
|
|
889
|
+
dst_path = self.from_path(str(dst_path).rstrip('/'))
|
|
890
|
+
|
|
874
891
|
src_stat = self.stat()
|
|
875
892
|
|
|
876
893
|
if self._is_same_backend(dst_path):
|
|
877
|
-
|
|
894
|
+
if overwrite:
|
|
895
|
+
dst_path.remove(missing_ok=True)
|
|
878
896
|
self._client.rename(self._real_path, dst_path._real_path)
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
897
|
+
else:
|
|
898
|
+
self.sync(dst_path, overwrite=overwrite)
|
|
899
|
+
self.remove(missing_ok=True)
|
|
882
900
|
else:
|
|
883
901
|
if self.is_dir():
|
|
884
902
|
for file_entry in self.scandir():
|
|
885
903
|
self.from_path(file_entry.path).rename(
|
|
886
904
|
dst_path.joinpath(file_entry.name))
|
|
905
|
+
self._client.rmdir(self._real_path)
|
|
887
906
|
else:
|
|
888
|
-
|
|
889
|
-
with
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
907
|
+
if overwrite or not dst_path.exists():
|
|
908
|
+
with self.open('rb') as fsrc:
|
|
909
|
+
with dst_path.open('wb') as fdst:
|
|
910
|
+
length = 16 * 1024
|
|
911
|
+
while True:
|
|
912
|
+
buf = fsrc.read(length)
|
|
913
|
+
if not buf:
|
|
914
|
+
break
|
|
915
|
+
fdst.write(buf)
|
|
896
916
|
self.unlink()
|
|
897
917
|
|
|
898
918
|
dst_path.utime(src_stat.st_atime, src_stat.st_mtime)
|
|
899
919
|
dst_path.chmod(src_stat.st_mode)
|
|
900
920
|
return dst_path
|
|
901
921
|
|
|
902
|
-
def replace(self, dst_path: PathLike) -> 'SftpPath':
|
|
922
|
+
def replace(self, dst_path: PathLike, overwrite: bool = True) -> 'SftpPath':
|
|
903
923
|
'''
|
|
904
924
|
move file on sftp
|
|
905
925
|
|
|
906
926
|
:param dst_path: Given destination path
|
|
927
|
+
:param overwrite: whether or not overwrite file when exists
|
|
907
928
|
'''
|
|
908
|
-
return self.rename(dst_path=dst_path)
|
|
929
|
+
return self.rename(dst_path=dst_path, overwrite=overwrite)
|
|
909
930
|
|
|
910
931
|
def remove(self, missing_ok: bool = False) -> None:
|
|
911
932
|
'''
|
|
@@ -1235,7 +1256,8 @@ class SftpPath(URIPath):
|
|
|
1235
1256
|
self,
|
|
1236
1257
|
dst_path: PathLike,
|
|
1237
1258
|
callback: Optional[Callable[[int], None]] = None,
|
|
1238
|
-
followlinks: bool = False
|
|
1259
|
+
followlinks: bool = False,
|
|
1260
|
+
overwrite: bool = True):
|
|
1239
1261
|
"""
|
|
1240
1262
|
Copy the file to the given destination path.
|
|
1241
1263
|
|
|
@@ -1258,6 +1280,9 @@ class SftpPath(URIPath):
|
|
|
1258
1280
|
raise IsADirectoryError(
|
|
1259
1281
|
'Is a directory: %r' % self.path_with_protocol)
|
|
1260
1282
|
|
|
1283
|
+
if not overwrite and self.from_path(dst_path).exists():
|
|
1284
|
+
return
|
|
1285
|
+
|
|
1261
1286
|
self.from_path(os.path.dirname(dst_path)).makedirs(exist_ok=True)
|
|
1262
1287
|
dst_path = self.from_path(dst_path)
|
|
1263
1288
|
if self._is_same_backend(dst_path):
|
|
@@ -1292,12 +1317,14 @@ class SftpPath(URIPath):
|
|
|
1292
1317
|
self,
|
|
1293
1318
|
dst_path: PathLike,
|
|
1294
1319
|
followlinks: bool = False,
|
|
1295
|
-
force: bool = False
|
|
1320
|
+
force: bool = False,
|
|
1321
|
+
overwrite: bool = True):
|
|
1296
1322
|
'''Copy file/directory on src_url to dst_url
|
|
1297
1323
|
|
|
1298
1324
|
:param dst_url: Given destination path
|
|
1299
1325
|
:param followlinks: False if regard symlink as file, else True
|
|
1300
|
-
:param force: Sync file forcely, do not ignore same files
|
|
1326
|
+
:param force: Sync file forcely, do not ignore same files, priority is higher than 'overwrite', default is False
|
|
1327
|
+
:param overwrite: whether or not overwrite file when exists, default is True
|
|
1301
1328
|
'''
|
|
1302
1329
|
if not self._is_same_protocol(dst_path):
|
|
1303
1330
|
raise OSError('Not a %s path: %r' % (self.protocol, dst_path))
|
|
@@ -1306,11 +1333,15 @@ class SftpPath(URIPath):
|
|
|
1306
1333
|
self.path_with_protocol, dst_path):
|
|
1307
1334
|
dst_path = self.from_path(dst_file_path)
|
|
1308
1335
|
src_path = self.from_path(src_file_path)
|
|
1309
|
-
|
|
1310
|
-
|
|
1336
|
+
|
|
1337
|
+
if force:
|
|
1338
|
+
pass
|
|
1339
|
+
elif not overwrite and dst_path.exists():
|
|
1311
1340
|
continue
|
|
1312
|
-
|
|
1313
|
-
|
|
1341
|
+
elif dst_path.exists() and is_same_file(src_path.stat(),
|
|
1342
|
+
dst_path.stat(), 'copy'):
|
|
1343
|
+
continue
|
|
1344
|
+
|
|
1314
1345
|
self.from_path(src_file_path).copy(
|
|
1315
1346
|
dst_file_path, followlinks=followlinks)
|
|
1316
1347
|
|