rclone-api 1.0.88__tar.gz → 1.0.90__tar.gz

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.
Files changed (81) hide show
  1. {rclone_api-1.0.88 → rclone_api-1.0.90}/.gitignore +5 -0
  2. {rclone_api-1.0.88 → rclone_api-1.0.90}/PKG-INFO +2 -3
  3. {rclone_api-1.0.88 → rclone_api-1.0.90}/pyproject.toml +9 -2
  4. {rclone_api-1.0.88 → rclone_api-1.0.90}/setup.py +0 -1
  5. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/__init__.py +5 -1
  6. rclone_api-1.0.90/src/rclone_api/cmd/copy_large_s3.py +99 -0
  7. rclone_api-1.0.90/src/rclone_api/config.py +87 -0
  8. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/group_files.py +4 -1
  9. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/rclone.py +238 -49
  10. rclone_api-1.0.90/src/rclone_api/s3/api.py +73 -0
  11. rclone_api-1.0.90/src/rclone_api/s3/basic_ops.py +61 -0
  12. rclone_api-1.0.90/src/rclone_api/s3/chunk_uploader.py +538 -0
  13. rclone_api-1.0.90/src/rclone_api/s3/create.py +69 -0
  14. rclone_api-1.0.90/src/rclone_api/s3/types.py +58 -0
  15. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/types.py +5 -3
  16. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/util.py +32 -4
  17. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api.egg-info/PKG-INFO +2 -3
  18. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api.egg-info/SOURCES.txt +11 -2
  19. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api.egg-info/entry_points.txt +1 -0
  20. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api.egg-info/requires.txt +1 -0
  21. rclone_api-1.0.90/tests/archive/test_paramiko.py.disabled +326 -0
  22. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_diff.py +2 -2
  23. rclone_api-1.0.90/tests/test_mounted_ranged_download.py +151 -0
  24. rclone_api-1.0.90/tests/test_rclone_config.py +70 -0
  25. rclone_api-1.0.90/tests/test_s3.py +113 -0
  26. rclone_api-1.0.88/src/rclone_api/config.py +0 -8
  27. rclone_api-1.0.88/tests/test_serve_webdav.py +0 -108
  28. {rclone_api-1.0.88 → rclone_api-1.0.90}/.aiderignore +0 -0
  29. {rclone_api-1.0.88 → rclone_api-1.0.90}/.github/workflows/lint.yml +0 -0
  30. {rclone_api-1.0.88 → rclone_api-1.0.90}/.github/workflows/push_macos.yml +0 -0
  31. {rclone_api-1.0.88 → rclone_api-1.0.90}/.github/workflows/push_ubuntu.yml +0 -0
  32. {rclone_api-1.0.88 → rclone_api-1.0.90}/.github/workflows/push_win.yml +0 -0
  33. {rclone_api-1.0.88 → rclone_api-1.0.90}/.pylintrc +0 -0
  34. {rclone_api-1.0.88 → rclone_api-1.0.90}/.vscode/launch.json +0 -0
  35. {rclone_api-1.0.88 → rclone_api-1.0.90}/.vscode/settings.json +0 -0
  36. {rclone_api-1.0.88 → rclone_api-1.0.90}/.vscode/tasks.json +0 -0
  37. {rclone_api-1.0.88 → rclone_api-1.0.90}/LICENSE +0 -0
  38. {rclone_api-1.0.88 → rclone_api-1.0.90}/MANIFEST.in +0 -0
  39. {rclone_api-1.0.88 → rclone_api-1.0.90}/README.md +0 -0
  40. {rclone_api-1.0.88 → rclone_api-1.0.90}/clean +0 -0
  41. {rclone_api-1.0.88 → rclone_api-1.0.90}/install +0 -0
  42. {rclone_api-1.0.88 → rclone_api-1.0.90}/lint +0 -0
  43. {rclone_api-1.0.88 → rclone_api-1.0.90}/requirements.testing.txt +0 -0
  44. {rclone_api-1.0.88 → rclone_api-1.0.90}/setup.cfg +0 -0
  45. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/assets/example.txt +0 -0
  46. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/cli.py +0 -0
  47. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/cmd/list_files.py +0 -0
  48. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/completed_process.py +0 -0
  49. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/convert.py +0 -0
  50. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/deprecated.py +0 -0
  51. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/diff.py +0 -0
  52. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/dir.py +0 -0
  53. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/dir_listing.py +0 -0
  54. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/exec.py +0 -0
  55. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/file.py +0 -0
  56. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/filelist.py +0 -0
  57. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/process.py +0 -0
  58. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/remote.py +0 -0
  59. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/rpath.py +0 -0
  60. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/scan_missing_folders.py +0 -0
  61. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api/walk.py +0 -0
  62. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api.egg-info/dependency_links.txt +0 -0
  63. {rclone_api-1.0.88 → rclone_api-1.0.90}/src/rclone_api.egg-info/top_level.txt +0 -0
  64. {rclone_api-1.0.88 → rclone_api-1.0.90}/test +0 -0
  65. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_cmd_list_files.py +0 -0
  66. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_copy.py +0 -0
  67. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_copy_files.py +0 -0
  68. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_group_files.py +0 -0
  69. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_is_synced.py +0 -0
  70. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_ls.py +0 -0
  71. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_mount.py +0 -0
  72. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_mount_s3.py +0 -0
  73. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_mount_webdav.py +0 -0
  74. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_obscure.py +0 -0
  75. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_remote_control.py +0 -0
  76. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_remotes.py +0 -0
  77. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_scan_missing_folders.py +0 -0
  78. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_size_files.py +0 -0
  79. {rclone_api-1.0.88 → rclone_api-1.0.90}/tests/test_walk.py +0 -0
  80. {rclone_api-1.0.88 → rclone_api-1.0.90}/tox.ini +0 -0
  81. {rclone_api-1.0.88 → rclone_api-1.0.90}/upload_package.sh +0 -0
@@ -145,3 +145,8 @@ uv.lock
145
145
  !.aiderignore
146
146
 
147
147
  rclone*.conf
148
+ test_mount2
149
+ t.py
150
+ mount
151
+ t2.py
152
+ chunk_store
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rclone_api
3
- Version: 1.0.88
3
+ Version: 1.0.90
4
4
  Summary: rclone api in python
5
5
  Home-page: https://github.com/zackees/rclone-api
6
- Maintainer: Zachary Vorhies
7
6
  License: BSD 3-Clause License
8
7
  Keywords: template-python-cmd
9
8
  Classifier: Programming Language :: Python :: 3
@@ -12,8 +11,8 @@ Description-Content-Type: text/markdown
12
11
  License-File: LICENSE
13
12
  Requires-Dist: pyright>=1.1.393
14
13
  Requires-Dist: python-dotenv>=1.0.0
14
+ Requires-Dist: boto3<=1.35.99,>=1.20.1
15
15
  Dynamic: home-page
16
- Dynamic: maintainer
17
16
 
18
17
  # rclone-api
19
18
 
@@ -13,9 +13,15 @@ classifiers = ["Programming Language :: Python :: 3"]
13
13
  dependencies = [
14
14
  "pyright>=1.1.393",
15
15
  "python-dotenv>=1.0.0",
16
+
17
+ # BOTO3 Library needs to be pinned to a specific version
18
+ # BackBlaze S3 fails with checksum header which it doesn't support after 1.35.99
19
+ # The 1.20.1 was the earliest one I checked that worked and is not the true lower bound.
20
+ "boto3>=1.20.1,<=1.35.99",
16
21
  ]
22
+
17
23
  # Change this with the version number bump.
18
- version = "1.0.88"
24
+ version = "1.0.90"
19
25
 
20
26
  [tool.setuptools]
21
27
  package-dir = {"" = "src"}
@@ -45,4 +51,5 @@ ignore_missing_imports = true
45
51
  disable_error_code = ["import-untyped"]
46
52
 
47
53
  [project.scripts]
48
- rclone-api-listfiles = "rclone_api.cmd.list_files:main"
54
+ rclone-api-listfiles = "rclone_api.cmd.list_files:main"
55
+ rclone-api-copylarge-s3 = "rclone_api.cmd.copy_large_s3:main"
@@ -15,7 +15,6 @@ HERE = os.path.dirname(os.path.abspath(__file__))
15
15
 
16
16
  if __name__ == "__main__":
17
17
  setup(
18
- maintainer="Zachary Vorhies",
19
18
  keywords=KEYWORDS,
20
19
  url=URL,
21
20
  package_data={"": ["assets/example.txt"]},
@@ -1,5 +1,5 @@
1
1
  from .completed_process import CompletedProcess
2
- from .config import Config
2
+ from .config import Config, Parsed, Section
3
3
  from .diff import DiffItem, DiffOption, DiffType
4
4
  from .dir import Dir
5
5
  from .dir_listing import DirListing
@@ -9,6 +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 .s3.types import MultiUploadResult
12
13
  from .types import ListingOption, Order, SizeResult
13
14
 
14
15
  __all__ = [
@@ -30,4 +31,7 @@ __all__ = [
30
31
  "Order",
31
32
  "ListingOption",
32
33
  "SizeResult",
34
+ "Parsed",
35
+ "Section",
36
+ "MultiUploadResult",
33
37
  ]
@@ -0,0 +1,99 @@
1
+ import argparse
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+ from rclone_api import MultiUploadResult, Rclone
6
+
7
+ _1MB = 1024 * 1024
8
+
9
+
10
+ @dataclass
11
+ class Args:
12
+ config_path: Path
13
+ src: str
14
+ dst: str
15
+ chunk_size_mb: int
16
+ read_concurrent_chunks: int
17
+ retries: int
18
+ save_state_json: Path
19
+
20
+
21
+ def list_files(rclone: Rclone, path: str):
22
+ """List files in a remote path."""
23
+ for dirlisting in rclone.walk(path):
24
+ for file in dirlisting.files:
25
+ print(file.path)
26
+
27
+
28
+ def _parse_args() -> Args:
29
+ parser = argparse.ArgumentParser(description="List files in a remote path.")
30
+ parser.add_argument("src", help="File to copy")
31
+ parser.add_argument("dst", help="Destination file")
32
+ parser.add_argument(
33
+ "--config", help="Path to rclone config file", type=Path, required=True
34
+ )
35
+ parser.add_argument(
36
+ "--chunk-size-mb", help="Chunk size in MB", type=int, default=256
37
+ )
38
+ parser.add_argument(
39
+ "--read-concurrent-chunks",
40
+ help="Maximum number of chunks to read",
41
+ type=int,
42
+ default=4,
43
+ )
44
+ parser.add_argument("--retries", help="Number of retries", type=int, default=3)
45
+ parser.add_argument(
46
+ "--resumable-json",
47
+ help="Path to resumable JSON file",
48
+ type=Path,
49
+ default="resume.json",
50
+ )
51
+
52
+ args = parser.parse_args()
53
+ out = Args(
54
+ config_path=Path(args.config),
55
+ src=args.src,
56
+ dst=args.dst,
57
+ chunk_size_mb=args.chunk_size_mb,
58
+ read_concurrent_chunks=args.read_concurrent_chunks,
59
+ retries=args.retries,
60
+ save_state_json=args.resumable_json,
61
+ )
62
+ return out
63
+
64
+
65
+ def main() -> int:
66
+ """Main entry point."""
67
+ args = _parse_args()
68
+ rclone = Rclone(rclone_conf=args.config_path)
69
+ rslt: MultiUploadResult = rclone.copy_file_resumable_s3(
70
+ src=args.src,
71
+ dst=args.dst,
72
+ chunk_size=args.chunk_size_mb * _1MB,
73
+ concurrent_chunks=args.read_concurrent_chunks,
74
+ retries=args.retries,
75
+ save_state_json=args.save_state_json,
76
+ )
77
+ print(rslt)
78
+ return 0
79
+
80
+
81
+ if __name__ == "__main__":
82
+ import os
83
+ import sys
84
+
85
+ here = Path(__file__).parent
86
+ project_root = here.parent.parent.parent
87
+ print(f"project_root: {project_root}")
88
+ os.chdir(str(project_root))
89
+ cwd = Path(__file__).parent
90
+ print(f"cwd: {cwd}")
91
+ sys.argv.append("--config")
92
+ sys.argv.append("rclone.conf")
93
+ sys.argv.append(
94
+ "45061:aa_misc_data/aa_misc_data/world_lending_library_2024_11.tar.zst.torrent"
95
+ )
96
+ sys.argv.append(
97
+ "dst:TorrentBooks/aa_misc_data/aa_misc_data/world_lending_library_2024_11.tar.zst.torrent"
98
+ )
99
+ main()
@@ -0,0 +1,87 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict, List
3
+
4
+
5
+ @dataclass
6
+ class Section:
7
+ name: str
8
+ data: Dict[str, str] = field(default_factory=dict)
9
+
10
+ def add(self, key: str, value: str) -> None:
11
+ self.data[key] = value
12
+
13
+ def type(self) -> str:
14
+ return self.data["type"]
15
+
16
+ def provider(self) -> str | None:
17
+ return self.data.get("provider")
18
+
19
+ def access_key_id(self) -> str:
20
+ if "access_key_id" in self.data:
21
+ return self.data["access_key_id"]
22
+ elif "account" in self.data:
23
+ return self.data["account"]
24
+ raise KeyError("No access key found")
25
+
26
+ def secret_access_key(self) -> str:
27
+ # return self.data["secret_access_key"]
28
+ if "secret_access_key" in self.data:
29
+ return self.data["secret_access_key"]
30
+ elif "key" in self.data:
31
+ return self.data["key"]
32
+ raise KeyError("No secret access key found")
33
+
34
+ def endpoint(self) -> str | None:
35
+ return self.data.get("endpoint")
36
+
37
+
38
+ @dataclass
39
+ class Parsed:
40
+ # sections: List[ParsedSection]
41
+ sections: dict[str, Section]
42
+
43
+ @staticmethod
44
+ def parse(content: str) -> "Parsed":
45
+ return parse_rclone_config(content)
46
+
47
+
48
+ @dataclass
49
+ class Config:
50
+ """Rclone configuration dataclass."""
51
+
52
+ text: str
53
+
54
+ def parse(self) -> Parsed:
55
+ return Parsed.parse(self.text)
56
+
57
+
58
+ def parse_rclone_config(content: str) -> Parsed:
59
+ """
60
+ Parses an rclone configuration file and returns a list of RcloneConfigSection objects.
61
+
62
+ Each section in the file starts with a line like [section_name]
63
+ followed by key=value pairs.
64
+ """
65
+ sections: List[Section] = []
66
+ current_section: Section | None = None
67
+
68
+ lines = content.splitlines()
69
+ for line in lines:
70
+ line = line.strip()
71
+ # Skip empty lines and comments (assumed to start with '#' or ';')
72
+ if not line or line.startswith(("#", ";")):
73
+ continue
74
+ # New section header detected
75
+ if line.startswith("[") and line.endswith("]"):
76
+ section_name = line[1:-1].strip()
77
+ current_section = Section(name=section_name)
78
+ sections.append(current_section)
79
+ elif "=" in line and current_section is not None:
80
+ # Parse key and value, splitting only on the first '=' found
81
+ key, value = line.split("=", 1)
82
+ current_section.add(key.strip(), value.strip())
83
+
84
+ data: dict[str, Section] = {}
85
+ for section in sections:
86
+ data[section.name] = section
87
+ return Parsed(sections=data)
@@ -68,7 +68,10 @@ class TreeNode:
68
68
  paths_reversed: list[str] = [self.name]
69
69
  node: TreeNode | None = self
70
70
  assert node is not None
71
- while node := node.parent:
71
+ while True:
72
+ node = node.parent
73
+ if node is None:
74
+ break
72
75
  paths_reversed.append(node.name)
73
76
  return "/".join(reversed(paths_reversed))
74
77