rclone-api 1.1.48__tar.gz → 1.1.50__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 (86) hide show
  1. {rclone_api-1.1.48 → rclone_api-1.1.50}/PKG-INFO +1 -1
  2. {rclone_api-1.1.48 → rclone_api-1.1.50}/pyproject.toml +1 -1
  3. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/mount.py +101 -11
  4. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api.egg-info/PKG-INFO +1 -1
  5. {rclone_api-1.1.48 → rclone_api-1.1.50}/.aiderignore +0 -0
  6. {rclone_api-1.1.48 → rclone_api-1.1.50}/.github/workflows/lint.yml +0 -0
  7. {rclone_api-1.1.48 → rclone_api-1.1.50}/.github/workflows/push_macos.yml +0 -0
  8. {rclone_api-1.1.48 → rclone_api-1.1.50}/.github/workflows/push_ubuntu.yml +0 -0
  9. {rclone_api-1.1.48 → rclone_api-1.1.50}/.github/workflows/push_win.yml +0 -0
  10. {rclone_api-1.1.48 → rclone_api-1.1.50}/.gitignore +0 -0
  11. {rclone_api-1.1.48 → rclone_api-1.1.50}/.pylintrc +0 -0
  12. {rclone_api-1.1.48 → rclone_api-1.1.50}/.vscode/launch.json +0 -0
  13. {rclone_api-1.1.48 → rclone_api-1.1.50}/.vscode/settings.json +0 -0
  14. {rclone_api-1.1.48 → rclone_api-1.1.50}/.vscode/tasks.json +0 -0
  15. {rclone_api-1.1.48 → rclone_api-1.1.50}/LICENSE +0 -0
  16. {rclone_api-1.1.48 → rclone_api-1.1.50}/MANIFEST.in +0 -0
  17. {rclone_api-1.1.48 → rclone_api-1.1.50}/README.md +0 -0
  18. {rclone_api-1.1.48 → rclone_api-1.1.50}/clean +0 -0
  19. {rclone_api-1.1.48 → rclone_api-1.1.50}/install +0 -0
  20. {rclone_api-1.1.48 → rclone_api-1.1.50}/lint +0 -0
  21. {rclone_api-1.1.48 → rclone_api-1.1.50}/requirements.testing.txt +0 -0
  22. {rclone_api-1.1.48 → rclone_api-1.1.50}/setup.cfg +0 -0
  23. {rclone_api-1.1.48 → rclone_api-1.1.50}/setup.py +0 -0
  24. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/__init__.py +0 -0
  25. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/assets/example.txt +0 -0
  26. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/cli.py +0 -0
  27. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/cmd/copy_large_s3.py +0 -0
  28. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/cmd/list_files.py +0 -0
  29. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/completed_process.py +0 -0
  30. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/config.py +0 -0
  31. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/convert.py +0 -0
  32. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/deprecated.py +0 -0
  33. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/diff.py +0 -0
  34. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/dir.py +0 -0
  35. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/dir_listing.py +0 -0
  36. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/exec.py +0 -0
  37. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/experimental/flags.py +0 -0
  38. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/experimental/flags_base.py +0 -0
  39. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/file.py +0 -0
  40. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/filelist.py +0 -0
  41. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/group_files.py +0 -0
  42. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/process.py +0 -0
  43. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/profile/mount_copy_bytes.py +0 -0
  44. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/rclone.py +0 -0
  45. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/remote.py +0 -0
  46. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/rpath.py +0 -0
  47. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/api.py +0 -0
  48. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/basic_ops.py +0 -0
  49. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/chunk_file.py +0 -0
  50. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/chunk_types.py +0 -0
  51. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/create.py +0 -0
  52. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/types.py +0 -0
  53. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/s3/upload_file_multipart.py +0 -0
  54. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/scan_missing_folders.py +0 -0
  55. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/types.py +0 -0
  56. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/util.py +0 -0
  57. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api/walk.py +0 -0
  58. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api.egg-info/SOURCES.txt +0 -0
  59. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  60. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api.egg-info/entry_points.txt +0 -0
  61. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api.egg-info/requires.txt +0 -0
  62. {rclone_api-1.1.48 → rclone_api-1.1.50}/src/rclone_api.egg-info/top_level.txt +0 -0
  63. {rclone_api-1.1.48 → rclone_api-1.1.50}/test +0 -0
  64. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/archive/test_paramiko.py.disabled +0 -0
  65. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_cmd_list_files.py +0 -0
  66. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_copy.py +0 -0
  67. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_copy_bytes.py +0 -0
  68. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_copy_file_resumable_s3.py +0 -0
  69. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_copy_files.py +0 -0
  70. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_diff.py +0 -0
  71. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_group_files.py +0 -0
  72. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_is_synced.py +0 -0
  73. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_ls.py +0 -0
  74. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_mount.py +0 -0
  75. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_mount_s3.py +0 -0
  76. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_obscure.py +0 -0
  77. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_rclone_config.py +0 -0
  78. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_remote_control.py +0 -0
  79. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_remotes.py +0 -0
  80. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_s3.py +0 -0
  81. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_scan_missing_folders.py +0 -0
  82. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_size_files.py +0 -0
  83. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_size_suffix.py +0 -0
  84. {rclone_api-1.1.48 → rclone_api-1.1.50}/tests/test_walk.py +0 -0
  85. {rclone_api-1.1.48 → rclone_api-1.1.50}/tox.ini +0 -0
  86. {rclone_api-1.1.48 → rclone_api-1.1.50}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.1.48
3
+ Version: 1.1.50
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -23,7 +23,7 @@ dependencies = [
23
23
  ]
24
24
 
25
25
  # Change this with the version number bump.
26
- version = "1.1.48"
26
+ version = "1.1.50"
27
27
 
28
28
  [tool.setuptools]
29
29
  package-dir = {"" = "src"}
@@ -1,8 +1,12 @@
1
+ import atexit
2
+ import os
1
3
  import platform
2
4
  import shutil
3
5
  import subprocess
4
6
  import time
5
7
  import warnings
8
+ import weakref
9
+ from concurrent.futures import ThreadPoolExecutor
6
10
  from dataclasses import dataclass
7
11
  from pathlib import Path
8
12
  from typing import Any
@@ -11,6 +15,27 @@ from rclone_api.process import Process
11
15
 
12
16
  _SYSTEM = platform.system() # "Linux", "Darwin", "Windows", etc.
13
17
 
18
+ _MOUNTS_FOR_GC: weakref.WeakSet = weakref.WeakSet()
19
+
20
+
21
+ def _add_mount_for_gc(mount: "Mount") -> None:
22
+ # weak reference to avoid circular references
23
+ _MOUNTS_FOR_GC.add(mount)
24
+
25
+
26
+ def _remove_mount_for_gc(mount: "Mount") -> None:
27
+ _MOUNTS_FOR_GC.discard(mount)
28
+
29
+
30
+ def _cleanup_mounts() -> None:
31
+ with ThreadPoolExecutor() as executor:
32
+ mount: Mount
33
+ for mount in _MOUNTS_FOR_GC:
34
+ executor.submit(mount.close, wait=False)
35
+
36
+
37
+ atexit.register(_cleanup_mounts)
38
+
14
39
 
15
40
  @dataclass
16
41
  class Mount:
@@ -27,15 +52,18 @@ class Mount:
27
52
  assert isinstance(self.mount_path, Path)
28
53
  assert self.process is not None
29
54
  wait_for_mount(self.mount_path, self.process)
55
+ _add_mount_for_gc(self)
30
56
 
31
57
  def close(self, wait=True) -> None:
32
58
  """Clean up the mount."""
33
59
  if self._closed:
34
60
  return
35
61
  self._closed = True
62
+ self.process.terminate()
36
63
  clean_mount(self, verbose=False, wait=wait)
37
64
  if self.cache_dir and self.cache_dir_delete_on_exit:
38
65
  _cache_dir_delete_on_exit(self.cache_dir)
66
+ _remove_mount_for_gc(self)
39
67
 
40
68
  def __enter__(self) -> "Mount":
41
69
  return self
@@ -46,6 +74,10 @@ class Mount:
46
74
  def __del__(self):
47
75
  self.close(wait=False)
48
76
 
77
+ # make this a hashable object
78
+ def __hash__(self):
79
+ return hash(self.mount_path)
80
+
49
81
 
50
82
  def run_command(cmd: str, verbose: bool) -> int:
51
83
  """Run a shell command and print its output if verbose is True."""
@@ -76,25 +108,83 @@ def prepare_mount(outdir: Path, verbose: bool) -> None:
76
108
  outdir.mkdir(parents=True, exist_ok=True)
77
109
 
78
110
 
79
- def wait_for_mount(path: Path, mount_process: Any, timeout: int = 10) -> None:
80
- from rclone_api.process import Process
111
+ def wait_for_mount(
112
+ path: Path,
113
+ mount_process: Any,
114
+ timeout: int = 10,
115
+ post_mount_delay: int = 5,
116
+ poll_interval: float = 1.0,
117
+ check_mount_flag: bool = False,
118
+ ) -> None:
119
+ """
120
+ Wait for a mount point to become available by checking if the directory exists,
121
+ optionally verifying that it is a mount point, and confirming that it contains files.
122
+ This function periodically polls for the mount status, ensures the mount process
123
+ is still running, and applies an extra delay after detecting content for stabilization.
124
+
125
+ Args:
126
+ path (Path): The mount point directory to check.
127
+ mount_process (Any): A Process instance handling the mount (must be an instance of Process).
128
+ timeout (int): Maximum time in seconds to wait for the mount to become available.
129
+ post_mount_delay (int): Additional seconds to wait after detecting files.
130
+ poll_interval (float): Seconds between each poll iteration.
131
+ check_mount_flag (bool): If True, verifies that the path is recognized as a mount point.
132
+
133
+ Raises:
134
+ subprocess.CalledProcessError: If the mount_process exits unexpectedly.
135
+ TimeoutError: If the mount is not available within the timeout period.
136
+ TypeError: If mount_process is not an instance of Process.
137
+ """
138
+
139
+ if not isinstance(mount_process, Process):
140
+ raise TypeError("mount_process must be an instance of Process")
81
141
 
82
- assert isinstance(mount_process, Process)
83
142
  expire_time = time.time() + timeout
143
+ last_error = None
144
+
84
145
  while time.time() < expire_time:
146
+ # Check if the mount process has terminated unexpectedly.
85
147
  rtn = mount_process.poll()
86
148
  if rtn is not None:
87
149
  cmd_str = subprocess.list2cmdline(mount_process.cmd)
150
+ print(f"Mount process terminated unexpectedly: {cmd_str}")
88
151
  raise subprocess.CalledProcessError(rtn, cmd_str)
152
+
153
+ # Check if the mount path exists.
89
154
  if path.exists():
90
- return
91
- # how many files?
92
- # dircontents = os.listdir(str(path))
93
- # if len(dircontents) > 0:
94
- # print(f"Mount point {path}, waiting 5 seconds for files to appear.")
95
- # time.sleep(5)
96
- # return
97
- time.sleep(1)
155
+ # Optionally check if path is a mount point.
156
+ if check_mount_flag:
157
+ try:
158
+ if not os.path.ismount(str(path)):
159
+ print(
160
+ f"{path} exists but is not recognized as a mount point yet."
161
+ )
162
+ time.sleep(poll_interval)
163
+ continue
164
+ except Exception as e:
165
+ print(f"Could not verify mount point status for {path}: {e}")
166
+
167
+ try:
168
+ # Check for at least one entry in the directory.
169
+ if any(path.iterdir()):
170
+ print(
171
+ f"Mount point {path} appears available with files. Waiting {post_mount_delay} seconds for stabilization."
172
+ )
173
+ time.sleep(post_mount_delay)
174
+ return
175
+ else:
176
+ print(f"Mount point {path} is empty. Waiting for files to appear.")
177
+ except Exception as e:
178
+ last_error = e
179
+ print(f"Error accessing {path}: {e}")
180
+ else:
181
+ print(f"Mount point {path} does not exist yet.")
182
+
183
+ time.sleep(poll_interval)
184
+
185
+ raise TimeoutError(
186
+ f"Mount point {path} did not become available within {timeout} seconds. Last error: {last_error}"
187
+ )
98
188
 
99
189
 
100
190
  def clean_mount(mount: Mount | Path, verbose: bool = False, wait=True) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.1.48
3
+ Version: 1.1.50
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
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