rclone-api 1.0.11__tar.gz → 1.0.13__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- rclone_api-1.0.13/PKG-INFO +34 -0
- rclone_api-1.0.13/README.md +18 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/pyproject.toml +44 -44
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/dir.py +9 -2
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/file.py +1 -4
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/rclone.py +97 -90
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/rpath.py +15 -8
- rclone_api-1.0.13/src/rclone_api/util.py +101 -0
- rclone_api-1.0.13/src/rclone_api.egg-info/PKG-INFO +34 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api.egg-info/SOURCES.txt +1 -0
- rclone_api-1.0.13/src/rclone_api.egg-info/requires.txt +1 -0
- rclone_api-1.0.13/tests/test_simple.py +119 -0
- rclone_api-1.0.11/PKG-INFO +0 -36
- rclone_api-1.0.11/README.md +0 -21
- rclone_api-1.0.11/src/rclone_api/util.py +0 -49
- rclone_api-1.0.11/src/rclone_api.egg-info/PKG-INFO +0 -36
- rclone_api-1.0.11/tests/test_simple.py +0 -16
- {rclone_api-1.0.11 → rclone_api-1.0.13}/.aiderignore +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/.gitignore +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/.pylintrc +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/.vscode/launch.json +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/.vscode/settings.json +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/.vscode/tasks.json +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/LICENSE +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/MANIFEST.in +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/clean +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/install +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/lint +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/requirements.testing.txt +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/setup.cfg +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/setup.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/__init__.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/config.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/dir_listing.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/remote.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/types.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api/walk.py +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/test +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/tox.ini +0 -0
- {rclone_api-1.0.11 → rclone_api-1.0.13}/upload_package.sh +0 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: rclone_api
|
3
|
+
Version: 1.0.13
|
4
|
+
Summary: rclone api in python
|
5
|
+
Home-page: https://github.com/zackees/rclone-api
|
6
|
+
Maintainer: Zachary Vorhies
|
7
|
+
License: BSD 3-Clause License
|
8
|
+
Keywords: template-python-cmd
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Requires-Python: >=3.10
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: python-dotenv>=1.0.0
|
14
|
+
Dynamic: home-page
|
15
|
+
Dynamic: maintainer
|
16
|
+
|
17
|
+
# rclone-api
|
18
|
+
|
19
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/lint.yml)
|
20
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml)
|
21
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml)
|
22
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml)
|
23
|
+
|
24
|
+
Api version of rclone. It's well tested. It's just released so this readme is a little to be desired.
|
25
|
+
|
26
|
+
To develop software, run `. ./activate`
|
27
|
+
|
28
|
+
# Windows
|
29
|
+
|
30
|
+
This environment requires you to use `git-bash`.
|
31
|
+
|
32
|
+
# Linting
|
33
|
+
|
34
|
+
Run `./lint`
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# rclone-api
|
2
|
+
|
3
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/lint.yml)
|
4
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml)
|
5
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml)
|
6
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml)
|
7
|
+
|
8
|
+
Api version of rclone. It's well tested. It's just released so this readme is a little to be desired.
|
9
|
+
|
10
|
+
To develop software, run `. ./activate`
|
11
|
+
|
12
|
+
# Windows
|
13
|
+
|
14
|
+
This environment requires you to use `git-bash`.
|
15
|
+
|
16
|
+
# Linting
|
17
|
+
|
18
|
+
Run `./lint`
|
@@ -1,44 +1,44 @@
|
|
1
|
-
[build-system]
|
2
|
-
requires = ["setuptools>=65.5.1", "setuptools-scm", "wheel"]
|
3
|
-
build-backend = "setuptools.build_meta"
|
4
|
-
|
5
|
-
[project]
|
6
|
-
name = "rclone_api"
|
7
|
-
readme = "README.md"
|
8
|
-
description = "rclone api in python"
|
9
|
-
requires-python = ">=3.
|
10
|
-
keywords = ["template-python-cmd"]
|
11
|
-
license = { text = "BSD 3-Clause License" }
|
12
|
-
classifiers = ["Programming Language :: Python :: 3"]
|
13
|
-
dependencies = [
|
14
|
-
|
15
|
-
]
|
16
|
-
# Change this with the version number bump.
|
17
|
-
version = "1.0.
|
18
|
-
|
19
|
-
[tool.setuptools]
|
20
|
-
package-dir = {"" = "src"}
|
21
|
-
|
22
|
-
[tool.ruff]
|
23
|
-
line-length = 200
|
24
|
-
|
25
|
-
[tool.pylint."MESSAGES CONTROL"]
|
26
|
-
good-names = [
|
27
|
-
"c",
|
28
|
-
"i",
|
29
|
-
"ok",
|
30
|
-
"id",
|
31
|
-
"e",
|
32
|
-
"f"
|
33
|
-
]
|
34
|
-
disable = [
|
35
|
-
"missing-function-docstring",
|
36
|
-
"missing-module-docstring"
|
37
|
-
]
|
38
|
-
|
39
|
-
[tool.isort]
|
40
|
-
profile = "black"
|
41
|
-
|
42
|
-
[tool.mypy]
|
43
|
-
ignore_missing_imports = true
|
44
|
-
disable_error_code = ["import-untyped"]
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=65.5.1", "setuptools-scm", "wheel"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "rclone_api"
|
7
|
+
readme = "README.md"
|
8
|
+
description = "rclone api in python"
|
9
|
+
requires-python = ">=3.10"
|
10
|
+
keywords = ["template-python-cmd"]
|
11
|
+
license = { text = "BSD 3-Clause License" }
|
12
|
+
classifiers = ["Programming Language :: Python :: 3"]
|
13
|
+
dependencies = [
|
14
|
+
"python-dotenv>=1.0.0"
|
15
|
+
]
|
16
|
+
# Change this with the version number bump.
|
17
|
+
version = "1.0.13"
|
18
|
+
|
19
|
+
[tool.setuptools]
|
20
|
+
package-dir = {"" = "src"}
|
21
|
+
|
22
|
+
[tool.ruff]
|
23
|
+
line-length = 200
|
24
|
+
|
25
|
+
[tool.pylint."MESSAGES CONTROL"]
|
26
|
+
good-names = [
|
27
|
+
"c",
|
28
|
+
"i",
|
29
|
+
"ok",
|
30
|
+
"id",
|
31
|
+
"e",
|
32
|
+
"f"
|
33
|
+
]
|
34
|
+
disable = [
|
35
|
+
"missing-function-docstring",
|
36
|
+
"missing-module-docstring"
|
37
|
+
]
|
38
|
+
|
39
|
+
[tool.isort]
|
40
|
+
profile = "black"
|
41
|
+
|
42
|
+
[tool.mypy]
|
43
|
+
ignore_missing_imports = true
|
44
|
+
disable_error_code = ["import-untyped"]
|
@@ -3,12 +3,15 @@ from typing import Generator
|
|
3
3
|
from rclone_api.dir_listing import DirListing
|
4
4
|
from rclone_api.remote import Remote
|
5
5
|
from rclone_api.rpath import RPath
|
6
|
-
from rclone_api.walk import walk
|
7
6
|
|
8
7
|
|
9
8
|
class Dir:
|
10
9
|
"""Remote file dataclass."""
|
11
10
|
|
11
|
+
@property
|
12
|
+
def remote(self) -> Remote:
|
13
|
+
return self.path.remote
|
14
|
+
|
12
15
|
def __init__(self, path: RPath | Remote) -> None:
|
13
16
|
"""Initialize Dir with either an RPath or Remote.
|
14
17
|
|
@@ -18,6 +21,7 @@ class Dir:
|
|
18
21
|
if isinstance(path, Remote):
|
19
22
|
# Need to create an RPath for the Remote's root
|
20
23
|
self.path = RPath(
|
24
|
+
remote=path,
|
21
25
|
path=str(path),
|
22
26
|
name=str(path),
|
23
27
|
size=0,
|
@@ -33,10 +37,13 @@ class Dir:
|
|
33
37
|
def ls(self, max_depth: int = 0) -> DirListing:
|
34
38
|
"""List files and directories in the given path."""
|
35
39
|
assert self.path.rclone is not None
|
36
|
-
|
40
|
+
dir = Dir(self.path)
|
41
|
+
return self.path.rclone.ls(dir, max_depth=max_depth)
|
37
42
|
|
38
43
|
def walk(self, max_depth: int = -1) -> Generator[DirListing, None, None]:
|
39
44
|
"""List files and directories in the given path."""
|
45
|
+
from rclone_api.walk import walk
|
46
|
+
|
40
47
|
assert self.path.rclone is not None
|
41
48
|
return walk(self, max_depth=max_depth)
|
42
49
|
|
@@ -1,90 +1,97 @@
|
|
1
|
-
"""
|
2
|
-
Unit test file.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import subprocess
|
6
|
-
from pathlib import Path
|
7
|
-
from typing import Generator
|
8
|
-
|
9
|
-
from rclone_api import Dir
|
10
|
-
from rclone_api.dir_listing import DirListing
|
11
|
-
from rclone_api.remote import Remote
|
12
|
-
from rclone_api.rpath import RPath
|
13
|
-
from rclone_api.types import Config, RcloneExec
|
14
|
-
from rclone_api.util import get_rclone_exe
|
15
|
-
from rclone_api.walk import walk
|
16
|
-
|
17
|
-
|
18
|
-
class Rclone:
|
19
|
-
def __init__(
|
20
|
-
self, rclone_conf: Path | Config, rclone_exe: Path | None = None
|
21
|
-
) -> None:
|
22
|
-
if isinstance(rclone_conf, Path):
|
23
|
-
if not rclone_conf.exists():
|
24
|
-
raise ValueError(f"Rclone config file not found: {rclone_conf}")
|
25
|
-
self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
|
26
|
-
|
27
|
-
def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
28
|
-
return self._exec.execute(cmd)
|
29
|
-
|
30
|
-
def ls(self, path:
|
31
|
-
"""List files in the given path.
|
32
|
-
|
33
|
-
Args:
|
34
|
-
path: Remote path or Remote object to list
|
35
|
-
max_depth: Maximum recursion depth (0 means no recursion)
|
36
|
-
|
37
|
-
Returns:
|
38
|
-
List of File objects found at the path
|
39
|
-
"""
|
40
|
-
cmd = ["lsjson"]
|
41
|
-
if max_depth
|
42
|
-
cmd.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
1
|
+
"""
|
2
|
+
Unit test file.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import subprocess
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Generator
|
8
|
+
|
9
|
+
from rclone_api import Dir
|
10
|
+
from rclone_api.dir_listing import DirListing
|
11
|
+
from rclone_api.remote import Remote
|
12
|
+
from rclone_api.rpath import RPath
|
13
|
+
from rclone_api.types import Config, RcloneExec
|
14
|
+
from rclone_api.util import get_rclone_exe
|
15
|
+
from rclone_api.walk import walk
|
16
|
+
|
17
|
+
|
18
|
+
class Rclone:
|
19
|
+
def __init__(
|
20
|
+
self, rclone_conf: Path | Config, rclone_exe: Path | None = None
|
21
|
+
) -> None:
|
22
|
+
if isinstance(rclone_conf, Path):
|
23
|
+
if not rclone_conf.exists():
|
24
|
+
raise ValueError(f"Rclone config file not found: {rclone_conf}")
|
25
|
+
self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
|
26
|
+
|
27
|
+
def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
28
|
+
return self._exec.execute(cmd)
|
29
|
+
|
30
|
+
def ls(self, path: Dir | Remote | str, max_depth: int | None = None) -> DirListing:
|
31
|
+
"""List files in the given path.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
path: Remote path or Remote object to list
|
35
|
+
max_depth: Maximum recursion depth (0 means no recursion)
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
List of File objects found at the path
|
39
|
+
"""
|
40
|
+
cmd = ["lsjson"]
|
41
|
+
if max_depth is not None:
|
42
|
+
cmd.append("--recursive")
|
43
|
+
if max_depth > -1:
|
44
|
+
cmd.append("--max-depth")
|
45
|
+
cmd.append(str(max_depth))
|
46
|
+
cmd.append(str(path))
|
47
|
+
remote = path.remote if isinstance(path, Dir) else path
|
48
|
+
assert isinstance(remote, Remote)
|
49
|
+
|
50
|
+
cp = self._run(cmd)
|
51
|
+
text = cp.stdout
|
52
|
+
paths: list[RPath] = RPath.from_json_str(text, remote)
|
53
|
+
for o in paths:
|
54
|
+
o.set_rclone(self)
|
55
|
+
return DirListing(paths)
|
56
|
+
|
57
|
+
def listremotes(self) -> list[Remote]:
|
58
|
+
cmd = ["listremotes"]
|
59
|
+
cp = self._run(cmd)
|
60
|
+
text: str = cp.stdout
|
61
|
+
tmp = text.splitlines()
|
62
|
+
tmp = [t.strip() for t in tmp]
|
63
|
+
# strip out ":" from the end
|
64
|
+
tmp = [t.replace(":", "") for t in tmp]
|
65
|
+
out = [Remote(name=t, rclone=self) for t in tmp]
|
66
|
+
return out
|
67
|
+
|
68
|
+
def walk(
|
69
|
+
self, path: Dir | Remote, max_depth: int = -1
|
70
|
+
) -> Generator[DirListing, None, None]:
|
71
|
+
"""Walk through the given path recursively.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
path: Remote path or Remote object to walk through
|
75
|
+
max_depth: Maximum depth to traverse (-1 for unlimited)
|
76
|
+
|
77
|
+
Yields:
|
78
|
+
DirListing: Directory listing for each directory encountered
|
79
|
+
"""
|
80
|
+
if isinstance(path, Dir):
|
81
|
+
# Create a Remote object for the path
|
82
|
+
remote = path.remote
|
83
|
+
rpath = RPath(
|
84
|
+
remote=remote,
|
85
|
+
path=path.path.path,
|
86
|
+
name=path.path.name,
|
87
|
+
size=0,
|
88
|
+
mime_type="inode/directory",
|
89
|
+
mod_time="",
|
90
|
+
is_dir=True,
|
91
|
+
)
|
92
|
+
rpath.set_rclone(self)
|
93
|
+
dir_obj = Dir(rpath)
|
94
|
+
else:
|
95
|
+
dir_obj = Dir(path)
|
96
|
+
|
97
|
+
yield from walk(dir_obj, max_depth=max_depth)
|
@@ -1,12 +1,15 @@
|
|
1
1
|
import json
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
+
from rclone_api.remote import Remote
|
5
|
+
|
4
6
|
|
5
7
|
class RPath:
|
6
8
|
"""Remote file dataclass."""
|
7
9
|
|
8
10
|
def __init__(
|
9
11
|
self,
|
12
|
+
remote: Remote,
|
10
13
|
path: str,
|
11
14
|
name: str,
|
12
15
|
size: int,
|
@@ -16,6 +19,10 @@ class RPath:
|
|
16
19
|
) -> None:
|
17
20
|
from rclone_api.rclone import Rclone
|
18
21
|
|
22
|
+
if "dst:" in path:
|
23
|
+
raise ValueError(f"Invalid path: {path}")
|
24
|
+
|
25
|
+
self.remote = remote
|
19
26
|
self.path = path
|
20
27
|
self.name = name
|
21
28
|
self.size = size
|
@@ -32,9 +39,10 @@ class RPath:
|
|
32
39
|
self.rclone = rclone
|
33
40
|
|
34
41
|
@staticmethod
|
35
|
-
def from_dict(data: dict) -> "RPath":
|
42
|
+
def from_dict(data: dict, remote: Remote) -> "RPath":
|
36
43
|
"""Create a File from a dictionary."""
|
37
44
|
return RPath(
|
45
|
+
remote,
|
38
46
|
data["Path"],
|
39
47
|
data["Name"],
|
40
48
|
data["Size"],
|
@@ -45,21 +53,21 @@ class RPath:
|
|
45
53
|
)
|
46
54
|
|
47
55
|
@staticmethod
|
48
|
-
def from_array(data: list[dict]) -> list["RPath"]:
|
56
|
+
def from_array(data: list[dict], remote: Remote) -> list["RPath"]:
|
49
57
|
"""Create a File from a dictionary."""
|
50
58
|
out: list[RPath] = []
|
51
59
|
for d in data:
|
52
|
-
file: RPath = RPath.from_dict(d)
|
60
|
+
file: RPath = RPath.from_dict(d, remote)
|
53
61
|
out.append(file)
|
54
62
|
return out
|
55
63
|
|
56
64
|
@staticmethod
|
57
|
-
def from_json_str(json_str: str) -> list["RPath"]:
|
65
|
+
def from_json_str(json_str: str, remote: Remote) -> list["RPath"]:
|
58
66
|
"""Create a File from a JSON string."""
|
59
67
|
json_obj = json.loads(json_str)
|
60
68
|
if isinstance(json_obj, dict):
|
61
|
-
return [RPath.from_dict(json_obj)]
|
62
|
-
return RPath.from_array(json_obj)
|
69
|
+
return [RPath.from_dict(json_obj, remote)]
|
70
|
+
return RPath.from_array(json_obj, remote)
|
63
71
|
|
64
72
|
def to_json(self) -> dict:
|
65
73
|
return {
|
@@ -73,5 +81,4 @@ class RPath:
|
|
73
81
|
}
|
74
82
|
|
75
83
|
def __str__(self) -> str:
|
76
|
-
|
77
|
-
return json.dumps(out)
|
84
|
+
return f"{self.remote.name}:{self.path}"
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
import subprocess
|
4
|
+
from pathlib import Path
|
5
|
+
from tempfile import TemporaryDirectory
|
6
|
+
from typing import Any
|
7
|
+
|
8
|
+
from rclone_api.dir import Dir
|
9
|
+
from rclone_api.remote import Remote
|
10
|
+
from rclone_api.rpath import RPath
|
11
|
+
from rclone_api.types import Config
|
12
|
+
|
13
|
+
# from .rclone import Rclone
|
14
|
+
|
15
|
+
|
16
|
+
def to_path(item: Dir | Remote | str, rclone: Any) -> RPath:
|
17
|
+
from rclone_api.rclone import Rclone
|
18
|
+
|
19
|
+
assert isinstance(rclone, Rclone)
|
20
|
+
# if str then it will be remote:path
|
21
|
+
if isinstance(item, str):
|
22
|
+
# return RPath(item)
|
23
|
+
# remote_name: str = item.split(":")[0]
|
24
|
+
parts = item.split(":")
|
25
|
+
remote_name = parts[0]
|
26
|
+
path = ":".join(parts[1:])
|
27
|
+
remote = Remote(name=remote_name, rclone=rclone)
|
28
|
+
return RPath(
|
29
|
+
remote=remote,
|
30
|
+
path=path,
|
31
|
+
name="",
|
32
|
+
size=0,
|
33
|
+
mime_type="",
|
34
|
+
mod_time="",
|
35
|
+
is_dir=True,
|
36
|
+
)
|
37
|
+
elif isinstance(item, Dir):
|
38
|
+
return item.path
|
39
|
+
elif isinstance(item, Remote):
|
40
|
+
return RPath(
|
41
|
+
remote=item,
|
42
|
+
path=str(item),
|
43
|
+
name=str(item),
|
44
|
+
size=0,
|
45
|
+
mime_type="inode/directory",
|
46
|
+
mod_time="",
|
47
|
+
is_dir=True,
|
48
|
+
)
|
49
|
+
else:
|
50
|
+
raise ValueError(f"Invalid type for item: {type(item)}")
|
51
|
+
|
52
|
+
|
53
|
+
def _get_verbose(verbose: bool | None) -> bool:
|
54
|
+
if verbose is not None:
|
55
|
+
return verbose
|
56
|
+
# get it from the environment
|
57
|
+
return bool(int(os.getenv("RCLONE_API_VERBOSE", "0")))
|
58
|
+
|
59
|
+
|
60
|
+
def get_rclone_exe(rclone_exe: Path | None) -> Path:
|
61
|
+
if rclone_exe is None:
|
62
|
+
|
63
|
+
rclone_which_path = shutil.which("rclone")
|
64
|
+
if rclone_which_path is None:
|
65
|
+
raise ValueError("rclone executable not found")
|
66
|
+
return Path(rclone_which_path)
|
67
|
+
return rclone_exe
|
68
|
+
|
69
|
+
|
70
|
+
def rclone_execute(
|
71
|
+
cmd: list[str],
|
72
|
+
rclone_conf: Path | Config,
|
73
|
+
rclone_exe: Path,
|
74
|
+
verbose: bool | None = None,
|
75
|
+
) -> subprocess.CompletedProcess:
|
76
|
+
tempdir: TemporaryDirectory | None = None
|
77
|
+
verbose = _get_verbose(verbose)
|
78
|
+
assert verbose is not None
|
79
|
+
|
80
|
+
try:
|
81
|
+
if isinstance(rclone_conf, Config):
|
82
|
+
tempdir = TemporaryDirectory()
|
83
|
+
tmpfile = Path(tempdir.name) / "rclone.conf"
|
84
|
+
tmpfile.write_text(rclone_conf.text, encoding="utf-8")
|
85
|
+
rclone_conf = tmpfile
|
86
|
+
cmd = (
|
87
|
+
[str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
|
88
|
+
)
|
89
|
+
if verbose:
|
90
|
+
cmd_str = subprocess.list2cmdline(cmd)
|
91
|
+
print(f"Running: {cmd_str}")
|
92
|
+
cp = subprocess.run(
|
93
|
+
cmd, capture_output=True, encoding="utf-8", check=True, shell=False
|
94
|
+
)
|
95
|
+
return cp
|
96
|
+
finally:
|
97
|
+
if tempdir:
|
98
|
+
try:
|
99
|
+
tempdir.cleanup()
|
100
|
+
except Exception as e:
|
101
|
+
print(f"Error cleaning up tempdir: {e}")
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: rclone_api
|
3
|
+
Version: 1.0.13
|
4
|
+
Summary: rclone api in python
|
5
|
+
Home-page: https://github.com/zackees/rclone-api
|
6
|
+
Maintainer: Zachary Vorhies
|
7
|
+
License: BSD 3-Clause License
|
8
|
+
Keywords: template-python-cmd
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Requires-Python: >=3.10
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: python-dotenv>=1.0.0
|
14
|
+
Dynamic: home-page
|
15
|
+
Dynamic: maintainer
|
16
|
+
|
17
|
+
# rclone-api
|
18
|
+
|
19
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/lint.yml)
|
20
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_macos.yml)
|
21
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_ubuntu.yml)
|
22
|
+
[](https://github.com/zackees/rclone-api/actions/workflows/push_win.yml)
|
23
|
+
|
24
|
+
Api version of rclone. It's well tested. It's just released so this readme is a little to be desired.
|
25
|
+
|
26
|
+
To develop software, run `. ./activate`
|
27
|
+
|
28
|
+
# Windows
|
29
|
+
|
30
|
+
This environment requires you to use `git-bash`.
|
31
|
+
|
32
|
+
# Linting
|
33
|
+
|
34
|
+
Run `./lint`
|
@@ -31,6 +31,7 @@ src/rclone_api/walk.py
|
|
31
31
|
src/rclone_api.egg-info/PKG-INFO
|
32
32
|
src/rclone_api.egg-info/SOURCES.txt
|
33
33
|
src/rclone_api.egg-info/dependency_links.txt
|
34
|
+
src/rclone_api.egg-info/requires.txt
|
34
35
|
src/rclone_api.egg-info/top_level.txt
|
35
36
|
src/rclone_api/assets/example.txt
|
36
37
|
tests/test_simple.py
|
@@ -0,0 +1 @@
|
|
1
|
+
python-dotenv>=1.0.0
|
@@ -0,0 +1,119 @@
|
|
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, DirListing, File, Rclone, Remote
|
11
|
+
from rclone_api.util import to_path
|
12
|
+
|
13
|
+
load_dotenv()
|
14
|
+
|
15
|
+
|
16
|
+
def _generate_rclone_config() -> Config:
|
17
|
+
|
18
|
+
# BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
|
19
|
+
|
20
|
+
# Load additional environment variables
|
21
|
+
BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
|
22
|
+
BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
|
23
|
+
# BUCKET_URL = os.getenv("BUCKET_URL")
|
24
|
+
BUCKET_URL = "sfo3.digitaloceanspaces.com"
|
25
|
+
|
26
|
+
config_text = f"""
|
27
|
+
[dst]
|
28
|
+
type = s3
|
29
|
+
provider = DigitalOcean
|
30
|
+
access_key_id = {BUCKET_KEY_PUBLIC}
|
31
|
+
secret_access_key = {BUCKET_KEY_SECRET}
|
32
|
+
endpoint = {BUCKET_URL}
|
33
|
+
"""
|
34
|
+
|
35
|
+
out = Config(config_text)
|
36
|
+
return out
|
37
|
+
|
38
|
+
|
39
|
+
class RcloneTests(unittest.TestCase):
|
40
|
+
"""Test rclone functionality."""
|
41
|
+
|
42
|
+
def setUp(self) -> None:
|
43
|
+
"""Check if all required environment variables are set before running tests."""
|
44
|
+
required_vars = [
|
45
|
+
"BUCKET_NAME",
|
46
|
+
"BUCKET_KEY_SECRET",
|
47
|
+
"BUCKET_KEY_PUBLIC",
|
48
|
+
"BUCKET_URL",
|
49
|
+
]
|
50
|
+
missing = [var for var in required_vars if not os.getenv(var)]
|
51
|
+
if missing:
|
52
|
+
self.skipTest(
|
53
|
+
f"Missing required environment variables: {', '.join(missing)}"
|
54
|
+
)
|
55
|
+
os.environ["RCLONE_API_VERBOSE"] = "1"
|
56
|
+
|
57
|
+
def test_list_remotes(self) -> None:
|
58
|
+
rclone = Rclone(_generate_rclone_config())
|
59
|
+
|
60
|
+
remotes: list[Remote] = rclone.listremotes()
|
61
|
+
self.assertGreater(len(remotes), 0)
|
62
|
+
for remote in remotes:
|
63
|
+
self.assertIsInstance(remote, Remote)
|
64
|
+
print(remote)
|
65
|
+
print("done")
|
66
|
+
|
67
|
+
def test_ls_root(self) -> None:
|
68
|
+
"""Test listing the root directory of the bucket.
|
69
|
+
|
70
|
+
Verifies that we can:
|
71
|
+
1. Connect to the bucket
|
72
|
+
2. List its contents
|
73
|
+
3. Get both directories and files as proper types
|
74
|
+
"""
|
75
|
+
BUCKET_NAME = os.getenv("BUCKET_NAME") # Default if not in .env
|
76
|
+
self.assertIsNotNone(BUCKET_NAME)
|
77
|
+
rclone = Rclone(_generate_rclone_config())
|
78
|
+
path = to_path(f"dst:{BUCKET_NAME}", rclone)
|
79
|
+
dir = Dir(path)
|
80
|
+
listing: DirListing = rclone.ls(dir, max_depth=-1)
|
81
|
+
|
82
|
+
# Verify we got a valid listing
|
83
|
+
self.assertIsInstance(listing, DirListing)
|
84
|
+
self.assertGreater(len(listing.dirs), 0)
|
85
|
+
self.assertGreater(len(listing.files), 0)
|
86
|
+
|
87
|
+
# Verify dirs are properly typed
|
88
|
+
for dir in listing.dirs:
|
89
|
+
self.assertIsInstance(dir, Dir)
|
90
|
+
print(dir)
|
91
|
+
|
92
|
+
# Verify files are properly typed
|
93
|
+
for file in listing.files:
|
94
|
+
self.assertIsInstance(file, File)
|
95
|
+
print(file)
|
96
|
+
|
97
|
+
print("done")
|
98
|
+
|
99
|
+
# def test_zlib1(self) -> None:
|
100
|
+
# rclone = Rclone(_RCLONE_CONFIG)
|
101
|
+
# path = f"dst:{BUCKET_NAME}/zlib1"
|
102
|
+
# listing: DirListing = rclone.ls(path)
|
103
|
+
# print("dirs:")
|
104
|
+
# for dir in listing.dirs:
|
105
|
+
# print(dir)
|
106
|
+
# print("files:")
|
107
|
+
# for file in listing.files:
|
108
|
+
# print(file)
|
109
|
+
|
110
|
+
# def test_zlib_walk(self) -> None:
|
111
|
+
# rclone = Rclone(_RCLONE_CONFIG)
|
112
|
+
# # rclone.walk
|
113
|
+
# dirlisting: DirListing
|
114
|
+
# for dirlisting in rclone.walk(f"dst:{BUCKET_NAME}", max_depth=1):
|
115
|
+
# print(dirlisting)
|
116
|
+
|
117
|
+
|
118
|
+
if __name__ == "__main__":
|
119
|
+
unittest.main()
|
rclone_api-1.0.11/PKG-INFO
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.2
|
2
|
-
Name: rclone_api
|
3
|
-
Version: 1.0.11
|
4
|
-
Summary: rclone api in python
|
5
|
-
Home-page: https://github.com/zackees/rclone-api
|
6
|
-
Maintainer: Zachary Vorhies
|
7
|
-
License: BSD 3-Clause License
|
8
|
-
Keywords: template-python-cmd
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
10
|
-
Requires-Python: >=3.7
|
11
|
-
Description-Content-Type: text/markdown
|
12
|
-
License-File: LICENSE
|
13
|
-
Dynamic: home-page
|
14
|
-
Dynamic: maintainer
|
15
|
-
|
16
|
-
# template-python-cmd
|
17
|
-
A template for quickly making a python lib that has a command line program attached
|
18
|
-
|
19
|
-
[](../../actions/workflows/lint.yml)
|
20
|
-
|
21
|
-
[](../../actions/workflows/push_macos.yml)
|
22
|
-
[](../../actions/workflows/push_ubuntu.yml)
|
23
|
-
[](../../actions/workflows/push_win.yml)
|
24
|
-
|
25
|
-
Replace `template-python-cmd` and `template_python_cmd` with your command. Run tox until it's
|
26
|
-
correct.
|
27
|
-
|
28
|
-
To develop software, run `. ./activate.sh`
|
29
|
-
|
30
|
-
# Windows
|
31
|
-
|
32
|
-
This environment requires you to use `git-bash`.
|
33
|
-
|
34
|
-
# Linting
|
35
|
-
|
36
|
-
Run `./lint.sh` to find linting errors using `pylint`, `flake8` and `mypy`.
|
rclone_api-1.0.11/README.md
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# template-python-cmd
|
2
|
-
A template for quickly making a python lib that has a command line program attached
|
3
|
-
|
4
|
-
[](../../actions/workflows/lint.yml)
|
5
|
-
|
6
|
-
[](../../actions/workflows/push_macos.yml)
|
7
|
-
[](../../actions/workflows/push_ubuntu.yml)
|
8
|
-
[](../../actions/workflows/push_win.yml)
|
9
|
-
|
10
|
-
Replace `template-python-cmd` and `template_python_cmd` with your command. Run tox until it's
|
11
|
-
correct.
|
12
|
-
|
13
|
-
To develop software, run `. ./activate.sh`
|
14
|
-
|
15
|
-
# Windows
|
16
|
-
|
17
|
-
This environment requires you to use `git-bash`.
|
18
|
-
|
19
|
-
# Linting
|
20
|
-
|
21
|
-
Run `./lint.sh` to find linting errors using `pylint`, `flake8` and `mypy`.
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import shutil
|
2
|
-
import subprocess
|
3
|
-
from pathlib import Path
|
4
|
-
from tempfile import TemporaryDirectory
|
5
|
-
|
6
|
-
from rclone_api.types import Config
|
7
|
-
|
8
|
-
|
9
|
-
def get_rclone_exe(rclone_exe: Path | None) -> Path:
|
10
|
-
if rclone_exe is None:
|
11
|
-
|
12
|
-
rclone_which_path = shutil.which("rclone")
|
13
|
-
if rclone_which_path is None:
|
14
|
-
raise ValueError("rclone executable not found")
|
15
|
-
return Path(rclone_which_path)
|
16
|
-
return rclone_exe
|
17
|
-
|
18
|
-
|
19
|
-
def rclone_execute(
|
20
|
-
cmd: list[str],
|
21
|
-
rclone_conf: Path | Config,
|
22
|
-
rclone_exe: Path,
|
23
|
-
verbose: bool = False,
|
24
|
-
) -> subprocess.CompletedProcess:
|
25
|
-
print(subprocess.list2cmdline(cmd))
|
26
|
-
tempdir: TemporaryDirectory | None = None
|
27
|
-
|
28
|
-
try:
|
29
|
-
if isinstance(rclone_conf, Config):
|
30
|
-
tempdir = TemporaryDirectory()
|
31
|
-
tmpfile = Path(tempdir.name) / "rclone.conf"
|
32
|
-
tmpfile.write_text(rclone_conf.text, encoding="utf-8")
|
33
|
-
rclone_conf = tmpfile
|
34
|
-
cmd = (
|
35
|
-
[str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
|
36
|
-
)
|
37
|
-
if verbose:
|
38
|
-
cmd_str = subprocess.list2cmdline(cmd)
|
39
|
-
print(f"Running: {cmd_str}")
|
40
|
-
cp = subprocess.run(
|
41
|
-
cmd, capture_output=True, encoding="utf-8", check=True, shell=False
|
42
|
-
)
|
43
|
-
return cp
|
44
|
-
finally:
|
45
|
-
if tempdir:
|
46
|
-
try:
|
47
|
-
tempdir.cleanup()
|
48
|
-
except Exception as e:
|
49
|
-
print(f"Error cleaning up tempdir: {e}")
|
@@ -1,36 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.2
|
2
|
-
Name: rclone_api
|
3
|
-
Version: 1.0.11
|
4
|
-
Summary: rclone api in python
|
5
|
-
Home-page: https://github.com/zackees/rclone-api
|
6
|
-
Maintainer: Zachary Vorhies
|
7
|
-
License: BSD 3-Clause License
|
8
|
-
Keywords: template-python-cmd
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
10
|
-
Requires-Python: >=3.7
|
11
|
-
Description-Content-Type: text/markdown
|
12
|
-
License-File: LICENSE
|
13
|
-
Dynamic: home-page
|
14
|
-
Dynamic: maintainer
|
15
|
-
|
16
|
-
# template-python-cmd
|
17
|
-
A template for quickly making a python lib that has a command line program attached
|
18
|
-
|
19
|
-
[](../../actions/workflows/lint.yml)
|
20
|
-
|
21
|
-
[](../../actions/workflows/push_macos.yml)
|
22
|
-
[](../../actions/workflows/push_ubuntu.yml)
|
23
|
-
[](../../actions/workflows/push_win.yml)
|
24
|
-
|
25
|
-
Replace `template-python-cmd` and `template_python_cmd` with your command. Run tox until it's
|
26
|
-
correct.
|
27
|
-
|
28
|
-
To develop software, run `. ./activate.sh`
|
29
|
-
|
30
|
-
# Windows
|
31
|
-
|
32
|
-
This environment requires you to use `git-bash`.
|
33
|
-
|
34
|
-
# Linting
|
35
|
-
|
36
|
-
Run `./lint.sh` to find linting errors using `pylint`, `flake8` and `mypy`.
|
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
|
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
|
File without changes
|