pyinfra/pyinfra/storage.py
Matthias Bisping 5cd52928ce refactorig WIP
2022-02-16 15:49:38 +01:00

180 lines
5.7 KiB
Python

import abc
import gzip
import logging
import os
from itertools import repeat
from operator import attrgetter
from typing import Iterable
from pyinfra.utils.file import path_to_compressed_storage_pdf_object_name, provide_directory
from pyinfra.utils.meta import NoAttemptsLeft, max_attempts
class StorageHandle:
"""Storage API base"""
def __init__(self):
self.default_container_name = None
@abc.abstractmethod
def __provide_container(self, container_name):
pass
@abc.abstractmethod
def __add_file(self, path, filename, container_name=None):
pass
@max_attempts(n_attempts=10, max_timeout=60)
def add_file(self, path: str, folder: str = None, container_name: str = None) -> None:
"""Adds a file to the store.
Args:
path: Path to file to add to store.
folder: Folder to hold file.
container_name: container to hold file.
"""
storage_path = self.__storage_path(path, folder=folder)
self.__add_file(path, storage_path, container_name)
@max_attempts()
def _list_files(self, object_name_attr="object_name", container_name=None) -> Iterable[str]:
"""List all files in a container.
Args:
container_name: container to list files from.
Returns:
Iterable of filenames.
"""
if container_name is None:
container_name = self.default_container_name
return map(attrgetter(object_name_attr), self.get_objects(container_name))
@abc.abstractmethod
def list_files(self, container_name=None) -> Iterable[str]:
pass
@abc.abstractmethod
def get_objects(self, container_name=None):
pass
@abc.abstractmethod
def __list_containers(self):
pass
@max_attempts()
def get_all_objects(self) -> Iterable:
"""Gets all objects in the store
Returns:
Iterable over all objects in the store.
"""
for container in self.__list_containers():
yield from zip(repeat(container), self.get_objects(container.name))
@abc.abstractmethod
def __purge(self) -> None:
pass
@max_attempts()
def purge(self) -> None:
self.__purge()
def list_files_by_type(self, container_name=None, extension=".pdf.gz"):
return filter(lambda p: p.endswith(extension), self.list_files(container_name))
@abc.abstractmethod
def __fget_object(self, *args, **kwargs):
pass
@staticmethod
def __storage_path(path, folder: str = None):
def path_to_filename(path):
return os.path.basename(path)
storage_path = path_to_filename(path)
if folder is not None:
storage_path = os.path.join(folder, storage_path)
return storage_path
@max_attempts()
def list_folders_and_files(self, container_name: str = None) -> Iterable[str]:
"""Lists pairs of folder name (dossier-IDs) and file name (file-IDs) of items in a container.
Args:
container_name: container to list items for.
Returns:
Iterable of pairs folder name (dossier-ID) and file names (file-ID)
"""
return map(lambda p: p.split("/"), self.list_files_by_type(container_name))
@abc.abstractmethod
def __remove_file(self, folder: str, filename: str, container_name: str = None) -> None:
pass
@max_attempts()
def remove_file(self, folder: str, filename: str, container_name: str = None) -> None:
self.__remove_file(folder, filename, container_name)
def add_file_compressed(self, path, folder: str = None, container_name: str = None) -> None:
"""Adds a file as a .gz archive to the store.
Args:
path: Path to file to add to store.
folder: Folder to hold file.
container_name: container to hold file.
"""
def compress(path_in: str, path_out: str):
with open(path_in, "rb") as f_in, gzip.open(path_out, "wb") as f_out:
f_out.writelines(f_in)
path_gz = path_to_compressed_storage_pdf_object_name(path)
compress(path, path_gz)
self.add_file(path_gz, folder, container_name)
os.unlink(path_gz)
@max_attempts()
def download_file(self, object_names: str, target_root_dir: str, container_name: str = None) -> str:
"""Downloads a file from the store.
Args:
object_names: Complete object name (folder and file).
target_root_dir: Root directory to download file into (including its folder).
container_name: container to load file from.
Returns:
str: Path to downloaded file.
"""
@max_attempts(5, exceptions=(FileNotFoundError,))
def download(object_name: str) -> str:
path, basename = os.path.split(object_name)
target_dir = os.path.join(target_root_dir, path)
provide_directory(target_dir)
target_path = os.path.join(target_dir, basename)
logging.log(msg=f"Downloading {object_name}...", level=logging.DEBUG)
try:
self.__fget_object(container_name, object_name, target_path)
logging.log(msg=f"Downloaded {object_name}.", level=logging.DEBUG)
except Exception as err:
logging.log(msg=f"Downloading {object_name} failed.", level=logging.ERROR)
raise err
return target_path
if container_name is None:
container_name = self.default_container_name
try:
target_path = download(object_names)
except NoAttemptsLeft as err:
logging.log(msg=f"{err}", level=logging.ERROR)
raise err
return target_path