rclone-api 1.0.98__tar.gz → 1.0.100__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 (81) hide show
  1. {rclone_api-1.0.98 → rclone_api-1.0.100}/PKG-INFO +1 -1
  2. {rclone_api-1.0.98 → rclone_api-1.0.100}/pyproject.toml +1 -1
  3. rclone_api-1.0.100/src/rclone_api/experimental/flags.py +121 -0
  4. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/rclone.py +1 -0
  5. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/s3/chunk_uploader.py +31 -4
  6. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api.egg-info/PKG-INFO +1 -1
  7. rclone_api-1.0.98/src/rclone_api/experimental/flags.py +0 -93
  8. {rclone_api-1.0.98 → rclone_api-1.0.100}/.aiderignore +0 -0
  9. {rclone_api-1.0.98 → rclone_api-1.0.100}/.github/workflows/lint.yml +0 -0
  10. {rclone_api-1.0.98 → rclone_api-1.0.100}/.github/workflows/push_macos.yml +0 -0
  11. {rclone_api-1.0.98 → rclone_api-1.0.100}/.github/workflows/push_ubuntu.yml +0 -0
  12. {rclone_api-1.0.98 → rclone_api-1.0.100}/.github/workflows/push_win.yml +0 -0
  13. {rclone_api-1.0.98 → rclone_api-1.0.100}/.gitignore +0 -0
  14. {rclone_api-1.0.98 → rclone_api-1.0.100}/.pylintrc +0 -0
  15. {rclone_api-1.0.98 → rclone_api-1.0.100}/.vscode/launch.json +0 -0
  16. {rclone_api-1.0.98 → rclone_api-1.0.100}/.vscode/settings.json +0 -0
  17. {rclone_api-1.0.98 → rclone_api-1.0.100}/.vscode/tasks.json +0 -0
  18. {rclone_api-1.0.98 → rclone_api-1.0.100}/LICENSE +0 -0
  19. {rclone_api-1.0.98 → rclone_api-1.0.100}/MANIFEST.in +0 -0
  20. {rclone_api-1.0.98 → rclone_api-1.0.100}/README.md +0 -0
  21. {rclone_api-1.0.98 → rclone_api-1.0.100}/clean +0 -0
  22. {rclone_api-1.0.98 → rclone_api-1.0.100}/install +0 -0
  23. {rclone_api-1.0.98 → rclone_api-1.0.100}/lint +0 -0
  24. {rclone_api-1.0.98 → rclone_api-1.0.100}/requirements.testing.txt +0 -0
  25. {rclone_api-1.0.98 → rclone_api-1.0.100}/setup.cfg +0 -0
  26. {rclone_api-1.0.98 → rclone_api-1.0.100}/setup.py +0 -0
  27. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/__init__.py +0 -0
  28. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/assets/example.txt +0 -0
  29. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/cli.py +0 -0
  30. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/cmd/copy_large_s3.py +0 -0
  31. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/cmd/list_files.py +0 -0
  32. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/completed_process.py +0 -0
  33. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/config.py +0 -0
  34. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/convert.py +0 -0
  35. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/deprecated.py +0 -0
  36. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/diff.py +0 -0
  37. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/dir.py +0 -0
  38. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/dir_listing.py +0 -0
  39. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/exec.py +0 -0
  40. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/file.py +0 -0
  41. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/filelist.py +0 -0
  42. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/group_files.py +0 -0
  43. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/process.py +0 -0
  44. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/remote.py +0 -0
  45. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/rpath.py +0 -0
  46. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/s3/api.py +0 -0
  47. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/s3/basic_ops.py +0 -0
  48. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/s3/create.py +0 -0
  49. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/s3/types.py +0 -0
  50. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/scan_missing_folders.py +0 -0
  51. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/types.py +0 -0
  52. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/util.py +0 -0
  53. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api/walk.py +0 -0
  54. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api.egg-info/SOURCES.txt +0 -0
  55. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  56. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api.egg-info/entry_points.txt +0 -0
  57. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api.egg-info/requires.txt +0 -0
  58. {rclone_api-1.0.98 → rclone_api-1.0.100}/src/rclone_api.egg-info/top_level.txt +0 -0
  59. {rclone_api-1.0.98 → rclone_api-1.0.100}/test +0 -0
  60. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/archive/test_paramiko.py.disabled +0 -0
  61. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_cmd_list_files.py +0 -0
  62. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_copy.py +0 -0
  63. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_copy_files.py +0 -0
  64. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_diff.py +0 -0
  65. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_group_files.py +0 -0
  66. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_is_synced.py +0 -0
  67. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_ls.py +0 -0
  68. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_mount.py +0 -0
  69. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_mount_s3.py +0 -0
  70. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_mount_webdav.py +0 -0
  71. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_mounted_ranged_download.py +0 -0
  72. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_obscure.py +0 -0
  73. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_rclone_config.py +0 -0
  74. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_remote_control.py +0 -0
  75. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_remotes.py +0 -0
  76. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_s3.py +0 -0
  77. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_scan_missing_folders.py +0 -0
  78. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_size_files.py +0 -0
  79. {rclone_api-1.0.98 → rclone_api-1.0.100}/tests/test_walk.py +0 -0
  80. {rclone_api-1.0.98 → rclone_api-1.0.100}/tox.ini +0 -0
  81. {rclone_api-1.0.98 → rclone_api-1.0.100}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.98
3
+ Version: 1.0.100
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.0.98"
24
+ version = "1.0.100"
25
25
 
26
26
  [tool.setuptools]
27
27
  package-dir = {"" = "src"}
@@ -0,0 +1,121 @@
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()
@@ -919,6 +919,7 @@ class Rclone:
919
919
  if outdir.exists():
920
920
  print(f"{outdir} mount still exists, attempting to remove")
921
921
  if not _IS_WINDOWS:
922
+
922
923
  def exec(cmd: str) -> int:
923
924
  if verbose:
924
925
  print(f"Executing: {cmd}")
@@ -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
- file_size = os.path.getsize(file_path)
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
- file_chunker(upload_state=upload_state, output=output, max_chunks=max_chunks)
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
- return handle_upload(upload_info, file_chunk)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.98
3
+ Version: 1.0.100
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,93 +0,0 @@
1
- from dataclasses import dataclass, field
2
-
3
-
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
-
40
-
41
- @dataclass
42
- class PerformanceFlags:
43
- buffer_size: str | None = "16MiB"
44
- checkers: int = 8
45
- transfers: int = 4
46
-
47
-
48
- @dataclass
49
- class ConfigFlags:
50
- config: str | None = None
51
- ask_password: bool = True
52
- auto_confirm: bool = False
53
-
54
-
55
- @dataclass
56
- class DebuggingFlags:
57
- cpuprofile: str | None = None
58
- memprofile: str | None = None
59
-
60
-
61
- @dataclass
62
- class FilterFlags:
63
- exclude: list[str] = field(default_factory=list)
64
- include: list[str] = field(default_factory=list)
65
- max_age: str | None = None
66
- min_size: str | None = None
67
-
68
-
69
- @dataclass
70
- class ListingFlags:
71
- fast_list: bool = False
72
-
73
-
74
- @dataclass
75
- class LoggingFlags:
76
- log_file: str | None = None
77
- log_level: str = "NOTICE" # Options: DEBUG, INFO, NOTICE, ERROR
78
- stats: str | None = "1m0s"
79
- progress: bool = False
80
-
81
-
82
- @dataclass
83
- class Flags:
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
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