rclone-api 1.0.11__py2.py3-none-any.whl → 1.0.13__py2.py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- rclone_api/dir.py +9 -2
- rclone_api/file.py +1 -4
- rclone_api/rclone.py +97 -90
- rclone_api/rpath.py +15 -8
- rclone_api/util.py +54 -2
- rclone_api-1.0.13.dist-info/METADATA +34 -0
- {rclone_api-1.0.11.dist-info → rclone_api-1.0.13.dist-info}/RECORD +10 -10
- rclone_api-1.0.11.dist-info/METADATA +0 -36
- {rclone_api-1.0.11.dist-info → rclone_api-1.0.13.dist-info}/LICENSE +0 -0
- {rclone_api-1.0.11.dist-info → rclone_api-1.0.13.dist-info}/WHEEL +0 -0
- {rclone_api-1.0.11.dist-info → rclone_api-1.0.13.dist-info}/top_level.txt +0 -0
rclone_api/dir.py
CHANGED
@@ -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
|
|
rclone_api/file.py
CHANGED
rclone_api/rclone.py
CHANGED
@@ -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)
|
rclone_api/rpath.py
CHANGED
@@ -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}"
|
rclone_api/util.py
CHANGED
@@ -1,10 +1,61 @@
|
|
1
|
+
import os
|
1
2
|
import shutil
|
2
3
|
import subprocess
|
3
4
|
from pathlib import Path
|
4
5
|
from tempfile import TemporaryDirectory
|
6
|
+
from typing import Any
|
5
7
|
|
8
|
+
from rclone_api.dir import Dir
|
9
|
+
from rclone_api.remote import Remote
|
10
|
+
from rclone_api.rpath import RPath
|
6
11
|
from rclone_api.types import Config
|
7
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
|
+
|
8
59
|
|
9
60
|
def get_rclone_exe(rclone_exe: Path | None) -> Path:
|
10
61
|
if rclone_exe is None:
|
@@ -20,10 +71,11 @@ def rclone_execute(
|
|
20
71
|
cmd: list[str],
|
21
72
|
rclone_conf: Path | Config,
|
22
73
|
rclone_exe: Path,
|
23
|
-
verbose: bool =
|
74
|
+
verbose: bool | None = None,
|
24
75
|
) -> subprocess.CompletedProcess:
|
25
|
-
print(subprocess.list2cmdline(cmd))
|
26
76
|
tempdir: TemporaryDirectory | None = None
|
77
|
+
verbose = _get_verbose(verbose)
|
78
|
+
assert verbose is not None
|
27
79
|
|
28
80
|
try:
|
29
81
|
if isinstance(rclone_conf, Config):
|
@@ -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`
|
@@ -1,18 +1,18 @@
|
|
1
1
|
rclone_api/__init__.py,sha256=YH7KQaPwUiJWJiRf0NRKD7XHhMXsxWdXDjt9WLSwdjA,265
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
3
|
rclone_api/config.py,sha256=UujjDNcpwgipgdyFPMhC3zikvlsrurUvvZiwXm5NlPg,471
|
4
|
-
rclone_api/dir.py,sha256=
|
4
|
+
rclone_api/dir.py,sha256=tu0-Yy1s3O1MVY7Nkb6NOh6t9dXJ9DQvqOZVlWIexrI,1629
|
5
5
|
rclone_api/dir_listing.py,sha256=NWleKHCCRW7_eh9JfRwE6r3QbjmiHD5ZEGQcd2vm4uY,458
|
6
|
-
rclone_api/file.py,sha256=
|
7
|
-
rclone_api/rclone.py,sha256=
|
6
|
+
rclone_api/file.py,sha256=nJwJT7v2KwY_8g7UB3Y7O6pFfHn3nri9dJ3GS-cN3EE,874
|
7
|
+
rclone_api/rclone.py,sha256=upMqUxYKC3LCjHlhI66_IdOD1t5PKo7V64sSdw0vAlk,3074
|
8
8
|
rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
|
9
|
-
rclone_api/rpath.py,sha256=
|
9
|
+
rclone_api/rpath.py,sha256=QHD5zDjDIkivs3kCRYtTL8mP-DspXIvBVYWHSf_Y6Rg,2263
|
10
10
|
rclone_api/types.py,sha256=Yp15HrjwZonSQhE323R0WP7fA4NWqKjAfM7z3OwHpWI,518
|
11
|
-
rclone_api/util.py,sha256=
|
11
|
+
rclone_api/util.py,sha256=xYUj0B9W5HDauTHh7Z8cnEQsTEuXvRcVNLtk3ZanlGM,2894
|
12
12
|
rclone_api/walk.py,sha256=3GKnu9P5aPNiftfoi52U7wo1wixZxwxdqqS0_IlMGCE,2816
|
13
13
|
rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
|
14
|
-
rclone_api-1.0.
|
15
|
-
rclone_api-1.0.
|
16
|
-
rclone_api-1.0.
|
17
|
-
rclone_api-1.0.
|
18
|
-
rclone_api-1.0.
|
14
|
+
rclone_api-1.0.13.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
15
|
+
rclone_api-1.0.13.dist-info/METADATA,sha256=yEL4GNzAc-CwJt-rJVmDAl08L13RqytSSs1ZQR4KMA0,1375
|
16
|
+
rclone_api-1.0.13.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
|
17
|
+
rclone_api-1.0.13.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
18
|
+
rclone_api-1.0.13.dist-info/RECORD,,
|
@@ -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
|