rclone-api 1.0.2__tar.gz → 1.0.4__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.
- {rclone_api-1.0.2 → rclone_api-1.0.4}/.gitignore +1 -0
- {rclone_api-1.0.2/src/rclone_api.egg-info → rclone_api-1.0.4}/PKG-INFO +1 -1
- {rclone_api-1.0.2 → rclone_api-1.0.4}/pyproject.toml +1 -1
- rclone_api-1.0.4/src/rclone_api/__init__.py +8 -0
- rclone_api-1.0.4/src/rclone_api/config.py +19 -0
- rclone_api-1.0.4/src/rclone_api/dir.py +27 -0
- rclone_api-1.0.4/src/rclone_api/file.py +35 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api/rclone.py +57 -43
- rclone_api-1.0.4/src/rclone_api/remote.py +8 -0
- rclone_api-1.0.2/src/rclone_api/file.py → rclone_api-1.0.4/src/rclone_api/rpath.py +45 -9
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api/types.py +0 -10
- {rclone_api-1.0.2 → rclone_api-1.0.4/src/rclone_api.egg-info}/PKG-INFO +1 -1
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api.egg-info/SOURCES.txt +4 -0
- rclone_api-1.0.2/src/rclone_api/__init__.py +0 -4
- {rclone_api-1.0.2 → rclone_api-1.0.4}/.pylintrc +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/.vscode/launch.json +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/.vscode/settings.json +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/.vscode/tasks.json +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/LICENSE +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/MANIFEST.in +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/README.md +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/clean +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/install +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/lint +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/requirements.testing.txt +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/setup.cfg +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/setup.py +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api/util.py +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/test +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/tests/test_simple.py +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/tox.ini +0 -0
- {rclone_api-1.0.2 → rclone_api-1.0.4}/upload_package.sh +0 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
import subprocess
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
from rclone_api.types import Config
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class RcloneExec:
|
10
|
+
"""Rclone execution dataclass."""
|
11
|
+
|
12
|
+
rclone_config: Path | Config
|
13
|
+
rclone_exe: Path
|
14
|
+
|
15
|
+
def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
16
|
+
"""Execute rclone command."""
|
17
|
+
from rclone_api.util import rclone_execute
|
18
|
+
|
19
|
+
return rclone_execute(cmd, self.rclone_config, self.rclone_exe)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from rclone_api.file import File
|
2
|
+
from rclone_api.rpath import RPath
|
3
|
+
|
4
|
+
|
5
|
+
class Dir:
|
6
|
+
"""Remote file dataclass."""
|
7
|
+
|
8
|
+
def __init__(self, path: RPath) -> None:
|
9
|
+
self.path = path
|
10
|
+
|
11
|
+
def ls(self) -> tuple[list["Dir"], list[File]]:
|
12
|
+
"""List files and directories in the given path."""
|
13
|
+
cmd = ["lsjson", "--files-only", "--dirs-only", "--json", str(self.path)]
|
14
|
+
assert self.path.rclone is not None
|
15
|
+
cp = self.path.rclone._run(cmd)
|
16
|
+
text = cp.stdout
|
17
|
+
tmp: list[RPath] = RPath.from_json_str(text)
|
18
|
+
for t in tmp:
|
19
|
+
t.set_rclone(self.path.rclone)
|
20
|
+
# dirs = [o for o in out if o.is_dir]
|
21
|
+
# files = [o for o in out if not o.is_dir]
|
22
|
+
dirs = [Dir(p) for p in tmp if p.is_dir]
|
23
|
+
files = [File(p) for p in tmp if not p.is_dir]
|
24
|
+
return dirs, files
|
25
|
+
|
26
|
+
def __str__(self) -> str:
|
27
|
+
return str(self.path)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
from rclone_api.rpath import RPath
|
4
|
+
|
5
|
+
|
6
|
+
class File:
|
7
|
+
"""Remote file dataclass."""
|
8
|
+
|
9
|
+
def __init__(
|
10
|
+
self,
|
11
|
+
path: RPath,
|
12
|
+
) -> None:
|
13
|
+
self.path = path
|
14
|
+
|
15
|
+
def read_text(self) -> str:
|
16
|
+
"""Read the file contents as bytes.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
bytes: The file contents
|
20
|
+
|
21
|
+
Raises:
|
22
|
+
RuntimeError: If no rclone instance is associated with this file
|
23
|
+
RuntimeError: If the path represents a directory
|
24
|
+
"""
|
25
|
+
if self.path.rclone is None:
|
26
|
+
raise RuntimeError("No rclone instance associated with this file")
|
27
|
+
if self.path.is_dir:
|
28
|
+
raise RuntimeError("Cannot read a directory as bytes")
|
29
|
+
|
30
|
+
result = self.path.rclone._run(["cat", self.path.path])
|
31
|
+
return result.stdout
|
32
|
+
|
33
|
+
def __str__(self) -> str:
|
34
|
+
out = self.path.to_json()
|
35
|
+
return json.dumps(out)
|
@@ -1,43 +1,57 @@
|
|
1
|
-
"""
|
2
|
-
Unit test file.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import subprocess
|
6
|
-
from pathlib import Path
|
7
|
-
|
8
|
-
from rclone_api.
|
9
|
-
from rclone_api.
|
10
|
-
from rclone_api.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
1
|
+
"""
|
2
|
+
Unit test file.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import subprocess
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from rclone_api.remote import Remote
|
9
|
+
from rclone_api.rpath import RPath
|
10
|
+
from rclone_api.types import Config, RcloneExec
|
11
|
+
from rclone_api.util import get_rclone_exe
|
12
|
+
|
13
|
+
|
14
|
+
class Rclone:
|
15
|
+
def __init__(
|
16
|
+
self, rclone_conf: Path | Config, rclone_exe: Path | None = None
|
17
|
+
) -> None:
|
18
|
+
if isinstance(rclone_conf, Path):
|
19
|
+
if not rclone_conf.exists():
|
20
|
+
raise ValueError(f"Rclone config file not found: {rclone_conf}")
|
21
|
+
self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
|
22
|
+
|
23
|
+
def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
24
|
+
return self._exec.execute(cmd)
|
25
|
+
|
26
|
+
def ls(self, path: str | Remote, max_depth: int = 0) -> list[RPath]:
|
27
|
+
"""List files in the given path.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
path: Remote path or Remote object to list
|
31
|
+
max_depth: Maximum recursion depth (0 means no recursion)
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
List of File objects found at the path
|
35
|
+
"""
|
36
|
+
cmd = ["lsjson"]
|
37
|
+
if max_depth > 0:
|
38
|
+
cmd.extend(["--recursive", "--max-depth", str(max_depth)])
|
39
|
+
cmd.append(str(path))
|
40
|
+
|
41
|
+
cp = self._run(cmd)
|
42
|
+
text = cp.stdout
|
43
|
+
out: list[RPath] = RPath.from_json_str(text)
|
44
|
+
for o in out:
|
45
|
+
o.set_rclone(self)
|
46
|
+
return out
|
47
|
+
|
48
|
+
def listremotes(self) -> list[Remote]:
|
49
|
+
cmd = ["listremotes"]
|
50
|
+
cp = self._run(cmd)
|
51
|
+
text: str = cp.stdout
|
52
|
+
tmp = text.splitlines()
|
53
|
+
tmp = [t.strip() for t in tmp]
|
54
|
+
# strip out ":" from the end
|
55
|
+
tmp = [t.replace(":", "") for t in tmp]
|
56
|
+
out = [Remote(name=t) for t in tmp]
|
57
|
+
return out
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import json
|
2
|
+
from pathlib import Path
|
2
3
|
from typing import Any
|
3
4
|
|
4
5
|
|
5
|
-
class
|
6
|
+
class RPath:
|
6
7
|
"""Remote file dataclass."""
|
7
8
|
|
8
9
|
def __init__(
|
@@ -32,9 +33,9 @@ class File:
|
|
32
33
|
self.rclone = rclone
|
33
34
|
|
34
35
|
@staticmethod
|
35
|
-
def from_dict(data: dict) -> "
|
36
|
+
def from_dict(data: dict) -> "RPath":
|
36
37
|
"""Create a File from a dictionary."""
|
37
|
-
return
|
38
|
+
return RPath(
|
38
39
|
data["Path"],
|
39
40
|
data["Name"],
|
40
41
|
data["Size"],
|
@@ -45,21 +46,21 @@ class File:
|
|
45
46
|
)
|
46
47
|
|
47
48
|
@staticmethod
|
48
|
-
def from_array(data: list[dict]) -> list["
|
49
|
+
def from_array(data: list[dict]) -> list["RPath"]:
|
49
50
|
"""Create a File from a dictionary."""
|
50
|
-
out: list[
|
51
|
+
out: list[RPath] = []
|
51
52
|
for d in data:
|
52
|
-
file:
|
53
|
+
file: RPath = RPath.from_dict(d)
|
53
54
|
out.append(file)
|
54
55
|
return out
|
55
56
|
|
56
57
|
@staticmethod
|
57
|
-
def from_json_str(json_str: str) -> list["
|
58
|
+
def from_json_str(json_str: str) -> list["RPath"]:
|
58
59
|
"""Create a File from a JSON string."""
|
59
60
|
json_obj = json.loads(json_str)
|
60
61
|
if isinstance(json_obj, dict):
|
61
|
-
return [
|
62
|
-
return
|
62
|
+
return [RPath.from_dict(json_obj)]
|
63
|
+
return RPath.from_array(json_obj)
|
63
64
|
|
64
65
|
def to_json(self) -> dict:
|
65
66
|
return {
|
@@ -72,6 +73,41 @@ class File:
|
|
72
73
|
# "IsBucket": self.is_bucket,
|
73
74
|
}
|
74
75
|
|
76
|
+
def read_text(self) -> str:
|
77
|
+
"""Read the file contents as bytes.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
bytes: The file contents
|
81
|
+
|
82
|
+
Raises:
|
83
|
+
RuntimeError: If no rclone instance is associated with this file
|
84
|
+
RuntimeError: If the path represents a directory
|
85
|
+
"""
|
86
|
+
if self.rclone is None:
|
87
|
+
raise RuntimeError("No rclone instance associated with this file")
|
88
|
+
if self.is_dir:
|
89
|
+
raise RuntimeError("Cannot read a directory as bytes")
|
90
|
+
|
91
|
+
result = self.rclone._run(["cat", self.path])
|
92
|
+
return result.stdout
|
93
|
+
|
94
|
+
def read(self, dest: Path) -> None:
|
95
|
+
"""Copy the remote file to a local destination path.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
dest: Local destination path where the file will be copied
|
99
|
+
|
100
|
+
Raises:
|
101
|
+
RuntimeError: If no rclone instance is associated with this file
|
102
|
+
RuntimeError: If the path represents a directory
|
103
|
+
"""
|
104
|
+
if self.rclone is None:
|
105
|
+
raise RuntimeError("No rclone instance associated with this file")
|
106
|
+
if self.is_dir:
|
107
|
+
raise RuntimeError("Cannot read a directory as a file")
|
108
|
+
|
109
|
+
self.rclone._run(["copyto", self.path, str(dest)])
|
110
|
+
|
75
111
|
def __str__(self) -> str:
|
76
112
|
out = self.to_json()
|
77
113
|
return json.dumps(out)
|
@@ -22,13 +22,3 @@ class RcloneExec:
|
|
22
22
|
from rclone_api.util import rclone_execute
|
23
23
|
|
24
24
|
return rclone_execute(cmd, self.rclone_config, self.rclone_exe)
|
25
|
-
|
26
|
-
|
27
|
-
class Remote:
|
28
|
-
"""Remote dataclass."""
|
29
|
-
|
30
|
-
def __init__(self, name: str) -> None:
|
31
|
-
self.name = name
|
32
|
-
|
33
|
-
def __str__(self) -> str:
|
34
|
-
return f"{self.name}:"
|
@@ -17,8 +17,12 @@ upload_package.sh
|
|
17
17
|
.vscode/tasks.json
|
18
18
|
src/rclone_api/__init__.py
|
19
19
|
src/rclone_api/cli.py
|
20
|
+
src/rclone_api/config.py
|
21
|
+
src/rclone_api/dir.py
|
20
22
|
src/rclone_api/file.py
|
21
23
|
src/rclone_api/rclone.py
|
24
|
+
src/rclone_api/remote.py
|
25
|
+
src/rclone_api/rpath.py
|
22
26
|
src/rclone_api/types.py
|
23
27
|
src/rclone_api/util.py
|
24
28
|
src/rclone_api.egg-info/PKG-INFO
|
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
|