rclone-api 1.0.18__tar.gz → 1.0.19__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. {rclone_api-1.0.18 → rclone_api-1.0.19}/PKG-INFO +2 -1
  2. {rclone_api-1.0.18 → rclone_api-1.0.19}/lint +2 -0
  3. {rclone_api-1.0.18 → rclone_api-1.0.19}/pyproject.toml +3 -2
  4. {rclone_api-1.0.18 → rclone_api-1.0.19}/requirements.testing.txt +2 -1
  5. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/dir.py +4 -2
  6. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/rclone.py +13 -4
  7. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/walk.py +40 -7
  8. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/PKG-INFO +2 -1
  9. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/SOURCES.txt +2 -0
  10. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/requires.txt +1 -0
  11. {rclone_api-1.0.18 → rclone_api-1.0.19}/test +1 -1
  12. rclone_api-1.0.18/tests/test_serve_webdav.py → rclone_api-1.0.19/tests/test_mount_webdav.py +11 -55
  13. rclone_api-1.0.18/tests/test_walk.py → rclone_api-1.0.19/tests/test_obscure.py +7 -8
  14. rclone_api-1.0.19/tests/test_serve_webdav.py +108 -0
  15. rclone_api-1.0.19/tests/test_walk.py +86 -0
  16. {rclone_api-1.0.18 → rclone_api-1.0.19}/.aiderignore +0 -0
  17. {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/lint.yml +0 -0
  18. {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/push_macos.yml +0 -0
  19. {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/push_ubuntu.yml +0 -0
  20. {rclone_api-1.0.18 → rclone_api-1.0.19}/.github/workflows/push_win.yml +0 -0
  21. {rclone_api-1.0.18 → rclone_api-1.0.19}/.gitignore +0 -0
  22. {rclone_api-1.0.18 → rclone_api-1.0.19}/.pylintrc +0 -0
  23. {rclone_api-1.0.18 → rclone_api-1.0.19}/.vscode/launch.json +0 -0
  24. {rclone_api-1.0.18 → rclone_api-1.0.19}/.vscode/settings.json +0 -0
  25. {rclone_api-1.0.18 → rclone_api-1.0.19}/.vscode/tasks.json +0 -0
  26. {rclone_api-1.0.18 → rclone_api-1.0.19}/LICENSE +0 -0
  27. {rclone_api-1.0.18 → rclone_api-1.0.19}/MANIFEST.in +0 -0
  28. {rclone_api-1.0.18 → rclone_api-1.0.19}/README.md +0 -0
  29. {rclone_api-1.0.18 → rclone_api-1.0.19}/clean +0 -0
  30. {rclone_api-1.0.18 → rclone_api-1.0.19}/install +0 -0
  31. {rclone_api-1.0.18 → rclone_api-1.0.19}/setup.cfg +0 -0
  32. {rclone_api-1.0.18 → rclone_api-1.0.19}/setup.py +0 -0
  33. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/__init__.py +0 -0
  34. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/assets/example.txt +0 -0
  35. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/cli.py +0 -0
  36. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/config.py +0 -0
  37. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/convert.py +0 -0
  38. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/dir_listing.py +0 -0
  39. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/exec.py +0 -0
  40. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/file.py +0 -0
  41. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/filelist.py +0 -0
  42. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/process.py +0 -0
  43. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/remote.py +0 -0
  44. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/rpath.py +0 -0
  45. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api/util.py +0 -0
  46. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  47. {rclone_api-1.0.18 → rclone_api-1.0.19}/src/rclone_api.egg-info/top_level.txt +0 -0
  48. {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_copy.py +0 -0
  49. {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_is_synced.py +0 -0
  50. {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_ls.py +0 -0
  51. {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_mount.py +0 -0
  52. {rclone_api-1.0.18 → rclone_api-1.0.19}/tests/test_remotes.py +0 -0
  53. {rclone_api-1.0.18 → rclone_api-1.0.19}/tox.ini +0 -0
  54. {rclone_api-1.0.18 → rclone_api-1.0.19}/upload_package.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.18
3
+ Version: 1.0.19
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: pyright>=1.1.393
13
14
  Requires-Dist: python-dotenv>=1.0.0
14
15
  Dynamic: home-page
15
16
  Dynamic: maintainer
@@ -11,5 +11,7 @@ echo Running isort src tests
11
11
  uv run isort --profile black src tests
12
12
  echo Running mypy src
13
13
  uv run mypy src tests
14
+ echo Running pyright src tests
15
+ uv run pyright src tests --threads
14
16
  echo Linting complete!
15
17
  exit 0
@@ -11,10 +11,11 @@ keywords = ["template-python-cmd"]
11
11
  license = { text = "BSD 3-Clause License" }
12
12
  classifiers = ["Programming Language :: Python :: 3"]
13
13
  dependencies = [
14
- "python-dotenv>=1.0.0"
14
+ "pyright>=1.1.393",
15
+ "python-dotenv>=1.0.0",
15
16
  ]
16
17
  # Change this with the version number bump.
17
- version = "1.0.18"
18
+ version = "1.0.19"
18
19
 
19
20
  [tool.setuptools]
20
21
  package-dir = {"" = "src"}
@@ -5,4 +5,5 @@ pytest
5
5
  tox
6
6
  ruff
7
7
  pytest-xdist
8
- httpx
8
+ httpx
9
+ pyright
@@ -47,12 +47,14 @@ class Dir:
47
47
  dir = Dir(self.path)
48
48
  return self.path.rclone.ls(dir, max_depth=max_depth)
49
49
 
50
- def walk(self, max_depth: int = -1) -> Generator[DirListing, None, None]:
50
+ def walk(
51
+ self, breadth_first: bool, max_depth: int = -1
52
+ ) -> Generator[DirListing, None, None]:
51
53
  """List files and directories in the given path."""
52
54
  from rclone_api.walk import walk
53
55
 
54
56
  assert self.path.rclone is not None
55
- return walk(self, max_depth=max_depth)
57
+ return walk(self, breadth_first=breadth_first, max_depth=max_depth)
56
58
 
57
59
  def to_json(self) -> dict:
58
60
  """Convert the Dir to a JSON serializable dictionary."""
@@ -38,6 +38,12 @@ class Rclone:
38
38
  def _launch_process(self, cmd: list[str]) -> Process:
39
39
  return self._exec.launch_process(cmd)
40
40
 
41
+ def obscure(self, password: str) -> str:
42
+ """Obscure a password for use in rclone config files."""
43
+ cmd_list: list[str] = ["obscure", password]
44
+ cp = self._run(cmd_list)
45
+ return cp.stdout.strip()
46
+
41
47
  def ls(
42
48
  self,
43
49
  path: Dir | Remote | str,
@@ -61,8 +67,9 @@ class Rclone:
61
67
 
62
68
  cmd = ["lsjson"]
63
69
  if max_depth is not None:
64
- cmd.append("--recursive")
65
- if max_depth > -1:
70
+ if max_depth < 0:
71
+ cmd.append("--recursive")
72
+ if max_depth > 0:
66
73
  cmd.append("--max-depth")
67
74
  cmd.append(str(max_depth))
68
75
  cmd.append(str(path))
@@ -96,7 +103,7 @@ class Rclone:
96
103
  return out
97
104
 
98
105
  def walk(
99
- self, path: Dir | Remote | str, max_depth: int = -1
106
+ self, path: Dir | Remote | str, max_depth: int = -1, breadth_first: bool = True
100
107
  ) -> Generator[DirListing, None, None]:
101
108
  """Walk through the given path recursively.
102
109
 
@@ -107,6 +114,7 @@ class Rclone:
107
114
  Yields:
108
115
  DirListing: Directory listing for each directory encountered
109
116
  """
117
+ dir_obj: Dir
110
118
  if isinstance(path, Dir):
111
119
  # Create a Remote object for the path
112
120
  remote = path.remote
@@ -126,9 +134,10 @@ class Rclone:
126
134
  elif isinstance(path, Remote):
127
135
  dir_obj = Dir(path)
128
136
  else:
137
+ dir_obj = Dir(path) # shut up pyright
129
138
  assert f"Invalid type for path: {type(path)}"
130
139
 
131
- yield from walk(dir_obj, max_depth=max_depth)
140
+ yield from walk(dir_obj, max_depth=max_depth, breadth_first=breadth_first)
132
141
 
133
142
  def copyfile(self, src: File | str, dst: File | str) -> None:
134
143
  """Copy a single file from source to destination.
@@ -9,9 +9,11 @@ from rclone_api.remote import Remote
9
9
  _MAX_OUT_QUEUE_SIZE = 50
10
10
 
11
11
 
12
- def _walk_runner(
13
- queue: Queue[Dir], max_depth: int, out_queue: Queue[DirListing | None]
12
+ def _walk_runner_breadth_first(
13
+ dir: Dir, max_depth: int, out_queue: Queue[DirListing | None]
14
14
  ) -> None:
15
+ queue: Queue[Dir] = Queue()
16
+ queue.put(dir)
15
17
  try:
16
18
  while not queue.empty():
17
19
  current_dir = queue.get()
@@ -35,7 +37,35 @@ def _walk_runner(
35
37
  _thread.interrupt_main()
36
38
 
37
39
 
38
- def walk(dir: Dir | Remote, max_depth: int = -1) -> Generator[DirListing, None, None]:
40
+ def _walk_runner_depth_first(
41
+ dir: Dir, max_depth: int, out_queue: Queue[DirListing | None]
42
+ ) -> None:
43
+ try:
44
+ stack = [(dir, max_depth)]
45
+ while stack:
46
+ current_dir, depth = stack.pop()
47
+ dirlisting = current_dir.ls()
48
+ if depth != 0:
49
+ for subdir in reversed(
50
+ dirlisting.dirs
51
+ ): # Process deeper directories first
52
+ # stack.append((child, depth - 1 if depth > 0 else depth))
53
+ next_depth = depth - 1 if depth > 0 else depth
54
+ _walk_runner_depth_first(subdir, next_depth, out_queue)
55
+ out_queue.put(dirlisting)
56
+ out_queue.put(None)
57
+ except KeyboardInterrupt:
58
+ import _thread
59
+
60
+ out_queue.put(None)
61
+ _thread.interrupt_main()
62
+
63
+
64
+ def walk(
65
+ dir: Dir | Remote,
66
+ breadth_first: bool,
67
+ max_depth: int = -1,
68
+ ) -> Generator[DirListing, None, None]:
39
69
  """Walk through the given directory recursively.
40
70
 
41
71
  Args:
@@ -49,14 +79,17 @@ def walk(dir: Dir | Remote, max_depth: int = -1) -> Generator[DirListing, None,
49
79
  # Convert Remote to Dir if needed
50
80
  if isinstance(dir, Remote):
51
81
  dir = Dir(dir)
52
-
53
- in_queue: Queue[Dir] = Queue()
54
82
  out_queue: Queue[DirListing] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
55
- in_queue.put(dir)
83
+
84
+ strategy = (
85
+ _walk_runner_breadth_first if breadth_first else _walk_runner_depth_first
86
+ )
56
87
 
57
88
  # Start worker thread
58
89
  worker = Thread(
59
- target=_walk_runner, args=(in_queue, max_depth, out_queue), daemon=True
90
+ target=strategy,
91
+ args=(dir, max_depth, out_queue),
92
+ daemon=True,
60
93
  )
61
94
  worker.start()
62
95
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.18
3
+ Version: 1.0.19
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: pyright>=1.1.393
13
14
  Requires-Dist: python-dotenv>=1.0.0
14
15
  Dynamic: home-page
15
16
  Dynamic: maintainer
@@ -45,6 +45,8 @@ tests/test_copy.py
45
45
  tests/test_is_synced.py
46
46
  tests/test_ls.py
47
47
  tests/test_mount.py
48
+ tests/test_mount_webdav.py
49
+ tests/test_obscure.py
48
50
  tests/test_remotes.py
49
51
  tests/test_serve_webdav.py
50
52
  tests/test_walk.py
@@ -1 +1,2 @@
1
+ pyright>=1.1.393
1
2
  python-dotenv>=1.0.0
@@ -2,4 +2,4 @@
2
2
 
3
3
  set -e
4
4
  echo "Running unittests"
5
- uv run pytest tests -v
5
+ uv run pytest -n auto tests -v
@@ -6,17 +6,16 @@ import os
6
6
  import unittest
7
7
  from pathlib import Path
8
8
 
9
- import httpx
10
9
  from dotenv import load_dotenv
11
10
 
12
- from rclone_api import Process, Rclone, Remote
11
+ from rclone_api import Config, Process, Rclone, Remote
13
12
 
14
13
  load_dotenv()
15
14
 
16
15
  BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
17
16
 
18
17
 
19
- def _generate_rclone_config() -> Path:
18
+ def _generate_rclone_config(port: int) -> Config:
20
19
 
21
20
  # BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
22
21
 
@@ -39,19 +38,17 @@ bucket = {BUCKET_NAME}
39
38
  [webdav]
40
39
  type = webdav
41
40
  user = guest
42
- # encrypted password for "1234"
41
+ # obscured password for "1234", use Rclone.obscure("1234") to generate
43
42
  pass = d4IbQLV9W0JhI2tm5Zp88hpMtEg
44
- url = http://localhost:8089
43
+ url = http://localhost:{port}
45
44
  vendor = rclone
46
45
  """
47
46
 
48
- # out = Config(config_text)
49
- out = Path("rclone_nfs.conf")
50
- out.write_text(config_text, encoding="utf-8")
47
+ out = Config(config_text)
51
48
  return out
52
49
 
53
50
 
54
- class RcloneNfsServeTest(unittest.TestCase):
51
+ class RcloneMountWebdavTester(unittest.TestCase):
55
52
  """Test rclone functionality."""
56
53
 
57
54
  def setUp(self) -> None:
@@ -68,58 +65,17 @@ class RcloneNfsServeTest(unittest.TestCase):
68
65
  f"Missing required environment variables: {', '.join(missing)}"
69
66
  )
70
67
  os.environ["RCLONE_API_VERBOSE"] = "1"
71
- self.config = _generate_rclone_config()
72
- self.rclone = Rclone(self.config)
73
-
74
- def tearDown(self):
75
- if self.config.exists():
76
- self.config.unlink()
77
-
78
- def test_serve_webdav(self) -> None:
79
- """Test basic NFS serve functionality."""
80
- config = _generate_rclone_config()
81
- rclone = Rclone(config)
82
-
83
- # Start NFS server for the remote
84
- remote = Remote("dst", rclone=rclone)
85
- # serve = Remote("webdav", rclone=rclone)
86
- test_addr = "localhost:8089"
87
- user = "guest"
88
- password = "1234"
89
-
90
- process = rclone.serve_webdav(
91
- f"{remote.name}:{BUCKET_NAME}", addr=test_addr, user=user, password=password
92
- )
93
- mount_proc: Process | None = None
94
-
95
- try:
96
- # Verify process is running
97
- self.assertIsNone(process.poll())
98
- response = httpx.get(f"http://{test_addr}/", auth=(user, password))
99
- # Note that windows is kinda broken and returns internal server error
100
- is_serving = response.status_code == 200
101
- self.assertTrue(is_serving)
102
-
103
- finally:
104
- # Clean up
105
- process.terminate()
106
- process.wait()
107
- if mount_proc:
108
- mount_proc.terminate()
109
- mount_proc.wait()
110
-
111
- # Verify process terminated
112
- self.assertIsNotNone(process.poll())
113
68
 
114
69
  def test_serve_webdav_and_mount(self) -> None:
115
70
  """Test basic NFS serve functionality."""
116
- config = _generate_rclone_config()
71
+ port = 8090
72
+ config = _generate_rclone_config(port)
117
73
  rclone = Rclone(config)
118
74
 
119
75
  # Start NFS server for the remote
120
76
  remote = Remote("dst", rclone=rclone)
121
77
  # serve = Remote("webdav", rclone=rclone)
122
- test_addr = "localhost:8089"
78
+ test_addr = f"localhost:{port}"
123
79
  user = "guest"
124
80
  password = "1234"
125
81
 
@@ -136,8 +92,8 @@ class RcloneNfsServeTest(unittest.TestCase):
136
92
  # test that the mount point exists
137
93
  self.assertTrue(mount_point.exists())
138
94
  # test the folder is not empty
139
- dirs = next(mount_point.iterdir())
140
- self.assertTrue(dirs.is_dir())
95
+ next_path = next(mount_point.iterdir())
96
+ self.assertIsNotNone(next_path)
141
97
 
142
98
  finally:
143
99
  # Clean up
@@ -7,7 +7,7 @@ import unittest
7
7
 
8
8
  from dotenv import load_dotenv
9
9
 
10
- from rclone_api import Config, DirListing, Rclone
10
+ from rclone_api import Config, Rclone
11
11
 
12
12
  load_dotenv()
13
13
 
@@ -31,13 +31,14 @@ provider = DigitalOcean
31
31
  access_key_id = {BUCKET_KEY_PUBLIC}
32
32
  secret_access_key = {BUCKET_KEY_SECRET}
33
33
  endpoint = {BUCKET_URL}
34
+ bucket = {BUCKET_NAME}
34
35
  """
35
36
 
36
37
  out = Config(config_text)
37
38
  return out
38
39
 
39
40
 
40
- class RcloneWalkTest(unittest.TestCase):
41
+ class RcloneLsTests(unittest.TestCase):
41
42
  """Test rclone functionality."""
42
43
 
43
44
  def setUp(self) -> None:
@@ -55,13 +56,11 @@ class RcloneWalkTest(unittest.TestCase):
55
56
  )
56
57
  os.environ["RCLONE_API_VERBOSE"] = "1"
57
58
 
58
- def test_walk(self) -> None:
59
+ def test_list_remotes(self) -> None:
59
60
  rclone = Rclone(_generate_rclone_config())
60
- # rclone.walk
61
- dirlisting: DirListing
62
- for dirlisting in rclone.walk(f"dst:{BUCKET_NAME}", max_depth=1):
63
- print(dirlisting)
64
- print("done")
61
+ obscured = rclone.obscure("1234")
62
+ self.assertNotEqual(obscured, "1234")
63
+ print()
65
64
 
66
65
 
67
66
  if __name__ == "__main__":
@@ -0,0 +1,108 @@
1
+ """
2
+ Unit test file.
3
+ """
4
+
5
+ import os
6
+ import unittest
7
+
8
+ import httpx
9
+ from dotenv import load_dotenv
10
+
11
+ from rclone_api import Config, Process, Rclone, Remote
12
+
13
+ load_dotenv()
14
+
15
+ BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
16
+
17
+
18
+ def _generate_rclone_config(port: int) -> Config:
19
+
20
+ # BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
21
+
22
+ # Load additional environment variables
23
+ BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
24
+ BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
25
+ # BUCKET_URL = os.getenv("BUCKET_URL")
26
+ BUCKET_URL = "sfo3.digitaloceanspaces.com"
27
+
28
+ config_text = f"""
29
+ [dst]
30
+ type = s3
31
+ provider = DigitalOcean
32
+ access_key_id = {BUCKET_KEY_PUBLIC}
33
+ secret_access_key = {BUCKET_KEY_SECRET}
34
+ endpoint = {BUCKET_URL}
35
+ bucket = {BUCKET_NAME}
36
+
37
+
38
+ [webdav]
39
+ type = webdav
40
+ user = guest
41
+ # obscured password for "1234", use Rclone.obscure("1234") to generate
42
+ pass = d4IbQLV9W0JhI2tm5Zp88hpMtEg
43
+ url = http://localhost:{port}
44
+ vendor = rclone
45
+ """
46
+
47
+ out = Config(config_text)
48
+ return out
49
+
50
+
51
+ class RcloneServeWebdavTester(unittest.TestCase):
52
+ """Test rclone functionality."""
53
+
54
+ def setUp(self) -> None:
55
+ """Check if all required environment variables are set before running tests."""
56
+ required_vars = [
57
+ "BUCKET_NAME",
58
+ "BUCKET_KEY_SECRET",
59
+ "BUCKET_KEY_PUBLIC",
60
+ "BUCKET_URL",
61
+ ]
62
+ missing = [var for var in required_vars if not os.getenv(var)]
63
+ if missing:
64
+ self.skipTest(
65
+ f"Missing required environment variables: {', '.join(missing)}"
66
+ )
67
+ os.environ["RCLONE_API_VERBOSE"] = "1"
68
+
69
+ def test_serve_webdav(self) -> None:
70
+ """Test basic NFS serve functionality."""
71
+ port = 8089
72
+ config = _generate_rclone_config(port)
73
+ rclone = Rclone(config)
74
+
75
+ # Start NFS server for the remote
76
+ remote = Remote("dst", rclone=rclone)
77
+ # serve = Remote("webdav", rclone=rclone)
78
+ test_addr = f"localhost:{port}"
79
+ user = "guest"
80
+ password = "1234"
81
+
82
+ process = rclone.serve_webdav(
83
+ f"{remote.name}:{BUCKET_NAME}", addr=test_addr, user=user, password=password
84
+ )
85
+ mount_proc: Process | None = None
86
+
87
+ try:
88
+ # Verify process is running
89
+ self.assertIsNone(process.poll())
90
+ response = httpx.get(f"http://{test_addr}/", auth=(user, password))
91
+ # Note that windows is kinda broken and returns internal server error
92
+ is_serving = response.status_code == 200
93
+ self.assertTrue(is_serving)
94
+
95
+ finally:
96
+ # Clean up
97
+ process.terminate()
98
+ process.wait()
99
+ if mount_proc:
100
+ mount_proc.terminate()
101
+ mount_proc.wait()
102
+
103
+ # Verify process terminated
104
+ self.assertIsNotNone(process.poll())
105
+
106
+
107
+ if __name__ == "__main__":
108
+ unittest.main()
@@ -0,0 +1,86 @@
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, DirListing, 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 RcloneWalkTest(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_walk(self) -> None:
59
+ rclone = Rclone(_generate_rclone_config())
60
+ # rclone.walk
61
+ dirlisting: DirListing
62
+ is_first = True
63
+ for dirlisting in rclone.walk(f"dst:{BUCKET_NAME}", max_depth=1):
64
+ if is_first:
65
+ # assert just one file
66
+ # assert len(dirlisting.files) == 1
67
+ self.assertEqual(len(dirlisting.files), 1)
68
+ # assert it's first.txt
69
+ self.assertEqual(dirlisting.files[0].name, "first.txt")
70
+ is_first = False
71
+ print(dirlisting)
72
+ print("done")
73
+
74
+ def test_walk_depth_first(self) -> None:
75
+ rclone = Rclone(_generate_rclone_config())
76
+ # rclone.walk
77
+ dirlisting: DirListing
78
+ for dirlisting in rclone.walk(
79
+ f"dst:{BUCKET_NAME}", max_depth=1, breadth_first=False
80
+ ):
81
+ print(dirlisting)
82
+ print("done")
83
+
84
+
85
+ if __name__ == "__main__":
86
+ 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