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.
@@ -1,66 +1,9 @@
1
- from dataclasses import dataclass, fields, is_dataclass
2
- from typing import Type, TypeVar
1
+ from dataclasses import dataclass
3
2
 
4
- T = TypeVar("T")
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(repr=False)
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())
@@ -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
- pass
273
- if time.time() - start > timeout:
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -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=AHbTaFHuyYFm3pjdvbQ100jztOXOdNuxalMr8UjXnV4,4097
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=txnNtugKcTb1UDmPbqWp3K-yLUDEkeW72nEnQnDcA8o,18336
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.0.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
32
- rclone_api-1.1.0.dist-info/METADATA,sha256=lWksanXlrw5bUyZxWDjFpntBvxsBWd2IBImny4OZRZs,4478
33
- rclone_api-1.1.0.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
34
- rclone_api-1.1.0.dist-info/entry_points.txt,sha256=6eNqTRXKhVf8CpWNjXiOa_0Du9tHiW_HD2iQSXRsUg8,132
35
- rclone_api-1.1.0.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
36
- rclone_api-1.1.0.dist-info/RECORD,,
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,,