rclone-api 1.5.63__py3-none-any.whl → 1.5.65__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/__init__.py CHANGED
@@ -7,6 +7,8 @@ The API wraps the rclone command-line tool, providing a Pythonic interface
7
7
  for common operations like copying, listing, and managing remote storage.
8
8
  """
9
9
 
10
+ import os
11
+
10
12
  # Import core components and utilities
11
13
  from datetime import datetime
12
14
  from pathlib import Path
@@ -64,6 +66,18 @@ def rclone_verbose(val: bool | None) -> bool:
64
66
  return _rclone_verbose(val)
65
67
 
66
68
 
69
+ class Logging:
70
+ @staticmethod
71
+ def enable_upload_parts_logging(value: bool) -> None:
72
+ """
73
+ Enable or disable logging of upload parts.
74
+
75
+ Args:
76
+ value: If True, enables upload parts logging; otherwise disables it.
77
+ """
78
+ os.environ["LOG_UPLOAD_S3_RESUMABLE"] = "1" if value else "0"
79
+
80
+
67
81
  class Rclone:
68
82
  """
69
83
  Main interface for interacting with Rclone.
@@ -17,11 +17,28 @@ from rclone_api.types import (
17
17
  SizeSuffix,
18
18
  )
19
19
 
20
+ _LOCK = threading.Lock()
21
+
22
+
23
+ def _maybe_log(msg: str) -> None:
24
+ print(msg)
25
+ if os.getenv("LOG_UPLOAD_S3_RESUMABLE") == "1":
26
+ log_path = Path("log") / "s3_resumable_upload.log"
27
+ with _LOCK:
28
+ log_path.parent.mkdir(parents=True, exist_ok=True)
29
+ # log_path.write_text(msg, append=True)
30
+ with open(log_path, mode="a", encoding="utf-8") as f:
31
+ f.write(msg)
32
+ f.write("\n")
33
+
20
34
 
21
35
  @dataclass
22
36
  class UploadPart:
23
37
  chunk: Path
24
38
  dst_part: str
39
+ part_num: int
40
+ total_parts: int
41
+ total_size: SizeSuffix
25
42
  exception: Exception | None = None
26
43
  finished: bool = False
27
44
 
@@ -46,10 +63,18 @@ def upload_task(self: RcloneImpl, upload_part: UploadPart) -> UploadPart:
46
63
  if upload_part.exception is not None:
47
64
  return upload_part
48
65
  # print(f"Uploading {upload_part.chunk} to {upload_part.dst_part}")
49
- msg = "\n#########################################\n"
66
+ num_parts = upload_part.total_parts
67
+ total_size = upload_part.total_size
68
+ part_num = upload_part.part_num
69
+ msg = "\n#############################################################\n"
50
70
  msg += f"# Uploading {upload_part.chunk} to {upload_part.dst_part}\n"
51
- msg += "#########################################\n"
52
- print(msg)
71
+ msg += f"# Part number: {part_num} / {num_parts}\n"
72
+ msg += f"# Total parts: {num_parts}\n"
73
+ msg += f"# Total size: {total_size.as_int()} bytes\n"
74
+ msg += f"# Chunk size: {upload_part.chunk.stat().st_size} bytes\n"
75
+ msg += f"# Range: {upload_part.chunk.name}\n"
76
+ msg += "##############################################################\n"
77
+ _maybe_log(msg)
53
78
  self.copy_to(upload_part.chunk.as_posix(), upload_part.dst_part)
54
79
  return upload_part
55
80
  except Exception as e:
@@ -66,6 +91,9 @@ def read_task(
66
91
  offset: SizeSuffix,
67
92
  length: SizeSuffix,
68
93
  part_dst: str,
94
+ part_number: int,
95
+ total_parts: int,
96
+ total_size: SizeSuffix,
69
97
  ) -> UploadPart:
70
98
  outchunk: Path = tmpdir / f"{offset.as_int()}-{(offset + length).as_int()}.chunk"
71
99
  range = Range(offset.as_int(), (offset + length).as_int())
@@ -77,10 +105,23 @@ def read_task(
77
105
  dst=outchunk,
78
106
  )
79
107
  if isinstance(err, Exception):
80
- out = UploadPart(chunk=outchunk, dst_part="", exception=err)
108
+ out = UploadPart(
109
+ chunk=outchunk,
110
+ dst_part="",
111
+ part_num=-1,
112
+ total_parts=total_parts,
113
+ total_size=SizeSuffix(0),
114
+ exception=err,
115
+ )
81
116
  out.dispose()
82
117
  return out
83
- return UploadPart(chunk=outchunk, dst_part=part_dst)
118
+ return UploadPart(
119
+ chunk=outchunk,
120
+ dst_part=part_dst,
121
+ part_num=part_number,
122
+ total_parts=total_parts,
123
+ total_size=total_size,
124
+ )
84
125
  except KeyboardInterrupt as ke:
85
126
  _thread.interrupt_main()
86
127
  raise ke
@@ -88,7 +129,14 @@ def read_task(
88
129
  _thread.interrupt_main()
89
130
  raise se
90
131
  except Exception as e:
91
- return UploadPart(chunk=outchunk, dst_part=part_dst, exception=e)
132
+ return UploadPart(
133
+ chunk=outchunk,
134
+ dst_part=part_dst,
135
+ part_num=part_number,
136
+ total_parts=total_parts,
137
+ total_size=total_size,
138
+ exception=e,
139
+ )
92
140
 
93
141
 
94
142
  def collapse_runs(numbers: list[int]) -> list[str]:
@@ -199,6 +247,7 @@ def upload_parts_resumable(
199
247
  f"all_numbers_already_done: {collapse_runs(sorted(list(all_numbers_already_done)))}"
200
248
  )
201
249
 
250
+ total_parts = len(part_infos)
202
251
  filtered_part_infos: list[PartInfo] = []
203
252
  for part_info in part_infos:
204
253
  if part_info.part_number not in all_numbers_already_done:
@@ -261,6 +310,9 @@ def upload_parts_resumable(
261
310
  offset=offset,
262
311
  length=length,
263
312
  part_dst=part_dst,
313
+ part_number=part_number,
314
+ total_parts=total_parts,
315
+ total_size=src_size,
264
316
  )
265
317
 
266
318
  read_fut: Future[UploadPart] = read_executor.submit(_read_task)
@@ -293,12 +345,17 @@ def upload_parts_resumable(
293
345
  shutil.rmtree(tmp_dir, ignore_errors=True)
294
346
 
295
347
  if len(exceptions) > 0:
296
- return Exception(f"Failed to copy parts: {exceptions}", exceptions)
348
+ msg = f"Failed to copy parts: {exceptions}"
349
+ _maybe_log(msg)
350
+ return Exception(msg, exceptions)
297
351
 
298
352
  finished_parts: list[int] = info_json.fetch_all_finished_part_numbers()
299
353
  print(f"finished_names: {finished_parts}")
300
354
 
301
355
  diff_set = set(all_part_numbers).symmetric_difference(set(finished_parts))
302
356
  all_part_numbers_done = len(diff_set) == 0
303
- print(f"all_part_numbers_done: {all_part_numbers_done}")
357
+ # print(f"all_part_numbers_done: {all_part_numbers_done}")
358
+ msg = f"all_part_numbers_done: {all_part_numbers_done}"
359
+ _maybe_log(msg)
360
+
304
361
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rclone_api
3
- Version: 1.5.63
3
+ Version: 1.5.65
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -1,4 +1,4 @@
1
- rclone_api/__init__.py,sha256=gIONLifrNkReIlQkpJOk3TTueJ9jrJ2ZdM2scQSUvSY,34885
1
+ rclone_api/__init__.py,sha256=AH0_iCsWfPCphaXgN0eV7zeZ5WvinUYpyizwPIuUqmQ,35244
2
2
  rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
3
3
  rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
4
4
  rclone_api/config.py,sha256=URZwMME01f0EZymprCESuZ5dk4IuUSKbHhwIeTHrn7A,6131
@@ -55,12 +55,12 @@ rclone_api/s3/multipart/info_json.py,sha256=-e8UCwrqjAP64U8PmH-o2ciJ6TN48DwHktJf
55
55
  rclone_api/s3/multipart/merge_state.py,sha256=ziTB9CYV-OWaky5C1fOT9hifSY2zgUrk5HmX1Xeu2UA,4978
56
56
  rclone_api/s3/multipart/upload_info.py,sha256=d6_OfzFR_vtDzCEegFfzCfWi2kUBUV4aXZzqAEVp1c4,1874
57
57
  rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9akiew3MFjGmufLo2w,12503
58
- rclone_api/s3/multipart/upload_parts_resumable.py,sha256=6-nlMclS8jyVvMvFbQDcZOX9MY1WbCcKA_s9bwuYxnk,9793
58
+ rclone_api/s3/multipart/upload_parts_resumable.py,sha256=Th6m3owHS6Z-ieNhhDtU0_A6nDgKMo9y_Qv6e7lRia8,11634
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.63.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
62
- rclone_api-1.5.63.dist-info/METADATA,sha256=9XSnDbl63rF0hMxexBm7t65FmtxbjMyNiIURw5ixrxo,37305
63
- rclone_api-1.5.63.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
64
- rclone_api-1.5.63.dist-info/entry_points.txt,sha256=ognh2e11HTjn73_KL5MWI67pBKS2jekBi-QTiRXySXA,316
65
- rclone_api-1.5.63.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
66
- rclone_api-1.5.63.dist-info/RECORD,,
61
+ rclone_api-1.5.65.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
62
+ rclone_api-1.5.65.dist-info/METADATA,sha256=tx0O8RMD9s9oAQYXW79wpP9HupCOuh0fb13px0uy9b0,37305
63
+ rclone_api-1.5.65.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
64
+ rclone_api-1.5.65.dist-info/entry_points.txt,sha256=ognh2e11HTjn73_KL5MWI67pBKS2jekBi-QTiRXySXA,316
65
+ rclone_api-1.5.65.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
66
+ rclone_api-1.5.65.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5