mirror of https://github.com/JDAI-CV/fast-reid.git
521 lines
19 KiB
Python
521 lines
19 KiB
Python
#!/usr/bin/env python3
|
||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||
|
||
import errno
|
||
import logging
|
||
import os
|
||
import shutil
|
||
from collections import OrderedDict
|
||
from typing import (
|
||
IO,
|
||
Any,
|
||
Callable,
|
||
Dict,
|
||
List,
|
||
MutableMapping,
|
||
Optional,
|
||
Union,
|
||
)
|
||
|
||
__all__ = ["PathManager", "get_cache_dir"]
|
||
|
||
|
||
def get_cache_dir(cache_dir: Optional[str] = None) -> str:
|
||
"""
|
||
Returns a default directory to cache static files
|
||
(usually downloaded from Internet), if None is provided.
|
||
Args:
|
||
cache_dir (None or str): if not None, will be returned as is.
|
||
If None, returns the default cache directory as:
|
||
1) $FVCORE_CACHE, if set
|
||
2) otherwise ~/.torch/fvcore_cache
|
||
"""
|
||
if cache_dir is None:
|
||
cache_dir = os.path.expanduser(
|
||
os.getenv("FVCORE_CACHE", "~/.torch/fvcore_cache")
|
||
)
|
||
return cache_dir
|
||
|
||
|
||
class PathHandler:
|
||
"""
|
||
PathHandler is a base class that defines common I/O functionality for a URI
|
||
protocol. It routes I/O for a generic URI which may look like "protocol://*"
|
||
or a canonical filepath "/foo/bar/baz".
|
||
"""
|
||
|
||
_strict_kwargs_check = True
|
||
|
||
def _check_kwargs(self, kwargs: Dict[str, Any]) -> None:
|
||
"""
|
||
Checks if the given arguments are empty. Throws a ValueError if strict
|
||
kwargs checking is enabled and args are non-empty. If strict kwargs
|
||
checking is disabled, only a warning is logged.
|
||
Args:
|
||
kwargs (Dict[str, Any])
|
||
"""
|
||
if self._strict_kwargs_check:
|
||
if len(kwargs) > 0:
|
||
raise ValueError("Unused arguments: {}".format(kwargs))
|
||
else:
|
||
logger = logging.getLogger(__name__)
|
||
for k, v in kwargs.items():
|
||
logger.warning(
|
||
"[PathManager] {}={} argument ignored".format(k, v)
|
||
)
|
||
|
||
def _get_supported_prefixes(self) -> List[str]:
|
||
"""
|
||
Returns:
|
||
List[str]: the list of URI prefixes this PathHandler can support
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _get_local_path(self, path: str, **kwargs: Any) -> str:
|
||
"""
|
||
Get a filepath which is compatible with native Python I/O such as `open`
|
||
and `os.path`.
|
||
If URI points to a remote resource, this function may download and cache
|
||
the resource to local disk. In this case, this function is meant to be
|
||
used with read-only resources.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
local_path (str): a file path which exists on the local file system
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _open(
|
||
self, path: str, mode: str = "r", buffering: int = -1, **kwargs: Any
|
||
) -> Union[IO[str], IO[bytes]]:
|
||
"""
|
||
Open a stream to a URI, similar to the built-in `open`.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
mode (str): Specifies the mode in which the file is opened. It defaults
|
||
to 'r'.
|
||
buffering (int): An optional integer used to set the buffering policy.
|
||
Pass 0 to switch buffering off and an integer >= 1 to indicate the
|
||
size in bytes of a fixed-size chunk buffer. When no buffering
|
||
argument is given, the default buffering policy depends on the
|
||
underlying I/O implementation.
|
||
Returns:
|
||
file: a file-like object.
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _copy(
|
||
self,
|
||
src_path: str,
|
||
dst_path: str,
|
||
overwrite: bool = False,
|
||
**kwargs: Any,
|
||
) -> bool:
|
||
"""
|
||
Copies a source path to a destination path.
|
||
Args:
|
||
src_path (str): A URI supported by this PathHandler
|
||
dst_path (str): A URI supported by this PathHandler
|
||
overwrite (bool): Bool flag for forcing overwrite of existing file
|
||
Returns:
|
||
status (bool): True on success
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _exists(self, path: str, **kwargs: Any) -> bool:
|
||
"""
|
||
Checks if there is a resource at the given URI.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
bool: true if the path exists
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _isfile(self, path: str, **kwargs: Any) -> bool:
|
||
"""
|
||
Checks if the resource at the given URI is a file.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
bool: true if the path is a file
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _isdir(self, path: str, **kwargs: Any) -> bool:
|
||
"""
|
||
Checks if the resource at the given URI is a directory.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
bool: true if the path is a directory
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _ls(self, path: str, **kwargs: Any) -> List[str]:
|
||
"""
|
||
List the contents of the directory at the provided URI.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
List[str]: list of contents in given path
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _mkdirs(self, path: str, **kwargs: Any) -> None:
|
||
"""
|
||
Recursive directory creation function. Like mkdir(), but makes all
|
||
intermediate-level directories needed to contain the leaf directory.
|
||
Similar to the native `os.makedirs`.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
def _rm(self, path: str, **kwargs: Any) -> None:
|
||
"""
|
||
Remove the file (not directory) at the provided URI.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
"""
|
||
raise NotImplementedError()
|
||
|
||
|
||
class NativePathHandler(PathHandler):
|
||
"""
|
||
Handles paths that can be accessed using Python native system calls. This
|
||
handler uses `open()` and `os.*` calls on the given path.
|
||
"""
|
||
|
||
def _get_local_path(self, path: str, **kwargs: Any) -> str:
|
||
self._check_kwargs(kwargs)
|
||
return path
|
||
|
||
def _open(
|
||
self,
|
||
path: str,
|
||
mode: str = "r",
|
||
buffering: int = -1,
|
||
encoding: Optional[str] = None,
|
||
errors: Optional[str] = None,
|
||
newline: Optional[str] = None,
|
||
closefd: bool = True,
|
||
opener: Optional[Callable] = None,
|
||
**kwargs: Any,
|
||
) -> Union[IO[str], IO[bytes]]:
|
||
"""
|
||
Open a path.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
mode (str): Specifies the mode in which the file is opened. It defaults
|
||
to 'r'.
|
||
buffering (int): An optional integer used to set the buffering policy.
|
||
Pass 0 to switch buffering off and an integer >= 1 to indicate the
|
||
size in bytes of a fixed-size chunk buffer. When no buffering
|
||
argument is given, the default buffering policy works as follows:
|
||
* Binary files are buffered in fixed-size chunks; the size of
|
||
the buffer is chosen using a heuristic trying to determine the
|
||
underlying device’s “block size” and falling back on
|
||
io.DEFAULT_BUFFER_SIZE. On many systems, the buffer will
|
||
typically be 4096 or 8192 bytes long.
|
||
encoding (Optional[str]): the name of the encoding used to decode or
|
||
encode the file. This should only be used in text mode.
|
||
errors (Optional[str]): an optional string that specifies how encoding
|
||
and decoding errors are to be handled. This cannot be used in binary
|
||
mode.
|
||
newline (Optional[str]): controls how universal newlines mode works
|
||
(it only applies to text mode). It can be None, '', '\n', '\r',
|
||
and '\r\n'.
|
||
closefd (bool): If closefd is False and a file descriptor rather than
|
||
a filename was given, the underlying file descriptor will be kept
|
||
open when the file is closed. If a filename is given closefd must
|
||
be True (the default) otherwise an error will be raised.
|
||
opener (Optional[Callable]): A custom opener can be used by passing
|
||
a callable as opener. The underlying file descriptor for the file
|
||
object is then obtained by calling opener with (file, flags).
|
||
opener must return an open file descriptor (passing os.open as opener
|
||
results in functionality similar to passing None).
|
||
See https://docs.python.org/3/library/functions.html#open for details.
|
||
Returns:
|
||
file: a file-like object.
|
||
"""
|
||
self._check_kwargs(kwargs)
|
||
return open( # type: ignore
|
||
path,
|
||
mode,
|
||
buffering=buffering,
|
||
encoding=encoding,
|
||
errors=errors,
|
||
newline=newline,
|
||
closefd=closefd,
|
||
opener=opener,
|
||
)
|
||
|
||
def _copy(
|
||
self,
|
||
src_path: str,
|
||
dst_path: str,
|
||
overwrite: bool = False,
|
||
**kwargs: Any,
|
||
) -> bool:
|
||
"""
|
||
Copies a source path to a destination path.
|
||
Args:
|
||
src_path (str): A URI supported by this PathHandler
|
||
dst_path (str): A URI supported by this PathHandler
|
||
overwrite (bool): Bool flag for forcing overwrite of existing file
|
||
Returns:
|
||
status (bool): True on success
|
||
"""
|
||
self._check_kwargs(kwargs)
|
||
|
||
if os.path.exists(dst_path) and not overwrite:
|
||
logger = logging.getLogger(__name__)
|
||
logger.error("Destination file {} already exists.".format(dst_path))
|
||
return False
|
||
|
||
try:
|
||
shutil.copyfile(src_path, dst_path)
|
||
return True
|
||
except Exception as e:
|
||
logger = logging.getLogger(__name__)
|
||
logger.error("Error in file copy - {}".format(str(e)))
|
||
return False
|
||
|
||
def _exists(self, path: str, **kwargs: Any) -> bool:
|
||
self._check_kwargs(kwargs)
|
||
return os.path.exists(path)
|
||
|
||
def _isfile(self, path: str, **kwargs: Any) -> bool:
|
||
self._check_kwargs(kwargs)
|
||
return os.path.isfile(path)
|
||
|
||
def _isdir(self, path: str, **kwargs: Any) -> bool:
|
||
self._check_kwargs(kwargs)
|
||
return os.path.isdir(path)
|
||
|
||
def _ls(self, path: str, **kwargs: Any) -> List[str]:
|
||
self._check_kwargs(kwargs)
|
||
return os.listdir(path)
|
||
|
||
def _mkdirs(self, path: str, **kwargs: Any) -> None:
|
||
self._check_kwargs(kwargs)
|
||
try:
|
||
os.makedirs(path, exist_ok=True)
|
||
except OSError as e:
|
||
# EEXIST it can still happen if multiple processes are creating the dir
|
||
if e.errno != errno.EEXIST:
|
||
raise
|
||
|
||
def _rm(self, path: str, **kwargs: Any) -> None:
|
||
self._check_kwargs(kwargs)
|
||
os.remove(path)
|
||
|
||
|
||
class PathManager:
|
||
"""
|
||
A class for users to open generic paths or translate generic paths to file names.
|
||
"""
|
||
|
||
_PATH_HANDLERS: MutableMapping[str, PathHandler] = OrderedDict()
|
||
_NATIVE_PATH_HANDLER = NativePathHandler()
|
||
|
||
@staticmethod
|
||
def __get_path_handler(path: str) -> PathHandler:
|
||
"""
|
||
Finds a PathHandler that supports the given path. Falls back to the native
|
||
PathHandler if no other handler is found.
|
||
Args:
|
||
path (str): URI path to resource
|
||
Returns:
|
||
handler (PathHandler)
|
||
"""
|
||
for p in PathManager._PATH_HANDLERS.keys():
|
||
if path.startswith(p):
|
||
return PathManager._PATH_HANDLERS[p]
|
||
return PathManager._NATIVE_PATH_HANDLER
|
||
|
||
@staticmethod
|
||
def open(
|
||
path: str, mode: str = "r", buffering: int = -1, **kwargs: Any
|
||
) -> Union[IO[str], IO[bytes]]:
|
||
"""
|
||
Open a stream to a URI, similar to the built-in `open`.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
mode (str): Specifies the mode in which the file is opened. It defaults
|
||
to 'r'.
|
||
buffering (int): An optional integer used to set the buffering policy.
|
||
Pass 0 to switch buffering off and an integer >= 1 to indicate the
|
||
size in bytes of a fixed-size chunk buffer. When no buffering
|
||
argument is given, the default buffering policy depends on the
|
||
underlying I/O implementation.
|
||
Returns:
|
||
file: a file-like object.
|
||
"""
|
||
return PathManager.__get_path_handler(path)._open( # type: ignore
|
||
path, mode, buffering=buffering, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def copy(
|
||
src_path: str, dst_path: str, overwrite: bool = False, **kwargs: Any
|
||
) -> bool:
|
||
"""
|
||
Copies a source path to a destination path.
|
||
Args:
|
||
src_path (str): A URI supported by this PathHandler
|
||
dst_path (str): A URI supported by this PathHandler
|
||
overwrite (bool): Bool flag for forcing overwrite of existing file
|
||
Returns:
|
||
status (bool): True on success
|
||
"""
|
||
|
||
# Copying across handlers is not supported.
|
||
assert PathManager.__get_path_handler( # type: ignore
|
||
src_path
|
||
) == PathManager.__get_path_handler(dst_path)
|
||
return PathManager.__get_path_handler(src_path)._copy(
|
||
src_path, dst_path, overwrite, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def get_local_path(path: str, **kwargs: Any) -> str:
|
||
"""
|
||
Get a filepath which is compatible with native Python I/O such as `open`
|
||
and `os.path`.
|
||
If URI points to a remote resource, this function may download and cache
|
||
the resource to local disk.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
local_path (str): a file path which exists on the local file system
|
||
"""
|
||
return PathManager.__get_path_handler( # type: ignore
|
||
path
|
||
)._get_local_path(path, **kwargs)
|
||
|
||
@staticmethod
|
||
def exists(path: str, **kwargs: Any) -> bool:
|
||
"""
|
||
Checks if there is a resource at the given URI.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
bool: true if the path exists
|
||
"""
|
||
return PathManager.__get_path_handler(path)._exists( # type: ignore
|
||
path, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def isfile(path: str, **kwargs: Any) -> bool:
|
||
"""
|
||
Checks if there the resource at the given URI is a file.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
bool: true if the path is a file
|
||
"""
|
||
return PathManager.__get_path_handler(path)._isfile( # type: ignore
|
||
path, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def isdir(path: str, **kwargs: Any) -> bool:
|
||
"""
|
||
Checks if the resource at the given URI is a directory.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
bool: true if the path is a directory
|
||
"""
|
||
return PathManager.__get_path_handler(path)._isdir( # type: ignore
|
||
path, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def ls(path: str, **kwargs: Any) -> List[str]:
|
||
"""
|
||
List the contents of the directory at the provided URI.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
Returns:
|
||
List[str]: list of contents in given path
|
||
"""
|
||
return PathManager.__get_path_handler(path)._ls( # type: ignore
|
||
path, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def mkdirs(path: str, **kwargs: Any) -> None:
|
||
"""
|
||
Recursive directory creation function. Like mkdir(), but makes all
|
||
intermediate-level directories needed to contain the leaf directory.
|
||
Similar to the native `os.makedirs`.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
"""
|
||
return PathManager.__get_path_handler(path)._mkdirs( # type: ignore
|
||
path, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def rm(path: str, **kwargs: Any) -> None:
|
||
"""
|
||
Remove the file (not directory) at the provided URI.
|
||
Args:
|
||
path (str): A URI supported by this PathHandler
|
||
"""
|
||
return PathManager.__get_path_handler(path)._rm( # type: ignore
|
||
path, **kwargs
|
||
)
|
||
|
||
@staticmethod
|
||
def register_handler(handler: PathHandler) -> None:
|
||
"""
|
||
Register a path handler associated with `handler._get_supported_prefixes`
|
||
URI prefixes.
|
||
Args:
|
||
handler (PathHandler)
|
||
"""
|
||
assert isinstance(handler, PathHandler), handler
|
||
for prefix in handler._get_supported_prefixes():
|
||
assert prefix not in PathManager._PATH_HANDLERS
|
||
PathManager._PATH_HANDLERS[prefix] = handler
|
||
|
||
# Sort path handlers in reverse order so longer prefixes take priority,
|
||
# eg: http://foo/bar before http://foo
|
||
PathManager._PATH_HANDLERS = OrderedDict(
|
||
sorted(
|
||
PathManager._PATH_HANDLERS.items(),
|
||
key=lambda t: t[0],
|
||
reverse=True,
|
||
)
|
||
)
|
||
|
||
@staticmethod
|
||
def set_strict_kwargs_checking(enable: bool) -> None:
|
||
"""
|
||
Toggles strict kwargs checking. If enabled, a ValueError is thrown if any
|
||
unused parameters are passed to a PathHandler function. If disabled, only
|
||
a warning is given.
|
||
With a centralized file API, there's a tradeoff of convenience and
|
||
correctness delegating arguments to the proper I/O layers. An underlying
|
||
`PathHandler` may support custom arguments which should not be statically
|
||
exposed on the `PathManager` function. For example, a custom `HTTPURLHandler`
|
||
may want to expose a `cache_timeout` argument for `open()` which specifies
|
||
how old a locally cached resource can be before it's refetched from the
|
||
remote server. This argument would not make sense for a `NativePathHandler`.
|
||
If strict kwargs checking is disabled, `cache_timeout` can be passed to
|
||
`PathManager.open` which will forward the arguments to the underlying
|
||
handler. By default, checking is enabled since it is innately unsafe:
|
||
multiple `PathHandler`s could reuse arguments with different semantic
|
||
meanings or types.
|
||
Args:
|
||
enable (bool)
|
||
"""
|
||
PathManager._NATIVE_PATH_HANDLER._strict_kwargs_check = enable
|
||
for handler in PathManager._PATH_HANDLERS.values():
|
||
handler._strict_kwargs_check = enable
|