megfile 2.2.5.post1__py3-none-any.whl → 2.2.6a1__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.
- megfile/cli.py +78 -57
- megfile/errors.py +1 -1
- megfile/lib/s3_buffered_writer.py +6 -2
- megfile/lib/s3_cached_handler.py +3 -1
- megfile/lib/s3_limited_seekable_writer.py +4 -2
- megfile/lib/s3_memory_handler.py +12 -2
- megfile/lib/s3_pipe_handler.py +6 -2
- megfile/lib/s3_prefetch_reader.py +6 -2
- megfile/lib/s3_share_cache_reader.py +3 -1
- megfile/s3_path.py +45 -15
- megfile/sftp_path.py +20 -0
- megfile/version.py +1 -1
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/METADATA +1 -1
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/RECORD +19 -19
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/LICENSE +0 -0
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/LICENSE.pyre +0 -0
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/WHEEL +0 -0
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/entry_points.txt +0 -0
- {megfile-2.2.5.post1.dist-info → megfile-2.2.6a1.dist-info}/top_level.txt +0 -0
megfile/cli.py
CHANGED
|
@@ -4,13 +4,14 @@ import shutil
|
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
|
+
from functools import partial
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
from tqdm import tqdm
|
|
10
11
|
|
|
11
12
|
from megfile.interfaces import FileEntry
|
|
12
13
|
from megfile.lib.glob import get_non_glob_dir, has_magic
|
|
13
|
-
from megfile.smart import smart_copy, smart_getmd5, smart_getmtime, smart_getsize,
|
|
14
|
+
from megfile.smart import _smart_sync_single_file, smart_copy, smart_getmd5, smart_getmtime, smart_getsize, smart_glob_stat, smart_isdir, smart_isfile, smart_makedirs, smart_move, smart_open, smart_path_join, smart_remove, smart_rename, smart_scan_stat, smart_scandir, smart_stat, smart_sync, smart_sync_with_progress, smart_touch, smart_unlink
|
|
14
15
|
from megfile.smart_path import SmartPath
|
|
15
16
|
from megfile.utils import get_human_size
|
|
16
17
|
from megfile.version import VERSION
|
|
@@ -32,34 +33,36 @@ def safe_cli(): # pragma: no cover
|
|
|
32
33
|
click.echo(f"\n[{type(e).__name__}] {e}", err=True)
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def get_echo_path(file_stat, base_path: str = ""):
|
|
36
|
+
def get_echo_path(file_stat, base_path: str = "", full_path: bool = False):
|
|
36
37
|
if base_path == file_stat.path:
|
|
37
38
|
path = file_stat.name
|
|
39
|
+
elif full_path:
|
|
40
|
+
path = file_stat.path
|
|
38
41
|
else:
|
|
39
42
|
path = os.path.relpath(file_stat.path, start=base_path)
|
|
40
43
|
return path
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
def simple_echo(file_stat, base_path: str = ""):
|
|
44
|
-
click.echo(get_echo_path(file_stat, base_path))
|
|
46
|
+
def simple_echo(file_stat, base_path: str = "", full_path: bool = False):
|
|
47
|
+
click.echo(get_echo_path(file_stat, base_path, full_path))
|
|
45
48
|
|
|
46
49
|
|
|
47
|
-
def long_echo(file_stat, base_path: str = ""):
|
|
50
|
+
def long_echo(file_stat, base_path: str = "", full_path: bool = False):
|
|
48
51
|
click.echo(
|
|
49
52
|
'%12d %s %s' % (
|
|
50
53
|
file_stat.stat.size,
|
|
51
54
|
time.strftime(
|
|
52
55
|
"%Y-%m-%d %H:%M:%S", time.localtime(file_stat.stat.mtime)),
|
|
53
|
-
get_echo_path(file_stat, base_path)))
|
|
56
|
+
get_echo_path(file_stat, base_path, full_path)))
|
|
54
57
|
|
|
55
58
|
|
|
56
|
-
def human_echo(file_stat, base_path: str = ""):
|
|
59
|
+
def human_echo(file_stat, base_path: str = "", full_path: bool = False):
|
|
57
60
|
click.echo(
|
|
58
61
|
'%10s %s %s' % (
|
|
59
62
|
get_human_size(file_stat.stat.size),
|
|
60
63
|
time.strftime(
|
|
61
64
|
"%Y-%m-%d %H:%M:%S", time.localtime(file_stat.stat.mtime)),
|
|
62
|
-
get_echo_path(file_stat, base_path)))
|
|
65
|
+
get_echo_path(file_stat, base_path, full_path)))
|
|
63
66
|
|
|
64
67
|
|
|
65
68
|
def smart_list_stat(path):
|
|
@@ -91,9 +94,11 @@ def smart_list_stat(path):
|
|
|
91
94
|
help='Displays file sizes in human readable format.')
|
|
92
95
|
def ls(path: str, long: bool, recursive: bool, human_readable: bool):
|
|
93
96
|
base_path = path
|
|
97
|
+
full_path = False
|
|
94
98
|
if has_magic(path):
|
|
95
99
|
scan_func = smart_glob_stat
|
|
96
100
|
base_path = get_non_glob_dir(path)
|
|
101
|
+
full_path = True
|
|
97
102
|
elif recursive:
|
|
98
103
|
scan_func = smart_scan_stat
|
|
99
104
|
else:
|
|
@@ -107,7 +112,7 @@ def ls(path: str, long: bool, recursive: bool, human_readable: bool):
|
|
|
107
112
|
echo_func = simple_echo
|
|
108
113
|
|
|
109
114
|
for file_stat in scan_func(path):
|
|
110
|
-
echo_func(file_stat, base_path)
|
|
115
|
+
echo_func(file_stat, base_path, full_path=full_path)
|
|
111
116
|
|
|
112
117
|
|
|
113
118
|
@cli.command(
|
|
@@ -264,65 +269,81 @@ def rm(path: str, recursive: bool):
|
|
|
264
269
|
'--force',
|
|
265
270
|
is_flag=True,
|
|
266
271
|
help='Copy files forcely, ignore same files.')
|
|
272
|
+
@click.option('-q', '--quiet', is_flag=True, help='Not show any progress log.')
|
|
267
273
|
def sync(
|
|
268
274
|
src_path: str, dst_path: str, progress_bar: bool, worker: int,
|
|
269
|
-
force: bool):
|
|
275
|
+
force: bool, quiet: bool):
|
|
270
276
|
with ThreadPoolExecutor(max_workers=worker) as executor:
|
|
271
277
|
if has_magic(src_path):
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
src_root_path = get_non_glob_dir(src_path)
|
|
279
|
+
|
|
280
|
+
def scan_func(path):
|
|
281
|
+
for glob_file_entry in smart_glob_stat(path):
|
|
282
|
+
if glob_file_entry.is_file():
|
|
283
|
+
yield glob_file_entry
|
|
284
|
+
else:
|
|
285
|
+
for file_entry in smart_scan_stat(glob_file_entry.path,
|
|
286
|
+
followlinks=True):
|
|
287
|
+
yield file_entry
|
|
288
|
+
else:
|
|
289
|
+
src_root_path = src_path
|
|
290
|
+
scan_func = partial(smart_scan_stat, followlinks=True)
|
|
291
|
+
|
|
292
|
+
if progress_bar and not quiet:
|
|
293
|
+
print('building progress bar', end='\r')
|
|
294
|
+
file_entries = []
|
|
295
|
+
total_count = total_size = 0
|
|
296
|
+
for total_count, file_entry in enumerate(scan_func(src_path),
|
|
297
|
+
start=1):
|
|
298
|
+
if total_count > 1024 * 128:
|
|
299
|
+
file_entries = []
|
|
300
|
+
else:
|
|
301
|
+
file_entries.append(file_entry)
|
|
302
|
+
total_size += file_entry.stat.size
|
|
303
|
+
print(
|
|
304
|
+
f'building progress bar, find {total_count} files',
|
|
305
|
+
end='\r')
|
|
306
|
+
|
|
307
|
+
if not file_entries:
|
|
308
|
+
file_entries = scan_func(src_path)
|
|
309
|
+
else:
|
|
310
|
+
total_count = total_size = None
|
|
311
|
+
file_entries = scan_func(src_path)
|
|
277
312
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
313
|
+
if quiet:
|
|
314
|
+
callback = callback_after_copy_file = None
|
|
315
|
+
else:
|
|
316
|
+
tbar = tqdm(total=total_count, ascii=True)
|
|
317
|
+
sbar = tqdm(
|
|
318
|
+
unit='B',
|
|
319
|
+
ascii=True,
|
|
320
|
+
unit_scale=True,
|
|
321
|
+
unit_divisor=1024,
|
|
322
|
+
total=total_size)
|
|
282
323
|
|
|
283
|
-
|
|
284
|
-
|
|
324
|
+
def callback(_filename: str, length: int):
|
|
325
|
+
sbar.update(length)
|
|
285
326
|
|
|
286
|
-
|
|
287
|
-
|
|
327
|
+
def callback_after_copy_file(src_file_path, dst_file_path):
|
|
328
|
+
tbar.update(1)
|
|
288
329
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
330
|
+
for file_entry in file_entries:
|
|
331
|
+
executor.submit(
|
|
332
|
+
_smart_sync_single_file,
|
|
333
|
+
dict(
|
|
334
|
+
src_root_path=src_root_path,
|
|
335
|
+
dst_root_path=dst_path,
|
|
336
|
+
src_file_path=file_entry.path,
|
|
292
337
|
callback=callback,
|
|
293
|
-
callback_after_copy_file=callback_after_copy_file,
|
|
294
|
-
src_file_stats=path_stats,
|
|
295
|
-
map_func=executor.map,
|
|
296
|
-
force=force,
|
|
297
338
|
followlinks=True,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
tbar.close()
|
|
301
|
-
sbar.close()
|
|
302
|
-
else:
|
|
303
|
-
smart_sync(
|
|
304
|
-
root_dir,
|
|
305
|
-
dst_path,
|
|
306
|
-
src_file_stats=path_stats,
|
|
307
|
-
map_func=executor.map,
|
|
339
|
+
callback_after_copy_file=callback_after_copy_file,
|
|
308
340
|
force=force,
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
dst_path,
|
|
316
|
-
followlinks=True,
|
|
317
|
-
map_func=executor.map,
|
|
318
|
-
force=force)
|
|
319
|
-
else:
|
|
320
|
-
smart_sync(
|
|
321
|
-
src_path,
|
|
322
|
-
dst_path,
|
|
323
|
-
followlinks=True,
|
|
324
|
-
map_func=executor.map,
|
|
325
|
-
force=force)
|
|
341
|
+
))
|
|
342
|
+
if not quiet:
|
|
343
|
+
tbar.close()
|
|
344
|
+
if progress_bar:
|
|
345
|
+
sbar.update(sbar.total - sbar.n)
|
|
346
|
+
sbar.close()
|
|
326
347
|
|
|
327
348
|
|
|
328
349
|
@cli.command(short_help="Make the path if it doesn't already exist.")
|
megfile/errors.py
CHANGED
|
@@ -110,7 +110,7 @@ def s3_should_retry(error: Exception) -> bool:
|
|
|
110
110
|
if isinstance(error, botocore.exceptions.ClientError):
|
|
111
111
|
return client_error_code(error) in (
|
|
112
112
|
'500', '501', '502', '503', 'InternalError', 'ServiceUnavailable',
|
|
113
|
-
'SlowDown')
|
|
113
|
+
'SlowDown', 'ContextCanceled')
|
|
114
114
|
return False
|
|
115
115
|
|
|
116
116
|
|
|
@@ -52,11 +52,13 @@ class S3BufferedWriter(Writable):
|
|
|
52
52
|
block_size: int = DEFAULT_BLOCK_SIZE,
|
|
53
53
|
max_block_size: int = DEFAULT_MAX_BLOCK_SIZE,
|
|
54
54
|
max_buffer_size: int = DEFAULT_MAX_BUFFER_SIZE,
|
|
55
|
-
max_workers: Optional[int] = None
|
|
55
|
+
max_workers: Optional[int] = None,
|
|
56
|
+
profile_name: Optional[str] = None):
|
|
56
57
|
|
|
57
58
|
self._bucket = bucket
|
|
58
59
|
self._key = key
|
|
59
60
|
self._client = s3_client
|
|
61
|
+
self._profile_name = profile_name
|
|
60
62
|
|
|
61
63
|
self._block_size = block_size
|
|
62
64
|
self._max_block_size = max_block_size
|
|
@@ -86,7 +88,9 @@ class S3BufferedWriter(Writable):
|
|
|
86
88
|
|
|
87
89
|
@property
|
|
88
90
|
def name(self) -> str:
|
|
89
|
-
return 's3://%s/%s' % (
|
|
91
|
+
return 's3%s://%s/%s' % (
|
|
92
|
+
f"+{self._profile_name}" if self._profile_name else "",
|
|
93
|
+
self._bucket, self._key)
|
|
90
94
|
|
|
91
95
|
@property
|
|
92
96
|
def mode(self) -> str:
|
megfile/lib/s3_cached_handler.py
CHANGED
|
@@ -17,7 +17,8 @@ class S3CachedHandler(S3MemoryHandler):
|
|
|
17
17
|
*,
|
|
18
18
|
s3_client,
|
|
19
19
|
cache_path: Optional[str] = None,
|
|
20
|
-
remove_cache_when_open: bool = True
|
|
20
|
+
remove_cache_when_open: bool = True,
|
|
21
|
+
profile_name: Optional[str] = None):
|
|
21
22
|
|
|
22
23
|
assert mode in ('rb', 'wb', 'ab', 'rb+', 'wb+', 'ab+')
|
|
23
24
|
|
|
@@ -25,6 +26,7 @@ class S3CachedHandler(S3MemoryHandler):
|
|
|
25
26
|
self._key = key
|
|
26
27
|
self._mode = mode
|
|
27
28
|
self._client = s3_client
|
|
29
|
+
self._profile_name = profile_name
|
|
28
30
|
|
|
29
31
|
if cache_path is None:
|
|
30
32
|
cache_path = generate_cache_path(self.name)
|
|
@@ -29,7 +29,8 @@ class S3LimitedSeekableWriter(Seekable, S3BufferedWriter):
|
|
|
29
29
|
tail_block_size: Optional[int] = None,
|
|
30
30
|
max_block_size: int = DEFAULT_MAX_BLOCK_SIZE,
|
|
31
31
|
max_buffer_size: int = DEFAULT_MAX_BUFFER_SIZE,
|
|
32
|
-
max_workers: Optional[int] = None
|
|
32
|
+
max_workers: Optional[int] = None,
|
|
33
|
+
profile_name: Optional[str] = None):
|
|
33
34
|
|
|
34
35
|
super().__init__(
|
|
35
36
|
bucket,
|
|
@@ -38,7 +39,8 @@ class S3LimitedSeekableWriter(Seekable, S3BufferedWriter):
|
|
|
38
39
|
block_size=block_size,
|
|
39
40
|
max_block_size=max_block_size,
|
|
40
41
|
max_buffer_size=max_buffer_size,
|
|
41
|
-
max_workers=max_workers
|
|
42
|
+
max_workers=max_workers,
|
|
43
|
+
profile_name=profile_name)
|
|
42
44
|
|
|
43
45
|
self._head_block_size = head_block_size or block_size
|
|
44
46
|
self._tail_block_size = tail_block_size or block_size
|
megfile/lib/s3_memory_handler.py
CHANGED
|
@@ -8,7 +8,14 @@ from megfile.interfaces import Readable, Seekable, Writable
|
|
|
8
8
|
|
|
9
9
|
class S3MemoryHandler(Readable, Seekable, Writable):
|
|
10
10
|
|
|
11
|
-
def __init__(
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
bucket: str,
|
|
14
|
+
key: str,
|
|
15
|
+
mode: str,
|
|
16
|
+
*,
|
|
17
|
+
s3_client,
|
|
18
|
+
profile_name: Optional[str] = None):
|
|
12
19
|
|
|
13
20
|
assert mode in ('rb', 'wb', 'ab', 'rb+', 'wb+', 'ab+')
|
|
14
21
|
|
|
@@ -16,13 +23,16 @@ class S3MemoryHandler(Readable, Seekable, Writable):
|
|
|
16
23
|
self._key = key
|
|
17
24
|
self._mode = mode
|
|
18
25
|
self._client = s3_client
|
|
26
|
+
self._profile_name = profile_name
|
|
19
27
|
|
|
20
28
|
self._fileobj = BytesIO()
|
|
21
29
|
self._download_fileobj()
|
|
22
30
|
|
|
23
31
|
@property
|
|
24
32
|
def name(self) -> str:
|
|
25
|
-
return 's3://%s/%s' % (
|
|
33
|
+
return 's3%s://%s/%s' % (
|
|
34
|
+
f"+{self._profile_name}" if self._profile_name else "",
|
|
35
|
+
self._bucket, self._key)
|
|
26
36
|
|
|
27
37
|
@property
|
|
28
38
|
def mode(self) -> str:
|
megfile/lib/s3_pipe_handler.py
CHANGED
|
@@ -33,7 +33,8 @@ class S3PipeHandler(Readable, Writable):
|
|
|
33
33
|
mode: str,
|
|
34
34
|
*,
|
|
35
35
|
s3_client,
|
|
36
|
-
join_thread: bool = True
|
|
36
|
+
join_thread: bool = True,
|
|
37
|
+
profile_name: Optional[str] = None):
|
|
37
38
|
|
|
38
39
|
assert mode in ('rb', 'wb')
|
|
39
40
|
|
|
@@ -43,6 +44,7 @@ class S3PipeHandler(Readable, Writable):
|
|
|
43
44
|
self._client = s3_client
|
|
44
45
|
self._join_thread = join_thread
|
|
45
46
|
self._offset = 0
|
|
47
|
+
self._profile_name = profile_name
|
|
46
48
|
|
|
47
49
|
self._exc = None
|
|
48
50
|
self._pipe = os.pipe()
|
|
@@ -59,7 +61,9 @@ class S3PipeHandler(Readable, Writable):
|
|
|
59
61
|
|
|
60
62
|
@property
|
|
61
63
|
def name(self) -> str:
|
|
62
|
-
return 's3://%s/%s' % (
|
|
64
|
+
return 's3%s://%s/%s' % (
|
|
65
|
+
f"+{self._profile_name}" if self._profile_name else "",
|
|
66
|
+
self._bucket, self._key)
|
|
63
67
|
|
|
64
68
|
@property
|
|
65
69
|
def mode(self) -> str:
|
|
@@ -34,11 +34,13 @@ class S3PrefetchReader(BasePrefetchReader):
|
|
|
34
34
|
block_capacity: int = DEFAULT_BLOCK_CAPACITY,
|
|
35
35
|
block_forward: Optional[int] = None,
|
|
36
36
|
max_retries: int = 10,
|
|
37
|
-
max_workers: Optional[int] = None
|
|
37
|
+
max_workers: Optional[int] = None,
|
|
38
|
+
profile_name: Optional[str] = None):
|
|
38
39
|
|
|
39
40
|
self._bucket = bucket
|
|
40
41
|
self._key = key
|
|
41
42
|
self._client = s3_client
|
|
43
|
+
self._profile_name = profile_name
|
|
42
44
|
|
|
43
45
|
super().__init__(
|
|
44
46
|
block_size=block_size,
|
|
@@ -68,7 +70,9 @@ class S3PrefetchReader(BasePrefetchReader):
|
|
|
68
70
|
|
|
69
71
|
@property
|
|
70
72
|
def name(self) -> str:
|
|
71
|
-
return 's3://%s/%s' % (
|
|
73
|
+
return 's3%s://%s/%s' % (
|
|
74
|
+
f"+{self._profile_name}" if self._profile_name else "",
|
|
75
|
+
self._bucket, self._key)
|
|
72
76
|
|
|
73
77
|
def _fetch_response(
|
|
74
78
|
self, start: Optional[int] = None,
|
|
@@ -34,7 +34,8 @@ class S3ShareCacheReader(S3PrefetchReader):
|
|
|
34
34
|
block_forward: Optional[int] = None,
|
|
35
35
|
max_retries: int = 10,
|
|
36
36
|
cache_key: str = 'lru',
|
|
37
|
-
max_workers: Optional[int] = None
|
|
37
|
+
max_workers: Optional[int] = None,
|
|
38
|
+
profile_name: Optional[str] = None):
|
|
38
39
|
|
|
39
40
|
self._cache_key = cache_key
|
|
40
41
|
|
|
@@ -47,6 +48,7 @@ class S3ShareCacheReader(S3PrefetchReader):
|
|
|
47
48
|
block_forward=block_forward,
|
|
48
49
|
max_retries=max_retries,
|
|
49
50
|
max_workers=max_workers,
|
|
51
|
+
profile_name=profile_name,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
def _get_block_forward(
|
megfile/s3_path.py
CHANGED
|
@@ -193,6 +193,13 @@ def get_s3_client(
|
|
|
193
193
|
|
|
194
194
|
:returns: S3 client
|
|
195
195
|
'''
|
|
196
|
+
if cache_key is not None:
|
|
197
|
+
return thread_local(
|
|
198
|
+
f"{cache_key}:{profile_name}",
|
|
199
|
+
get_s3_client,
|
|
200
|
+
config=config,
|
|
201
|
+
profile_name=profile_name)
|
|
202
|
+
|
|
196
203
|
if config:
|
|
197
204
|
config = config.merge(botocore.config.Config(connect_timeout=5))
|
|
198
205
|
else:
|
|
@@ -207,10 +214,6 @@ def get_s3_client(
|
|
|
207
214
|
config = config.merge(
|
|
208
215
|
botocore.config.Config(s3={'addressing_style': addressing_style}))
|
|
209
216
|
|
|
210
|
-
if cache_key is not None:
|
|
211
|
-
return thread_local(
|
|
212
|
-
cache_key, get_s3_client, config=config, profile_name=profile_name)
|
|
213
|
-
|
|
214
217
|
access_key, secret_key = get_access_token(profile_name)
|
|
215
218
|
try:
|
|
216
219
|
session = get_s3_session(profile_name=profile_name)
|
|
@@ -595,7 +598,8 @@ def s3_prefetch_open(
|
|
|
595
598
|
s3_client=client,
|
|
596
599
|
max_retries=max_retries,
|
|
597
600
|
max_workers=max_concurrency,
|
|
598
|
-
block_size=max_block_size
|
|
601
|
+
block_size=max_block_size,
|
|
602
|
+
profile_name=s3_url._profile_name)
|
|
599
603
|
|
|
600
604
|
|
|
601
605
|
@_s3_binary_mode
|
|
@@ -646,7 +650,8 @@ def s3_share_cache_open(
|
|
|
646
650
|
s3_client=client,
|
|
647
651
|
max_retries=max_retries,
|
|
648
652
|
max_workers=max_concurrency,
|
|
649
|
-
block_size=max_block_size
|
|
653
|
+
block_size=max_block_size,
|
|
654
|
+
profile_name=s3_url._profile_name)
|
|
650
655
|
|
|
651
656
|
|
|
652
657
|
@_s3_binary_mode
|
|
@@ -693,7 +698,12 @@ def s3_pipe_open(
|
|
|
693
698
|
cache_key='s3_filelike_client',
|
|
694
699
|
profile_name=s3_url._profile_name)
|
|
695
700
|
return S3PipeHandler(
|
|
696
|
-
bucket,
|
|
701
|
+
bucket,
|
|
702
|
+
key,
|
|
703
|
+
mode,
|
|
704
|
+
s3_client=client,
|
|
705
|
+
join_thread=join_thread,
|
|
706
|
+
profile_name=s3_url._profile_name)
|
|
697
707
|
|
|
698
708
|
|
|
699
709
|
@_s3_binary_mode
|
|
@@ -734,7 +744,12 @@ def s3_cached_open(
|
|
|
734
744
|
cache_key='s3_filelike_client',
|
|
735
745
|
profile_name=s3_url._profile_name)
|
|
736
746
|
return S3CachedHandler(
|
|
737
|
-
bucket,
|
|
747
|
+
bucket,
|
|
748
|
+
key,
|
|
749
|
+
mode,
|
|
750
|
+
s3_client=client,
|
|
751
|
+
cache_path=cache_path,
|
|
752
|
+
profile_name=s3_url._profile_name)
|
|
738
753
|
|
|
739
754
|
|
|
740
755
|
@_s3_binary_mode
|
|
@@ -789,9 +804,19 @@ def s3_buffered_open(
|
|
|
789
804
|
|
|
790
805
|
if 'a' in mode or '+' in mode:
|
|
791
806
|
if cache_path is None:
|
|
792
|
-
return S3MemoryHandler(
|
|
807
|
+
return S3MemoryHandler(
|
|
808
|
+
bucket,
|
|
809
|
+
key,
|
|
810
|
+
mode,
|
|
811
|
+
s3_client=client,
|
|
812
|
+
profile_name=s3_url._profile_name)
|
|
793
813
|
return S3CachedHandler(
|
|
794
|
-
bucket,
|
|
814
|
+
bucket,
|
|
815
|
+
key,
|
|
816
|
+
mode,
|
|
817
|
+
s3_client=client,
|
|
818
|
+
cache_path=cache_path,
|
|
819
|
+
profile_name=s3_url._profile_name)
|
|
795
820
|
|
|
796
821
|
if mode == 'rb':
|
|
797
822
|
# A rough conversion algorithm to align 2 types of Reader / Writer parameters
|
|
@@ -810,7 +835,8 @@ def s3_buffered_open(
|
|
|
810
835
|
max_retries=max_retries,
|
|
811
836
|
max_workers=max_concurrency,
|
|
812
837
|
block_size=block_size,
|
|
813
|
-
block_forward=block_forward
|
|
838
|
+
block_forward=block_forward,
|
|
839
|
+
profile_name=s3_url._profile_name)
|
|
814
840
|
else:
|
|
815
841
|
reader = S3PrefetchReader(
|
|
816
842
|
bucket,
|
|
@@ -820,7 +846,8 @@ def s3_buffered_open(
|
|
|
820
846
|
max_workers=max_concurrency,
|
|
821
847
|
block_capacity=block_capacity,
|
|
822
848
|
block_forward=block_forward,
|
|
823
|
-
block_size=block_size
|
|
849
|
+
block_size=block_size,
|
|
850
|
+
profile_name=s3_url._profile_name)
|
|
824
851
|
if buffered:
|
|
825
852
|
reader = io.BufferedReader(reader) # pytype: disable=wrong-arg-types
|
|
826
853
|
return reader
|
|
@@ -832,7 +859,8 @@ def s3_buffered_open(
|
|
|
832
859
|
s3_client=client,
|
|
833
860
|
max_workers=max_concurrency,
|
|
834
861
|
max_buffer_size=max_buffer_size,
|
|
835
|
-
block_size=block_size
|
|
862
|
+
block_size=block_size,
|
|
863
|
+
profile_name=s3_url._profile_name)
|
|
836
864
|
else:
|
|
837
865
|
writer = S3BufferedWriter(
|
|
838
866
|
bucket,
|
|
@@ -840,7 +868,8 @@ def s3_buffered_open(
|
|
|
840
868
|
s3_client=client,
|
|
841
869
|
max_workers=max_concurrency,
|
|
842
870
|
max_buffer_size=max_buffer_size,
|
|
843
|
-
block_size=block_size
|
|
871
|
+
block_size=block_size,
|
|
872
|
+
profile_name=s3_url._profile_name)
|
|
844
873
|
if buffered:
|
|
845
874
|
writer = io.BufferedWriter(writer) # pytype: disable=wrong-arg-types
|
|
846
875
|
return writer
|
|
@@ -877,7 +906,8 @@ def s3_memory_open(
|
|
|
877
906
|
config=config,
|
|
878
907
|
cache_key='s3_filelike_client',
|
|
879
908
|
profile_name=s3_url._profile_name)
|
|
880
|
-
return S3MemoryHandler(
|
|
909
|
+
return S3MemoryHandler(
|
|
910
|
+
bucket, key, mode, s3_client=client, profile_name=s3_url._profile_name)
|
|
881
911
|
|
|
882
912
|
|
|
883
913
|
s3_open = s3_buffered_open
|
megfile/sftp_path.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import atexit
|
|
2
|
+
import fcntl
|
|
2
3
|
import hashlib
|
|
3
4
|
import io
|
|
4
5
|
import os
|
|
6
|
+
import random
|
|
5
7
|
import shlex
|
|
6
8
|
import socket
|
|
7
9
|
import subprocess
|
|
@@ -43,6 +45,7 @@ SFTP_PASSWORD = "SFTP_PASSWORD"
|
|
|
43
45
|
SFTP_PRIVATE_KEY_PATH = "SFTP_PRIVATE_KEY_PATH"
|
|
44
46
|
SFTP_PRIVATE_KEY_TYPE = "SFTP_PRIVATE_KEY_TYPE"
|
|
45
47
|
SFTP_PRIVATE_KEY_PASSWORD = "SFTP_PRIVATE_KEY_PASSWORD"
|
|
48
|
+
SFTP_MAX_UNAUTH_CONN = "SFTP_MAX_UNAUTH_CONN"
|
|
46
49
|
MAX_RETRIES = 10
|
|
47
50
|
DEFAULT_SSH_CONNECT_TIMEOUT = 5
|
|
48
51
|
DEFAULT_SSH_KEEPALIVE_INTERVAL = 15
|
|
@@ -194,6 +197,20 @@ def _get_ssh_client(
|
|
|
194
197
|
|
|
195
198
|
ssh_client = paramiko.SSHClient()
|
|
196
199
|
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
200
|
+
max_unauth_connections = int(os.getenv(SFTP_MAX_UNAUTH_CONN, 10))
|
|
201
|
+
try:
|
|
202
|
+
fd = os.open(
|
|
203
|
+
os.path.join(
|
|
204
|
+
'/tmp',
|
|
205
|
+
f'megfile-sftp-{hostname}-{random.randint(1, max_unauth_connections)}'
|
|
206
|
+
), os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
|
|
207
|
+
except Exception:
|
|
208
|
+
_logger.warning(
|
|
209
|
+
"Can't create file lock in '/tmp', please control the SFTP concurrency count by yourself."
|
|
210
|
+
)
|
|
211
|
+
fd = None
|
|
212
|
+
if fd:
|
|
213
|
+
fcntl.flock(fd, fcntl.LOCK_EX)
|
|
197
214
|
ssh_client.connect(
|
|
198
215
|
hostname=hostname,
|
|
199
216
|
username=username,
|
|
@@ -203,6 +220,9 @@ def _get_ssh_client(
|
|
|
203
220
|
auth_timeout=DEFAULT_SSH_CONNECT_TIMEOUT,
|
|
204
221
|
banner_timeout=DEFAULT_SSH_CONNECT_TIMEOUT,
|
|
205
222
|
)
|
|
223
|
+
if fd:
|
|
224
|
+
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
225
|
+
os.close(fd)
|
|
206
226
|
atexit.register(ssh_client.close)
|
|
207
227
|
return ssh_client
|
|
208
228
|
|
megfile/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = "2.2.
|
|
1
|
+
VERSION = "2.2.6a1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
megfile/__init__.py,sha256=Qsi3XNP_0XYoSol-1AGutZqo0rfBnzaiZ-HVXll4fB0,5721
|
|
2
|
-
megfile/cli.py,sha256=
|
|
3
|
-
megfile/errors.py,sha256=
|
|
2
|
+
megfile/cli.py,sha256=m6nqGc_vjA68SMv0NsqM-R5RpnZNczA4JI4Gt_mcFPw,14743
|
|
3
|
+
megfile/errors.py,sha256=IDqfdz0WjCwvV1Zp8N9iKFE8Y0nrYBH6c7_4EW_CJaM,11953
|
|
4
4
|
megfile/fs.py,sha256=LtrzQsyZgogTJeoRFz4L52gxx0jByzRBLkpWYpvkp5I,11819
|
|
5
5
|
megfile/fs_path.py,sha256=JkY8qGIIboK5MK2rSagYEvnu5FTzmk9OHXIhTO7BjeY,38767
|
|
6
6
|
megfile/http.py,sha256=a3oAuARSSaIU8VMx86Mui0N5Vh-EI0AoHnwxRU5DSMU,2032
|
|
@@ -8,14 +8,14 @@ megfile/http_path.py,sha256=9X9klm6CtEfu4V0_LUWtSXC8JRYiNjVAJjdzEBfxUa8,9272
|
|
|
8
8
|
megfile/interfaces.py,sha256=h3tWE8hVt5S-HopaMAX6lunPJ97vzhv6jH_2HubcDNc,6219
|
|
9
9
|
megfile/pathlike.py,sha256=WpP8zWSOAcAfYrD65hZS08UEi4_iCoEMs2xvfFMwZvY,29264
|
|
10
10
|
megfile/s3.py,sha256=7XZSWjcSY-hoLhLH9dtfyRpokfYH9raTO_Mf69RjpEs,12560
|
|
11
|
-
megfile/s3_path.py,sha256=
|
|
11
|
+
megfile/s3_path.py,sha256=1bxhf8-Lg29oFB6BFbIQGDvKvSIE7ZS-YORHbe6kOiM,88409
|
|
12
12
|
megfile/sftp.py,sha256=CZYv1WKL0d_vuJ5aPgMhm0W8uskzaO5zbYGhGwt_mQs,12771
|
|
13
|
-
megfile/sftp_path.py,sha256=
|
|
13
|
+
megfile/sftp_path.py,sha256=DG-X79bcPc1hikZ_PNha3K_ber8bqjx-KnW3jZG8iCw,50529
|
|
14
14
|
megfile/smart.py,sha256=JH5zed90eQchS8GNQ7mbXvif-pNKjPjvnadMazKfQMs,33278
|
|
15
15
|
megfile/smart_path.py,sha256=Y0UFh4J2ccydRY2W-wX2ubaf9zzJx1M2nf-VLBGe4mk,6749
|
|
16
16
|
megfile/stdio.py,sha256=yRhlfUA2DHi3bq-9cXsSlbLCnHvS_zvglO2IYYyPsGc,707
|
|
17
17
|
megfile/stdio_path.py,sha256=eQulTXUwHvUKA-5PKCGfVNiEPkJhG9YtVhtU58OcmoM,2873
|
|
18
|
-
megfile/version.py,sha256=
|
|
18
|
+
megfile/version.py,sha256=s1UlNdCKmZ0EggJzQ2Wu1Nz0xneCzoLAvSAoUlQHunY,21
|
|
19
19
|
megfile/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
megfile/lib/base_prefetch_reader.py,sha256=SjrBffHVgvJnYtr8HNqiOozP9OJRYS37Eu1KQcZu1Z8,13221
|
|
21
21
|
megfile/lib/combine_reader.py,sha256=XFSqEY5A5X5Uf7eQ6AXAzrvNteESSXvKNVPktGjo3KY,4546
|
|
@@ -26,22 +26,22 @@ megfile/lib/glob.py,sha256=7i9dIput9rI9JIPyTZX-JDmFS7IP_THlX1k-35foAfw,9732
|
|
|
26
26
|
megfile/lib/http_prefetch_reader.py,sha256=YDtQXRX-yxyaFzqI_CL3X73-Idkdz1aPIDL29uY77zw,3326
|
|
27
27
|
megfile/lib/joinpath.py,sha256=D4Px6-lnDDpYs1LMUHkTIGqMPJQ0oCBGfTzREs373iU,929
|
|
28
28
|
megfile/lib/lazy_handler.py,sha256=f1rip2_T57vVo0WRNXve2bAa4LArvVheMfQg1S0vFzg,1915
|
|
29
|
-
megfile/lib/s3_buffered_writer.py,sha256=
|
|
30
|
-
megfile/lib/s3_cached_handler.py,sha256=
|
|
31
|
-
megfile/lib/s3_limited_seekable_writer.py,sha256=
|
|
32
|
-
megfile/lib/s3_memory_handler.py,sha256=
|
|
33
|
-
megfile/lib/s3_pipe_handler.py,sha256=
|
|
34
|
-
megfile/lib/s3_prefetch_reader.py,sha256=
|
|
35
|
-
megfile/lib/s3_share_cache_reader.py,sha256=
|
|
29
|
+
megfile/lib/s3_buffered_writer.py,sha256=9wiOXY7MgHrP7ulrN-QE2GcfwTfsTBniMxL9iDulpFw,7107
|
|
30
|
+
megfile/lib/s3_cached_handler.py,sha256=xuWiThi6pJtGL_ErSBmcu8rDv1XyXNmEhiFBnRF4NWU,1412
|
|
31
|
+
megfile/lib/s3_limited_seekable_writer.py,sha256=Dz4e5Gu-b9BoIzGHh5AAEkIGGUEB9Hg7MDToHS_05gk,6124
|
|
32
|
+
megfile/lib/s3_memory_handler.py,sha256=6Tj89xzc8z-FycVggGpjF_8lEbPsqRVB6undZwWsugo,3971
|
|
33
|
+
megfile/lib/s3_pipe_handler.py,sha256=hG8sEajO9dv9bLTeXsERxDioHHhzi4t8NC61lSbYk94,3557
|
|
34
|
+
megfile/lib/s3_prefetch_reader.py,sha256=vRZfFSV7bNliBrdD3cxgUrYSzLrgPPrzBT8zJMeAB8Q,4223
|
|
35
|
+
megfile/lib/s3_share_cache_reader.py,sha256=bPnD82lqrXtdLMegxyY15MzbWUHXuX0KKGtJHNOWD-w,3860
|
|
36
36
|
megfile/lib/shadow_handler.py,sha256=IbFyTw107t-yWH0cGrDjAJX-CS3xeEr77_PTGsnSgk4,2683
|
|
37
37
|
megfile/lib/stdio_handler.py,sha256=QDWtcZxz-hzi-rqQUiSlR3NrihX1fjK_Rj9T2mdTFEg,2044
|
|
38
38
|
megfile/lib/url.py,sha256=VbQLjo0s4AaV0iSk66BcjI68aUTcN9zBZ5x6-cM4Qvs,103
|
|
39
39
|
megfile/utils/__init__.py,sha256=qdX8FF_dYFKwp1BIWx3JeSGd91s7AKUDSEpDv9tORcM,9162
|
|
40
40
|
megfile/utils/mutex.py,sha256=-2KH3bNovKRd9zvsXq9n3bWM7rQdoG9hO7tUPxVG_Po,2538
|
|
41
|
-
megfile-2.2.
|
|
42
|
-
megfile-2.2.
|
|
43
|
-
megfile-2.2.
|
|
44
|
-
megfile-2.2.
|
|
45
|
-
megfile-2.2.
|
|
46
|
-
megfile-2.2.
|
|
47
|
-
megfile-2.2.
|
|
41
|
+
megfile-2.2.6a1.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
42
|
+
megfile-2.2.6a1.dist-info/LICENSE.pyre,sha256=9lf5nT-5ZH25JijpYAequ0bl8E8z5JmZB1qrjiUMp84,1080
|
|
43
|
+
megfile-2.2.6a1.dist-info/METADATA,sha256=rDzvBg6_knLWNEyWEX5k_afGpOcGkjNDk3XQoho_O7g,10744
|
|
44
|
+
megfile-2.2.6a1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
45
|
+
megfile-2.2.6a1.dist-info/entry_points.txt,sha256=M6ZWSSv5_5_QtIpZafy3vq7WuOJ_5dSGQQnEZbByt2Q,49
|
|
46
|
+
megfile-2.2.6a1.dist-info/top_level.txt,sha256=i3rMgdU1ZAJekAceojhA-bkm3749PzshtRmLTbeLUPQ,8
|
|
47
|
+
megfile-2.2.6a1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|