Skip to content

Commit f7024a4

Browse files
committed
feat: add backupper
1 parent 7658649 commit f7024a4

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

package.py

+7
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,16 @@ def archiver() -> None:
2222
_create_archive("archiver", ["archiver.py", "common.py"])
2323

2424

25+
def backupper() -> None:
26+
_create_archive(
27+
"backupper", ["backupper.py", "common.py", "archiver.py", "checksum.py"]
28+
)
29+
30+
2531
if __name__ == "__main__":
2632
if len(sys.argv) > 1:
2733
globals()[sys.argv[1]]()
2834
else:
2935
# TODO Make generic # pylint: disable=fixme
3036
archiver()
37+
backupper()

scripts/backupper.py

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
3+
from argparse import ArgumentParser, ArgumentTypeError, Namespace
4+
import os
5+
from typing import Iterator
6+
7+
from common import Config, Logger
8+
from archiver import Archiver
9+
import checksum
10+
11+
12+
logger = Logger()
13+
14+
15+
class Backupper: # pylint: disable=too-few-public-methods
16+
def __init__(self, verbose: bool = False) -> None:
17+
self._config = Config("backupper.json")
18+
self._archiver = Archiver()
19+
self._verbose = verbose
20+
if verbose:
21+
logger.verbose()
22+
23+
def run(
24+
self, destination: str | None = None, password: str | None = None
25+
) -> Iterator[tuple[str, bool]]:
26+
config = self._config.get_config()
27+
sources = config["sources"]
28+
29+
if not sources:
30+
logger.warning("No sources defined")
31+
return iter(())
32+
33+
paths = [s["path"] for s in sources]
34+
checksums = checksum.generate(paths)
35+
36+
archives: list[str] = []
37+
for path, digest in checksums:
38+
for source in sources:
39+
if not os.path.samefile(path, source["path"]):
40+
continue
41+
if digest != source.get("checksum", None):
42+
source["checksum"] = digest
43+
archives.append(path)
44+
45+
self._config.set_config(config)
46+
47+
return self._archiver.create_archives(
48+
archives, destination=destination, password=password, verbose=self._verbose
49+
)
50+
51+
52+
def _check_directory(directory_string):
53+
if not os.path.isdir(directory_string):
54+
raise ArgumentTypeError("invalid destination directory")
55+
return directory_string
56+
57+
58+
def _create_argument_parser() -> ArgumentParser:
59+
parser: ArgumentParser = ArgumentParser(
60+
description="Backups data by creating 7zip archives"
61+
)
62+
parser.add_argument("-p", "--password", type=str, help="password")
63+
parser.add_argument("-v", "--verbose", action="store_true", help="verbose")
64+
parser.add_argument(
65+
"-d", "--destination", type=_check_directory, help="destination directory"
66+
)
67+
return parser
68+
69+
70+
def main() -> None:
71+
parser: ArgumentParser = _create_argument_parser()
72+
args: Namespace = parser.parse_args()
73+
74+
backupper = Backupper(args.verbose)
75+
backupper.run(args.destination, args.password)
76+
77+
78+
if __name__ == "__main__":
79+
main()

tests/test_backupper.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import json
2+
import os
3+
from pathlib import Path
4+
import shutil
5+
import uuid
6+
import pytest
7+
8+
from scripts.backupper import Backupper
9+
from scripts.common import Config
10+
11+
12+
TEST_PATH = "./out"
13+
TARGET_DIR = f"{TEST_PATH}/test"
14+
TEST_ARCHIVE = f"{TEST_PATH}/test.7z"
15+
PASSWORD = "test"
16+
17+
18+
@pytest.fixture(autouse=True)
19+
def prepare():
20+
# pylint: disable=duplicate-code
21+
if os.path.exists(TEST_PATH):
22+
shutil.rmtree(TEST_PATH)
23+
Path(TARGET_DIR).mkdir(parents=True, exist_ok=True)
24+
for i in range(1, 4):
25+
with open(f"{TARGET_DIR}/file{i}.txt", "w", encoding="utf-8") as file:
26+
file.write(f"Random string in file {i} {uuid.uuid4()}")
27+
28+
29+
def test_backup():
30+
with open(f"{TEST_PATH}/backupper.json", "w", encoding="utf-8") as file:
31+
json.dump({"sources": [{"path": TARGET_DIR}]}, file)
32+
33+
config = Config(f"{TEST_PATH}/backupper.json")
34+
35+
backupper = Backupper()
36+
backupper._config = config # pylint: disable=protected-access
37+
38+
result = dict(backupper.run(destination=TEST_PATH, password=PASSWORD))
39+
40+
assert os.path.isfile(TEST_ARCHIVE)
41+
assert result[os.path.normpath(TEST_ARCHIVE)]
42+
43+
with open(f"{TEST_PATH}/backupper.json", "r", encoding="utf-8") as file:
44+
source = json.load(file)["sources"][0]
45+
assert len(source["checksum"]) == 40

0 commit comments

Comments
 (0)