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 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
- from rclone_api import Rclone
62
- from rclone_api.rclone_impl import RcloneImpl
63
-
64
- # if os.environ.get("RCLONE_CONFIG"):
65
- # return Path(os.environ["RCLONE_CONFIG"])
66
- # return None
67
- # rclone_conf = rclone_conf or Path.cwd() / "rclone.conf"
68
-
69
- if os.environ.get("RCLONE_CONFIG"):
70
- return Path(os.environ["RCLONE_CONFIG"])
71
- if (conf := Path.cwd() / "rclone.conf").exists():
72
- return conf
73
- if rclone is not None:
74
- if isinstance(rclone, Rclone):
75
- rclone = rclone.impl
76
- else:
77
- assert isinstance(rclone, RcloneImpl)
78
- rclone_impl: RcloneImpl = rclone
79
- assert isinstance(rclone_impl, RcloneImpl)
80
- paths_or_err = rclone_impl.config_paths()
81
- if isinstance(paths_or_err, Exception):
82
- return None
83
- paths = paths_or_err
84
- path: Path
85
- for path in paths:
86
- if path.exists():
87
- return path
88
- return None
89
-
90
-
91
- def parse_rclone_config(content: str) -> Parsed:
92
- """
93
- Parses an rclone configuration file and returns a list of RcloneConfigSection objects.
94
-
95
- Each section in the file starts with a line like [section_name]
96
- followed by key=value pairs.
97
- """
98
- sections: List[Section] = []
99
- current_section: Section | None = None
100
-
101
- lines = content.splitlines()
102
- for line in lines:
103
- line = line.strip()
104
- # Skip empty lines and comments (assumed to start with '#' or ';')
105
- if not line or line.startswith(("#", ";")):
106
- continue
107
- # New section header detected
108
- if line.startswith("[") and line.endswith("]"):
109
- section_name = line[1:-1].strip()
110
- current_section = Section(name=section_name)
111
- sections.append(current_section)
112
- elif "=" in line and current_section is not None:
113
- # Parse key and value, splitting only on the first '=' found
114
- key, value = line.split("=", 1)
115
- current_section.add(key.strip(), value.strip())
116
-
117
- data: dict[str, Section] = {}
118
- for section in sections:
119
- data[section.name] = section
120
- return Parsed(sections=data)
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: HttpServer = self.rclone.serve_http(src=src)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rclone_api
3
- Version: 1.5.33
3
+ Version: 1.5.35
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  License: BSD 3-Clause License
@@ -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=uOf2d-T_Df-ELCA74hdBMO-DbmshuTw3gL_d4nP7bLY,3610
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=of8Wn_kjIZE39tor5GYqgKt1OJM24uahFEGDHHQUyAM,7909
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.33.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
58
- rclone_api-1.5.33.dist-info/METADATA,sha256=rDdeLpWCrkDBZNdVjE4f23KslC0YBQj6RbuUg6SDMrg,37155
59
- rclone_api-1.5.33.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
60
- rclone_api-1.5.33.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
61
- rclone_api-1.5.33.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
62
- rclone_api-1.5.33.dist-info/RECORD,,
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,,