style: linting of backups module

This commit is contained in:
Inex Code 2023-07-19 15:59:51 +03:00
parent 3067d353d8
commit 5253780cc8
3 changed files with 79 additions and 33 deletions

View file

@ -5,7 +5,7 @@ from selfprivacy_api.models.backup.snapshot import Snapshot
class AbstractBackupper(ABC): class AbstractBackupper(ABC):
def __init__(self): def __init__(self) -> None:
pass pass
@abstractmethod @abstractmethod
@ -13,11 +13,11 @@ class AbstractBackupper(ABC):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def set_creds(self, account: str, key: str, repo: str): def set_creds(self, account: str, key: str, repo: str) -> None:
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def start_backup(self, folders: List[str], repo_name: str): def start_backup(self, folders: List[str], repo_name: str) -> Snapshot:
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
@ -26,7 +26,7 @@ class AbstractBackupper(ABC):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def init(self): def init(self) -> None:
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
@ -35,7 +35,7 @@ class AbstractBackupper(ABC):
snapshot_id: str, snapshot_id: str,
folders: List[str], folders: List[str],
verify=True, verify=True,
): ) -> None:
"""Restore a target folder using a snapshot""" """Restore a target folder using a snapshot"""
raise NotImplementedError raise NotImplementedError
@ -44,5 +44,5 @@ class AbstractBackupper(ABC):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def forget_snapshot(self, snapshot_id): def forget_snapshot(self, snapshot_id) -> None:
raise NotImplementedError raise NotImplementedError

View file

@ -21,7 +21,7 @@ from selfprivacy_api.backup.local_secret import LocalBackupSecret
class ResticBackupper(AbstractBackupper): class ResticBackupper(AbstractBackupper):
def __init__(self, login_flag: str, key_flag: str, type: str): def __init__(self, login_flag: str, key_flag: str, type: str) -> None:
self.login_flag = login_flag self.login_flag = login_flag
self.key_flag = key_flag self.key_flag = key_flag
self.type = type self.type = type
@ -29,7 +29,7 @@ class ResticBackupper(AbstractBackupper):
self.key = "" self.key = ""
self.repo = "" self.repo = ""
def set_creds(self, account: str, key: str, repo: str): def set_creds(self, account: str, key: str, repo: str) -> None:
self.account = account self.account = account
self.key = key self.key = key
self.repo = repo self.repo = repo
@ -79,7 +79,11 @@ class ResticBackupper(AbstractBackupper):
def mount_repo(self, dir): def mount_repo(self, dir):
mount_command = self.restic_command("mount", dir) mount_command = self.restic_command("mount", dir)
mount_command.insert(0, "nohup") mount_command.insert(0, "nohup")
handle = subprocess.Popen(mount_command, stdout=subprocess.DEVNULL, shell=False) handle = subprocess.Popen(
mount_command,
stdout=subprocess.DEVNULL,
shell=False,
)
sleep(2) sleep(2)
if "ids" not in listdir(dir): if "ids" not in listdir(dir):
raise IOError("failed to mount dir ", dir) raise IOError("failed to mount dir ", dir)
@ -109,12 +113,13 @@ class ResticBackupper(AbstractBackupper):
result.append(item) result.append(item)
return result return result
def start_backup(self, folders: List[str], tag: str): def start_backup(self, folders: List[str], tag: str) -> Snapshot:
""" """
Start backup with restic Start backup with restic
""" """
# but maybe it is ok to accept a union of a string and an array of strings # but maybe it is ok to accept a union
# of a string and an array of strings
assert not isinstance(folders, str) assert not isinstance(folders, str)
backup_command = self.restic_command( backup_command = self.restic_command(
@ -125,20 +130,34 @@ class ResticBackupper(AbstractBackupper):
) )
messages = [] messages = []
job = get_backup_job(get_service_by_id(tag))
service = get_service_by_id(tag)
if service is None:
raise ValueError("No service with id ", tag)
job = get_backup_job(service)
try: try:
for raw_message in output_yielder(backup_command): for raw_message in output_yielder(backup_command):
message = self.parse_message(raw_message, job) message = self.parse_message(
raw_message,
job,
)
messages.append(message) messages.append(message)
return ResticBackupper._snapshot_from_backup_messages(messages, tag) return ResticBackupper._snapshot_from_backup_messages(
messages,
tag,
)
except ValueError as e: except ValueError as e:
raise ValueError("could not create a snapshot: ", messages) from e raise ValueError("Could not create a snapshot: ", messages) from e
@staticmethod @staticmethod
def _snapshot_from_backup_messages(messages, repo_name) -> Snapshot: def _snapshot_from_backup_messages(messages, repo_name) -> Snapshot:
for message in messages: for message in messages:
if message["message_type"] == "summary": if message["message_type"] == "summary":
return ResticBackupper._snapshot_from_fresh_summary(message, repo_name) return ResticBackupper._snapshot_from_fresh_summary(
message,
repo_name,
)
raise ValueError("no summary message in restic json output") raise ValueError("no summary message in restic json output")
def parse_message(self, raw_message_line: str, job=None) -> dict: def parse_message(self, raw_message_line: str, job=None) -> dict:
@ -162,7 +181,7 @@ class ResticBackupper(AbstractBackupper):
service_name=repo_name, service_name=repo_name,
) )
def init(self): def init(self) -> None:
init_command = self.restic_command( init_command = self.restic_command(
"init", "init",
) )
@ -173,7 +192,7 @@ class ResticBackupper(AbstractBackupper):
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
) as process_handle: ) as process_handle:
output = process_handle.communicate()[0].decode("utf-8") output = process_handle.communicate()[0].decode("utf-8")
if not "created restic repository" in output: if "created restic repository" not in output:
raise ValueError("cannot init a repo: " + output) raise ValueError("cannot init a repo: " + output)
def is_initted(self) -> bool: def is_initted(self) -> bool:
@ -182,7 +201,11 @@ class ResticBackupper(AbstractBackupper):
"--json", "--json",
) )
with subprocess.Popen(command, stdout=subprocess.PIPE, shell=False) as handle: with subprocess.Popen(
command,
stdout=subprocess.PIPE,
shell=False,
) as handle:
output = handle.communicate()[0].decode("utf-8") output = handle.communicate()[0].decode("utf-8")
if not ResticBackupper.has_json(output): if not ResticBackupper.has_json(output):
return False return False
@ -216,7 +239,7 @@ class ResticBackupper(AbstractBackupper):
snapshot_id, snapshot_id,
folders: List[str], folders: List[str],
verify=True, verify=True,
): ) -> None:
""" """
Restore from backup with restic Restore from backup with restic
""" """
@ -235,9 +258,7 @@ class ResticBackupper(AbstractBackupper):
for folder in folders: for folder in folders:
src = join(snapshot_root, folder.strip("/")) src = join(snapshot_root, folder.strip("/"))
if not exists(src): if not exists(src):
raise ValueError( raise ValueError(f"No such path: {src}. We tried to find {folder}")
f"there is no such path: {src}. We tried to find {folder}"
)
dst = folder dst = folder
sync(src, dst) sync(src, dst)
@ -254,7 +275,8 @@ class ResticBackupper(AbstractBackupper):
restore_command, stdout=subprocess.PIPE, shell=False restore_command, stdout=subprocess.PIPE, shell=False
) as handle: ) as handle:
# for some reason restore does not support nice reporting of progress via json # for some reason restore does not support
# nice reporting of progress via json
output = handle.communicate()[0].decode("utf-8") output = handle.communicate()[0].decode("utf-8")
if "restoring" not in output: if "restoring" not in output:
raise ValueError("cannot restore a snapshot: " + output) raise ValueError("cannot restore a snapshot: " + output)
@ -264,21 +286,36 @@ class ResticBackupper(AbstractBackupper):
) # none should be impossible after communicate ) # none should be impossible after communicate
if handle.returncode != 0: if handle.returncode != 0:
raise ValueError( raise ValueError(
"restore exited with errorcode", returncode, ":", output "restore exited with errorcode",
handle.returncode,
":",
output,
) )
def forget_snapshot(self, snapshot_id): def forget_snapshot(self, snapshot_id) -> None:
"""either removes snapshot or marks it for deletion later depending on server settings""" """
Either removes snapshot or marks it for deletion later,
depending on server settings
"""
forget_command = self.restic_command( forget_command = self.restic_command(
"forget", "forget",
snapshot_id, snapshot_id,
) )
with subprocess.Popen( with subprocess.Popen(
forget_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False forget_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
) as handle: ) as handle:
# for some reason restore does not support nice reporting of progress via json # for some reason restore does not support
output, err = [string.decode("utf-8") for string in handle.communicate()] # nice reporting of progress via json
output, err = [
string.decode(
"utf-8",
)
for string in handle.communicate()
]
if "no matching ID found" in err: if "no matching ID found" in err:
raise ValueError( raise ValueError(
@ -290,7 +327,10 @@ class ResticBackupper(AbstractBackupper):
) # none should be impossible after communicate ) # none should be impossible after communicate
if handle.returncode != 0: if handle.returncode != 0:
raise ValueError( raise ValueError(
"forget exited with errorcode", returncode, ":", output "forget exited with errorcode",
handle.returncode,
":",
output,
) )
def _load_snapshots(self) -> object: def _load_snapshots(self) -> object:
@ -336,7 +376,7 @@ class ResticBackupper(AbstractBackupper):
starting_index = ResticBackupper.json_start(output) starting_index = ResticBackupper.json_start(output)
if starting_index == -1: if starting_index == -1:
raise ValueError("There is no json in the restic output : " + output) raise ValueError("There is no json in the restic output: " + output)
truncated_output = output[starting_index:] truncated_output = output[starting_index:]
json_messages = truncated_output.splitlines() json_messages = truncated_output.splitlines()

View file

@ -74,11 +74,17 @@ class Storage:
return None return None
snapshot = hash_as_model(redis, key, Snapshot) snapshot = hash_as_model(redis, key, Snapshot)
if not snapshot:
return None
return snapshot.created_at return snapshot.created_at
@staticmethod @staticmethod
def store_last_timestamp(service_id: str, snapshot: Snapshot): def store_last_timestamp(service_id: str, snapshot: Snapshot):
store_model_as_hash(redis, Storage.__last_backup_key(service_id), snapshot) store_model_as_hash(
redis,
Storage.__last_backup_key(service_id),
snapshot,
)
@staticmethod @staticmethod
def cache_snapshot(snapshot: Snapshot): def cache_snapshot(snapshot: Snapshot):