rclone-api 1.1.0__py2.py3-none-any.whl → 1.1.2__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 +21 -60
- rclone_api/experimental/flags_base.py +58 -0
- rclone_api/s3/chunk_uploader.py +18 -4
- {rclone_api-1.1.0.dist-info → rclone_api-1.1.2.dist-info}/METADATA +1 -1
- {rclone_api-1.1.0.dist-info → rclone_api-1.1.2.dist-info}/RECORD +9 -8
- {rclone_api-1.1.0.dist-info → rclone_api-1.1.2.dist-info}/LICENSE +0 -0
- {rclone_api-1.1.0.dist-info → rclone_api-1.1.2.dist-info}/WHEEL +0 -0
- {rclone_api-1.1.0.dist-info → rclone_api-1.1.2.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.1.0.dist-info → rclone_api-1.1.2.dist-info}/top_level.txt +0 -0
rclone_api/experimental/flags.py
CHANGED
|
@@ -1,66 +1,9 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Type, TypeVar
|
|
1
|
+
from dataclasses import dataclass
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
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")
|
|
12
|
-
|
|
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)
|
|
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
|
|
22
|
-
|
|
23
|
-
return cls(**merged_kwargs)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _field_name_to_flag(field_name: str) -> str:
|
|
27
|
-
return f"--{field_name.replace('_', '-')}"
|
|
3
|
+
from rclone_api.experimental.flags_base import BaseFlags, merge_flags
|
|
28
4
|
|
|
29
5
|
|
|
30
6
|
@dataclass
|
|
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
7
|
class CopyFlags(BaseFlags):
|
|
65
8
|
check_first: bool | None = None
|
|
66
9
|
checksum: bool | None = False
|
|
@@ -96,11 +39,29 @@ class CopyFlags(BaseFlags):
|
|
|
96
39
|
streaming_upload_cutoff: str | None = None
|
|
97
40
|
update: bool | None = None
|
|
98
41
|
|
|
42
|
+
def to_args(self) -> list[str]:
|
|
43
|
+
return super().to_args()
|
|
44
|
+
|
|
45
|
+
def merge(self, other: "CopyFlags") -> "CopyFlags":
|
|
46
|
+
return merge_flags(CopyFlags, self, other)
|
|
47
|
+
|
|
48
|
+
def __repr__(self):
|
|
49
|
+
return super().__repr__()
|
|
50
|
+
|
|
99
51
|
|
|
100
|
-
@dataclass
|
|
52
|
+
@dataclass
|
|
101
53
|
class Flags(BaseFlags):
|
|
102
54
|
copy: CopyFlags | None = None
|
|
103
55
|
|
|
56
|
+
def to_args(self) -> list[str]:
|
|
57
|
+
return super().to_args()
|
|
58
|
+
|
|
59
|
+
def merge(self, other: "Flags") -> "Flags":
|
|
60
|
+
return merge_flags(Flags, self, other)
|
|
61
|
+
|
|
62
|
+
def __repr__(self):
|
|
63
|
+
return super().__repr__()
|
|
64
|
+
|
|
104
65
|
|
|
105
66
|
def unit_test() -> None:
|
|
106
67
|
copy_flags_a = CopyFlags(compare_dest=["a", "b"])
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
2
|
+
from typing import Type, TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def merge_flags(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")
|
|
12
|
+
|
|
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)
|
|
17
|
+
|
|
18
|
+
if is_dataclass(a_value) and is_dataclass(b_value):
|
|
19
|
+
merged_kwargs[field.name] = merge_flags(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
|
|
22
|
+
|
|
23
|
+
return cls(**merged_kwargs)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _field_name_to_flag(field_name: str) -> str:
|
|
27
|
+
return f"--{field_name.replace('_', '-')}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class BaseFlags:
|
|
32
|
+
"""provides to_args(), merge() and __repr__ methods for flags dataclasses"""
|
|
33
|
+
|
|
34
|
+
def to_args(self) -> list[str]:
|
|
35
|
+
args = []
|
|
36
|
+
for field in fields(self):
|
|
37
|
+
value = getattr(self, field.name)
|
|
38
|
+
if value is None:
|
|
39
|
+
continue
|
|
40
|
+
# If the field value is a nested dataclass that supports to_args, use it.
|
|
41
|
+
if is_dataclass(value) and hasattr(value, "to_args"):
|
|
42
|
+
to_args = getattr(value, "to_args")
|
|
43
|
+
args.extend(to_args())
|
|
44
|
+
elif isinstance(value, bool):
|
|
45
|
+
# Only include the flag if the boolean is True.
|
|
46
|
+
if value:
|
|
47
|
+
args.append(_field_name_to_flag(field.name))
|
|
48
|
+
else:
|
|
49
|
+
args.append(_field_name_to_flag(field.name))
|
|
50
|
+
if isinstance(value, list):
|
|
51
|
+
# Join list values with a comma.
|
|
52
|
+
args.append(",".join(map(str, value)))
|
|
53
|
+
else:
|
|
54
|
+
args.append(str(value))
|
|
55
|
+
return args
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return str(self.to_args())
|
rclone_api/s3/chunk_uploader.py
CHANGED
|
@@ -264,13 +264,20 @@ def _get_chunk_tmpdir() -> Path:
|
|
|
264
264
|
def _get_file_size(file_path: Path, timeout: int = 60) -> int:
|
|
265
265
|
sleep_time = timeout / 60 if timeout > 0 else 1
|
|
266
266
|
start = time.time()
|
|
267
|
+
not_windows = os.name != "nt"
|
|
267
268
|
while True:
|
|
269
|
+
expired = time.time() - start > timeout
|
|
268
270
|
try:
|
|
271
|
+
if not_windows:
|
|
272
|
+
# Force a refresh of the directory cache
|
|
273
|
+
os.system(f"ls -l {file_path.parent}")
|
|
269
274
|
if file_path.exists():
|
|
270
275
|
return file_path.stat().st_size
|
|
271
|
-
except FileNotFoundError:
|
|
272
|
-
|
|
273
|
-
|
|
276
|
+
except FileNotFoundError as e:
|
|
277
|
+
if expired:
|
|
278
|
+
print(f"File not found: {file_path}, exception is {e}")
|
|
279
|
+
raise
|
|
280
|
+
if expired:
|
|
274
281
|
raise TimeoutError(f"File {file_path} not found after {timeout} seconds")
|
|
275
282
|
time.sleep(sleep_time)
|
|
276
283
|
|
|
@@ -492,16 +499,20 @@ def upload_file_multipart(
|
|
|
492
499
|
upload_info = upload_state.upload_info
|
|
493
500
|
max_workers = 8
|
|
494
501
|
|
|
502
|
+
chunker_errors: Queue[Exception] = Queue()
|
|
503
|
+
|
|
495
504
|
def chunker_task(
|
|
496
505
|
upload_state=upload_state,
|
|
497
506
|
output=filechunks,
|
|
498
507
|
max_chunks=max_chunks_before_suspension,
|
|
508
|
+
queue_errors=chunker_errors,
|
|
499
509
|
) -> None:
|
|
500
510
|
try:
|
|
501
511
|
file_chunker(
|
|
502
512
|
upload_state=upload_state, output=output, max_chunks=max_chunks
|
|
503
513
|
)
|
|
504
|
-
except Exception:
|
|
514
|
+
except Exception as e:
|
|
515
|
+
queue_errors.put(e)
|
|
505
516
|
_thread.interrupt_main()
|
|
506
517
|
raise
|
|
507
518
|
|
|
@@ -533,6 +544,9 @@ def upload_file_multipart(
|
|
|
533
544
|
# upload_state.finished_parts.put(None) # Signal the end of the queue
|
|
534
545
|
upload_state.add_finished(None)
|
|
535
546
|
thread_chunker.join()
|
|
547
|
+
|
|
548
|
+
if not chunker_errors.empty():
|
|
549
|
+
raise chunker_errors.get()
|
|
536
550
|
if not upload_state.is_done():
|
|
537
551
|
upload_state.save()
|
|
538
552
|
return MultiUploadResult.SUSPENDED
|
|
@@ -22,15 +22,16 @@ 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=0-mtXg9J4MoMm2uBKbsMLj4pSGRLQUAqNRDJWGttnAQ,2443
|
|
26
|
+
rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
|
|
26
27
|
rclone_api/s3/api.py,sha256=VstlaEnBjO2JDQuCRLdTfUGvQLbfshlXXhAzimFv4Vc,3763
|
|
27
28
|
rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
|
|
28
|
-
rclone_api/s3/chunk_uploader.py,sha256=
|
|
29
|
+
rclone_api/s3/chunk_uploader.py,sha256=6EB7FH4HftkQRyHijOU8O8CWShFE0qQY-lDo5oU-xIw,18843
|
|
29
30
|
rclone_api/s3/create.py,sha256=SK3IGHZwsSkoG4Zb4NCphcVg9_f7VifDKng-tExMS2s,3088
|
|
30
31
|
rclone_api/s3/types.py,sha256=81_3jwg6MGIxC-GxL-6zANzKO6au9C0BWvAqRyODxOM,1361
|
|
31
|
-
rclone_api-1.1.
|
|
32
|
-
rclone_api-1.1.
|
|
33
|
-
rclone_api-1.1.
|
|
34
|
-
rclone_api-1.1.
|
|
35
|
-
rclone_api-1.1.
|
|
36
|
-
rclone_api-1.1.
|
|
32
|
+
rclone_api-1.1.2.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
|
33
|
+
rclone_api-1.1.2.dist-info/METADATA,sha256=gcug-ENlJLW8DD1HdmG1eeyb6ajIVVVO_luxxZqk2pc,4478
|
|
34
|
+
rclone_api-1.1.2.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
|
|
35
|
+
rclone_api-1.1.2.dist-info/entry_points.txt,sha256=6eNqTRXKhVf8CpWNjXiOa_0Du9tHiW_HD2iQSXRsUg8,132
|
|
36
|
+
rclone_api-1.1.2.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
|
37
|
+
rclone_api-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|