rclone-api 1.0.76__py2.py3-none-any.whl → 1.0.77__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
@@ -9,7 +9,7 @@ from .process import Process
9
9
  from .rclone import Rclone, rclone_verbose
10
10
  from .remote import Remote
11
11
  from .rpath import RPath
12
- from .types import ListingOption, Order
12
+ from .types import GroupingOption, ListingOption, Order, SizeResult
13
13
 
14
14
  __all__ = [
15
15
  "Rclone",
@@ -29,4 +29,6 @@ __all__ = [
29
29
  "ListingOption",
30
30
  "Order",
31
31
  "ListingOption",
32
+ "GroupingOption",
33
+ "SizeResult",
32
34
  ]
@@ -47,3 +47,14 @@ class CompletedProcess:
47
47
  if rtn != 0:
48
48
  return rtn
49
49
  return 0
50
+
51
+ def __str__(self) -> str:
52
+
53
+ cmd_strs: list[str] = []
54
+ rtn_cods: list[int] = []
55
+ for cp in self.completed:
56
+ cmd_strs.append(subprocess.list2cmdline(cp.args))
57
+ rtn_cods.append(cp.returncode)
58
+ msg = f"CompletedProcess: {len(cmd_strs)} commands\n"
59
+ msg += "\n".join([f"{cmd} -> {rtn}" for cmd, rtn in zip(cmd_strs, rtn_cods)])
60
+ return msg
rclone_api/file.py CHANGED
@@ -40,10 +40,17 @@ class File:
40
40
 
41
41
  def to_string(self, include_remote: bool = True) -> str:
42
42
  """Convert the File to a string."""
43
- out = str(self.path)
44
- if not include_remote:
45
- _, out = out.split(":", 1)
46
- return out
43
+ # out = str(self.path)
44
+ remote = self.path.remote
45
+ rest = self.path.path
46
+ if include_remote:
47
+ return f"{remote.name}:{rest}"
48
+ return rest
49
+
50
+ @property
51
+ def size(self) -> int:
52
+ """Get the size of the file."""
53
+ return self.path.size
47
54
 
48
55
  def __str__(self) -> str:
49
56
  return str(self.path)
rclone_api/group_files.py CHANGED
@@ -9,6 +9,19 @@ class FilePathParts:
9
9
  parents: list[str]
10
10
  name: str
11
11
 
12
+ def to_string(self, include_remote: bool, include_bucket: bool) -> str:
13
+ """Convert to string, may throw for not include_bucket=False."""
14
+ parents = list(self.parents)
15
+ if not include_bucket:
16
+ parents.pop(0)
17
+ path = "/".join(parents)
18
+ if path:
19
+ path += "/"
20
+ path += self.name
21
+ if include_remote:
22
+ return f"{self.remote}{path}"
23
+ return path
24
+
12
25
 
13
26
  def parse_file(file_path: str) -> FilePathParts:
14
27
  """Parse file path into parts."""
@@ -165,4 +178,35 @@ def group_files(files: list[str], fully_qualified: bool = True) -> dict[str, lis
165
178
  return out
166
179
 
167
180
 
168
- __all__ = ["group_files"]
181
+ def group_under_remote(
182
+ files: list[str], fully_qualified: bool = True
183
+ ) -> dict[str, list[str]]:
184
+ """split between filename and remote"""
185
+ assert fully_qualified is True, "Not implemented for fully_qualified=False"
186
+ out: dict[str, list[str]] = {}
187
+ for file in files:
188
+ parsed = parse_file(file)
189
+ remote = f"{parsed.remote}:"
190
+ file_list = out.setdefault(remote, [])
191
+ file_list.append(parsed.to_string(include_remote=False, include_bucket=True))
192
+ return out
193
+
194
+
195
+ def group_under_remote_bucket(
196
+ files: list[str], fully_qualified: bool = True
197
+ ) -> dict[str, list[str]]:
198
+ """split between filename and bucket"""
199
+ assert fully_qualified is True, "Not implemented for fully_qualified=False"
200
+ out: dict[str, list[str]] = {}
201
+ for file in files:
202
+ parsed = parse_file(file)
203
+ remote = f"{parsed.remote}:"
204
+ parts = parsed.parents
205
+ bucket = parts[0]
206
+ remote_bucket = f"{remote}{bucket}"
207
+ file_list = out.setdefault(remote_bucket, [])
208
+ file_list.append(parsed.to_string(include_remote=False, include_bucket=False))
209
+ return out
210
+
211
+
212
+ __all__ = ["group_files", "group_under_remote", "group_under_remote_bucket"]
rclone_api/rclone.py CHANGED
@@ -22,11 +22,21 @@ from rclone_api.diff import DiffItem, DiffOption, diff_stream_from_running_proce
22
22
  from rclone_api.dir_listing import DirListing
23
23
  from rclone_api.exec import RcloneExec
24
24
  from rclone_api.file import File
25
- from rclone_api.group_files import group_files
25
+ from rclone_api.group_files import (
26
+ group_files,
27
+ group_under_remote,
28
+ group_under_remote_bucket,
29
+ )
26
30
  from rclone_api.process import Process
27
31
  from rclone_api.remote import Remote
28
32
  from rclone_api.rpath import RPath
29
- from rclone_api.types import ListingOption, ModTimeStrategy, Order
33
+ from rclone_api.types import (
34
+ GroupingOption,
35
+ ListingOption,
36
+ ModTimeStrategy,
37
+ Order,
38
+ SizeResult,
39
+ )
30
40
  from rclone_api.util import (
31
41
  get_check,
32
42
  get_rclone_exe,
@@ -827,3 +837,68 @@ class Rclone:
827
837
  if proc.poll() is not None:
828
838
  raise ValueError("NFS serve process failed to start")
829
839
  return proc
840
+
841
+ def size_files(
842
+ self,
843
+ files: list[str],
844
+ fast_list: bool = True,
845
+ other_args: list[str] | None = None,
846
+ grouping: GroupingOption = GroupingOption.BUCKET,
847
+ check: bool | None = False,
848
+ verbose: bool | None = None,
849
+ ) -> SizeResult:
850
+ """Get the size of a list of files. Example of files items: "remote:bucket/to/file"."""
851
+ verbose = get_verbose(verbose)
852
+ check = get_check(check)
853
+ file_list: dict[str, list[str]]
854
+ if grouping == GroupingOption.BUCKET:
855
+ file_list = group_under_remote_bucket(files)
856
+ elif grouping == GroupingOption.REMOTE:
857
+ file_list = group_under_remote(files)
858
+ all_files: list[File] = []
859
+ for src_path, files in file_list.items():
860
+ cmd = ["lsjson", src_path, "--files-only", "-R"]
861
+ with TemporaryDirectory() as tmpdir:
862
+ # print("files: " + ",".join(files))
863
+ include_files_txt = Path(tmpdir) / "include_files.txt"
864
+ include_files_txt.write_text("\n".join(files), encoding="utf-8")
865
+ cmd += ["--files-from", str(include_files_txt)]
866
+ if fast_list:
867
+ cmd.append("--fast-list")
868
+ if other_args:
869
+ cmd += other_args
870
+ cp = self._run(cmd, check=check)
871
+
872
+ if cp.returncode != 0:
873
+ if check:
874
+ raise ValueError(f"Error getting file sizes: {cp.stderr}")
875
+ else:
876
+ warnings.warn(f"Error getting file sizes: {cp.stderr}")
877
+ stdout = cp.stdout
878
+ pieces = src_path.split(":", 1)
879
+ remote_name = pieces[0]
880
+ parent_path: str | None
881
+ if len(pieces) > 1:
882
+ parent_path = pieces[1]
883
+ else:
884
+ parent_path = None
885
+ remote = Remote(name=remote_name, rclone=self)
886
+ paths: list[RPath] = RPath.from_json_str(
887
+ stdout, remote, parent_path=parent_path
888
+ )
889
+ # print(paths)
890
+ all_files += [File(p) for p in paths]
891
+ file_sizes: dict[str, int] = {}
892
+ f: File
893
+ for f in all_files:
894
+ p = f.to_string(include_remote=True)
895
+ if p in file_sizes:
896
+ warnings.warn(f"Duplicate file found: {p}")
897
+ continue
898
+ size = f.size
899
+ if size == 0:
900
+ warnings.warn(f"File size is 0: {p}")
901
+ file_sizes[p] = f.size
902
+ total_size = sum(file_sizes.values())
903
+ out: SizeResult = SizeResult(total_size=total_size, file_sizes=file_sizes)
904
+ return out
rclone_api/remote.py CHANGED
@@ -7,6 +7,9 @@ class Remote:
7
7
  def __init__(self, name: str, rclone: Any) -> None:
8
8
  from rclone_api.rclone import Rclone
9
9
 
10
+ if ":" in name:
11
+ raise ValueError("Remote name cannot contain ':'")
12
+
10
13
  assert isinstance(rclone, Rclone)
11
14
  self.name = name
12
15
  self.rclone: Rclone = rclone
rclone_api/types.py CHANGED
@@ -1,3 +1,4 @@
1
+ from dataclasses import dataclass
1
2
  from enum import Enum
2
3
 
3
4
 
@@ -16,3 +17,16 @@ class Order(Enum):
16
17
  NORMAL = "normal"
17
18
  REVERSE = "reverse"
18
19
  RANDOM = "random"
20
+
21
+
22
+ class GroupingOption(Enum):
23
+ BUCKET = "bucket"
24
+ REMOTE = "remote"
25
+
26
+
27
+ @dataclass
28
+ class SizeResult:
29
+ """Size result dataclass."""
30
+
31
+ total_size: int
32
+ file_sizes: dict[str, int]
rclone_api/util.py CHANGED
@@ -104,7 +104,7 @@ def rclone_execute(
104
104
  )
105
105
  if verbose:
106
106
  cmd_str = subprocess.list2cmdline(cmd)
107
- print(f"Running: {cmd_str}")
107
+ print(f"\nRunning: {cmd_str}")
108
108
  cp = subprocess.run(
109
109
  cmd, capture_output=capture, encoding="utf-8", check=False, shell=False
110
110
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.76
3
+ Version: 1.0.77
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
6
  Maintainer: Zachary Vorhies
@@ -1,6 +1,6 @@
1
- rclone_api/__init__.py,sha256=kJUk9KAxQ2AEms9lM5yqw-dqlabarhU69b2FAxJVBlY,692
1
+ rclone_api/__init__.py,sha256=5KkqA2OnclssekOdx9ZdT1fFRlxzhpB9zBvXhpG44Ag,760
2
2
  rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
3
- rclone_api/completed_process.py,sha256=Pp-hXnLgej0IGO5ee9Fmx64dGzIofbQFEUyXdFCvO54,1371
3
+ rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
4
4
  rclone_api/config.py,sha256=tP6cU9DnCCEIRc_KP9HPur1jFLLg2QGFSxNwFm6_MVw,118
5
5
  rclone_api/convert.py,sha256=Mx9Qo7zhkOedJd8LdhPvNGHp8znJzOk4f_2KWnoGc78,1012
6
6
  rclone_api/deprecated.py,sha256=qWKpnZdYcBK7YQZKuVoWWXDwi-uqiAtbjgPcci_efow,590
@@ -8,22 +8,22 @@ rclone_api/diff.py,sha256=tMoJMAGmLSE6Q_7QhPf6PnCzb840djxMZtDmhc2GlGQ,5227
8
8
  rclone_api/dir.py,sha256=i4h7LX5hB_WmVixxDRWL_l1nifvscrdWct_8Wx7wHZc,3540
9
9
  rclone_api/dir_listing.py,sha256=9Qqf2SUswrOEkyqmaH23V51I18X6ePiXb9B1vUwRF5o,1571
10
10
  rclone_api/exec.py,sha256=1ovvaMXDEfLiT7BrYZyE85u_yFhEUwUNW3jPOzqknR8,1023
11
- rclone_api/file.py,sha256=YtR5Y6c0YfXTS-sReOy2UgiSnafcAeO6b2hnbojBQD4,1423
11
+ rclone_api/file.py,sha256=1rrZk_L2mdkgsvhr57dbs89wUaDb2SZBSwJg4e13xrk,1602
12
12
  rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
13
- rclone_api/group_files.py,sha256=kOHh6ysFDkxjldSwvW6KqmiADUC1yFCdrZRY57TvbGY,5328
13
+ rclone_api/group_files.py,sha256=pjZAjy01rh5zQ9fExXHDQ4mxmSClVVp_L_3uUWqrXsE,6939
14
14
  rclone_api/process.py,sha256=RrMfTe0bndmJ6gBK67ioqNvCstJ8aTC8RlGX1XBLlcw,4191
15
- rclone_api/rclone.py,sha256=JeeqJsJH8rBZW895rkT3HMCkaWeKllnG-6xj_PzLwXY,29738
16
- rclone_api/remote.py,sha256=c9hlRKBCg1BFB9MCINaQIoCg10qyAkeqiS4brl8ce-8,343
15
+ rclone_api/rclone.py,sha256=ptozXdIK9H3wEjcVBjZLLYh2sU9rTbrAc0SQfvKON-k,32598
16
+ rclone_api/remote.py,sha256=O9WDUFQy9f6oT1HdUbTixK2eg0xtBBm8k4Xl6aa6K00,431
17
17
  rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
18
18
  rclone_api/scan_missing_folders.py,sha256=Kulca2Q6WZodt00ATFHkmqqInuoPvBkhTcS9703y6po,4740
19
- rclone_api/types.py,sha256=DcbNw1R6j2f_1zHrhqEJcpCR-8kJfFJawMY0AmPsCnM,321
20
- rclone_api/util.py,sha256=_cvmHcJPRl2yXw4zgZiop3z-riA_8Ek6S5NDPw8cqSY,4198
19
+ rclone_api/types.py,sha256=yysVEP4xxmYWl5DoLoRzBFaMgmJXFxRvQF34FqMtxGE,545
20
+ rclone_api/util.py,sha256=XMrA2m_di4h8JTM-qyx2iyrFZn-l-or_SJOa5tEsDsI,4200
21
21
  rclone_api/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
22
22
  rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
23
23
  rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
24
- rclone_api-1.0.76.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
25
- rclone_api-1.0.76.dist-info/METADATA,sha256=13_xQBLBt8rYCmVYCXjHahKRMdC8p1Ao0lKOY8xhlVU,4489
26
- rclone_api-1.0.76.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
27
- rclone_api-1.0.76.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
28
- rclone_api-1.0.76.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
29
- rclone_api-1.0.76.dist-info/RECORD,,
24
+ rclone_api-1.0.77.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
25
+ rclone_api-1.0.77.dist-info/METADATA,sha256=8SsedCvu64dTTGi_C-uaTKrDOnKZ5NMzUM9y1WvzoAE,4489
26
+ rclone_api-1.0.77.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
27
+ rclone_api-1.0.77.dist-info/entry_points.txt,sha256=XUoTX3m7CWxdj2VAKhEuO0NMOfX2qf-OcEDFwdyk9ZE,72
28
+ rclone_api-1.0.77.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
29
+ rclone_api-1.0.77.dist-info/RECORD,,