rclone-api 1.0.96__tar.gz → 1.0.99__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 (80) hide show
  1. {rclone_api-1.0.96 → rclone_api-1.0.99}/PKG-INFO +1 -1
  2. {rclone_api-1.0.96 → rclone_api-1.0.99}/pyproject.toml +1 -1
  3. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/cmd/copy_large_s3.py +4 -1
  4. rclone_api-1.0.99/src/rclone_api/experimental/flags.py +121 -0
  5. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/rclone.py +26 -11
  6. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/chunk_uploader.py +16 -1
  7. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/PKG-INFO +1 -1
  8. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/SOURCES.txt +1 -0
  9. {rclone_api-1.0.96 → rclone_api-1.0.99}/.aiderignore +0 -0
  10. {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/lint.yml +0 -0
  11. {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/push_macos.yml +0 -0
  12. {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/push_ubuntu.yml +0 -0
  13. {rclone_api-1.0.96 → rclone_api-1.0.99}/.github/workflows/push_win.yml +0 -0
  14. {rclone_api-1.0.96 → rclone_api-1.0.99}/.gitignore +0 -0
  15. {rclone_api-1.0.96 → rclone_api-1.0.99}/.pylintrc +0 -0
  16. {rclone_api-1.0.96 → rclone_api-1.0.99}/.vscode/launch.json +0 -0
  17. {rclone_api-1.0.96 → rclone_api-1.0.99}/.vscode/settings.json +0 -0
  18. {rclone_api-1.0.96 → rclone_api-1.0.99}/.vscode/tasks.json +0 -0
  19. {rclone_api-1.0.96 → rclone_api-1.0.99}/LICENSE +0 -0
  20. {rclone_api-1.0.96 → rclone_api-1.0.99}/MANIFEST.in +0 -0
  21. {rclone_api-1.0.96 → rclone_api-1.0.99}/README.md +0 -0
  22. {rclone_api-1.0.96 → rclone_api-1.0.99}/clean +0 -0
  23. {rclone_api-1.0.96 → rclone_api-1.0.99}/install +0 -0
  24. {rclone_api-1.0.96 → rclone_api-1.0.99}/lint +0 -0
  25. {rclone_api-1.0.96 → rclone_api-1.0.99}/requirements.testing.txt +0 -0
  26. {rclone_api-1.0.96 → rclone_api-1.0.99}/setup.cfg +0 -0
  27. {rclone_api-1.0.96 → rclone_api-1.0.99}/setup.py +0 -0
  28. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/__init__.py +0 -0
  29. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/assets/example.txt +0 -0
  30. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/cli.py +0 -0
  31. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/cmd/list_files.py +0 -0
  32. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/completed_process.py +0 -0
  33. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/config.py +0 -0
  34. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/convert.py +0 -0
  35. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/deprecated.py +0 -0
  36. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/diff.py +0 -0
  37. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/dir.py +0 -0
  38. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/dir_listing.py +0 -0
  39. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/exec.py +0 -0
  40. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/file.py +0 -0
  41. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/filelist.py +0 -0
  42. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/group_files.py +0 -0
  43. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/process.py +0 -0
  44. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/remote.py +0 -0
  45. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/rpath.py +0 -0
  46. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/api.py +0 -0
  47. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/basic_ops.py +0 -0
  48. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/create.py +0 -0
  49. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/s3/types.py +0 -0
  50. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/scan_missing_folders.py +0 -0
  51. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/types.py +0 -0
  52. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/util.py +0 -0
  53. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api/walk.py +0 -0
  54. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  55. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/entry_points.txt +0 -0
  56. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/requires.txt +0 -0
  57. {rclone_api-1.0.96 → rclone_api-1.0.99}/src/rclone_api.egg-info/top_level.txt +0 -0
  58. {rclone_api-1.0.96 → rclone_api-1.0.99}/test +0 -0
  59. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/archive/test_paramiko.py.disabled +0 -0
  60. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_cmd_list_files.py +0 -0
  61. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_copy.py +0 -0
  62. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_copy_files.py +0 -0
  63. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_diff.py +0 -0
  64. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_group_files.py +0 -0
  65. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_is_synced.py +0 -0
  66. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_ls.py +0 -0
  67. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mount.py +0 -0
  68. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mount_s3.py +0 -0
  69. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mount_webdav.py +0 -0
  70. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_mounted_ranged_download.py +0 -0
  71. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_obscure.py +0 -0
  72. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_rclone_config.py +0 -0
  73. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_remote_control.py +0 -0
  74. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_remotes.py +0 -0
  75. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_s3.py +0 -0
  76. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_scan_missing_folders.py +0 -0
  77. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_size_files.py +0 -0
  78. {rclone_api-1.0.96 → rclone_api-1.0.99}/tests/test_walk.py +0 -0
  79. {rclone_api-1.0.96 → rclone_api-1.0.99}/tox.ini +0 -0
  80. {rclone_api-1.0.96 → rclone_api-1.0.99}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.96
3
+ Version: 1.0.99
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.96"
24
+ version = "1.0.99"
25
25
 
26
26
  [tool.setuptools]
27
27
  package-dir = {"" = "src"}
@@ -16,6 +16,7 @@ class Args:
16
16
  read_concurrent_chunks: int
17
17
  retries: int
18
18
  save_state_json: Path
19
+ verbose: bool
19
20
 
20
21
 
21
22
  def list_files(rclone: Rclone, path: str):
@@ -29,6 +30,7 @@ def _parse_args() -> Args:
29
30
  parser = argparse.ArgumentParser(description="List files in a remote path.")
30
31
  parser.add_argument("src", help="File to copy")
31
32
  parser.add_argument("dst", help="Destination file")
33
+ parser.add_argument("-v", "--verbose", help="Verbose output", action="store_true")
32
34
  parser.add_argument(
33
35
  "--config", help="Path to rclone config file", type=Path, required=True
34
36
  )
@@ -58,6 +60,7 @@ def _parse_args() -> Args:
58
60
  read_concurrent_chunks=args.read_concurrent_chunks,
59
61
  retries=args.retries,
60
62
  save_state_json=args.resumable_json,
63
+ verbose=args.verbose,
61
64
  )
62
65
  return out
63
66
 
@@ -73,7 +76,7 @@ def main() -> int:
73
76
  concurrent_chunks=args.read_concurrent_chunks,
74
77
  retries=args.retries,
75
78
  save_state_json=args.save_state_json,
76
- verbose=True,
79
+ verbose=args.verbose,
77
80
  )
78
81
  print(rslt)
79
82
  return 0
@@ -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()
@@ -893,6 +893,7 @@ class Rclone:
893
893
  ) -> Generator[Process, None, None]:
894
894
  """Like mount, but can be used in a context manager."""
895
895
  error_happened = False
896
+ verbose = get_verbose(verbose)
896
897
  proc = self.mount(
897
898
  src,
898
899
  outdir,
@@ -918,19 +919,33 @@ class Rclone:
918
919
  if outdir.exists():
919
920
  print(f"{outdir} mount still exists, attempting to remove")
920
921
  if not _IS_WINDOWS:
921
- # attempt
922
- os.system(f"fusermount -u {outdir}")
923
- os.system(f"umount {outdir}")
922
+
923
+ def exec(cmd: str) -> int:
924
+ if verbose:
925
+ print(f"Executing: {cmd}")
926
+ rtn = os.system(cmd)
927
+ if rtn != 0 and verbose:
928
+ print(f"Failed to execute: {cmd}")
929
+ return rtn
930
+
931
+ exec(f"fusermount -u {outdir}")
932
+ exec(f"umount {outdir}")
924
933
  time.sleep(2)
925
934
  if outdir.exists():
926
- is_empty = not list(outdir.iterdir())
927
- if not is_empty:
928
- warnings.warn(f"Failed to unmount {outdir}")
929
- else:
930
- try:
931
- outdir.rmdir()
932
- except Exception as e:
933
- warnings.warn(f"Failed to remove {outdir}: {e}")
935
+ is_empty = True
936
+ try:
937
+ is_empty = not list(outdir.iterdir())
938
+ if not is_empty:
939
+ warnings.warn(f"Failed to unmount {outdir}")
940
+ else:
941
+ try:
942
+ outdir.rmdir()
943
+ except Exception as e:
944
+ warnings.warn(f"Failed to remove {outdir}: {e}")
945
+ except Exception as e:
946
+ warnings.warn(
947
+ f"Failed during mount cleanup of {outdir}: because {e}"
948
+ )
934
949
 
935
950
  @deprecated("mount")
936
951
  def mount_webdav(
@@ -260,6 +260,20 @@ def _get_chunk_tmpdir() -> Path:
260
260
  return out
261
261
 
262
262
 
263
+ def _get_file_size(file_path: Path, timeout: int = 60) -> int:
264
+ sleep_time = timeout / 60 if timeout > 0 else 1
265
+ start = time.time()
266
+ while True:
267
+ try:
268
+ if file_path.exists():
269
+ return file_path.stat().st_size
270
+ except FileNotFoundError:
271
+ pass
272
+ if time.time() - start > timeout:
273
+ raise TimeoutError(f"File {file_path} not found after {timeout} seconds")
274
+ time.sleep(sleep_time)
275
+
276
+
263
277
  def file_chunker(
264
278
  upload_state: UploadState, max_chunks: int | None, output: Queue[FileChunk | None]
265
279
  ) -> None:
@@ -279,7 +293,8 @@ def file_chunker(
279
293
  file_path = upload_info.src_file_path
280
294
  chunk_size = upload_info.chunk_size
281
295
  src = Path(file_path)
282
- file_size = os.path.getsize(file_path)
296
+ # Mounted files may take a while to appear, so keep retrying.
297
+ file_size = _get_file_size(src, timeout=60)
283
298
  part_number = 1
284
299
  done_part_numbers: set[int] = {
285
300
  p.part_number for p in upload_state.parts if p is not None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.96
3
+ Version: 1.0.99
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -50,6 +50,7 @@ src/rclone_api.egg-info/top_level.txt
50
50
  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
+ src/rclone_api/experimental/flags.py
53
54
  src/rclone_api/s3/api.py
54
55
  src/rclone_api/s3/basic_ops.py
55
56
  src/rclone_api/s3/chunk_uploader.py
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