megfile 3.0.4__py3-none-any.whl → 3.0.6__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/config.py +9 -0
- megfile/errors.py +37 -21
- 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 +151 -22
- megfile/lib/base_prefetch_reader.py +2 -2
- megfile/lib/hdfs_prefetch_reader.py +2 -2
- megfile/lib/http_prefetch_reader.py +28 -12
- megfile/lib/s3_prefetch_reader.py +2 -2
- megfile/lib/s3_share_cache_reader.py +2 -2
- megfile/pathlike.py +10 -3
- megfile/s3.py +14 -7
- megfile/s3_path.py +57 -27
- megfile/sftp.py +18 -9
- megfile/sftp_path.py +63 -31
- megfile/smart.py +60 -16
- megfile/version.py +1 -1
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/METADATA +1 -1
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/RECORD +27 -27
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/LICENSE +0 -0
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/LICENSE.pyre +0 -0
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/WHEEL +0 -0
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/entry_points.txt +0 -0
- {megfile-3.0.4.dist-info → megfile-3.0.6.dist-info}/top_level.txt +0 -0
megfile/s3_path.py
CHANGED
|
@@ -12,7 +12,7 @@ import boto3
|
|
|
12
12
|
import botocore
|
|
13
13
|
from botocore.awsrequest import AWSResponse
|
|
14
14
|
|
|
15
|
-
from megfile.config import DEFAULT_BLOCK_SIZE, GLOBAL_MAX_WORKERS, S3_CLIENT_CACHE_MODE
|
|
15
|
+
from megfile.config import DEFAULT_BLOCK_SIZE, GLOBAL_MAX_WORKERS, S3_CLIENT_CACHE_MODE, S3_MAX_RETRY_TIMES
|
|
16
16
|
from megfile.errors import S3BucketNotFoundError, S3ConfigError, S3FileExistsError, S3FileNotFoundError, S3IsADirectoryError, S3NameTooLongError, S3NotADirectoryError, S3NotALinkError, S3PermissionError, S3UnknownError, SameFileError, UnsupportedError, _create_missing_ok_generator
|
|
17
17
|
from megfile.errors import _logger as error_logger
|
|
18
18
|
from megfile.errors import patch_method, raise_s3_error, s3_error_code_should_retry, s3_should_retry, translate_fs_error, translate_s3_error
|
|
@@ -69,7 +69,7 @@ _logger = get_logger(__name__)
|
|
|
69
69
|
content_md5_header = 'megfile-content-md5'
|
|
70
70
|
endpoint_url = 'https://s3.amazonaws.com'
|
|
71
71
|
max_pool_connections = GLOBAL_MAX_WORKERS # for compatibility
|
|
72
|
-
max_retries =
|
|
72
|
+
max_retries = S3_MAX_RETRY_TIMES
|
|
73
73
|
max_keys = 1000
|
|
74
74
|
|
|
75
75
|
|
|
@@ -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
|
@@ -14,6 +14,7 @@ from urllib.parse import urlsplit, urlunsplit
|
|
|
14
14
|
|
|
15
15
|
import paramiko
|
|
16
16
|
|
|
17
|
+
from megfile.config import SFTP_MAX_RETRY_TIMES
|
|
17
18
|
from megfile.errors import SameFileError, _create_missing_ok_generator, patch_method
|
|
18
19
|
from megfile.interfaces import ContextIterator, FileEntry, PathLike, StatResult
|
|
19
20
|
from megfile.lib.compare import is_same_file
|
|
@@ -47,7 +48,7 @@ SFTP_PRIVATE_KEY_PATH = "SFTP_PRIVATE_KEY_PATH"
|
|
|
47
48
|
SFTP_PRIVATE_KEY_TYPE = "SFTP_PRIVATE_KEY_TYPE"
|
|
48
49
|
SFTP_PRIVATE_KEY_PASSWORD = "SFTP_PRIVATE_KEY_PASSWORD"
|
|
49
50
|
SFTP_MAX_UNAUTH_CONN = "SFTP_MAX_UNAUTH_CONN"
|
|
50
|
-
MAX_RETRIES =
|
|
51
|
+
MAX_RETRIES = SFTP_MAX_RETRY_TIMES
|
|
51
52
|
DEFAULT_SSH_CONNECT_TIMEOUT = 5
|
|
52
53
|
DEFAULT_SSH_KEEPALIVE_INTERVAL = 15
|
|
53
54
|
|
|
@@ -416,9 +417,15 @@ def sftp_download(
|
|
|
416
417
|
src_url: PathLike,
|
|
417
418
|
dst_url: PathLike,
|
|
418
419
|
callback: Optional[Callable[[int], None]] = None,
|
|
419
|
-
followlinks: bool = False
|
|
420
|
+
followlinks: bool = False,
|
|
421
|
+
overwrite: bool = True):
|
|
420
422
|
'''
|
|
421
|
-
|
|
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
|
|
422
429
|
'''
|
|
423
430
|
from megfile.fs import is_fs
|
|
424
431
|
from megfile.fs_path import FSPath
|
|
@@ -428,6 +435,10 @@ def sftp_download(
|
|
|
428
435
|
if not is_sftp(src_url) and not isinstance(src_url, SftpPath):
|
|
429
436
|
raise OSError(f'src_url is not sftp path: {src_url}')
|
|
430
437
|
|
|
438
|
+
dst_path = FSPath(dst_url)
|
|
439
|
+
if not overwrite and dst_path.exists():
|
|
440
|
+
return
|
|
441
|
+
|
|
431
442
|
if isinstance(src_url, SftpPath):
|
|
432
443
|
src_path = src_url
|
|
433
444
|
else:
|
|
@@ -440,7 +451,6 @@ def sftp_download(
|
|
|
440
451
|
if str(dst_url).endswith('/'):
|
|
441
452
|
raise IsADirectoryError('Is a directory: %r' % dst_url)
|
|
442
453
|
|
|
443
|
-
dst_path = FSPath(dst_url)
|
|
444
454
|
dst_path.parent.makedirs(exist_ok=True)
|
|
445
455
|
|
|
446
456
|
sftp_callback = None
|
|
@@ -466,9 +476,14 @@ def sftp_upload(
|
|
|
466
476
|
src_url: PathLike,
|
|
467
477
|
dst_url: PathLike,
|
|
468
478
|
callback: Optional[Callable[[int], None]] = None,
|
|
469
|
-
followlinks: bool = False
|
|
479
|
+
followlinks: bool = False,
|
|
480
|
+
overwrite: bool = True):
|
|
470
481
|
'''
|
|
471
|
-
|
|
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
|
|
472
487
|
'''
|
|
473
488
|
from megfile.fs import is_fs
|
|
474
489
|
from megfile.fs_path import FSPath
|
|
@@ -490,6 +505,9 @@ def sftp_upload(
|
|
|
490
505
|
dst_path = dst_url
|
|
491
506
|
else:
|
|
492
507
|
dst_path = SftpPath(dst_url)
|
|
508
|
+
if not overwrite and dst_path.exists():
|
|
509
|
+
return
|
|
510
|
+
|
|
493
511
|
dst_path.parent.makedirs(exist_ok=True)
|
|
494
512
|
|
|
495
513
|
sftp_callback = None
|
|
@@ -858,53 +876,57 @@ class SftpPath(URIPath):
|
|
|
858
876
|
def _is_same_protocol(self, path):
|
|
859
877
|
return is_sftp(path)
|
|
860
878
|
|
|
861
|
-
def rename(self, dst_path: PathLike) -> 'SftpPath':
|
|
879
|
+
def rename(self, dst_path: PathLike, overwrite: bool = True) -> 'SftpPath':
|
|
862
880
|
'''
|
|
863
881
|
rename file on sftp
|
|
864
882
|
|
|
865
883
|
:param dst_path: Given destination path
|
|
884
|
+
:param overwrite: whether or not overwrite file when exists
|
|
866
885
|
'''
|
|
867
886
|
if not self._is_same_protocol(dst_path):
|
|
868
887
|
raise OSError('Not a %s path: %r' % (self.protocol, dst_path))
|
|
869
|
-
if str(dst_path).endswith('/'):
|
|
870
|
-
raise IsADirectoryError('Is a directory: %r' % dst_path)
|
|
871
888
|
|
|
872
|
-
dst_path = self.from_path(dst_path)
|
|
889
|
+
dst_path = self.from_path(str(dst_path).rstrip('/'))
|
|
890
|
+
|
|
873
891
|
src_stat = self.stat()
|
|
874
892
|
|
|
875
893
|
if self._is_same_backend(dst_path):
|
|
876
|
-
|
|
894
|
+
if overwrite:
|
|
895
|
+
dst_path.remove(missing_ok=True)
|
|
877
896
|
self._client.rename(self._real_path, dst_path._real_path)
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
897
|
+
else:
|
|
898
|
+
self.sync(dst_path, overwrite=overwrite)
|
|
899
|
+
self.remove(missing_ok=True)
|
|
881
900
|
else:
|
|
882
901
|
if self.is_dir():
|
|
883
902
|
for file_entry in self.scandir():
|
|
884
903
|
self.from_path(file_entry.path).rename(
|
|
885
904
|
dst_path.joinpath(file_entry.name))
|
|
905
|
+
self._client.rmdir(self._real_path)
|
|
886
906
|
else:
|
|
887
|
-
|
|
888
|
-
with
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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)
|
|
895
916
|
self.unlink()
|
|
896
917
|
|
|
897
918
|
dst_path.utime(src_stat.st_atime, src_stat.st_mtime)
|
|
898
919
|
dst_path.chmod(src_stat.st_mode)
|
|
899
920
|
return dst_path
|
|
900
921
|
|
|
901
|
-
def replace(self, dst_path: PathLike) -> 'SftpPath':
|
|
922
|
+
def replace(self, dst_path: PathLike, overwrite: bool = True) -> 'SftpPath':
|
|
902
923
|
'''
|
|
903
924
|
move file on sftp
|
|
904
925
|
|
|
905
926
|
:param dst_path: Given destination path
|
|
927
|
+
:param overwrite: whether or not overwrite file when exists
|
|
906
928
|
'''
|
|
907
|
-
return self.rename(dst_path=dst_path)
|
|
929
|
+
return self.rename(dst_path=dst_path, overwrite=overwrite)
|
|
908
930
|
|
|
909
931
|
def remove(self, missing_ok: bool = False) -> None:
|
|
910
932
|
'''
|
|
@@ -1234,7 +1256,8 @@ class SftpPath(URIPath):
|
|
|
1234
1256
|
self,
|
|
1235
1257
|
dst_path: PathLike,
|
|
1236
1258
|
callback: Optional[Callable[[int], None]] = None,
|
|
1237
|
-
followlinks: bool = False
|
|
1259
|
+
followlinks: bool = False,
|
|
1260
|
+
overwrite: bool = True):
|
|
1238
1261
|
"""
|
|
1239
1262
|
Copy the file to the given destination path.
|
|
1240
1263
|
|
|
@@ -1257,6 +1280,9 @@ class SftpPath(URIPath):
|
|
|
1257
1280
|
raise IsADirectoryError(
|
|
1258
1281
|
'Is a directory: %r' % self.path_with_protocol)
|
|
1259
1282
|
|
|
1283
|
+
if not overwrite and self.from_path(dst_path).exists():
|
|
1284
|
+
return
|
|
1285
|
+
|
|
1260
1286
|
self.from_path(os.path.dirname(dst_path)).makedirs(exist_ok=True)
|
|
1261
1287
|
dst_path = self.from_path(dst_path)
|
|
1262
1288
|
if self._is_same_backend(dst_path):
|
|
@@ -1291,12 +1317,14 @@ class SftpPath(URIPath):
|
|
|
1291
1317
|
self,
|
|
1292
1318
|
dst_path: PathLike,
|
|
1293
1319
|
followlinks: bool = False,
|
|
1294
|
-
force: bool = False
|
|
1320
|
+
force: bool = False,
|
|
1321
|
+
overwrite: bool = True):
|
|
1295
1322
|
'''Copy file/directory on src_url to dst_url
|
|
1296
1323
|
|
|
1297
1324
|
:param dst_url: Given destination path
|
|
1298
1325
|
:param followlinks: False if regard symlink as file, else True
|
|
1299
|
-
: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
|
|
1300
1328
|
'''
|
|
1301
1329
|
if not self._is_same_protocol(dst_path):
|
|
1302
1330
|
raise OSError('Not a %s path: %r' % (self.protocol, dst_path))
|
|
@@ -1305,11 +1333,15 @@ class SftpPath(URIPath):
|
|
|
1305
1333
|
self.path_with_protocol, dst_path):
|
|
1306
1334
|
dst_path = self.from_path(dst_file_path)
|
|
1307
1335
|
src_path = self.from_path(src_file_path)
|
|
1308
|
-
|
|
1309
|
-
|
|
1336
|
+
|
|
1337
|
+
if force:
|
|
1338
|
+
pass
|
|
1339
|
+
elif not overwrite and dst_path.exists():
|
|
1310
1340
|
continue
|
|
1311
|
-
|
|
1312
|
-
|
|
1341
|
+
elif dst_path.exists() and is_same_file(src_path.stat(),
|
|
1342
|
+
dst_path.stat(), 'copy'):
|
|
1343
|
+
continue
|
|
1344
|
+
|
|
1313
1345
|
self.from_path(src_file_path).copy(
|
|
1314
1346
|
dst_file_path, followlinks=followlinks)
|
|
1315
1347
|
|