rclone-api 1.0.98__py2.py3-none-any.whl → 1.0.100__py2.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/experimental/flags.py +104 -76
- rclone_api/rclone.py +1 -0
- rclone_api/s3/chunk_uploader.py +31 -4
- {rclone_api-1.0.98.dist-info → rclone_api-1.0.100.dist-info}/METADATA +1 -1
- {rclone_api-1.0.98.dist-info → rclone_api-1.0.100.dist-info}/RECORD +9 -9
- {rclone_api-1.0.98.dist-info → rclone_api-1.0.100.dist-info}/LICENSE +0 -0
- {rclone_api-1.0.98.dist-info → rclone_api-1.0.100.dist-info}/WHEEL +0 -0
- {rclone_api-1.0.98.dist-info → rclone_api-1.0.100.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.0.98.dist-info → rclone_api-1.0.100.dist-info}/top_level.txt +0 -0
rclone_api/experimental/flags.py
CHANGED
|
@@ -1,93 +1,121 @@
|
|
|
1
|
-
from dataclasses import dataclass,
|
|
1
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
2
|
+
from typing import Type, TypeVar
|
|
2
3
|
|
|
4
|
+
T = TypeVar("T")
|
|
3
5
|
|
|
4
|
-
@dataclass
|
|
5
|
-
class CopyFlags:
|
|
6
|
-
check_first: bool = False
|
|
7
|
-
checksum: bool = False
|
|
8
|
-
ignore_existing: bool = False
|
|
9
|
-
ignore_times: bool = False
|
|
10
|
-
immutable: bool = False
|
|
11
|
-
inplace: bool = False
|
|
12
|
-
links: bool = False
|
|
13
|
-
metadata: bool = False
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass
|
|
17
|
-
class SyncFlags:
|
|
18
|
-
backup_dir: str | None = None
|
|
19
|
-
delete_after: bool = False
|
|
20
|
-
delete_before: bool = False
|
|
21
|
-
delete_during: bool = False
|
|
22
|
-
ignore_errors: bool = False
|
|
23
|
-
track_renames: bool = False
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass
|
|
27
|
-
class ImportantFlags:
|
|
28
|
-
dry_run: bool = False
|
|
29
|
-
interactive: bool = False
|
|
30
|
-
verbose: int = 0 # 0 = default, 1 = -v, 2 = -vv, etc.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass
|
|
34
|
-
class NetworkingFlags:
|
|
35
|
-
bwlimit: str | None = None
|
|
36
|
-
timeout: str | None = "5m0s"
|
|
37
|
-
tpslimit: float | None = None
|
|
38
|
-
user_agent: str | None = "rclone/v1.69.1"
|
|
39
6
|
|
|
7
|
+
def _merge(cls: Type[T], dataclass_a: T, dataclass_b: T) -> T:
|
|
8
|
+
if not is_dataclass(dataclass_a) or not is_dataclass(dataclass_b):
|
|
9
|
+
raise ValueError("Both inputs must be dataclass instances")
|
|
10
|
+
if type(dataclass_a) is not type(dataclass_b):
|
|
11
|
+
raise ValueError("Dataclass instances must be of the same type")
|
|
40
12
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
transfers: int = 4
|
|
13
|
+
merged_kwargs = {}
|
|
14
|
+
for field in fields(dataclass_a):
|
|
15
|
+
a_value = getattr(dataclass_a, field.name)
|
|
16
|
+
b_value = getattr(dataclass_b, field.name)
|
|
46
17
|
|
|
18
|
+
if is_dataclass(a_value) and is_dataclass(b_value):
|
|
19
|
+
merged_kwargs[field.name] = _merge(type(a_value), a_value, b_value)
|
|
20
|
+
else:
|
|
21
|
+
merged_kwargs[field.name] = b_value if b_value is not None else a_value
|
|
47
22
|
|
|
48
|
-
|
|
49
|
-
class ConfigFlags:
|
|
50
|
-
config: str | None = None
|
|
51
|
-
ask_password: bool = True
|
|
52
|
-
auto_confirm: bool = False
|
|
23
|
+
return cls(**merged_kwargs)
|
|
53
24
|
|
|
54
25
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
cpuprofile: str | None = None
|
|
58
|
-
memprofile: str | None = None
|
|
26
|
+
def _field_name_to_flag(field_name: str) -> str:
|
|
27
|
+
return f"--{field_name.replace('_', '-')}"
|
|
59
28
|
|
|
60
29
|
|
|
61
30
|
@dataclass
|
|
62
|
-
class
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
31
|
+
class BaseFlags:
|
|
32
|
+
def to_args(self) -> list[str]:
|
|
33
|
+
args = []
|
|
34
|
+
for field in fields(self):
|
|
35
|
+
value = getattr(self, field.name)
|
|
36
|
+
if value is None:
|
|
37
|
+
continue
|
|
38
|
+
# If the field value is a nested dataclass that supports to_args, use it.
|
|
39
|
+
if is_dataclass(value) and hasattr(value, "to_args"):
|
|
40
|
+
to_args = getattr(value, "to_args")
|
|
41
|
+
args.extend(to_args())
|
|
42
|
+
elif isinstance(value, bool):
|
|
43
|
+
# Only include the flag if the boolean is True.
|
|
44
|
+
if value:
|
|
45
|
+
args.append(_field_name_to_flag(field.name))
|
|
46
|
+
else:
|
|
47
|
+
args.append(_field_name_to_flag(field.name))
|
|
48
|
+
if isinstance(value, list):
|
|
49
|
+
# Join list values with a comma.
|
|
50
|
+
args.append(",".join(map(str, value)))
|
|
51
|
+
else:
|
|
52
|
+
args.append(str(value))
|
|
53
|
+
return args
|
|
54
|
+
|
|
55
|
+
def merge(self, other: "BaseFlags") -> "BaseFlags":
|
|
56
|
+
# Use the type of self, so merging CopyFlags returns a CopyFlags instance.
|
|
57
|
+
return _merge(type(self), self, other)
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
return str(self.to_args())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(repr=False)
|
|
64
|
+
class CopyFlags(BaseFlags):
|
|
65
|
+
check_first: bool | None = None
|
|
66
|
+
checksum: bool | None = False
|
|
67
|
+
compare_dest: list[str] | None = None
|
|
68
|
+
copy_dest: list[str] | None = None
|
|
69
|
+
cutoff_mode: str | None = None
|
|
70
|
+
ignore_case_sync: bool | None = None
|
|
71
|
+
ignore_checksum: bool | None = None
|
|
72
|
+
ignore_existing: bool | None = None
|
|
73
|
+
ignore_size: bool | None = None
|
|
74
|
+
ignore_times: bool | None = None
|
|
75
|
+
immutable: bool | None = None
|
|
76
|
+
inplace: bool | None = None
|
|
77
|
+
links: bool | None = None
|
|
78
|
+
max_backlog: int | None = None
|
|
79
|
+
max_duration: str | None = None
|
|
80
|
+
max_transfer: str | None = None
|
|
81
|
+
metadata: bool | None = None
|
|
82
|
+
modify_window: str | None = None
|
|
83
|
+
multi_thread_chunk_size: str | None = None
|
|
84
|
+
multi_thread_cutoff: str | None = None
|
|
85
|
+
multi_thread_streams: int | None = None
|
|
86
|
+
multi_thread_write_buffer_size: str | None = None
|
|
87
|
+
no_check_dest: bool | None = None
|
|
88
|
+
no_traverse: bool | None = None
|
|
89
|
+
no_update_dir_modtime: bool | None = None
|
|
90
|
+
no_update_modtime: bool | None = None
|
|
91
|
+
order_by: str | None = None
|
|
92
|
+
partial_suffix: str | None = None
|
|
93
|
+
refresh_times: bool | None = None
|
|
94
|
+
server_side_across_configs: bool | None = None
|
|
95
|
+
size_only: bool | None = None
|
|
96
|
+
streaming_upload_cutoff: str | None = None
|
|
97
|
+
update: bool | None = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(repr=False)
|
|
101
|
+
class Flags(BaseFlags):
|
|
102
|
+
copy: CopyFlags | None = None
|
|
67
103
|
|
|
68
104
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
def unit_test() -> None:
|
|
106
|
+
copy_flags_a = CopyFlags(compare_dest=["a", "b"])
|
|
107
|
+
copy_flags_b = CopyFlags(checksum=False)
|
|
108
|
+
flags_a = copy_flags_a.merge(copy_flags_b)
|
|
109
|
+
print("A:", flags_a)
|
|
72
110
|
|
|
111
|
+
copy_flags_c = CopyFlags(checksum=True)
|
|
112
|
+
copy_flags_d = CopyFlags(checksum=False)
|
|
73
113
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
stats: str | None = "1m0s"
|
|
79
|
-
progress: bool = False
|
|
114
|
+
merged_c_d = copy_flags_c.merge(copy_flags_d)
|
|
115
|
+
print("B:", merged_c_d)
|
|
116
|
+
merged_d_c = copy_flags_d.merge(copy_flags_c)
|
|
117
|
+
print("C:", merged_d_c)
|
|
80
118
|
|
|
81
119
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
copy: CopyFlags | None = None
|
|
85
|
-
sync: SyncFlags | None = None
|
|
86
|
-
important: ImportantFlags | None = None
|
|
87
|
-
networking: NetworkingFlags | None = None
|
|
88
|
-
performance: PerformanceFlags | None = None
|
|
89
|
-
config: ConfigFlags | None = None
|
|
90
|
-
debugging: DebuggingFlags | None = None
|
|
91
|
-
filter: FilterFlags | None = None
|
|
92
|
-
listing: ListingFlags | None = None
|
|
93
|
-
logging: LoggingFlags | None = None
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
unit_test()
|
rclone_api/rclone.py
CHANGED
rclone_api/s3/chunk_uploader.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _thread
|
|
1
2
|
import json
|
|
2
3
|
import os
|
|
3
4
|
import time
|
|
@@ -260,6 +261,20 @@ def _get_chunk_tmpdir() -> Path:
|
|
|
260
261
|
return out
|
|
261
262
|
|
|
262
263
|
|
|
264
|
+
def _get_file_size(file_path: Path, timeout: int = 60) -> int:
|
|
265
|
+
sleep_time = timeout / 60 if timeout > 0 else 1
|
|
266
|
+
start = time.time()
|
|
267
|
+
while True:
|
|
268
|
+
try:
|
|
269
|
+
if file_path.exists():
|
|
270
|
+
return file_path.stat().st_size
|
|
271
|
+
except FileNotFoundError:
|
|
272
|
+
pass
|
|
273
|
+
if time.time() - start > timeout:
|
|
274
|
+
raise TimeoutError(f"File {file_path} not found after {timeout} seconds")
|
|
275
|
+
time.sleep(sleep_time)
|
|
276
|
+
|
|
277
|
+
|
|
263
278
|
def file_chunker(
|
|
264
279
|
upload_state: UploadState, max_chunks: int | None, output: Queue[FileChunk | None]
|
|
265
280
|
) -> None:
|
|
@@ -279,7 +294,8 @@ def file_chunker(
|
|
|
279
294
|
file_path = upload_info.src_file_path
|
|
280
295
|
chunk_size = upload_info.chunk_size
|
|
281
296
|
src = Path(file_path)
|
|
282
|
-
|
|
297
|
+
# Mounted files may take a while to appear, so keep retrying.
|
|
298
|
+
file_size = _get_file_size(src, timeout=60)
|
|
283
299
|
part_number = 1
|
|
284
300
|
done_part_numbers: set[int] = {
|
|
285
301
|
p.part_number for p in upload_state.parts if p is not None
|
|
@@ -418,6 +434,7 @@ def upload_file_multipart(
|
|
|
418
434
|
chunk_size: int = 16 * 1024 * 1024, # Default chunk size is 16MB; can be overridden
|
|
419
435
|
retries: int = 20,
|
|
420
436
|
max_chunks_before_suspension: int | None = None,
|
|
437
|
+
abort_transfer_on_failure: bool = False,
|
|
421
438
|
) -> MultiUploadResult:
|
|
422
439
|
"""Upload a file to the bucket using multipart upload with customizable chunk size."""
|
|
423
440
|
file_size = os.path.getsize(str(file_path))
|
|
@@ -479,7 +496,13 @@ def upload_file_multipart(
|
|
|
479
496
|
output=filechunks,
|
|
480
497
|
max_chunks=max_chunks_before_suspension,
|
|
481
498
|
) -> None:
|
|
482
|
-
|
|
499
|
+
try:
|
|
500
|
+
file_chunker(
|
|
501
|
+
upload_state=upload_state, output=output, max_chunks=max_chunks
|
|
502
|
+
)
|
|
503
|
+
except Exception:
|
|
504
|
+
_thread.interrupt_main()
|
|
505
|
+
raise
|
|
483
506
|
|
|
484
507
|
try:
|
|
485
508
|
thread_chunker = Thread(target=chunker_task, daemon=True)
|
|
@@ -492,7 +515,11 @@ def upload_file_multipart(
|
|
|
492
515
|
break
|
|
493
516
|
|
|
494
517
|
def task(upload_info=upload_info, file_chunk=file_chunk):
|
|
495
|
-
|
|
518
|
+
try:
|
|
519
|
+
return handle_upload(upload_info, file_chunk)
|
|
520
|
+
except Exception:
|
|
521
|
+
_thread.interrupt_main()
|
|
522
|
+
raise
|
|
496
523
|
|
|
497
524
|
fut = executor.submit(task)
|
|
498
525
|
|
|
@@ -525,7 +552,7 @@ def upload_file_multipart(
|
|
|
525
552
|
f"Multipart upload completed: {file_path} to {bucket_name}/{object_name}"
|
|
526
553
|
)
|
|
527
554
|
except Exception:
|
|
528
|
-
if upload_info.upload_id:
|
|
555
|
+
if upload_info.upload_id and abort_transfer_on_failure:
|
|
529
556
|
try:
|
|
530
557
|
s3_client.abort_multipart_upload(
|
|
531
558
|
Bucket=bucket_name, Key=object_name, UploadId=upload_info.upload_id
|
|
@@ -12,7 +12,7 @@ rclone_api/file.py,sha256=EP5yT2dZ0H2p7CY5n0y5k5pHhIliV25pm8KOwBklUTk,1863
|
|
|
12
12
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
|
13
13
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
|
14
14
|
rclone_api/process.py,sha256=RrMfTe0bndmJ6gBK67ioqNvCstJ8aTC8RlGX1XBLlcw,4191
|
|
15
|
-
rclone_api/rclone.py,sha256=
|
|
15
|
+
rclone_api/rclone.py,sha256=_CejQQ8jF-VYc_rO2u5i9qr8jmyksxyuoO4ZdHLW6tg,43540
|
|
16
16
|
rclone_api/remote.py,sha256=O9WDUFQy9f6oT1HdUbTixK2eg0xtBBm8k4Xl6aa6K00,431
|
|
17
17
|
rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
|
|
18
18
|
rclone_api/scan_missing_folders.py,sha256=Kulca2Q6WZodt00ATFHkmqqInuoPvBkhTcS9703y6po,4740
|
|
@@ -22,15 +22,15 @@ rclone_api/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
|
|
|
22
22
|
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
|
23
23
|
rclone_api/cmd/copy_large_s3.py,sha256=33KFvCrh5uk-rdRtkREdEs2WNwxGgTdCAWDLCE4dm0A,2855
|
|
24
24
|
rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
|
|
25
|
-
rclone_api/experimental/flags.py,sha256=
|
|
25
|
+
rclone_api/experimental/flags.py,sha256=AHbTaFHuyYFm3pjdvbQ100jztOXOdNuxalMr8UjXnV4,4097
|
|
26
26
|
rclone_api/s3/api.py,sha256=VstlaEnBjO2JDQuCRLdTfUGvQLbfshlXXhAzimFv4Vc,3763
|
|
27
27
|
rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
|
|
28
|
-
rclone_api/s3/chunk_uploader.py,sha256
|
|
28
|
+
rclone_api/s3/chunk_uploader.py,sha256=-GzafZ93Mm9go7BqOaq8svPsjAwtFbVuzVpFdtw8cVA,18283
|
|
29
29
|
rclone_api/s3/create.py,sha256=SK3IGHZwsSkoG4Zb4NCphcVg9_f7VifDKng-tExMS2s,3088
|
|
30
30
|
rclone_api/s3/types.py,sha256=81_3jwg6MGIxC-GxL-6zANzKO6au9C0BWvAqRyODxOM,1361
|
|
31
|
-
rclone_api-1.0.
|
|
32
|
-
rclone_api-1.0.
|
|
33
|
-
rclone_api-1.0.
|
|
34
|
-
rclone_api-1.0.
|
|
35
|
-
rclone_api-1.0.
|
|
36
|
-
rclone_api-1.0.
|
|
31
|
+
rclone_api-1.0.100.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
|
32
|
+
rclone_api-1.0.100.dist-info/METADATA,sha256=ZuM5x0cJJyMDGIi2HApzr4yqo9JdoMLCSOLyUiWkD9w,4480
|
|
33
|
+
rclone_api-1.0.100.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
|
34
|
+
rclone_api-1.0.100.dist-info/entry_points.txt,sha256=6eNqTRXKhVf8CpWNjXiOa_0Du9tHiW_HD2iQSXRsUg8,132
|
|
35
|
+
rclone_api-1.0.100.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
|
36
|
+
rclone_api-1.0.100.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|