rclone-api 1.3.28__py2.py3-none-any.whl → 1.4.1__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.
Files changed (34) hide show
  1. rclone_api/__init__.py +491 -4
  2. rclone_api/cmd/copy_large_s3.py +17 -10
  3. rclone_api/db/db.py +3 -3
  4. rclone_api/detail/copy_file_parts.py +382 -0
  5. rclone_api/dir.py +1 -1
  6. rclone_api/dir_listing.py +1 -1
  7. rclone_api/file.py +8 -0
  8. rclone_api/file_part.py +198 -0
  9. rclone_api/file_stream.py +52 -0
  10. rclone_api/http_server.py +15 -21
  11. rclone_api/{rclone.py → rclone_impl.py} +153 -321
  12. rclone_api/remote.py +3 -3
  13. rclone_api/rpath.py +11 -4
  14. rclone_api/s3/chunk_task.py +3 -19
  15. rclone_api/s3/multipart/file_info.py +7 -0
  16. rclone_api/s3/multipart/finished_piece.py +38 -0
  17. rclone_api/s3/multipart/upload_info.py +62 -0
  18. rclone_api/s3/{chunk_types.py → multipart/upload_state.py} +3 -99
  19. rclone_api/s3/s3_multipart_uploader.py +138 -28
  20. rclone_api/s3/types.py +1 -1
  21. rclone_api/s3/upload_file_multipart.py +6 -13
  22. rclone_api/scan_missing_folders.py +1 -1
  23. rclone_api/types.py +136 -165
  24. rclone_api/util.py +22 -2
  25. {rclone_api-1.3.28.dist-info → rclone_api-1.4.1.dist-info}/METADATA +1 -1
  26. rclone_api-1.4.1.dist-info/RECORD +55 -0
  27. rclone_api/mount_read_chunker.py +0 -130
  28. rclone_api/profile/mount_copy_bytes.py +0 -311
  29. rclone_api-1.3.28.dist-info/RECORD +0 -51
  30. /rclone_api/{walk.py → detail/walk.py} +0 -0
  31. {rclone_api-1.3.28.dist-info → rclone_api-1.4.1.dist-info}/LICENSE +0 -0
  32. {rclone_api-1.3.28.dist-info → rclone_api-1.4.1.dist-info}/WHEEL +0 -0
  33. {rclone_api-1.3.28.dist-info → rclone_api-1.4.1.dist-info}/entry_points.txt +0 -0
  34. {rclone_api-1.3.28.dist-info → rclone_api-1.4.1.dist-info}/top_level.txt +0 -0
@@ -1,311 +0,0 @@
1
- """
2
- Unit test file.
3
- """
4
-
5
- import argparse
6
- import os
7
- import shutil
8
- import time
9
- from concurrent.futures import Future
10
- from dataclasses import dataclass
11
- from pathlib import Path
12
-
13
- import psutil
14
- from dotenv import load_dotenv
15
-
16
- from rclone_api import Config, Rclone, SizeSuffix
17
- from rclone_api.mount_read_chunker import MultiMountFileChunker
18
- from rclone_api.types import FilePart
19
-
20
- os.environ["RCLONE_API_VERBOSE"] = "1"
21
-
22
-
23
- @dataclass
24
- class Args:
25
- direct_io: bool
26
- num: int
27
- size: SizeSuffix | None
28
-
29
-
30
- @dataclass
31
- class Credentials:
32
- BUCKET_KEY_SECRET: str
33
- BUCKET_KEY_PUBLIC: str
34
- BUCKET_NAME: str
35
- SRC_SFTP_HOST: str
36
- SRC_SFTP_USER: str
37
- SRC_SFTP_PORT: str
38
- SRC_SFTP_PASS: str
39
- BUCKET_URL: str
40
-
41
-
42
- def _generate_rclone_config() -> tuple[Config, Credentials]:
43
-
44
- cwd = Path.cwd()
45
- env_path = cwd / ".env"
46
- assert (
47
- env_path.exists()
48
- ), "this test requires that the secret .env file exists with the credentials"
49
- load_dotenv(env_path, verbose=True)
50
- print(f"Current working directory: {cwd}")
51
-
52
- # assert that .env exists for this test
53
- assert os.path.exists(
54
- ".env"
55
- ), "this test requires that the secret .env file exists with the credentials"
56
-
57
- # BUCKET_NAME = os.getenv("BUCKET_NAME", "TorrentBooks") # Default if not in .env
58
-
59
- # Load additional environment variables
60
- BUCKET_KEY_SECRET = os.getenv("BUCKET_KEY_SECRET")
61
- BUCKET_KEY_PUBLIC = os.getenv("BUCKET_KEY_PUBLIC")
62
- BUCKET_NAME = os.getenv("BUCKET_NAME")
63
- SRC_SFTP_HOST = os.getenv("SRC_SFTP_HOST")
64
- SRC_SFTP_USER = os.getenv("SRC_SFTP_USER")
65
- SRC_SFTP_PORT = os.getenv("SRC_SFTP_PORT")
66
- SRC_SFTP_PASS = os.getenv("SRC_SFTP_PASS")
67
- # BUCKET_URL = os.getenv("BUCKET_URL")
68
- BUCKET_URL = "sfo3.digitaloceanspaces.com"
69
-
70
- config_text = f"""
71
- [dst]
72
- type = s3
73
- provider = DigitalOcean
74
- access_key_id = {BUCKET_KEY_PUBLIC}
75
- secret_access_key = {BUCKET_KEY_SECRET}
76
- endpoint = {BUCKET_URL}
77
- bucket = {BUCKET_NAME}
78
-
79
- [src]
80
- type = sftp
81
- host = {SRC_SFTP_HOST}
82
- user = {SRC_SFTP_USER}
83
- port = {SRC_SFTP_PORT}
84
- pass = {SRC_SFTP_PASS}
85
-
86
- """
87
- # print("Config text:")
88
- # print(config_text)
89
- # _CONFIG_PATH.write_text(config_text, encoding="utf-8")
90
- # print(f"Config file written to: {_CONFIG_PATH}")
91
-
92
- creds = Credentials(
93
- BUCKET_KEY_SECRET=str(BUCKET_KEY_SECRET),
94
- BUCKET_KEY_PUBLIC=str(BUCKET_KEY_PUBLIC),
95
- BUCKET_NAME=str(BUCKET_NAME),
96
- SRC_SFTP_HOST=str(SRC_SFTP_HOST),
97
- SRC_SFTP_USER=str(SRC_SFTP_USER),
98
- SRC_SFTP_PORT=str(SRC_SFTP_PORT),
99
- SRC_SFTP_PASS=str(SRC_SFTP_PASS),
100
- BUCKET_URL=str(BUCKET_URL),
101
- )
102
-
103
- return Config(config_text), creds
104
-
105
-
106
- def _run_profile(
107
- rclone: Rclone,
108
- src_file: str,
109
- transfers: int,
110
- offset: SizeSuffix,
111
- size: SizeSuffix,
112
- log_dir: Path,
113
- num: int,
114
- direct_io: bool,
115
- ) -> None:
116
-
117
- mount_log = log_dir / f"mount_{SizeSuffix(size)}_threads_{transfers}.log"
118
- print("\n\n")
119
- print("#" * 80)
120
- print(f"# Started test download of {SizeSuffix(size)} with {transfers} transfers")
121
- print("#" * 80)
122
-
123
- chunk_size = size
124
-
125
- filechunker: MultiMountFileChunker = rclone.get_multi_mount_file_chunker(
126
- src=src_file,
127
- chunk_size=chunk_size,
128
- threads=transfers,
129
- direct_io=direct_io,
130
- mount_log=mount_log,
131
- )
132
- bytes_count = 0
133
-
134
- futures: list[Future[FilePart]] = []
135
- for i in range(num):
136
- offset = SizeSuffix(i * chunk_size.as_int()) + offset
137
- future = filechunker.fetch(offset.as_int(), size.as_int(), "TEST OBJECT")
138
- futures.append(future)
139
-
140
- # dry run to warm up the mounts, then read a different byte range.
141
- for future in futures:
142
- filepart_or_err = future.result()
143
- if isinstance(filepart_or_err, Exception):
144
- assert False, f"Error: {filepart_or_err}"
145
- filepart_or_err.dispose()
146
- futures.clear()
147
-
148
- start = time.time()
149
- net_io_start = psutil.net_io_counters()
150
-
151
- offset = SizeSuffix("1G")
152
-
153
- for i in range(num):
154
- offset = SizeSuffix(i * chunk_size.as_int()) + offset
155
- future = filechunker.fetch(offset.as_int(), size.as_int(), "TEST OBJECT")
156
- futures.append(future)
157
-
158
- for future in futures:
159
- bytes_or_err = future.result()
160
- if isinstance(bytes_or_err, Exception):
161
- assert False, f"Error: {bytes_or_err}"
162
- bytes_count += bytes_or_err.n_bytes()
163
-
164
- diff = (time.time() - start) / num
165
- net_io_end = psutil.net_io_counters()
166
- # self.assertEqual(len(bytes_or_err), size)
167
- # assert (
168
- # bytes_count == SizeSuffix(size * num).as_int()
169
- # ), f"Length: {SizeSuffix(bytes_count)} != {SizeSuffix(size* num)}"
170
-
171
- if bytes_count != SizeSuffix(size * num).as_int():
172
- print("\n######################## ERROR ########################")
173
- print(f"Error: Length: {SizeSuffix(bytes_count)} != {SizeSuffix(size* num)}")
174
- print(f" Bytes count: {bytes_count}")
175
- print(f" Size: {SizeSuffix(size * num)}")
176
- print(f" num: {num}")
177
- print("########################################################\n")
178
-
179
- # print io stats
180
- bytes_sent = (net_io_end.bytes_sent - net_io_start.bytes_sent) // num
181
- bytes_recv = (net_io_end.bytes_recv - net_io_start.bytes_recv) // num
182
- packets_sent = (net_io_end.packets_sent - net_io_start.packets_sent) // num
183
- efficiency = size.as_int() / (bytes_recv)
184
- efficiency_100 = efficiency * 100
185
- efficiency_str = f"{efficiency_100:.2f}"
186
-
187
- bytes_send_suffix = SizeSuffix(bytes_sent)
188
- bytes_recv_suffix = SizeSuffix(bytes_recv)
189
- range_size = SizeSuffix(size)
190
-
191
- print(f"\nFinished downloading {range_size} with {transfers} transfers")
192
- print("Net IO stats:")
193
- print(f"Bytes sent: {bytes_send_suffix}")
194
- print(f"Bytes received: {bytes_recv_suffix}")
195
- print(f"Packets sent: {packets_sent}")
196
- print(f"Efficiency: {efficiency_str}%")
197
- print(f"Time: {diff:.1f} seconds")
198
-
199
-
200
- def test_profile_copy_bytes(
201
- args: Args,
202
- rclone: Rclone,
203
- offset: SizeSuffix,
204
- transfer_list: list[int] | None,
205
- mount_root_path: Path,
206
- size: SizeSuffix | None,
207
- num: int,
208
- ) -> None:
209
-
210
- if size:
211
- sizes = [size.as_int()]
212
- else:
213
- sizes = [
214
- 1024 * 1024 * 1,
215
- # 1024 * 1024 * 2,
216
- 1024 * 1024 * 4,
217
- # 1024 * 1024 * 8,
218
- 1024 * 1024 * 16,
219
- # 1024 * 1024 * 32,
220
- 1024 * 1024 * 64,
221
- # 1024 * 1024 * 128,
222
- 1024 * 1024 * 256,
223
- ]
224
- # transfer_list = [1, 2, 4, 8, 16]
225
- transfer_list = transfer_list or [1, 2, 4]
226
-
227
- # src_file = "dst:rclone-api-unit-test/zachs_video/internaly_ai_alignment.mp4"
228
- # sftp mount
229
- src_file = "src:aa_misc_data/aa_misc_data/world_lending_library_2024_11.tar.zst"
230
-
231
- for sz in sizes:
232
- for transfers in transfer_list:
233
- _run_profile(
234
- rclone=rclone,
235
- src_file=src_file,
236
- transfers=transfers,
237
- offset=offset,
238
- size=SizeSuffix(sz),
239
- direct_io=args.direct_io,
240
- log_dir=mount_root_path,
241
- num=num,
242
- )
243
- print("done")
244
-
245
-
246
- def _parse_args() -> Args:
247
- parser = argparse.ArgumentParser(description="Profile copy_bytes")
248
- parser.add_argument("--direct-io", help="Use direct IO", action="store_true")
249
- parser.add_argument("-n", "--num", help="Number of workers", type=int, default=1)
250
- parser.add_argument(
251
- "--size", help="Size of the file to download", type=SizeSuffix, default=None
252
- )
253
- args = parser.parse_args()
254
- return Args(direct_io=args.direct_io, num=args.num, size=args.size)
255
-
256
-
257
- _SHOW_CREDS = False
258
-
259
-
260
- def main() -> None:
261
- """Main entry point."""
262
- print("Running test_profile_copy_bytes")
263
- config, creds = _generate_rclone_config()
264
- if _SHOW_CREDS:
265
- print("Config:")
266
- print(config)
267
- print("Credentials:")
268
- print(creds)
269
- rclone = Rclone(config)
270
-
271
- mount_root_path = Path("rclone_logs") / "mount"
272
- if mount_root_path.exists():
273
- shutil.rmtree(mount_root_path)
274
-
275
- args = _parse_args()
276
- transfer_list = None
277
- # parallel_workers = args.num
278
-
279
- def task(
280
- offset: SizeSuffix,
281
- args=args,
282
- rclone=rclone,
283
- transfer_list=transfer_list,
284
- mount_root_path=mount_root_path,
285
- ):
286
- return test_profile_copy_bytes(
287
- args=args,
288
- rclone=rclone,
289
- offset=offset,
290
- mount_root_path=mount_root_path,
291
- transfer_list=transfer_list,
292
- size=args.size,
293
- num=args.num,
294
- )
295
-
296
- task(offset=SizeSuffix(0))
297
-
298
- # with ThreadPoolExecutor(max_workers=parallel_workers) as _:
299
- # tasks: list[Future] = []
300
- # for i in range(parallel_workers):
301
- # offset = SizeSuffix(i * 1024 * 1024 * 256)
302
- # future = ThreadPoolExecutor().submit(lambda: task(offset=offset))
303
- # tasks.append(future)
304
-
305
-
306
- if __name__ == "__main__":
307
- import sys
308
-
309
- sys.argv.append("--size")
310
- sys.argv.append("16MB")
311
- main()
@@ -1,51 +0,0 @@
1
- rclone_api/__init__.py,sha256=gOQJgOs0_oaV_pOwlY00LXRYAHk1_MDwN59od1VpoC0,1334
2
- rclone_api/cli.py,sha256=dibfAZIh0kXWsBbfp3onKLjyZXo54mTzDjUdzJlDlWo,231
3
- rclone_api/completed_process.py,sha256=_IZ8IWK7DM1_tsbDEkH6wPZ-bbcrgf7A7smls854pmg,1775
4
- rclone_api/config.py,sha256=f6jEAxVorGFr31oHfcsu5AJTtOJj2wR5tTSsbGGZuIw,2558
5
- rclone_api/convert.py,sha256=Mx9Qo7zhkOedJd8LdhPvNGHp8znJzOk4f_2KWnoGc78,1012
6
- rclone_api/deprecated.py,sha256=qWKpnZdYcBK7YQZKuVoWWXDwi-uqiAtbjgPcci_efow,590
7
- rclone_api/diff.py,sha256=tMoJMAGmLSE6Q_7QhPf6PnCzb840djxMZtDmhc2GlGQ,5227
8
- rclone_api/dir.py,sha256=i4h7LX5hB_WmVixxDRWL_l1nifvscrdWct_8Wx7wHZc,3540
9
- rclone_api/dir_listing.py,sha256=GoziW8Sne6FY90MLNcb2aO3aaa3jphB6H8ExYrV0Ryo,1882
10
- rclone_api/exec.py,sha256=Bq0gkyZ10mEY0FRyzNZgdN4FaWP9vpeCk1kjpg-gN_8,1083
11
- rclone_api/file.py,sha256=cz-7_nJArkVdJ9z2QaC_XZYpihXe3IPBC90Z5_3g2aw,5419
12
- rclone_api/file_item.py,sha256=cH-AQYsxedhNPp4c8NHY1ad4Z7St4yf_VGbmiGD59no,1770
13
- rclone_api/filelist.py,sha256=xbiusvNgaB_b_kQOZoHMJJxn6TWGtPrWd2J042BI28o,767
14
- rclone_api/group_files.py,sha256=H92xPW9lQnbNw5KbtZCl00bD6iRh9yRbCuxku4j_3dg,8036
15
- rclone_api/http_server.py,sha256=SmeUDDKaMpJGDqRNkHoImHTRNkvHEtGFzu_8jYfoeZU,8113
16
- rclone_api/log.py,sha256=VZHM7pNSXip2ZLBKMP7M1u-rp_F7zoafFDuR8CPUoKI,1271
17
- rclone_api/mount.py,sha256=TE_VIBMW7J1UkF_6HRCt8oi_jGdMov4S51bm2OgxFAM,10045
18
- rclone_api/mount_read_chunker.py,sha256=7jaF1Rsjr-kXIZW--Ol1QuG7WArBgdIcpQ0AJMYn7bI,4764
19
- rclone_api/process.py,sha256=BGXJTZVT__jeaDyjN8_kRycliOhkBErMPdHO1hKRvJE,5271
20
- rclone_api/rclone.py,sha256=yRhQoCBJI-tfhxySR17a-vSEhWw5cMbk8_WbYs5WqRc,54117
21
- rclone_api/remote.py,sha256=jq3dPbAGvYZFW5cTviqxT2w6_jG2LLfS1RIcYSmMsQQ,503
22
- rclone_api/rpath.py,sha256=8ZA_1wxWtskwcy0I8V2VbjKDmzPkiWd8Q2JQSvh-sYE,2586
23
- rclone_api/scan_missing_folders.py,sha256=Kulca2Q6WZodt00ATFHkmqqInuoPvBkhTcS9703y6po,4740
24
- rclone_api/types.py,sha256=ous70ea5oEs0gz0KLCWfimVyF0_fgrXRA8e0_HLk8nk,12301
25
- rclone_api/util.py,sha256=F9Q3zbWRsgPF4NG6OWB63cZ7GVq82lsraP47gmmDohU,5416
26
- rclone_api/walk.py,sha256=-54NVE8EJcCstwDoaC_UtHm73R2HrZwVwQmsnv55xNU,3369
27
- rclone_api/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
28
- rclone_api/cmd/analyze.py,sha256=RHbvk1G5ZUc3qLqlm1AZEyQzd_W_ZjcbCNDvW4YpTKQ,1252
29
- rclone_api/cmd/copy_large_s3.py,sha256=etOMCyN2FECev7sF_Z3Ftp-2UHRE09Yhfg6AgflDHek,3573
30
- rclone_api/cmd/list_files.py,sha256=x8FHODEilwKqwdiU1jdkeJbLwOqUkUQuDWPo2u_zpf0,741
31
- rclone_api/cmd/save_to_db.py,sha256=ylvnhg_yzexM-m6Zr7XDiswvoDVSl56ELuFAdb9gqBY,1957
32
- rclone_api/db/__init__.py,sha256=OSRUdnSWUlDTOHmjdjVmxYTUNpTbtaJ5Ll9sl-PfZg0,40
33
- rclone_api/db/db.py,sha256=ZpYfeCUe8MKg_fdJucRSe6-fwGY_rWqUn7WkHCNFH_4,10074
34
- rclone_api/db/models.py,sha256=v7qaXUehvsDvU51uk69JI23fSIs9JFGcOa-Tv1c_wVs,1600
35
- rclone_api/experimental/flags.py,sha256=qCVD--fSTmzlk9hloRLr0q9elzAOFzPsvVpKM3aB1Mk,2739
36
- rclone_api/experimental/flags_base.py,sha256=ajU_czkTcAxXYU-SlmiCfHY7aCQGHvpCLqJ-Z8uZLk0,2102
37
- rclone_api/profile/mount_copy_bytes.py,sha256=M1vZn-Mrga14Ik7MHGZHbnwYli41Ep6Tyll7hQc7Wmo,9071
38
- rclone_api/s3/api.py,sha256=PafsIEyWDpLWAXsZAjFm9CY14vJpsDr9lOsn0kGRLZ0,4009
39
- rclone_api/s3/basic_ops.py,sha256=hK3366xhVEzEcjz9Gk_8lFx6MRceAk72cax6mUrr6ko,2104
40
- rclone_api/s3/chunk_task.py,sha256=AanVCygDoUjmMOUdEIYl-hEpPEGSJTIU_MSFGQ0tI0Q,7421
41
- rclone_api/s3/chunk_types.py,sha256=oSWv8No9V3BeM7IcGnowyR2a7YrszdAXzEJlxaeZcp0,8852
42
- rclone_api/s3/create.py,sha256=wgfkapv_j904CfKuWyiBIWJVxfAx_ftemFSUV14aT68,3149
43
- rclone_api/s3/s3_multipart_uploader.py,sha256=JxUjf-Dpeq_JnMr8RBGuNCFcljGr_GM11529W53n3AM,766
44
- rclone_api/s3/types.py,sha256=Elmh__gvZJyJyElYwMmvYZIBIunDJiTRAbEg21GmsRU,1604
45
- rclone_api/s3/upload_file_multipart.py,sha256=55TXp9q1It5nKkwBo49nCZNgvdMsqvIGByDMBFzo_ik,12576
46
- rclone_api-1.3.28.dist-info/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
47
- rclone_api-1.3.28.dist-info/METADATA,sha256=JkcK7CZWmK6d5PEF3vYvlWIOGnb7ZIXNraPEH3HQUHM,4628
48
- rclone_api-1.3.28.dist-info/WHEEL,sha256=rF4EZyR2XVS6irmOHQIJx2SUqXLZKRMUrjsg8UwN-XQ,109
49
- rclone_api-1.3.28.dist-info/entry_points.txt,sha256=fJteOlYVwgX3UbNuL9jJ0zUTuX2O79JFAeNgK7Sw7EQ,255
50
- rclone_api-1.3.28.dist-info/top_level.txt,sha256=EvZ7uuruUpe9RiUyEp25d1Keq7PWYNT0O_-mr8FCG5g,11
51
- rclone_api-1.3.28.dist-info/RECORD,,
File without changes