rclone-api 1.5.71__py3-none-any.whl → 1.5.73__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.
- rclone_api/fs/filesystem.py +92 -10
- rclone_api/group_files.py +4 -0
- rclone_api/rclone_impl.py +16 -8
- {rclone_api-1.5.71.dist-info → rclone_api-1.5.73.dist-info}/METADATA +2 -2
- {rclone_api-1.5.71.dist-info → rclone_api-1.5.73.dist-info}/RECORD +9 -9
- {rclone_api-1.5.71.dist-info → rclone_api-1.5.73.dist-info}/WHEEL +1 -1
- {rclone_api-1.5.71.dist-info → rclone_api-1.5.73.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.71.dist-info → rclone_api-1.5.73.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.71.dist-info → rclone_api-1.5.73.dist-info}/top_level.txt +0 -0
rclone_api/fs/filesystem.py
CHANGED
@@ -40,6 +40,16 @@ class FS(abc.ABC):
|
|
40
40
|
"""First is files and second is directories."""
|
41
41
|
pass
|
42
42
|
|
43
|
+
@abc.abstractmethod
|
44
|
+
def remove(self, path: Path | str) -> Exception | None:
|
45
|
+
"""Remove a file or symbolic link."""
|
46
|
+
pass
|
47
|
+
|
48
|
+
@abc.abstractmethod
|
49
|
+
def unlink(self, path: Path | str) -> Exception | None:
|
50
|
+
"""Remove a file or symbolic link."""
|
51
|
+
pass
|
52
|
+
|
43
53
|
@abc.abstractmethod
|
44
54
|
def cwd(self) -> "FSPath":
|
45
55
|
pass
|
@@ -86,6 +96,30 @@ class RealFS(FS):
|
|
86
96
|
def exists(self, path: Path | str) -> bool:
|
87
97
|
return Path(path).exists()
|
88
98
|
|
99
|
+
def unlink(self, path: Path | str) -> Exception | None:
|
100
|
+
"""Remove a file or symbolic link."""
|
101
|
+
try:
|
102
|
+
Path(path).unlink()
|
103
|
+
return None
|
104
|
+
except KeyboardInterrupt:
|
105
|
+
raise
|
106
|
+
except FileNotFoundError as e:
|
107
|
+
return e
|
108
|
+
|
109
|
+
def remove(self, path: Path | str, ignore_errors=False) -> Exception | None:
|
110
|
+
"""Remove a file or directory."""
|
111
|
+
try:
|
112
|
+
path = Path(path)
|
113
|
+
if path.is_dir():
|
114
|
+
shutil.rmtree(path, ignore_errors=ignore_errors)
|
115
|
+
else:
|
116
|
+
path.unlink()
|
117
|
+
return None
|
118
|
+
except KeyboardInterrupt:
|
119
|
+
raise
|
120
|
+
except Exception as e:
|
121
|
+
return e
|
122
|
+
|
89
123
|
def mkdir(self, path: str, parents=True, exist_ok=True) -> None:
|
90
124
|
Path(path).mkdir(parents=parents, exist_ok=exist_ok)
|
91
125
|
|
@@ -141,16 +175,16 @@ class RemoteFS(FS):
|
|
141
175
|
from rclone_api.completed_process import CompletedProcess
|
142
176
|
|
143
177
|
src = src if isinstance(src, Path) else Path(src)
|
144
|
-
if not src.is_file():
|
145
|
-
|
146
|
-
|
178
|
+
# if not src.is_file():
|
179
|
+
# raise FileNotFoundError(f"File not found: {src}")
|
180
|
+
dst_remote_path = self._to_remote_path(dst)
|
147
181
|
|
148
|
-
is_s3 = self.rclone.is_s3(
|
182
|
+
is_s3 = self.rclone.is_s3(dst_remote_path)
|
149
183
|
if is_s3:
|
150
184
|
filesize = src.stat().st_size
|
151
185
|
if filesize < 1024 * 1024 * 1024: # 1GB
|
152
|
-
logger.info(f"S3 OPTIMIZED: Copying {src} -> {
|
153
|
-
err = self.rclone.copy_file_s3(src,
|
186
|
+
logger.info(f"S3 OPTIMIZED: Copying {src} -> {dst_remote_path}")
|
187
|
+
err = self.rclone.copy_file_s3(src, dst_remote_path)
|
154
188
|
if isinstance(err, Exception):
|
155
189
|
raise FileNotFoundError(
|
156
190
|
f"File not found: {src}, specified by {err}"
|
@@ -159,7 +193,8 @@ class RemoteFS(FS):
|
|
159
193
|
# Fallback.
|
160
194
|
logging.info(f"Copying {src} -> {dst}")
|
161
195
|
src_path = src.as_posix()
|
162
|
-
|
196
|
+
dst = dst if isinstance(dst, str) else dst.as_posix()
|
197
|
+
cp: CompletedProcess = self.rclone.copy_to(src_path, dst)
|
163
198
|
if cp.returncode != 0:
|
164
199
|
raise FileNotFoundError(f"File not found: {src}, specified by {cp.stderr}")
|
165
200
|
|
@@ -216,6 +251,24 @@ class RemoteFS(FS):
|
|
216
251
|
raise FileNotFoundError(f"File not found: {path}, because of {err}")
|
217
252
|
return err
|
218
253
|
|
254
|
+
def unlink(self, path: Path | str) -> Exception | None:
|
255
|
+
return self.remove(path)
|
256
|
+
|
257
|
+
def remove(self, path: Path | str) -> Exception | None:
|
258
|
+
"""Remove a file or symbolic link."""
|
259
|
+
|
260
|
+
# remove this
|
261
|
+
# path = self._to_remote_path(path)
|
262
|
+
path = path if isinstance(path, str) else path.as_posix()
|
263
|
+
err = self.rclone.delete_files(path)
|
264
|
+
if isinstance(err, Exception):
|
265
|
+
return FileNotFoundError(f"File not found: {path}, because of {err}")
|
266
|
+
if isinstance(path, Path):
|
267
|
+
path = path.as_posix()
|
268
|
+
# assert isinstance(self.server, HttpServer)
|
269
|
+
# self.server.delete(path)
|
270
|
+
return None
|
271
|
+
|
219
272
|
def get_path(self, path: str) -> "FSPath":
|
220
273
|
return FSPath(self, path)
|
221
274
|
|
@@ -316,14 +369,35 @@ class FSPath:
|
|
316
369
|
encoding = "utf-8"
|
317
370
|
self.write_bytes(data.encode(encoding))
|
318
371
|
|
372
|
+
def moveTo(self, dst: "FSPath") -> None:
|
373
|
+
"""Move a file or directory."""
|
374
|
+
# assert self.exists(), f"Path does not exist: {self.path}"
|
375
|
+
# assert not dst.exists(), f"Destination path already exists: {dst.path}"
|
376
|
+
self.fs.copy(self.path, dst.path)
|
377
|
+
self.fs.remove(self.path)
|
378
|
+
|
319
379
|
def write_bytes(self, data: bytes) -> None:
|
320
380
|
self.fs.write_binary(self.path, data)
|
321
381
|
|
322
382
|
def rmtree(self, ignore_errors=False) -> None:
|
323
|
-
|
383
|
+
self_exists = self.exists()
|
384
|
+
if not ignore_errors:
|
385
|
+
assert self_exists, f"Path does not exist: {self.path}"
|
324
386
|
# check fs is RealFS
|
325
|
-
assert isinstance(self.fs, RealFS)
|
326
|
-
shutil.rmtree(self.path, ignore_errors=ignore_errors)
|
387
|
+
# assert isinstance(self.fs, RealFS)
|
388
|
+
# shutil.rmtree(self.path, ignore_errors=ignore_errors)
|
389
|
+
if isinstance(self.fs, RealFS):
|
390
|
+
shutil.rmtree(self.path, ignore_errors=ignore_errors)
|
391
|
+
return
|
392
|
+
assert isinstance(self.fs, RemoteFS)
|
393
|
+
# walk the directory
|
394
|
+
for root, _, filenames in self.walk():
|
395
|
+
# for dirname in dirnames:
|
396
|
+
# path = root / dirname
|
397
|
+
# path.remove()
|
398
|
+
for filename in filenames:
|
399
|
+
path = root / filename
|
400
|
+
path.remove()
|
327
401
|
|
328
402
|
def lspaths(self) -> "tuple[list[FSPath], list[FSPath]]":
|
329
403
|
filenames, dirnames = self.ls()
|
@@ -337,6 +411,14 @@ class FSPath:
|
|
337
411
|
filenames, dirnames = self.fs.ls(self.path)
|
338
412
|
return filenames, dirnames
|
339
413
|
|
414
|
+
def remove(self) -> Exception | None:
|
415
|
+
"""Remove a file or directory, there are subtle differences between the Real and RemoteFS."""
|
416
|
+
return self.fs.remove(self.path)
|
417
|
+
|
418
|
+
def unlink(self) -> Exception | None:
|
419
|
+
"""Remove a file or symbolic link, there are subtle differences between the Real and RemoteFS."""
|
420
|
+
return self.fs.unlink(self.path)
|
421
|
+
|
340
422
|
def with_suffix(self, suffix: str) -> "FSPath":
|
341
423
|
return FSPath(self.fs, Path(self.path).with_suffix(suffix).as_posix())
|
342
424
|
|
rclone_api/group_files.py
CHANGED
@@ -34,6 +34,10 @@ def parse_file(file_path: str) -> FilePathParts:
|
|
34
34
|
"""Parse file path into parts."""
|
35
35
|
assert not file_path.endswith("/"), "This looks like a directory path"
|
36
36
|
parts = file_path.split(":")
|
37
|
+
if len(parts) < 2:
|
38
|
+
raise ValueError(
|
39
|
+
f"Invalid file path: {file_path}, expected fully qualified path like dst:Bucket/subdir/file.txt"
|
40
|
+
)
|
37
41
|
remote = parts[0]
|
38
42
|
path = parts[1]
|
39
43
|
if path.startswith("/"):
|
rclone_api/rclone_impl.py
CHANGED
@@ -871,15 +871,21 @@ class RcloneImpl:
|
|
871
871
|
"""Check if a remote is an S3 remote."""
|
872
872
|
from rclone_api.util import S3PathInfo
|
873
873
|
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
874
|
+
try:
|
875
|
+
path_info: S3PathInfo = S3PathInfo.from_str(dst)
|
876
|
+
remote = path_info.remote
|
877
|
+
parsed: Parsed = self.config.parse()
|
878
|
+
sections: dict[str, Section] = parsed.sections
|
879
|
+
if remote not in sections:
|
880
|
+
return False
|
881
|
+
section: Section = sections[remote]
|
882
|
+
t = section.type()
|
883
|
+
return t in ["s3", "b2"]
|
884
|
+
except KeyboardInterrupt:
|
885
|
+
raise
|
886
|
+
except Exception as e:
|
887
|
+
logging.error(f"Error checking if remote is S3: {e}")
|
879
888
|
return False
|
880
|
-
section: Section = sections[remote]
|
881
|
-
t = section.type()
|
882
|
-
return t in ["s3", "b2"]
|
883
889
|
|
884
890
|
def copy_file_s3_resumable(
|
885
891
|
self,
|
@@ -1298,6 +1304,8 @@ class RcloneImpl:
|
|
1298
1304
|
"0",
|
1299
1305
|
"--vfs-read-chunk-size-limit",
|
1300
1306
|
"512M",
|
1307
|
+
# "--attr-timeout",
|
1308
|
+
# "0s",
|
1301
1309
|
]
|
1302
1310
|
|
1303
1311
|
if cache_mode:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: rclone_api
|
3
|
-
Version: 1.5.
|
3
|
+
Version: 1.5.73
|
4
4
|
Summary: rclone api in python
|
5
5
|
Home-page: https://github.com/zackees/rclone-api
|
6
6
|
License: BSD 3-Clause License
|
@@ -13,13 +13,13 @@ Requires-Dist: pyright>=1.1.393
|
|
13
13
|
Requires-Dist: python-dotenv>=1.0.0
|
14
14
|
Requires-Dist: certifi>=2025.1.31
|
15
15
|
Requires-Dist: psutil
|
16
|
-
Requires-Dist: boto3<=1.35.99,>=1.20.1
|
17
16
|
Requires-Dist: sqlmodel>=0.0.23
|
18
17
|
Requires-Dist: psycopg2-binary>=2.9.10
|
19
18
|
Requires-Dist: httpx>=0.28.1
|
20
19
|
Requires-Dist: download>=0.3.5
|
21
20
|
Requires-Dist: appdirs>=1.4.4
|
22
21
|
Requires-Dist: beautifulsoup4>=4.13.3
|
22
|
+
Requires-Dist: boto3>=1.28.23
|
23
23
|
Dynamic: home-page
|
24
24
|
Dynamic: license-file
|
25
25
|
|
@@ -13,14 +13,14 @@ rclone_api/file_item.py,sha256=cH-AQYsxedhNPp4c8NHY1ad4Z7St4yf_VGbmiGD59no,1770
|
|
13
13
|
rclone_api/file_part.py,sha256=i6ByS5_sae8Eba-4imBVTxd-xKC8ExWy7NR8QGr0ors,6155
|
14
14
|
rclone_api/file_stream.py,sha256=_W3qnwCuigqA0hzXl2q5pAxSZDRaUSwet4BkT0lpnzs,1431
|
15
15
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
16
|
-
rclone_api/group_files.py,sha256=
|
16
|
+
rclone_api/group_files.py,sha256=n-mzL94PxoKEASMCI1_OB5FEiFmcpjDpRLs1ynV4RrY,8204
|
17
17
|
rclone_api/http_server.py,sha256=ZzDVfuRtHBihxdVLUIHNvIZMixyYPm6N2iPHkfWx5eE,11942
|
18
18
|
rclone_api/install.py,sha256=ZDG8QNj1JciS_DSqYnMTECwhJksUPAoqZQxtX804TDk,5679
|
19
19
|
rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
|
20
20
|
rclone_api/mount.py,sha256=LZqEhuKZunbWVqmsOIqkkCotaxWJpdFRS1InXveoU5E,1428
|
21
21
|
rclone_api/mount_util.py,sha256=jqhJEVTHV6c6lOOzUYb4FLMbqDMHdz7-QRcdH-IobFc,10154
|
22
22
|
rclone_api/process.py,sha256=rNBflKF2QgiTVjOO52_YcrnQk7fdYr-U5divqO5I2ww,6048
|
23
|
-
rclone_api/rclone_impl.py,sha256=
|
23
|
+
rclone_api/rclone_impl.py,sha256=4QPfqKHfJ6_TrsJCURjZ6u-gpfWZJR7Ixji7Vw4SfmA,52932
|
24
24
|
rclone_api/remote.py,sha256=mTgMTQTwxUmbLjTpr-AGTId2ycXKI9mLX5L7PPpDIoc,520
|
25
25
|
rclone_api/rpath.py,sha256=Y1JjQWcie39EgQrq-UtbfDz5yDLCwwfu27W7AQXllSE,2860
|
26
26
|
rclone_api/scan_missing_folders.py,sha256=-8NCwpCaHeHrX-IepCoAEsX1rl8S-GOCxcIhTr_w3gA,4747
|
@@ -40,7 +40,7 @@ rclone_api/detail/copy_file_parts_resumable.py,sha256=RoUWV2eBWEvuuTfsvrz5BhtvX3
|
|
40
40
|
rclone_api/detail/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
|
41
41
|
rclone_api/experimental/flags.py,sha256=qCVD--fSTmzlk9hloRLr0q9elzAOFzPsvVpKM3aB1Mk,2739
|
42
42
|
rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
|
43
|
-
rclone_api/fs/filesystem.py,sha256=
|
43
|
+
rclone_api/fs/filesystem.py,sha256=iVkG4jw_lHiXOei1w02DrtSoKqZeTA-yQ68T5_uPNVs,15080
|
44
44
|
rclone_api/fs/walk.py,sha256=gqxfJqzFSQlLwZ18ttO25SbCCs7Vcz_CtlDgjiqpNZc,2183
|
45
45
|
rclone_api/fs/walk_threaded.py,sha256=zlbooQ7WbteUK1cCjm1Yj66VpDzS8Qm3DcHBSz1Gbjk,1591
|
46
46
|
rclone_api/fs/walk_threaded_walker.py,sha256=xi0QkmmzI35NcaA08FLjGe7ox9tLZcgXtOZCT8nMuWU,718
|
@@ -58,9 +58,9 @@ rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9
|
|
58
58
|
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=tdOkEfoK9zENaTfgVyTWB5GNqn-IYl-zJn-rJLcoVTo,12388
|
59
59
|
rclone_api/s3/multipart/upload_parts_server_side_merge.py,sha256=Fp2pdrs5dONQI9LkfNolgAGj1-Z2V1SsRd0r0sreuXI,18040
|
60
60
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
61
|
-
rclone_api-1.5.
|
62
|
-
rclone_api-1.5.
|
63
|
-
rclone_api-1.5.
|
64
|
-
rclone_api-1.5.
|
65
|
-
rclone_api-1.5.
|
66
|
-
rclone_api-1.5.
|
61
|
+
rclone_api-1.5.73.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
62
|
+
rclone_api-1.5.73.dist-info/METADATA,sha256=2C5YAjqXGRm8rdvnzPZB9HvEmWaXzlQjHTSA1bC5UeI,37296
|
63
|
+
rclone_api-1.5.73.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
64
|
+
rclone_api-1.5.73.dist-info/entry_points.txt,sha256=ognh2e11HTjn73_KL5MWI67pBKS2jekBi-QTiRXySXA,316
|
65
|
+
rclone_api-1.5.73.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
66
|
+
rclone_api-1.5.73.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|