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.
@@ -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
- raise FileNotFoundError(f"File not found: {src}")
146
- dst = self._to_remote_path(dst)
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(dst)
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} -> {dst}")
153
- err = self.rclone.copy_file_s3(src, dst)
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
- cp: CompletedProcess = self.rclone.copy(src_path, dst)
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
- assert self.exists(), f"Path does not exist: {self.path}"
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
- path_info: S3PathInfo = S3PathInfo.from_str(dst)
875
- remote = path_info.remote
876
- parsed: Parsed = self.config.parse()
877
- sections: dict[str, Section] = parsed.sections
878
- if remote not in sections:
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.71
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=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
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=PppFWk4qo7gg6p_k3K7am3_6pytsLG6qTAKo6dBT-tI,52649
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=2Ewa5g_842XU-UT4JDkPkq199pjknjoqpTiKtbRRRGk,11964
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.71.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
62
- rclone_api-1.5.71.dist-info/METADATA,sha256=qnLOllDyan73gwf8Nb5W5B9vhYnRUP1fRhRrA-5c56Q,37305
63
- rclone_api-1.5.71.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
64
- rclone_api-1.5.71.dist-info/entry_points.txt,sha256=ognh2e11HTjn73_KL5MWI67pBKS2jekBi-QTiRXySXA,316
65
- rclone_api-1.5.71.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
66
- rclone_api-1.5.71.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5