rclone-api 1.1.1__tar.gz → 1.1.3__tar.gz

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.
Files changed (82) hide show
  1. {rclone_api-1.1.1 → rclone_api-1.1.3}/PKG-INFO +1 -1
  2. {rclone_api-1.1.1 → rclone_api-1.1.3}/pyproject.toml +1 -1
  3. rclone_api-1.1.3/src/rclone_api/experimental/flags.py +82 -0
  4. rclone_api-1.1.3/src/rclone_api/experimental/flags_base.py +58 -0
  5. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/s3/chunk_uploader.py +37 -4
  6. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api.egg-info/PKG-INFO +1 -1
  7. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api.egg-info/SOURCES.txt +1 -0
  8. rclone_api-1.1.1/src/rclone_api/experimental/flags.py +0 -121
  9. {rclone_api-1.1.1 → rclone_api-1.1.3}/.aiderignore +0 -0
  10. {rclone_api-1.1.1 → rclone_api-1.1.3}/.github/workflows/lint.yml +0 -0
  11. {rclone_api-1.1.1 → rclone_api-1.1.3}/.github/workflows/push_macos.yml +0 -0
  12. {rclone_api-1.1.1 → rclone_api-1.1.3}/.github/workflows/push_ubuntu.yml +0 -0
  13. {rclone_api-1.1.1 → rclone_api-1.1.3}/.github/workflows/push_win.yml +0 -0
  14. {rclone_api-1.1.1 → rclone_api-1.1.3}/.gitignore +0 -0
  15. {rclone_api-1.1.1 → rclone_api-1.1.3}/.pylintrc +0 -0
  16. {rclone_api-1.1.1 → rclone_api-1.1.3}/.vscode/launch.json +0 -0
  17. {rclone_api-1.1.1 → rclone_api-1.1.3}/.vscode/settings.json +0 -0
  18. {rclone_api-1.1.1 → rclone_api-1.1.3}/.vscode/tasks.json +0 -0
  19. {rclone_api-1.1.1 → rclone_api-1.1.3}/LICENSE +0 -0
  20. {rclone_api-1.1.1 → rclone_api-1.1.3}/MANIFEST.in +0 -0
  21. {rclone_api-1.1.1 → rclone_api-1.1.3}/README.md +0 -0
  22. {rclone_api-1.1.1 → rclone_api-1.1.3}/clean +0 -0
  23. {rclone_api-1.1.1 → rclone_api-1.1.3}/install +0 -0
  24. {rclone_api-1.1.1 → rclone_api-1.1.3}/lint +0 -0
  25. {rclone_api-1.1.1 → rclone_api-1.1.3}/requirements.testing.txt +0 -0
  26. {rclone_api-1.1.1 → rclone_api-1.1.3}/setup.cfg +0 -0
  27. {rclone_api-1.1.1 → rclone_api-1.1.3}/setup.py +0 -0
  28. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/__init__.py +0 -0
  29. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/assets/example.txt +0 -0
  30. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/cli.py +0 -0
  31. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/cmd/copy_large_s3.py +0 -0
  32. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/cmd/list_files.py +0 -0
  33. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/completed_process.py +0 -0
  34. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/config.py +0 -0
  35. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/convert.py +0 -0
  36. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/deprecated.py +0 -0
  37. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/diff.py +0 -0
  38. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/dir.py +0 -0
  39. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/dir_listing.py +0 -0
  40. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/exec.py +0 -0
  41. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/file.py +0 -0
  42. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/filelist.py +0 -0
  43. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/group_files.py +0 -0
  44. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/process.py +0 -0
  45. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/rclone.py +0 -0
  46. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/remote.py +0 -0
  47. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/rpath.py +0 -0
  48. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/s3/api.py +0 -0
  49. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/s3/basic_ops.py +0 -0
  50. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/s3/create.py +0 -0
  51. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/s3/types.py +0 -0
  52. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/scan_missing_folders.py +0 -0
  53. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/types.py +0 -0
  54. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/util.py +0 -0
  55. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api/walk.py +0 -0
  56. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  57. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api.egg-info/entry_points.txt +0 -0
  58. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api.egg-info/requires.txt +0 -0
  59. {rclone_api-1.1.1 → rclone_api-1.1.3}/src/rclone_api.egg-info/top_level.txt +0 -0
  60. {rclone_api-1.1.1 → rclone_api-1.1.3}/test +0 -0
  61. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/archive/test_paramiko.py.disabled +0 -0
  62. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_cmd_list_files.py +0 -0
  63. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_copy.py +0 -0
  64. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_copy_files.py +0 -0
  65. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_diff.py +0 -0
  66. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_group_files.py +0 -0
  67. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_is_synced.py +0 -0
  68. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_ls.py +0 -0
  69. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_mount.py +0 -0
  70. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_mount_s3.py +0 -0
  71. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_mount_webdav.py +0 -0
  72. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_mounted_ranged_download.py +0 -0
  73. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_obscure.py +0 -0
  74. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_rclone_config.py +0 -0
  75. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_remote_control.py +0 -0
  76. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_remotes.py +0 -0
  77. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_s3.py +0 -0
  78. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_scan_missing_folders.py +0 -0
  79. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_size_files.py +0 -0
  80. {rclone_api-1.1.1 → rclone_api-1.1.3}/tests/test_walk.py +0 -0
  81. {rclone_api-1.1.1 → rclone_api-1.1.3}/tox.ini +0 -0
  82. {rclone_api-1.1.1 → rclone_api-1.1.3}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -21,7 +21,7 @@ dependencies = [
21
21
  ]
22
22
 
23
23
  # Change this with the version number bump.
24
- version = "1.1.1"
24
+ version = "1.1.3"
25
25
 
26
26
  [tool.setuptools]
27
27
  package-dir = {"" = "src"}
@@ -0,0 +1,82 @@
1
+ from dataclasses import dataclass
2
+
3
+ from rclone_api.experimental.flags_base import BaseFlags, merge_flags
4
+
5
+
6
+ @dataclass
7
+ class CopyFlags(BaseFlags):
8
+ check_first: bool | None = None
9
+ checksum: bool | None = False
10
+ compare_dest: list[str] | None = None
11
+ copy_dest: list[str] | None = None
12
+ cutoff_mode: str | None = None
13
+ ignore_case_sync: bool | None = None
14
+ ignore_checksum: bool | None = None
15
+ ignore_existing: bool | None = None
16
+ ignore_size: bool | None = None
17
+ ignore_times: bool | None = None
18
+ immutable: bool | None = None
19
+ inplace: bool | None = None
20
+ links: bool | None = None
21
+ max_backlog: int | None = None
22
+ max_duration: str | None = None
23
+ max_transfer: str | None = None
24
+ metadata: bool | None = None
25
+ modify_window: str | None = None
26
+ multi_thread_chunk_size: str | None = None
27
+ multi_thread_cutoff: str | None = None
28
+ multi_thread_streams: int | None = None
29
+ multi_thread_write_buffer_size: str | None = None
30
+ no_check_dest: bool | None = None
31
+ no_traverse: bool | None = None
32
+ no_update_dir_modtime: bool | None = None
33
+ no_update_modtime: bool | None = None
34
+ order_by: str | None = None
35
+ partial_suffix: str | None = None
36
+ refresh_times: bool | None = None
37
+ server_side_across_configs: bool | None = None
38
+ size_only: bool | None = None
39
+ streaming_upload_cutoff: str | None = None
40
+ update: bool | None = None
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
+
51
+
52
+ @dataclass
53
+ class Flags(BaseFlags):
54
+ copy: CopyFlags | None = None
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
+
65
+
66
+ def unit_test() -> None:
67
+ copy_flags_a = CopyFlags(compare_dest=["a", "b"])
68
+ copy_flags_b = CopyFlags(checksum=False)
69
+ flags_a = copy_flags_a.merge(copy_flags_b)
70
+ print("A:", flags_a)
71
+
72
+ copy_flags_c = CopyFlags(checksum=True)
73
+ copy_flags_d = CopyFlags(checksum=False)
74
+
75
+ merged_c_d = copy_flags_c.merge(copy_flags_d)
76
+ print("B:", merged_c_d)
77
+ merged_d_c = copy_flags_d.merge(copy_flags_c)
78
+ print("C:", merged_d_c)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ unit_test()
@@ -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())
@@ -127,6 +127,13 @@ class UploadState:
127
127
  lock: Lock = Lock()
128
128
  parts: list[FinishedPiece | None] = field(default_factory=list)
129
129
 
130
+ def update_source_file(self, src_file: Path) -> None:
131
+ new_file_size = os.path.getsize(src_file)
132
+ if new_file_size != self.upload_info.file_size:
133
+ raise ValueError("File size changed, cannot resume")
134
+ self.upload_info.src_file_path = src_file
135
+ self.save()
136
+
130
137
  def is_done(self) -> bool:
131
138
  return self.remaining() == 0
132
139
 
@@ -264,15 +271,22 @@ def _get_chunk_tmpdir() -> Path:
264
271
  def _get_file_size(file_path: Path, timeout: int = 60) -> int:
265
272
  sleep_time = timeout / 60 if timeout > 0 else 1
266
273
  start = time.time()
274
+ not_windows = os.name != "nt"
267
275
  while True:
276
+ expired = time.time() - start > timeout
268
277
  try:
278
+ time.sleep(sleep_time)
279
+ if not_windows:
280
+ # Force a refresh of the directory cache
281
+ os.system(f"ls -l {file_path.parent}")
269
282
  if file_path.exists():
270
283
  return file_path.stat().st_size
271
- except FileNotFoundError:
272
- pass
273
- if time.time() - start > timeout:
284
+ except FileNotFoundError as e:
285
+ if expired:
286
+ print(f"File not found: {file_path}, exception is {e}")
287
+ raise
288
+ if expired:
274
289
  raise TimeoutError(f"File {file_path} not found after {timeout} seconds")
275
- time.sleep(sleep_time)
276
290
 
277
291
 
278
292
  def file_chunker(
@@ -426,6 +440,18 @@ def prepare_upload_file_multipart(
426
440
  return upload_info
427
441
 
428
442
 
443
+ def _abort_previous_upload(upload_state: UploadState) -> None:
444
+ if upload_state.upload_info.upload_id:
445
+ try:
446
+ upload_state.upload_info.s3_client.abort_multipart_upload(
447
+ Bucket=upload_state.upload_info.bucket_name,
448
+ Key=upload_state.upload_info.object_name,
449
+ UploadId=upload_state.upload_info.upload_id,
450
+ )
451
+ except Exception as e:
452
+ locked_print(f"Error aborting previous upload: {e}")
453
+
454
+
429
455
  def upload_file_multipart(
430
456
  s3_client: BaseClient,
431
457
  bucket_name: str,
@@ -481,6 +507,13 @@ def upload_file_multipart(
481
507
 
482
508
  filechunks: Queue[FileChunk | None] = Queue(10)
483
509
  upload_state = get_upload_state() or make_new_state()
510
+ try:
511
+ upload_state.update_source_file(file_path)
512
+ except ValueError as e:
513
+ locked_print(f"Cannot resume upload: {e}, size changed, starting over")
514
+ _abort_previous_upload(upload_state)
515
+ upload_state = make_new_state()
516
+ upload_state.save()
484
517
  if upload_state.is_done():
485
518
  return MultiUploadResult.ALREADY_DONE
486
519
  finished = upload_state.finished()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -51,6 +51,7 @@ src/rclone_api/assets/example.txt
51
51
  src/rclone_api/cmd/copy_large_s3.py
52
52
  src/rclone_api/cmd/list_files.py
53
53
  src/rclone_api/experimental/flags.py
54
+ src/rclone_api/experimental/flags_base.py
54
55
  src/rclone_api/s3/api.py
55
56
  src/rclone_api/s3/basic_ops.py
56
57
  src/rclone_api/s3/chunk_uploader.py
@@ -1,121 +0,0 @@
1
- from dataclasses import dataclass, fields, is_dataclass
2
- from typing import Type, TypeVar
3
-
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('_', '-')}"
28
-
29
-
30
- @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
- 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
103
-
104
-
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)
110
-
111
- copy_flags_c = CopyFlags(checksum=True)
112
- copy_flags_d = CopyFlags(checksum=False)
113
-
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)
118
-
119
-
120
- if __name__ == "__main__":
121
- unit_test()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes