rclone-api 1.5.33__py3-none-any.whl → 1.5.35__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/config.py +153 -120
- rclone_api/fs.py +15 -2
- {rclone_api-1.5.33.dist-info → rclone_api-1.5.35.dist-info}/METADATA +1 -1
- {rclone_api-1.5.33.dist-info → rclone_api-1.5.35.dist-info}/RECORD +8 -8
- {rclone_api-1.5.33.dist-info → rclone_api-1.5.35.dist-info}/WHEEL +0 -0
- {rclone_api-1.5.33.dist-info → rclone_api-1.5.35.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.33.dist-info → rclone_api-1.5.35.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.33.dist-info → rclone_api-1.5.35.dist-info}/top_level.txt +0 -0
rclone_api/config.py
CHANGED
@@ -1,120 +1,153 @@
|
|
1
|
-
import os
|
2
|
-
from dataclasses import dataclass, field
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Any, Dict, List
|
5
|
-
|
6
|
-
|
7
|
-
@dataclass
|
8
|
-
class Section:
|
9
|
-
name: str
|
10
|
-
data: Dict[str, str] = field(default_factory=dict)
|
11
|
-
|
12
|
-
def add(self, key: str, value: str) -> None:
|
13
|
-
self.data[key] = value
|
14
|
-
|
15
|
-
def type(self) -> str:
|
16
|
-
return self.data["type"]
|
17
|
-
|
18
|
-
def provider(self) -> str | None:
|
19
|
-
return self.data.get("provider")
|
20
|
-
|
21
|
-
def access_key_id(self) -> str:
|
22
|
-
if "access_key_id" in self.data:
|
23
|
-
return self.data["access_key_id"]
|
24
|
-
elif "account" in self.data:
|
25
|
-
return self.data["account"]
|
26
|
-
raise KeyError("No access key found")
|
27
|
-
|
28
|
-
def secret_access_key(self) -> str:
|
29
|
-
# return self.data["secret_access_key"]
|
30
|
-
if "secret_access_key" in self.data:
|
31
|
-
return self.data["secret_access_key"]
|
32
|
-
elif "key" in self.data:
|
33
|
-
return self.data["key"]
|
34
|
-
raise KeyError("No secret access key found")
|
35
|
-
|
36
|
-
def endpoint(self) -> str | None:
|
37
|
-
return self.data.get("endpoint")
|
38
|
-
|
39
|
-
|
40
|
-
@dataclass
|
41
|
-
class Parsed:
|
42
|
-
# sections: List[ParsedSection]
|
43
|
-
sections: dict[str, Section]
|
44
|
-
|
45
|
-
@staticmethod
|
46
|
-
def parse(content: str) -> "Parsed":
|
47
|
-
return parse_rclone_config(content)
|
48
|
-
|
49
|
-
|
50
|
-
@dataclass
|
51
|
-
class Config:
|
52
|
-
"""Rclone configuration dataclass."""
|
53
|
-
|
54
|
-
text: str
|
55
|
-
|
56
|
-
def parse(self) -> Parsed:
|
57
|
-
return Parsed.parse(self.text)
|
58
|
-
|
59
|
-
|
60
|
-
def find_conf_file(rclone: Any | None = None) -> Path | None:
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
if
|
72
|
-
return
|
73
|
-
if
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
1
|
+
import os
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Dict, List
|
5
|
+
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class Section:
|
9
|
+
name: str
|
10
|
+
data: Dict[str, str] = field(default_factory=dict)
|
11
|
+
|
12
|
+
def add(self, key: str, value: str) -> None:
|
13
|
+
self.data[key] = value
|
14
|
+
|
15
|
+
def type(self) -> str:
|
16
|
+
return self.data["type"]
|
17
|
+
|
18
|
+
def provider(self) -> str | None:
|
19
|
+
return self.data.get("provider")
|
20
|
+
|
21
|
+
def access_key_id(self) -> str:
|
22
|
+
if "access_key_id" in self.data:
|
23
|
+
return self.data["access_key_id"]
|
24
|
+
elif "account" in self.data:
|
25
|
+
return self.data["account"]
|
26
|
+
raise KeyError("No access key found")
|
27
|
+
|
28
|
+
def secret_access_key(self) -> str:
|
29
|
+
# return self.data["secret_access_key"]
|
30
|
+
if "secret_access_key" in self.data:
|
31
|
+
return self.data["secret_access_key"]
|
32
|
+
elif "key" in self.data:
|
33
|
+
return self.data["key"]
|
34
|
+
raise KeyError("No secret access key found")
|
35
|
+
|
36
|
+
def endpoint(self) -> str | None:
|
37
|
+
return self.data.get("endpoint")
|
38
|
+
|
39
|
+
|
40
|
+
@dataclass
|
41
|
+
class Parsed:
|
42
|
+
# sections: List[ParsedSection]
|
43
|
+
sections: dict[str, Section]
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def parse(content: str) -> "Parsed":
|
47
|
+
return parse_rclone_config(content)
|
48
|
+
|
49
|
+
|
50
|
+
@dataclass
|
51
|
+
class Config:
|
52
|
+
"""Rclone configuration dataclass."""
|
53
|
+
|
54
|
+
text: str
|
55
|
+
|
56
|
+
def parse(self) -> Parsed:
|
57
|
+
return Parsed.parse(self.text)
|
58
|
+
|
59
|
+
|
60
|
+
def find_conf_file(rclone: Any | None = None) -> Path | None:
|
61
|
+
import subprocess
|
62
|
+
|
63
|
+
from rclone_api import Rclone
|
64
|
+
from rclone_api.rclone_impl import RcloneImpl
|
65
|
+
|
66
|
+
# if os.environ.get("RCLONE_CONFIG"):
|
67
|
+
# return Path(os.environ["RCLONE_CONFIG"])
|
68
|
+
# return None
|
69
|
+
# rclone_conf = rclone_conf or Path.cwd() / "rclone.conf"
|
70
|
+
|
71
|
+
if os.environ.get("RCLONE_CONFIG"):
|
72
|
+
return Path(os.environ["RCLONE_CONFIG"])
|
73
|
+
if (conf := Path.cwd() / "rclone.conf").exists():
|
74
|
+
return conf
|
75
|
+
|
76
|
+
if rclone is None:
|
77
|
+
from rclone_api.install import rclone_download
|
78
|
+
|
79
|
+
err = rclone_download(Path("."))
|
80
|
+
if isinstance(err, Exception):
|
81
|
+
import warnings
|
82
|
+
|
83
|
+
warnings.warn(f"rclone_download failed: {err}")
|
84
|
+
return None
|
85
|
+
cmd_list: list[str] = [
|
86
|
+
"rclone",
|
87
|
+
"config",
|
88
|
+
"paths",
|
89
|
+
]
|
90
|
+
subproc: subprocess.CompletedProcess = subprocess.run(
|
91
|
+
args=cmd_list,
|
92
|
+
shell=True,
|
93
|
+
capture_output=True,
|
94
|
+
text=True,
|
95
|
+
)
|
96
|
+
if subproc.returncode == 0:
|
97
|
+
stdout = subproc.stdout
|
98
|
+
for line in stdout.splitlines():
|
99
|
+
parts = line.split(":", 1)
|
100
|
+
if len(parts) == 2:
|
101
|
+
_, value = parts
|
102
|
+
value = value.strip()
|
103
|
+
value_path = Path(value.strip())
|
104
|
+
if value_path.exists():
|
105
|
+
return value_path
|
106
|
+
else:
|
107
|
+
if isinstance(rclone, Rclone):
|
108
|
+
rclone = rclone.impl
|
109
|
+
else:
|
110
|
+
assert isinstance(rclone, RcloneImpl)
|
111
|
+
rclone_impl: RcloneImpl = rclone
|
112
|
+
assert isinstance(rclone_impl, RcloneImpl)
|
113
|
+
paths_or_err = rclone_impl.config_paths()
|
114
|
+
if isinstance(paths_or_err, Exception):
|
115
|
+
return None
|
116
|
+
paths = paths_or_err
|
117
|
+
path: Path
|
118
|
+
for path in paths:
|
119
|
+
if path.exists():
|
120
|
+
return path
|
121
|
+
return None
|
122
|
+
|
123
|
+
|
124
|
+
def parse_rclone_config(content: str) -> Parsed:
|
125
|
+
"""
|
126
|
+
Parses an rclone configuration file and returns a list of RcloneConfigSection objects.
|
127
|
+
|
128
|
+
Each section in the file starts with a line like [section_name]
|
129
|
+
followed by key=value pairs.
|
130
|
+
"""
|
131
|
+
sections: List[Section] = []
|
132
|
+
current_section: Section | None = None
|
133
|
+
|
134
|
+
lines = content.splitlines()
|
135
|
+
for line in lines:
|
136
|
+
line = line.strip()
|
137
|
+
# Skip empty lines and comments (assumed to start with '#' or ';')
|
138
|
+
if not line or line.startswith(("#", ";")):
|
139
|
+
continue
|
140
|
+
# New section header detected
|
141
|
+
if line.startswith("[") and line.endswith("]"):
|
142
|
+
section_name = line[1:-1].strip()
|
143
|
+
current_section = Section(name=section_name)
|
144
|
+
sections.append(current_section)
|
145
|
+
elif "=" in line and current_section is not None:
|
146
|
+
# Parse key and value, splitting only on the first '=' found
|
147
|
+
key, value = line.split("=", 1)
|
148
|
+
current_section.add(key.strip(), value.strip())
|
149
|
+
|
150
|
+
data: dict[str, Section] = {}
|
151
|
+
for section in sections:
|
152
|
+
data[section.name] = section
|
153
|
+
return Parsed(sections=data)
|
rclone_api/fs.py
CHANGED
@@ -103,6 +103,7 @@ class RemoteFS(FS):
|
|
103
103
|
super().__init__()
|
104
104
|
self.src = src
|
105
105
|
self.shutdown = False
|
106
|
+
self.server: HttpServer | None = None
|
106
107
|
if rclone_conf is None:
|
107
108
|
from rclone_api.config import find_conf_file
|
108
109
|
|
@@ -111,7 +112,7 @@ class RemoteFS(FS):
|
|
111
112
|
raise FileNotFoundError("rclone.conf not found")
|
112
113
|
self.rclone_conf = rclone_conf
|
113
114
|
self.rclone: Rclone = Rclone(rclone_conf)
|
114
|
-
self.server
|
115
|
+
self.server = self.rclone.serve_http(src=src)
|
115
116
|
|
116
117
|
def root(self) -> "FSPath":
|
117
118
|
return FSPath(self, self.src)
|
@@ -144,6 +145,9 @@ class RemoteFS(FS):
|
|
144
145
|
self.rclone.write_bytes(data, path)
|
145
146
|
|
146
147
|
def exists(self, path: Path | str) -> bool:
|
148
|
+
from rclone_api.http_server import HttpServer
|
149
|
+
|
150
|
+
assert isinstance(self.server, HttpServer)
|
147
151
|
path = self._to_str(path)
|
148
152
|
dst_rel = self._to_remote_path(path)
|
149
153
|
return self.server.exists(dst_rel)
|
@@ -156,17 +160,26 @@ class RemoteFS(FS):
|
|
156
160
|
return None
|
157
161
|
|
158
162
|
def is_dir(self, path: Path | str) -> bool:
|
163
|
+
from rclone_api.http_server import HttpServer
|
164
|
+
|
165
|
+
assert isinstance(self.server, HttpServer)
|
159
166
|
path = self._to_remote_path(path)
|
160
167
|
err = self.server.list(path)
|
161
168
|
return isinstance(err, list)
|
162
169
|
|
163
170
|
def is_file(self, path: Path | str) -> bool:
|
171
|
+
from rclone_api.http_server import HttpServer
|
172
|
+
|
173
|
+
assert isinstance(self.server, HttpServer)
|
164
174
|
path = self._to_remote_path(path)
|
165
175
|
err = self.server.list(path)
|
166
176
|
# Make faster.
|
167
177
|
return isinstance(err, Exception) and self.exists(path)
|
168
178
|
|
169
179
|
def ls(self, path: Path | str) -> list[str]:
|
180
|
+
from rclone_api.http_server import HttpServer
|
181
|
+
|
182
|
+
assert isinstance(self.server, HttpServer)
|
170
183
|
path = self._to_remote_path(path)
|
171
184
|
err = self.server.list(path)
|
172
185
|
if isinstance(err, Exception):
|
@@ -177,7 +190,7 @@ class RemoteFS(FS):
|
|
177
190
|
return FSPath(self, path)
|
178
191
|
|
179
192
|
def dispose(self) -> None:
|
180
|
-
if self.shutdown:
|
193
|
+
if self.shutdown or not self.server:
|
181
194
|
return
|
182
195
|
self.shutdown = True
|
183
196
|
self.server.shutdown()
|
@@ -1,7 +1,7 @@
|
|
1
1
|
rclone_api/__init__.py,sha256=KxOU2UGnoqw1EDHwugFTHHgccfhq22trGiRDwX5Dsng,34086
|
2
2
|
rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
|
3
3
|
rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
|
4
|
-
rclone_api/config.py,sha256=
|
4
|
+
rclone_api/config.py,sha256=tahrSngQIX56jutiyMdk-TVmK8lMrLJ5H5rDsdFQRtM,4743
|
5
5
|
rclone_api/convert.py,sha256=Mx9Qo7zhkOedJd8LdhPvNGHp8znJzOk4f_2KWnoGc78,1012
|
6
6
|
rclone_api/deprecated.py,sha256=qWKpnZdYcBK7YQZKuVoWWXDwi-uqiAtbjgPcci_efow,590
|
7
7
|
rclone_api/diff.py,sha256=tMoJMAGmLSE6Q_7QhPf6PnCzb840djxMZtDmhc2GlGQ,5227
|
@@ -13,7 +13,7 @@ rclone_api/file_item.py,sha256=cH-AQYsxedhNPp4c8NHY1ad4Z7St4yf_VGbmiGD59no,1770
|
|
13
13
|
rclone_api/file_part.py,sha256=i6ByS5_sae8Eba-4imBVTxd-xKC8ExWy7NR8QGr0ors,6155
|
14
14
|
rclone_api/file_stream.py,sha256=_W3qnwCuigqA0hzXl2q5pAxSZDRaUSwet4BkT0lpnzs,1431
|
15
15
|
rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
|
16
|
-
rclone_api/fs.py,sha256=
|
16
|
+
rclone_api/fs.py,sha256=eCiY6Jw8tVC1Kcfr0334Mj29BpeBNF7OtebAPLZdY-0,8386
|
17
17
|
rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
|
18
18
|
rclone_api/http_server.py,sha256=ZdL-rGaq0zIjcaIiRIbPBQ4OIWZ7dCu71aq0nRlKtY4,11686
|
19
19
|
rclone_api/install.py,sha256=Xb1BRn8rQcSpSd4dzmvIOELP2zM2DytUeIZ6jzv738A,2893
|
@@ -54,9 +54,9 @@ rclone_api/s3/multipart/upload_parts_inline.py,sha256=V7syKjFyVIe4U9Ahl5XgqVTzt9
|
|
54
54
|
rclone_api/s3/multipart/upload_parts_resumable.py,sha256=6-nlMclS8jyVvMvFbQDcZOX9MY1WbCcKA_s9bwuYxnk,9793
|
55
55
|
rclone_api/s3/multipart/upload_parts_server_side_merge.py,sha256=Fp2pdrs5dONQI9LkfNolgAGj1-Z2V1SsRd0r0sreuXI,18040
|
56
56
|
rclone_api/s3/multipart/upload_state.py,sha256=f-Aq2NqtAaMUMhYitlICSNIxCKurWAl2gDEUVizLIqw,6019
|
57
|
-
rclone_api-1.5.
|
58
|
-
rclone_api-1.5.
|
59
|
-
rclone_api-1.5.
|
60
|
-
rclone_api-1.5.
|
61
|
-
rclone_api-1.5.
|
62
|
-
rclone_api-1.5.
|
57
|
+
rclone_api-1.5.35.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
58
|
+
rclone_api-1.5.35.dist-info/METADATA,sha256=bb59EsCikl2fAIPakFqltagrSVQdumz2Ip7KZTgHpHA,37155
|
59
|
+
rclone_api-1.5.35.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
60
|
+
rclone_api-1.5.35.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
|
61
|
+
rclone_api-1.5.35.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
|
62
|
+
rclone_api-1.5.35.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|