rclone-api 1.0.1__py2.py3-none-any.whl → 1.0.3__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 CHANGED
@@ -1,3 +1,7 @@
1
- from .rclone import Rclone, RcloneConfig, RemoteFile
1
+ from .dir import Dir
2
+ from .file import File
3
+ from .rclone import Rclone
4
+ from .rpath import RPath
5
+ from .types import Config, Remote
2
6
 
3
- __all__ = ["Rclone", "RemoteFile", "RcloneConfig"]
7
+ __all__ = ["Rclone", "File", "Config", "Remote", "Dir", "RPath"]
rclone_api/dir.py ADDED
@@ -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)
rclone_api/file.py ADDED
@@ -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)
rclone_api/rclone.py CHANGED
@@ -1,160 +1,56 @@
1
- """
2
- Unit test file.
3
- """
4
-
5
- import json
6
- import shutil
7
- import subprocess
8
- from dataclasses import dataclass
9
- from pathlib import Path
10
- from tempfile import TemporaryDirectory
11
-
12
-
13
- @dataclass
14
- class RcloneConfig:
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
131
-
132
-
133
- class Rclone:
134
- def __init__(
135
- self, rclone_conf: Path | RcloneConfig, rclone_exe: Path | None = None
136
- ) -> None:
137
- if isinstance(rclone_conf, Path):
138
- if not rclone_conf.exists():
139
- raise ValueError(f"Rclone config file not found: {rclone_conf}")
140
- self._exec = RcloneExec(rclone_conf, _get_rclone_exe(rclone_exe))
141
-
142
- def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
143
- return self._exec.execute(cmd)
144
-
145
- def ls(self, path: str) -> list[RemoteFile]:
146
- cmd = ["lsjson", path]
147
- cp = self._run(cmd)
148
- text = cp.stdout
149
- out: list[RemoteFile] = RemoteFile.from_json_str(text)
150
- return out
151
-
152
- def listremotes(self) -> list[str]:
153
- cmd = ["listremotes"]
154
- cp = self._run(cmd)
155
- text = cp.stdout
156
- out = text.splitlines()
157
- out = [o.strip() for o in out]
158
- # strip out ":" from the end
159
- out = [o.replace(":", "") for o in out]
160
- return out
1
+ """
2
+ Unit test file.
3
+ """
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from rclone_api.rpath import RPath
9
+ from rclone_api.types import Config, RcloneExec, Remote
10
+ from rclone_api.util import get_rclone_exe
11
+
12
+
13
+ class Rclone:
14
+ def __init__(
15
+ self, rclone_conf: Path | Config, rclone_exe: Path | None = None
16
+ ) -> None:
17
+ if isinstance(rclone_conf, Path):
18
+ if not rclone_conf.exists():
19
+ raise ValueError(f"Rclone config file not found: {rclone_conf}")
20
+ self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
21
+
22
+ def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
23
+ return self._exec.execute(cmd)
24
+
25
+ def ls(self, path: str | Remote, max_depth: int = 0) -> list[RPath]:
26
+ """List files in the given path.
27
+
28
+ Args:
29
+ path: Remote path or Remote object to list
30
+ max_depth: Maximum recursion depth (0 means no recursion)
31
+
32
+ Returns:
33
+ List of File objects found at the path
34
+ """
35
+ cmd = ["lsjson"]
36
+ if max_depth > 0:
37
+ cmd.extend(["--recursive", "--max-depth", str(max_depth)])
38
+ cmd.append(str(path))
39
+
40
+ cp = self._run(cmd)
41
+ text = cp.stdout
42
+ out: list[RPath] = RPath.from_json_str(text)
43
+ for o in out:
44
+ o.set_rclone(self)
45
+ return out
46
+
47
+ def listremotes(self) -> list[Remote]:
48
+ cmd = ["listremotes"]
49
+ cp = self._run(cmd)
50
+ text: str = cp.stdout
51
+ tmp = text.splitlines()
52
+ tmp = [t.strip() for t in tmp]
53
+ # strip out ":" from the end
54
+ tmp = [t.replace(":", "") for t in tmp]
55
+ out = [Remote(name=t) for t in tmp]
56
+ return out
rclone_api/rpath.py ADDED
@@ -0,0 +1,113 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+
6
+ class RPath:
7
+ """Remote file dataclass."""
8
+
9
+ def __init__(
10
+ self,
11
+ path: str,
12
+ name: str,
13
+ size: int,
14
+ mime_type: str,
15
+ mod_time: str,
16
+ is_dir: bool,
17
+ ) -> None:
18
+ from rclone_api.rclone import Rclone
19
+
20
+ self.path = path
21
+ self.name = name
22
+ self.size = size
23
+ self.mime_type = mime_type
24
+ self.mod_time = mod_time
25
+ self.is_dir = is_dir
26
+ self.rclone: Rclone | None = None
27
+
28
+ def set_rclone(self, rclone: Any) -> None:
29
+ """Set the rclone object."""
30
+ from rclone_api.rclone import Rclone
31
+
32
+ assert isinstance(rclone, Rclone)
33
+ self.rclone = rclone
34
+
35
+ @staticmethod
36
+ def from_dict(data: dict) -> "RPath":
37
+ """Create a File from a dictionary."""
38
+ return RPath(
39
+ data["Path"],
40
+ data["Name"],
41
+ data["Size"],
42
+ data["MimeType"],
43
+ data["ModTime"],
44
+ data["IsDir"],
45
+ # data["IsBucket"],
46
+ )
47
+
48
+ @staticmethod
49
+ def from_array(data: list[dict]) -> list["RPath"]:
50
+ """Create a File from a dictionary."""
51
+ out: list[RPath] = []
52
+ for d in data:
53
+ file: RPath = RPath.from_dict(d)
54
+ out.append(file)
55
+ return out
56
+
57
+ @staticmethod
58
+ def from_json_str(json_str: str) -> list["RPath"]:
59
+ """Create a File from a JSON string."""
60
+ json_obj = json.loads(json_str)
61
+ if isinstance(json_obj, dict):
62
+ return [RPath.from_dict(json_obj)]
63
+ return RPath.from_array(json_obj)
64
+
65
+ def to_json(self) -> dict:
66
+ return {
67
+ "Path": self.path,
68
+ "Name": self.name,
69
+ "Size": self.size,
70
+ "MimeType": self.mime_type,
71
+ "ModTime": self.mod_time,
72
+ "IsDir": self.is_dir,
73
+ # "IsBucket": self.is_bucket,
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
+
111
+ def __str__(self) -> str:
112
+ out = self.to_json()
113
+ return json.dumps(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}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -0,0 +1,14 @@
1
+ rclone_api/__init__.py,sha256=ygZEO9GcF4UfdIDIinrfL2ExoWe3pWwKYIL8COeswe4,196
2
+ rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
3
+ rclone_api/dir.py,sha256=H83MhwabdxjBiT_3IHvAgo9taeXq6ZhOKIrpFOZwHnY,896
4
+ rclone_api/file.py,sha256=409neYPfCUKQX2w7Uw7_D0wR2PtH42srNe9u_nnOLxM,925
5
+ rclone_api/rclone.py,sha256=YlaWsWg-vtbqAOp1QbfZCIQ0KwfT3fkZQ3lZVR1nNmw,1743
6
+ rclone_api/rpath.py,sha256=UQ08-crLe4t4IK1UCqijGybByj4vzRMY0vD7b5xYzMo,3237
7
+ rclone_api/types.py,sha256=I_7WSHF0INm-XA7jEWMsFiGkpXPNmij6B5exsFUKggI,693
8
+ rclone_api/util.py,sha256=EXnijLLdFHqwoZvHrZdqeM8r6T7ad9-pN7qBN1b0I_g,1465
9
+ rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
10
+ rclone_api-1.0.3.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
11
+ rclone_api-1.0.3.dist-info/METADATA,sha256=s-dUfLdnZCfaXSouGP_XypTADEUPub24idrl79fo21w,1244
12
+ rclone_api-1.0.3.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
13
+ rclone_api-1.0.3.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
14
+ rclone_api-1.0.3.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- rclone_api/__init__.py,sha256=UXQoNzJnfYO5rPVNOva-2m9yf5xvlZGi1yBfVBwBEmI,105
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.1.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
6
- rclone_api-1.0.1.dist-info/METADATA,sha256=jIg7R8nmIKLzLXsG10HPZCks0bv_j41OUam4uYPlkyo,1244
7
- rclone_api-1.0.1.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
8
- rclone_api-1.0.1.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
9
- rclone_api-1.0.1.dist-info/RECORD,,