rclone-api 1.0.68__tar.gz → 1.0.69__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. {rclone_api-1.0.68 → rclone_api-1.0.69}/PKG-INFO +1 -1
  2. {rclone_api-1.0.68 → rclone_api-1.0.69}/pyproject.toml +1 -1
  3. rclone_api-1.0.69/src/rclone_api/diff_walk.py +143 -0
  4. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/dir.py +46 -2
  5. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/rclone.py +32 -13
  6. rclone_api-1.0.69/src/rclone_api/types.py +12 -0
  7. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/walk.py +10 -9
  8. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api.egg-info/PKG-INFO +1 -1
  9. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api.egg-info/SOURCES.txt +3 -0
  10. rclone_api-1.0.69/tests/test_diff_walk.py +73 -0
  11. {rclone_api-1.0.68 → rclone_api-1.0.69}/.aiderignore +0 -0
  12. {rclone_api-1.0.68 → rclone_api-1.0.69}/.github/workflows/lint.yml +0 -0
  13. {rclone_api-1.0.68 → rclone_api-1.0.69}/.github/workflows/push_macos.yml +0 -0
  14. {rclone_api-1.0.68 → rclone_api-1.0.69}/.github/workflows/push_ubuntu.yml +0 -0
  15. {rclone_api-1.0.68 → rclone_api-1.0.69}/.github/workflows/push_win.yml +0 -0
  16. {rclone_api-1.0.68 → rclone_api-1.0.69}/.gitignore +0 -0
  17. {rclone_api-1.0.68 → rclone_api-1.0.69}/.pylintrc +0 -0
  18. {rclone_api-1.0.68 → rclone_api-1.0.69}/.vscode/launch.json +0 -0
  19. {rclone_api-1.0.68 → rclone_api-1.0.69}/.vscode/settings.json +0 -0
  20. {rclone_api-1.0.68 → rclone_api-1.0.69}/.vscode/tasks.json +0 -0
  21. {rclone_api-1.0.68 → rclone_api-1.0.69}/LICENSE +0 -0
  22. {rclone_api-1.0.68 → rclone_api-1.0.69}/MANIFEST.in +0 -0
  23. {rclone_api-1.0.68 → rclone_api-1.0.69}/README.md +0 -0
  24. {rclone_api-1.0.68 → rclone_api-1.0.69}/clean +0 -0
  25. {rclone_api-1.0.68 → rclone_api-1.0.69}/install +0 -0
  26. {rclone_api-1.0.68 → rclone_api-1.0.69}/lint +0 -0
  27. {rclone_api-1.0.68 → rclone_api-1.0.69}/requirements.testing.txt +0 -0
  28. {rclone_api-1.0.68 → rclone_api-1.0.69}/setup.cfg +0 -0
  29. {rclone_api-1.0.68 → rclone_api-1.0.69}/setup.py +0 -0
  30. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/__init__.py +0 -0
  31. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/assets/example.txt +0 -0
  32. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/cli.py +0 -0
  33. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/cmd/list_files.py +0 -0
  34. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/completed_process.py +0 -0
  35. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/config.py +0 -0
  36. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/convert.py +0 -0
  37. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/deprecated.py +0 -0
  38. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/diff.py +0 -0
  39. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/dir_listing.py +0 -0
  40. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/exec.py +0 -0
  41. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/file.py +0 -0
  42. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/filelist.py +0 -0
  43. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/group_files.py +0 -0
  44. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/process.py +0 -0
  45. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/remote.py +0 -0
  46. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/rpath.py +0 -0
  47. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api/util.py +0 -0
  48. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  49. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api.egg-info/entry_points.txt +0 -0
  50. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api.egg-info/requires.txt +0 -0
  51. {rclone_api-1.0.68 → rclone_api-1.0.69}/src/rclone_api.egg-info/top_level.txt +0 -0
  52. {rclone_api-1.0.68 → rclone_api-1.0.69}/test +0 -0
  53. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_cmd_list_files.py +0 -0
  54. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_copy.py +0 -0
  55. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_copy_files.py +0 -0
  56. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_diff.py +0 -0
  57. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_group_files.py +0 -0
  58. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_is_synced.py +0 -0
  59. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_ls.py +0 -0
  60. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_mount.py +0 -0
  61. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_mount_s3.py +0 -0
  62. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_mount_webdav.py +0 -0
  63. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_obscure.py +0 -0
  64. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_remote_control.py +0 -0
  65. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_remotes.py +0 -0
  66. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_serve_webdav.py +0 -0
  67. {rclone_api-1.0.68 → rclone_api-1.0.69}/tests/test_walk.py +0 -0
  68. {rclone_api-1.0.68 → rclone_api-1.0.69}/tox.ini +0 -0
  69. {rclone_api-1.0.68 → rclone_api-1.0.69}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.68
3
+ Version: 1.0.69
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -15,7 +15,7 @@ dependencies = [
15
15
  "python-dotenv>=1.0.0",
16
16
  ]
17
17
  # Change this with the version number bump.
18
- version = "1.0.68"
18
+ version = "1.0.69"
19
19
 
20
20
  [tool.setuptools]
21
21
  package-dir = {"" = "src"}
@@ -0,0 +1,143 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from queue import Queue
3
+ from threading import Thread
4
+ from typing import Generator
5
+
6
+ from rclone_api import Dir
7
+ from rclone_api.dir_listing import DirListing
8
+ from rclone_api.types import ListingOption
9
+ from rclone_api.walk import walk_runner_depth_first
10
+
11
+ _MAX_OUT_QUEUE_SIZE = 50
12
+
13
+
14
+ # ONLY Works from src -> dst diffing.
15
+ def _async_diff_dir_walk_task(
16
+ src: Dir, dst: Dir, max_depth: int, out_queue: Queue[Dir | None], reverse=False
17
+ ) -> None:
18
+
19
+ try:
20
+ stack = [(src, dst)]
21
+ while stack:
22
+ curr_src, curr_dst = stack.pop()
23
+ curr_src = curr_src
24
+ curr_dst = curr_dst
25
+
26
+ with ThreadPoolExecutor(max_workers=2) as executor:
27
+ # src_dir_listing = src.ls(listing_option=ListingOption.DIRS_ONLY)
28
+ # dst_dir_listing = dst.ls(listing_option=ListingOption.DIRS_ONLY)
29
+ t1 = executor.submit(
30
+ src.ls, listing_option=ListingOption.DIRS_ONLY, reverse=reverse
31
+ )
32
+ t2 = executor.submit(
33
+ dst.ls, listing_option=ListingOption.DIRS_ONLY, reverse=reverse
34
+ )
35
+ src_dir_listing: DirListing = t1.result()
36
+ dst_dir_listing: DirListing = t2.result()
37
+
38
+ # dirlisting = current_dir.ls()
39
+ # if reverse:
40
+ # dirlisting.dirs.reverse()
41
+ # if depth != 0:
42
+ # for subdir in dirlisting.dirs: # Process deeper directories first
43
+ # # stack.append((child, depth - 1 if depth > 0 else depth))
44
+ # next_depth = depth - 1 if depth > 0 else depth
45
+ # _walk_runner_depth_first(
46
+ # subdir, next_depth, out_queue, reverse=reverse
47
+ # )
48
+ # out_queue.put(dirlisting)
49
+
50
+ # for subdir in dst_dir_listing.dirs:
51
+ # subdir.to_string(include_remote=False)
52
+ # walk_runner_depth_first()
53
+
54
+ # find elements missing on dst
55
+ # missing_on_dst: set[Dir] = set(src_dir_listing.dirs) - set(
56
+ # dst_dir_listing.dirs
57
+ # )
58
+ # exists_on_dst: set[Dir] = set(src_dir_listing.dirs) - missing_on_dst
59
+
60
+ dst_files: list[str] = [d.name for d in dst_dir_listing.dirs]
61
+ src_files: list[str] = [d.name for d in src_dir_listing.dirs]
62
+
63
+ dst_files_set: set[str] = set(dst_files)
64
+ # src_files_set: set[str] = set(src_files)
65
+
66
+ # print(f"src_files: {src_files}")
67
+ # print(f"dst_files: {dst_files}")
68
+
69
+ matching_dirs: list[str] = []
70
+
71
+ for file in src_files:
72
+ if file not in dst_files_set:
73
+ # print(f"missing dir on src: {file}")
74
+ queue_dir_listing: Queue[DirListing | None] = Queue()
75
+ walk_runner_depth_first(
76
+ dir=curr_src,
77
+ max_depth=max_depth,
78
+ out_queue=queue_dir_listing,
79
+ reverse=reverse,
80
+ )
81
+ while dirlisting := queue_dir_listing.get():
82
+ if dirlisting is None:
83
+ break
84
+ # print(f"dirlisting: {dirlisting}")
85
+ for d in dirlisting.dirs:
86
+ out_queue.put(d)
87
+ else:
88
+ matching_dirs.append(file)
89
+
90
+ for matching_dir in matching_dirs:
91
+ # print(f"matching dir: {matching_dir}")
92
+ _async_diff_dir_walk_task(
93
+ src=curr_src / matching_dir,
94
+ dst=curr_dst / matching_dir,
95
+ max_depth=max_depth,
96
+ out_queue=out_queue,
97
+ reverse=reverse,
98
+ )
99
+
100
+ out_queue.put(None)
101
+ except KeyboardInterrupt:
102
+ import _thread
103
+
104
+ out_queue.put(None)
105
+ _thread.interrupt_main()
106
+
107
+
108
+ def diff_walk(
109
+ src: Dir,
110
+ dst: Dir,
111
+ max_depth: int = -1,
112
+ reverse: bool = False,
113
+ ) -> Generator[Dir, None, None]:
114
+ """Walk through the given directory recursively.
115
+
116
+ Args:
117
+ dir: Directory or Remote to walk through
118
+ max_depth: Maximum depth to traverse (-1 for unlimited)
119
+
120
+ Yields:
121
+ DirListing: Directory listing for each directory encountered
122
+ """
123
+
124
+ try:
125
+ out_queue: Queue[Dir | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
126
+
127
+ def task() -> None:
128
+ _async_diff_dir_walk_task(src, dst, max_depth, out_queue, reverse=reverse)
129
+
130
+ worker = Thread(
131
+ target=task,
132
+ daemon=True,
133
+ )
134
+ worker.start()
135
+
136
+ while dirlisting := out_queue.get():
137
+ if dirlisting is None:
138
+ break
139
+ yield dirlisting
140
+
141
+ worker.join()
142
+ except KeyboardInterrupt:
143
+ pass
@@ -1,9 +1,11 @@
1
1
  import json
2
+ from pathlib import Path
2
3
  from typing import Generator
3
4
 
4
5
  from rclone_api.dir_listing import DirListing
5
6
  from rclone_api.remote import Remote
6
7
  from rclone_api.rpath import RPath
8
+ from rclone_api.types import ListingOption
7
9
 
8
10
 
9
11
  class Dir:
@@ -41,11 +43,30 @@ class Dir:
41
43
  # self.path.set_rclone(self.path.remote.rclone)
42
44
  assert self.path.rclone is not None
43
45
 
44
- def ls(self, max_depth: int = 0, reverse: bool = False) -> DirListing:
46
+ def ls(
47
+ self,
48
+ max_depth: int | None = None,
49
+ glob: str | None = None,
50
+ reverse: bool = False,
51
+ listing_option: ListingOption = ListingOption.ALL,
52
+ ) -> DirListing:
45
53
  """List files and directories in the given path."""
46
54
  assert self.path.rclone is not None
47
55
  dir = Dir(self.path)
48
- return self.path.rclone.ls(dir, max_depth=max_depth, reverse=reverse)
56
+ return self.path.rclone.ls(
57
+ dir,
58
+ max_depth=max_depth,
59
+ glob=glob,
60
+ reverse=reverse,
61
+ listing_option=listing_option,
62
+ )
63
+
64
+ def relative_to(self, other: "Dir") -> str:
65
+ """Return the relative path to the other directory."""
66
+ self_path = Path(self.path.path)
67
+ other_path = Path(other.path.path)
68
+ rel_path = self_path.relative_to(other_path)
69
+ return str(rel_path)
49
70
 
50
71
  def walk(
51
72
  self, breadth_first: bool, max_depth: int = -1
@@ -67,3 +88,26 @@ class Dir:
67
88
  data = self.path.to_json()
68
89
  data_str = json.dumps(data)
69
90
  return data_str
91
+
92
+ def to_string(self, include_remote: bool = True) -> str:
93
+ """Convert the File to a string."""
94
+ out = str(self.path)
95
+ if not include_remote:
96
+ _, out = out.split(":", 1)
97
+ return out
98
+
99
+ # / operator
100
+ def __truediv__(self, other: str) -> "Dir":
101
+ """Join the current path with another path."""
102
+ path = Path(self.path.path) / other
103
+ rpath = RPath(
104
+ self.path.remote,
105
+ str(path),
106
+ name=other,
107
+ size=0,
108
+ mime_type="inode/directory",
109
+ mod_time="",
110
+ is_dir=True,
111
+ )
112
+ rpath.set_rclone(self.path.rclone)
113
+ return Dir(rpath)
@@ -7,7 +7,6 @@ import subprocess
7
7
  import time
8
8
  import warnings
9
9
  from concurrent.futures import Future, ThreadPoolExecutor
10
- from enum import Enum
11
10
  from fnmatch import fnmatch
12
11
  from pathlib import Path
13
12
  from tempfile import TemporaryDirectory
@@ -26,6 +25,7 @@ from rclone_api.group_files import group_files
26
25
  from rclone_api.process import Process
27
26
  from rclone_api.remote import Remote
28
27
  from rclone_api.rpath import RPath
28
+ from rclone_api.types import ListingOption, ModTimeStrategy
29
29
  from rclone_api.util import (
30
30
  get_check,
31
31
  get_rclone_exe,
@@ -42,17 +42,6 @@ def rclone_verbose(verbose: bool | None) -> bool:
42
42
  return bool(int(os.getenv("RCLONE_API_VERBOSE", "0")))
43
43
 
44
44
 
45
- class ModTimeStrategy(Enum):
46
- USE_SERVER_MODTIME = "use-server-modtime"
47
- NO_MODTIME = "no-modtime"
48
-
49
-
50
- class ListingOption(Enum):
51
- DIRS_ONLY = "dirs-only"
52
- FILES_ONLY = "files-only"
53
- ALL = "all"
54
-
55
-
56
45
  class Rclone:
57
46
  def __init__(
58
47
  self, rclone_conf: Path | Config, rclone_exe: Path | None = None
@@ -203,17 +192,20 @@ class Rclone:
203
192
  diff_option: DiffOption = DiffOption.COMBINED,
204
193
  fast_list: bool = True,
205
194
  size_only: bool | None = None,
195
+ checkers: int | None = None,
206
196
  other_args: list[str] | None = None,
207
197
  ) -> Generator[DiffItem, None, None]:
208
198
  """Be extra careful with the src and dst values. If you are off by one
209
199
  parent directory, you will get a huge amount of false diffs."""
210
200
  other_args = other_args or []
201
+ if checkers is None or checkers < 1:
202
+ checkers = 1000
211
203
  cmd = [
212
204
  "check",
213
205
  src,
214
206
  dst,
215
207
  "--checkers",
216
- "1000",
208
+ str(checkers),
217
209
  "--log-level",
218
210
  "INFO",
219
211
  f"--{diff_option.value}",
@@ -288,6 +280,33 @@ class Rclone:
288
280
  dir_obj, max_depth=max_depth, breadth_first=breadth_first, reverse=reverse
289
281
  )
290
282
 
283
+ def diff_walk(
284
+ self,
285
+ src: Dir | Remote | str,
286
+ dst: Dir | Remote | str,
287
+ max_depth: int = -1,
288
+ reverse: bool = False,
289
+ ) -> Generator[Dir, None, None]:
290
+ """Walk through the given path recursively.
291
+
292
+ WORK IN PROGRESS!!
293
+
294
+ Args:
295
+ src: Source directory or Remote to walk through
296
+ dst: Destination directory or Remote to walk through
297
+ max_depth: Maximum depth to traverse (-1 for unlimited)
298
+
299
+ Yields:
300
+ DirListing: Directory listing for each directory encountered
301
+ """
302
+ from rclone_api.diff_walk import diff_walk
303
+
304
+ src_dir = Dir(to_path(src, self))
305
+ dst_dir = Dir(to_path(dst, self))
306
+ yield from diff_walk(
307
+ src=src_dir, dst=dst_dir, max_depth=max_depth, reverse=reverse
308
+ )
309
+
291
310
  def cleanup(
292
311
  self, path: str, other_args: list[str] | None = None
293
312
  ) -> CompletedProcess:
@@ -0,0 +1,12 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ModTimeStrategy(Enum):
5
+ USE_SERVER_MODTIME = "use-server-modtime"
6
+ NO_MODTIME = "no-modtime"
7
+
8
+
9
+ class ListingOption(Enum):
10
+ DIRS_ONLY = "dirs-only"
11
+ FILES_ONLY = "files-only"
12
+ ALL = "all"
@@ -9,7 +9,7 @@ from rclone_api.remote import Remote
9
9
  _MAX_OUT_QUEUE_SIZE = 50
10
10
 
11
11
 
12
- def _walk_runner_breadth_first(
12
+ def walk_runner_breadth_first(
13
13
  dir: Dir,
14
14
  max_depth: int,
15
15
  out_queue: Queue[DirListing | None],
@@ -40,7 +40,7 @@ def _walk_runner_breadth_first(
40
40
  _thread.interrupt_main()
41
41
 
42
42
 
43
- def _walk_runner_depth_first(
43
+ def walk_runner_depth_first(
44
44
  dir: Dir, max_depth: int, out_queue: Queue[DirListing | None], reverse=False
45
45
  ) -> None:
46
46
  try:
@@ -54,7 +54,7 @@ def _walk_runner_depth_first(
54
54
  for subdir in dirlisting.dirs: # Process deeper directories first
55
55
  # stack.append((child, depth - 1 if depth > 0 else depth))
56
56
  next_depth = depth - 1 if depth > 0 else depth
57
- _walk_runner_depth_first(
57
+ walk_runner_depth_first(
58
58
  subdir, next_depth, out_queue, reverse=reverse
59
59
  )
60
60
  out_queue.put(dirlisting)
@@ -85,16 +85,17 @@ def walk(
85
85
  # Convert Remote to Dir if needed
86
86
  if isinstance(dir, Remote):
87
87
  dir = Dir(dir)
88
- out_queue: Queue[DirListing] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
88
+ out_queue: Queue[DirListing | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
89
89
 
90
- strategy = (
91
- _walk_runner_breadth_first if breadth_first else _walk_runner_depth_first
92
- )
90
+ def _task() -> None:
91
+ if breadth_first:
92
+ walk_runner_breadth_first(dir, max_depth, out_queue, reverse)
93
+ else:
94
+ walk_runner_depth_first(dir, max_depth, out_queue, reverse)
93
95
 
94
96
  # Start worker thread
95
97
  worker = Thread(
96
- target=strategy,
97
- args=(dir, max_depth, out_queue, reverse),
98
+ target=_task,
98
99
  daemon=True,
99
100
  )
100
101
  worker.start()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.68
3
+ Version: 1.0.69
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -27,6 +27,7 @@ src/rclone_api/config.py
27
27
  src/rclone_api/convert.py
28
28
  src/rclone_api/deprecated.py
29
29
  src/rclone_api/diff.py
30
+ src/rclone_api/diff_walk.py
30
31
  src/rclone_api/dir.py
31
32
  src/rclone_api/dir_listing.py
32
33
  src/rclone_api/exec.py
@@ -37,6 +38,7 @@ src/rclone_api/process.py
37
38
  src/rclone_api/rclone.py
38
39
  src/rclone_api/remote.py
39
40
  src/rclone_api/rpath.py
41
+ src/rclone_api/types.py
40
42
  src/rclone_api/util.py
41
43
  src/rclone_api/walk.py
42
44
  src/rclone_api.egg-info/PKG-INFO
@@ -51,6 +53,7 @@ tests/test_cmd_list_files.py
51
53
  tests/test_copy.py
52
54
  tests/test_copy_files.py
53
55
  tests/test_diff.py
56
+ tests/test_diff_walk.py
54
57
  tests/test_group_files.py
55
58
  tests/test_is_synced.py
56
59
  tests/test_ls.py
@@ -0,0 +1,73 @@
1
+ """
2
+ Unit test file.
3
+ """
4
+
5
+ import os
6
+ import unittest
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ from rclone_api import Config, Dir, Rclone
11
+
12
+ load_dotenv()
13
+
14
+ BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
15
+
16
+
17
+ def _generate_rclone_config() -> Config:
18
+
19
+ # BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
20
+
21
+ # Load additional environment variables
22
+ BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
23
+ BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
24
+ # BUCKET_URL = os.getenv("BUCKET_URL")
25
+ BUCKET_URL = "sfo3.digitaloceanspaces.com"
26
+
27
+ config_text = f"""
28
+ [dst]
29
+ type = s3
30
+ provider = DigitalOcean
31
+ access_key_id = {BUCKET_KEY_PUBLIC}
32
+ secret_access_key = {BUCKET_KEY_SECRET}
33
+ endpoint = {BUCKET_URL}
34
+ """
35
+
36
+ out = Config(config_text)
37
+ return out
38
+
39
+
40
+ class RcloneDiffTests(unittest.TestCase):
41
+ """Test rclone functionality."""
42
+
43
+ def setUp(self) -> None:
44
+ """Check if all required environment variables are set before running tests."""
45
+ required_vars = [
46
+ "BUCKET_NAME",
47
+ "BUCKET_KEY_SECRET",
48
+ "BUCKET_KEY_PUBLIC",
49
+ "BUCKET_URL",
50
+ ]
51
+ missing = [var for var in required_vars if not os.getenv(var)]
52
+ if missing:
53
+ self.skipTest(
54
+ f"Missing required environment variables: {', '.join(missing)}"
55
+ )
56
+ os.environ["RCLONE_API_VERBOSE"] = "1"
57
+
58
+ def test_diff_walk(self) -> None:
59
+ """Test copying a single file to remote storage."""
60
+ rclone = Rclone(_generate_rclone_config())
61
+ item: Dir
62
+ all: list[Dir] = []
63
+ for item in rclone.diff_walk(
64
+ "dst:rclone-api-unit-test", "dst:rclone-api-unit-test"
65
+ ):
66
+ all.append(item)
67
+ self.assertEqual(len(all), 0)
68
+ msg = "\n".join([str(item) for item in all])
69
+ print(msg)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ unittest.main()
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