rclone-api 1.0.0__py2.py3-none-any.whl → 1.0.2__py2.py3-none-any.whl
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/__init__.py +4 -0
- rclone_api/file.py +77 -0
- rclone_api/rclone.py +16 -133
- rclone_api/types.py +34 -0
- rclone_api/util.py +49 -0
- {rclone_api-1.0.0.dist-info → rclone_api-1.0.2.dist-info}/METADATA +1 -1
- rclone_api-1.0.2.dist-info/RECORD +12 -0
- rclone_api-1.0.0.dist-info/RECORD +0 -9
- {rclone_api-1.0.0.dist-info → rclone_api-1.0.2.dist-info}/LICENSE +0 -0
- {rclone_api-1.0.0.dist-info → rclone_api-1.0.2.dist-info}/WHEEL +0 -0
- {rclone_api-1.0.0.dist-info → rclone_api-1.0.2.dist-info}/top_level.txt +0 -0
rclone_api/__init__.py
CHANGED
rclone_api/file.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
|
5
|
+
class File:
|
6
|
+
"""Remote file dataclass."""
|
7
|
+
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
path: str,
|
11
|
+
name: str,
|
12
|
+
size: int,
|
13
|
+
mime_type: str,
|
14
|
+
mod_time: str,
|
15
|
+
is_dir: bool,
|
16
|
+
) -> None:
|
17
|
+
from rclone_api.rclone import Rclone
|
18
|
+
|
19
|
+
self.path = path
|
20
|
+
self.name = name
|
21
|
+
self.size = size
|
22
|
+
self.mime_type = mime_type
|
23
|
+
self.mod_time = mod_time
|
24
|
+
self.is_dir = is_dir
|
25
|
+
self.rclone: Rclone | None = None
|
26
|
+
|
27
|
+
def set_rclone(self, rclone: Any) -> None:
|
28
|
+
"""Set the rclone object."""
|
29
|
+
from rclone_api.rclone import Rclone
|
30
|
+
|
31
|
+
assert isinstance(rclone, Rclone)
|
32
|
+
self.rclone = rclone
|
33
|
+
|
34
|
+
@staticmethod
|
35
|
+
def from_dict(data: dict) -> "File":
|
36
|
+
"""Create a File from a dictionary."""
|
37
|
+
return File(
|
38
|
+
data["Path"],
|
39
|
+
data["Name"],
|
40
|
+
data["Size"],
|
41
|
+
data["MimeType"],
|
42
|
+
data["ModTime"],
|
43
|
+
data["IsDir"],
|
44
|
+
# data["IsBucket"],
|
45
|
+
)
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def from_array(data: list[dict]) -> list["File"]:
|
49
|
+
"""Create a File from a dictionary."""
|
50
|
+
out: list[File] = []
|
51
|
+
for d in data:
|
52
|
+
file: File = File.from_dict(d)
|
53
|
+
out.append(file)
|
54
|
+
return out
|
55
|
+
|
56
|
+
@staticmethod
|
57
|
+
def from_json_str(json_str: str) -> list["File"]:
|
58
|
+
"""Create a File from a JSON string."""
|
59
|
+
json_obj = json.loads(json_str)
|
60
|
+
if isinstance(json_obj, dict):
|
61
|
+
return [File.from_dict(json_obj)]
|
62
|
+
return File.from_array(json_obj)
|
63
|
+
|
64
|
+
def to_json(self) -> dict:
|
65
|
+
return {
|
66
|
+
"Path": self.path,
|
67
|
+
"Name": self.name,
|
68
|
+
"Size": self.size,
|
69
|
+
"MimeType": self.mime_type,
|
70
|
+
"ModTime": self.mod_time,
|
71
|
+
"IsDir": self.is_dir,
|
72
|
+
# "IsBucket": self.is_bucket,
|
73
|
+
}
|
74
|
+
|
75
|
+
def __str__(self) -> str:
|
76
|
+
out = self.to_json()
|
77
|
+
return json.dumps(out)
|
rclone_api/rclone.py
CHANGED
@@ -2,159 +2,42 @@
|
|
2
2
|
Unit test file.
|
3
3
|
"""
|
4
4
|
|
5
|
-
import json
|
6
|
-
import shutil
|
7
5
|
import subprocess
|
8
|
-
from dataclasses import dataclass
|
9
6
|
from pathlib import Path
|
10
|
-
from tempfile import TemporaryDirectory
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
"""Rclone configuration dataclass."""
|
16
|
-
|
17
|
-
text: str
|
18
|
-
|
19
|
-
|
20
|
-
def _rclone_execute(
|
21
|
-
cmd: list[str],
|
22
|
-
rclone_conf: Path | RcloneConfig,
|
23
|
-
rclone_exe: Path,
|
24
|
-
verbose: bool = False,
|
25
|
-
) -> subprocess.CompletedProcess:
|
26
|
-
print(subprocess.list2cmdline(cmd))
|
27
|
-
tempdir: TemporaryDirectory | None = None
|
28
|
-
|
29
|
-
try:
|
30
|
-
if isinstance(rclone_conf, RcloneConfig):
|
31
|
-
tempdir = TemporaryDirectory()
|
32
|
-
tmpfile = Path(tempdir.name) / "rclone.conf"
|
33
|
-
tmpfile.write_text(rclone_conf.text, encoding="utf-8")
|
34
|
-
rclone_conf = tmpfile
|
35
|
-
cmd = (
|
36
|
-
[str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
|
37
|
-
)
|
38
|
-
if verbose:
|
39
|
-
cmd_str = subprocess.list2cmdline(cmd)
|
40
|
-
print(f"Running: {cmd_str}")
|
41
|
-
cp = subprocess.run(
|
42
|
-
cmd, capture_output=True, encoding="utf-8", check=True, shell=False
|
43
|
-
)
|
44
|
-
return cp
|
45
|
-
finally:
|
46
|
-
if tempdir:
|
47
|
-
try:
|
48
|
-
tempdir.cleanup()
|
49
|
-
except Exception as e:
|
50
|
-
print(f"Error cleaning up tempdir: {e}")
|
51
|
-
|
52
|
-
|
53
|
-
@dataclass
|
54
|
-
class RcloneExec:
|
55
|
-
"""Rclone execution dataclass."""
|
56
|
-
|
57
|
-
rclone_config: Path | RcloneConfig
|
58
|
-
rclone_exe: Path
|
59
|
-
|
60
|
-
def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
61
|
-
"""Execute rclone command."""
|
62
|
-
return _rclone_execute(cmd, self.rclone_config, self.rclone_exe)
|
63
|
-
|
64
|
-
|
65
|
-
@dataclass
|
66
|
-
class RemoteFile:
|
67
|
-
"""Remote file dataclass."""
|
68
|
-
|
69
|
-
path: str
|
70
|
-
name: str
|
71
|
-
size: int
|
72
|
-
mime_type: str
|
73
|
-
mod_time: str
|
74
|
-
is_dir: bool
|
75
|
-
# is_bucket: bool
|
76
|
-
|
77
|
-
@staticmethod
|
78
|
-
def from_dict(data: dict) -> "RemoteFile":
|
79
|
-
"""Create a RemoteFile from a dictionary."""
|
80
|
-
return RemoteFile(
|
81
|
-
data["Path"],
|
82
|
-
data["Name"],
|
83
|
-
data["Size"],
|
84
|
-
data["MimeType"],
|
85
|
-
data["ModTime"],
|
86
|
-
data["IsDir"],
|
87
|
-
# data["IsBucket"],
|
88
|
-
)
|
89
|
-
|
90
|
-
@staticmethod
|
91
|
-
def from_array(data: list[dict]) -> list["RemoteFile"]:
|
92
|
-
"""Create a RemoteFile from a dictionary."""
|
93
|
-
out: list[RemoteFile] = []
|
94
|
-
for d in data:
|
95
|
-
file: RemoteFile = RemoteFile.from_dict(d)
|
96
|
-
out.append(file)
|
97
|
-
return out
|
98
|
-
|
99
|
-
@staticmethod
|
100
|
-
def from_json_str(json_str: str) -> list["RemoteFile"]:
|
101
|
-
"""Create a RemoteFile from a JSON string."""
|
102
|
-
json_obj = json.loads(json_str)
|
103
|
-
if isinstance(json_obj, dict):
|
104
|
-
return [RemoteFile.from_dict(json_obj)]
|
105
|
-
return RemoteFile.from_array(json_obj)
|
106
|
-
|
107
|
-
def to_json(self) -> dict:
|
108
|
-
return {
|
109
|
-
"Path": self.path,
|
110
|
-
"Name": self.name,
|
111
|
-
"Size": self.size,
|
112
|
-
"MimeType": self.mime_type,
|
113
|
-
"ModTime": self.mod_time,
|
114
|
-
"IsDir": self.is_dir,
|
115
|
-
# "IsBucket": self.is_bucket,
|
116
|
-
}
|
117
|
-
|
118
|
-
def __str__(self) -> str:
|
119
|
-
out = self.to_json()
|
120
|
-
return json.dumps(out)
|
121
|
-
|
122
|
-
|
123
|
-
def _get_rclone_exe(rclone_exe: Path | None) -> Path:
|
124
|
-
if rclone_exe is None:
|
125
|
-
|
126
|
-
rclone_which_path = shutil.which("rclone")
|
127
|
-
if rclone_which_path is None:
|
128
|
-
raise ValueError("rclone executable not found")
|
129
|
-
return Path(rclone_which_path)
|
130
|
-
return rclone_exe
|
8
|
+
from rclone_api.file import File
|
9
|
+
from rclone_api.types import Config, RcloneExec, Remote
|
10
|
+
from rclone_api.util import get_rclone_exe
|
131
11
|
|
132
12
|
|
133
13
|
class Rclone:
|
134
14
|
def __init__(
|
135
|
-
self, rclone_conf: Path |
|
15
|
+
self, rclone_conf: Path | Config, rclone_exe: Path | None = None
|
136
16
|
) -> None:
|
137
17
|
if isinstance(rclone_conf, Path):
|
138
18
|
if not rclone_conf.exists():
|
139
19
|
raise ValueError(f"Rclone config file not found: {rclone_conf}")
|
140
|
-
self._exec = RcloneExec(rclone_conf,
|
20
|
+
self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
|
141
21
|
|
142
22
|
def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
143
23
|
return self._exec.execute(cmd)
|
144
24
|
|
145
|
-
def ls(self, path: str) -> list[
|
146
|
-
cmd = ["lsjson", path]
|
25
|
+
def ls(self, path: str | Remote) -> list[File]:
|
26
|
+
cmd = ["lsjson", str(path)]
|
147
27
|
cp = self._run(cmd)
|
148
28
|
text = cp.stdout
|
149
|
-
out: list[
|
29
|
+
out: list[File] = File.from_json_str(text)
|
30
|
+
for o in out:
|
31
|
+
o.set_rclone(self)
|
150
32
|
return out
|
151
33
|
|
152
|
-
def listremotes(self) -> list[
|
34
|
+
def listremotes(self) -> list[Remote]:
|
153
35
|
cmd = ["listremotes"]
|
154
36
|
cp = self._run(cmd)
|
155
|
-
text = cp.stdout
|
156
|
-
|
157
|
-
|
37
|
+
text: str = cp.stdout
|
38
|
+
tmp = text.splitlines()
|
39
|
+
tmp = [t.strip() for t in tmp]
|
158
40
|
# strip out ":" from the end
|
159
|
-
|
41
|
+
tmp = [t.replace(":", "") for t in tmp]
|
42
|
+
out = [Remote(name=t) for t in tmp]
|
160
43
|
return out
|
rclone_api/types.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
import subprocess
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class Config:
|
8
|
+
"""Rclone configuration dataclass."""
|
9
|
+
|
10
|
+
text: str
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class RcloneExec:
|
15
|
+
"""Rclone execution dataclass."""
|
16
|
+
|
17
|
+
rclone_config: Path | Config
|
18
|
+
rclone_exe: Path
|
19
|
+
|
20
|
+
def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
21
|
+
"""Execute rclone command."""
|
22
|
+
from rclone_api.util import rclone_execute
|
23
|
+
|
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}:"
|
rclone_api/util.py
ADDED
@@ -0,0 +1,49 @@
|
|
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}")
|
@@ -0,0 +1,12 @@
|
|
1
|
+
rclone_api/__init__.py,sha256=be1jguNsC4mSSJRrHeCJoC0rN8OnI4kEVpZmmKlq-1w,117
|
2
|
+
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
|
+
rclone_api/file.py,sha256=7XirDJx3k37EBX78j_Y13rCs2lnGB8ID7A-s5kztQ2E,1999
|
4
|
+
rclone_api/rclone.py,sha256=KXnIPbl-tvj8ZiG3HVhahCbZdPHEJG-5tbrN7eazX_g,1283
|
5
|
+
rclone_api/types.py,sha256=I_7WSHF0INm-XA7jEWMsFiGkpXPNmij6B5exsFUKggI,693
|
6
|
+
rclone_api/util.py,sha256=EXnijLLdFHqwoZvHrZdqeM8r6T7ad9-pN7qBN1b0I_g,1465
|
7
|
+
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
8
|
+
rclone_api-1.0.2.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
9
|
+
rclone_api-1.0.2.dist-info/METADATA,sha256=4sPbtb1DX5n0gQaDoPDLHELayret5vUzeWl902l_lWE,1244
|
10
|
+
rclone_api-1.0.2.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
|
11
|
+
rclone_api-1.0.2.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
12
|
+
rclone_api-1.0.2.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
rclone_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
|
-
rclone_api/rclone.py,sha256=FszyEwR-z4OSiztS-nhAXlXKLpmjP5LG4fm2YHCTWvo,4439
|
4
|
-
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
5
|
-
rclone_api-1.0.0.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
6
|
-
rclone_api-1.0.0.dist-info/METADATA,sha256=dIogBa5fkcBwMguFEXnLPGLxgj_X0LtbFkpQUEzBZew,1244
|
7
|
-
rclone_api-1.0.0.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
|
8
|
-
rclone_api-1.0.0.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
9
|
-
rclone_api-1.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|