Files
modelscope-scepter/scepter/modules/utils/file_system.py
T
2024-02-06 17:58:30 +08:00

453 lines
16 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) Alibaba, Inc. and its affiliates.
import os
import threading
import time
import warnings
from contextlib import contextmanager
from queue import Queue
from scepter.modules.utils.config import Config
from scepter.modules.utils.file_clients.base_fs import BaseFs
from scepter.modules.utils.file_clients.local_fs import LocalFs
from scepter.modules.utils.file_clients.registry import FILE_SYSTEMS
from scepter.modules.utils.file_clients.utils import check_if_local_path
class IoString(str):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class IoBytes(bytes):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class ReadException(Exception):
pass
class WriteException(Exception):
pass
class FileSystem(object):
def __init__(self):
self._prefix_to_clients = {}
self._default_client = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def __del__(self):
for k, client in self._prefix_to_clients.items():
client.clear()
@property
def support_prefix(self):
return self._prefix_to_clients
def init_fs_client(self, cfg=None, logger=None, overwrite=True):
""" Initialize file system backend
Supported backend:
1. Local file system, e.g. /home/admin/work_dir, work_dir_bk/imagenet_pretrain
2. Aliyun Oss, e.g. oss://bucket_name/work_dir
3. Http, only support to read content, e.g.
https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png
4. other fs backend...
Args:
cfg (list, dict, optional):
list: list of file system configs to be initialized
dict: a dict contains file system configs as values or a file system config dict
optional: Will only use default LocalFs
"""
fs_cfg = cfg or Config(load=False)
if not isinstance(fs_cfg, Config):
raise '{} is not a Config Instance!'.format(fs_cfg)
if not fs_cfg.have('NAME'):
raise KeyError(f'{fs_cfg} does not contain key NAME!')
fs_client = FILE_SYSTEMS.build(fs_cfg, logger=logger)
_prefix = fs_client.get_prefix()
if _prefix in self._prefix_to_clients and not overwrite:
return _prefix
if _prefix in self._prefix_to_clients:
warnings.warn(
'File client {} has already been set, will be replaced by newer config.'
.format(_prefix))
self._prefix_to_clients[_prefix] = fs_client
return _prefix
def get_fs_client(self, target_path, safe=False) -> BaseFs:
""" Get the client by input path.
Every file system has its own identifier, default will use local file system to have a try.
If copy needed, only do shallow copy.
Args:
target_path (str):
safe (bool): In safe mode, get the copy of the client.
"""
obj = None
for prefix in sorted(list(self._prefix_to_clients.keys()),
key=lambda a: -len(a)):
if target_path.startswith(prefix):
obj = self._prefix_to_clients[prefix]
break
if obj is not None:
if safe:
return obj.copy()
else:
return obj
if not check_if_local_path(target_path):
warnings.warn(
f'{target_path} is not a local path, use LocalFs may cause an error.'
)
if self._default_client is None:
self._default_client = LocalFs(Config(load=False))
if safe:
return self._default_client.copy()
else:
return self._default_client
def get_dir_to_local_dir(self,
target_path,
local_path=None,
wait_finish=False,
timeout=3600,
multi_thread=False,
worker_id=0):
with self.get_fs_client(target_path) as client:
local_path = client.get_dir_to_local_dir(target_path,
local_path=local_path,
wait_finish=wait_finish,
timeout=timeout,
multi_thread=multi_thread,
worker_id=worker_id)
if local_path is None:
raise ReadException(
f'Failed to fetch {target_path} to {local_path}')
return IoString(local_path)
def add_target_local_map(self, target_dir, local_dir):
""" Map target directory to local file system directory
Args:
target_dir (str): Target directory.
local_dir (str): Directory in local file system.
"""
with self.get_fs_client(target_dir, safe=False) as client:
client.add_target_local_map(target_dir, local_dir)
def make_dir(self, target_dir):
""" Make a directory.
If target_dir is already exists, return True.
Args:
target_dir (str):
Returns:
True if target_dir exists or created.
"""
with self.get_fs_client(target_dir) as client:
return client.make_dir(target_dir)
def exists(self, target_path):
""" Check if target_path exists.
Args:
target_path (str):
Returns:
Bool.
"""
with self.get_fs_client(target_path) as client:
return client.exists(target_path)
def map_to_local(self, target_path):
""" Map target path to local file path. (NO IO HERE).
Args:
target_path (str): Target file path.
Returns:
A local path and a flag indicates if the local path is a temporary file.
"""
with self.get_fs_client(target_path) as client:
local_path, is_tmp = client.map_to_local(target_path)
return local_path, is_tmp
def put_dir_from_local_dir(self,
local_dir,
target_dir,
multi_thread=False):
""" Upload all contents in local_dir to target_dir, keep the file tree.
Args:
local_dir (str):
target_dir (str):
Returns:
Bool.
"""
with self.get_fs_client(target_dir) as client:
return client.put_dir_from_local_dir(local_dir,
target_dir,
multi_thread=multi_thread)
def walk_dir(self, target_dir, recurse=True):
""" Iterator to access the files of target dir.
Args:
target_dir (str):
Returns:
Generator.
"""
with self.get_fs_client(target_dir) as client:
return client.walk_dir(target_dir, recurse=recurse)
def is_local_client(self, target_path) -> bool:
""" Check if the client support read or write to target_path is a LocalFs.
Args:
target_path (str):
Returns:
Bool.
"""
with self.get_fs_client(target_path) as client:
return type(client) is LocalFs
def put_object_from_local_file(self, local_path, target_path) -> bool:
with self.get_fs_client(target_path) as client:
flag = client.put_object_from_local_file(local_path, target_path)
return flag
def get_from(self, target_path, local_path=None, wait_finish=False):
with self.get_fs_client(target_path) as client:
local_path = client.get_object_to_local_file(
target_path, local_path=local_path, wait_finish=wait_finish)
if local_path is None:
raise ReadException(
f'Failed to fetch {target_path} to {local_path}')
return IoString(local_path)
def get_url(self, target_path, set_public=False, lifecycle=3600 * 100):
with self.get_fs_client(target_path) as client:
output_url = client.get_url(target_path,
set_public=set_public,
lifecycle=lifecycle)
return output_url
def get_object(self, target_path):
with self.get_fs_client(target_path) as client:
local_data = client.get_object(target_path)
if local_data is None:
return IoBytes(None)
return IoBytes(local_data)
def put_object(self, local_data, target_path):
with self.get_fs_client(target_path) as client:
flg = client.put_object(local_data, target_path)
return flg
def delete_object(self, target_path):
with self.get_fs_client(target_path) as client:
if self.isfile(target_path):
flg = client.remove(target_path)
return flg
else:
return False
def get_batch_objects_from(self, target_path_list, wait_finish=False):
data_quene = Queue()
batch_size = 20
R = threading.Lock()
def get_one_object(target_path_list):
for target_path in target_path_list:
if self.exists(target_path):
local_path = self.get_from(target_path,
wait_finish=wait_finish)
else:
local_path = None
R.acquire()
try:
data_quene.put_nowait([target_path, local_path])
except Exception:
R.release()
R.release()
while True:
batch_list = target_path_list[:4 * batch_size]
if len(batch_list) < 1:
break
target_path_list = target_path_list[4 * batch_size:]
threading_list = []
for i in range(batch_size):
t = threading.Thread(target=get_one_object,
args=(batch_list[i::batch_size], ))
t.daemon = True
t.start()
threading_list.append(t)
[threading_t.join() for threading_t in threading_list]
file_dict = {}
while not data_quene.empty():
target_path, local_path = data_quene.get_nowait()
file_dict[target_path] = local_path
for target_path in batch_list:
local_path = file_dict.get(target_path, None)
yield local_path
def put_batch_objects_to(self,
local_path_list,
target_path_list,
batch_size=20,
wait_finish=False):
data_quene = Queue()
R = threading.Lock()
def put_one_object(local_path_list, target_path_list):
for local_path, target_path in zip(local_path_list,
target_path_list):
if local_path is None or target_path is None:
flg = False
elif self.exists(local_path):
local_cache = self.get_from(local_path,
local_path + f'{time.time()}',
wait_finish=wait_finish)
flg = self.put_object_from_local_file(
local_cache, target_path)
try:
if os.path.exists(local_cache):
os.remove(local_cache)
except Exception:
pass
else:
flg = False
R.acquire()
try:
data_quene.put_nowait([local_path, target_path, flg])
except Exception:
R.release()
R.release()
while True:
batch_local_list = local_path_list[:4 * batch_size]
batch_target_list = target_path_list[:4 * batch_size]
if len(batch_local_list) < 1:
break
local_path_list = local_path_list[4 * batch_size:]
target_path_list = target_path_list[4 * batch_size:]
threading_list = []
for i in range(batch_size):
t = threading.Thread(target=put_one_object,
args=(
batch_local_list[i::batch_size],
batch_target_list[i::batch_size],
))
t.daemon = True
t.start()
threading_list.append(t)
[threading_t.join() for threading_t in threading_list]
file_dict = {}
while not data_quene.empty():
local_path, target_path, flg = data_quene.get_nowait()
file_dict[local_path] = [local_path, target_path, flg]
for idx, local_path in enumerate(batch_local_list):
local_path, target_path, flg = file_dict.get(
local_path,
[batch_local_list[idx], batch_target_list[idx], False])
yield local_path, target_path, flg
def get_object_stream(self,
target_path,
start,
size=10000,
delimiter=None):
with self.get_fs_client(target_path) as client:
local_data, end = client.get_object_stream(target_path,
start,
size=size,
delimiter=delimiter)
return local_data, end
def get_object_chunk_list(self,
target_path,
chunk_num=1,
chunk_size=-1,
delimiter=None):
with self.get_fs_client(target_path) as client:
chunk_list = client.get_object_chunk_list(target_path,
chunk_num=chunk_num,
chunk_size=chunk_size,
delimiter=delimiter)
if chunk_list is None:
raise ReadException(f'Failed to fetch {target_path}')
return chunk_list
def size(self, target_path):
with self.get_fs_client(target_path) as client:
size = client.size(target_path)
return size
def isfile(self, target_path):
with self.get_fs_client(target_path) as client:
is_file = client.isfile(target_path)
return is_file
def isdir(self, target_path):
with self.get_fs_client(target_path) as client:
is_dir = client.isdir(target_path)
return is_dir
@contextmanager
def put_to(self, target_path):
with self.get_fs_client(target_path) as client:
local_path, is_tmp = client.map_to_local(target_path)
if is_tmp:
client.add_temp_file(local_path)
if not os.path.exists(os.path.dirname(local_path)):
os.makedirs(os.path.dirname(local_path))
yield local_path
status = client.put_object_from_local_file(local_path, target_path)
if not status:
raise WriteException(
f'Failed to upload from {local_path} to {target_path}')
if not isinstance(client, LocalFs):
try:
if os.path.exists(local_path):
os.remove(local_path)
except Exception:
pass
def __repr__(self) -> str:
s = 'Support prefix list:\n'
for prefix in sorted(list(self._prefix_to_clients.keys()),
key=lambda a: -len(a)):
s += f'\t{prefix} -> {self._prefix_to_clients[prefix]}\n'
return s
global FS, DATA_FS, MODEL_FS
# global instance, easy to use
FS = FileSystem()
DATA_FS = FS
MODEL_FS = FS