"""Storage prototype"""
# Copyright 2017 Qarnot computing
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=W0613
import inspect
import os
import os.path
import deprecation
from . import __version__
[docs]
class Storage(object):
"""Common architecture for storage providers
"""
[docs]
def get_all_files(self, output_dir, progress=None):
"""Get all files from the storage.
:param str output_dir: local directory for the retrieved files.
:param progress: can be a callback (read,total,filename) or True to display a progress bar
:type progress: bool or function(float, float, str)
:raises ~qarnot.exceptions.MissingBucketException: the bucket is not on the server
:raises ~qarnot.exceptions.QarnotGenericException: API general error, see message for details
:raises ~qarnot.exceptions.UnauthorizedException: invalid credentials
.. warning:: Will override *output_dir* content.
"""
for file_info in self:
outpath = os.path.normpath(file_info.key.lstrip('/'))
self.get_file(file_info.key, os.path.join(output_dir, outpath), progress)
[docs]
def __init__(self):
self._uuid = None
def _not_implemented(self):
raise NotImplementedError("Class %s doesn't implement %s" % (self.__class__.__name__, inspect.stack()[1][3]))
[docs]
def list_files(self):
"""List files on the storage.
... note:
File object returned *must* have a `key` property.
:returns: List of the files on the storage.
"""
self._not_implemented()
def _download_file(self, remote, local, progress=None):
"""Actual file downloader.
:param remote: the name of the remote file
:type remote: str
:param str local: local name of the retrieved file (defaults to *remote*)
:param progress: can be a callback (read,total,filename) or True to display a progress bar
:type progress: bool or function(float, float, str)
:rtype: :class:`str`
:returns: The name of the output file.
"""
self._not_implemented()
[docs]
def get_file(self, remote, local=None, progress=None):
"""Get a file from the storage.
Create needed subfolders.
:param remote: the name of the remote file
:type remote: str
:param str local: local name of the retrieved file (defaults to *remote*)
:param progress: can be a callback (read,total,filename) or True to display a progress bar
:type progress: bool or function(float, float, str)
:rtype: :class:`str`
:returns: The name of the output file.
:raises ValueError: no such file
"""
def make_dirs(_local):
"""Make directory if needed"""
directory = os.path.dirname(_local)
if directory != '' and not os.path.exists(directory):
os.makedirs(directory)
if local is None:
local = os.path.basename(remote)
if os.path.isdir(local):
local = os.path.join(local, os.path.basename(remote))
make_dirs(local)
if os.path.isdir(local):
return
return self._download_file(remote, local, progress)
[docs]
def copy_file(self, source, dest):
"""Create a copy of a file
:param str source: name of the existing file to duplicate
:param str dest: name of the created file
"""
self._not_implemented()
[docs]
def add_directory(self, local, remote):
""" Add a directory to the storage. Does not follow symlinks.
File hierarchy is preserved.
:param str local: path of the local directory to add
:param str remote: path of the directory on remote node
(defaults to *local*)
:raises IOError: not a valid directory
"""
self._not_implemented()
[docs]
def add_file(self, local_or_file, remote):
"""Add a local file or a Python File on the storage.
.. note::
You can also use **object[remote] = local**
:param local_or_file: path of the local file or an opened Python File
:type local_or_file: str or File
:param str remote: name of the remote file
(defaults to *local_or_file*)
"""
self._not_implemented()
[docs]
def delete_file(self, remote):
"""Delete a file from the storage.
:param str remote: the name of the remote file
"""
self._not_implemented()
[docs]
@deprecation.deprecated(deprecated_in="2.6.0", removed_in="3.0",
current_version=__version__, # type: ignore
details="Legacy function")
def update(self, flush=None):
"""Update object from remote endpoint
:param bool flush: bypass cache
"""
self._not_implemented()
[docs]
@deprecation.deprecated(deprecated_in="2.6.0", removed_in="3.0",
current_version=__version__, # type: ignore
details="Legacy function")
def flush(self):
"""Ensure all background uploads are complete
"""
self._not_implemented()
# Operators
def __getitem__(self, filename):
"""x.__getitem__(y) <==> x[y]"""
try:
return self.get_file(filename)
except ValueError as error:
raise KeyError(filename) from error
def __setitem__(self, remote, filename):
"""x.__setitem__(i, y) <==> x[i]=y"""
if os.path.isdir(filename):
return self.add_directory(filename, remote)
return self.add_file(filename, remote)
def __delitem__(self, filename):
"""x.__delitem__(y) <==> del x[y]"""
try:
return self.delete_file(filename)
except ValueError as error:
raise KeyError(filename) from error
def __contains__(self, item):
"""D.__contains__(k) -> True if D has a key k, else False"""
return item in self.list_files()
def __iter__(self):
"""x.__iter__() <==> iter(x)"""
return iter(self.list_files())
def __eq__(self, other):
"""x.__eq__(y) <==> x == y"""
if isinstance(other, self.__class__):
return self._uuid == other._uuid
return False
def __ne__(self, other):
"""x.__ne__(y) <==> x != y"""
return not self.__eq__(other)