131 lines
3.2 KiB
Python
131 lines
3.2 KiB
Python
"""
|
|
.. author:: Eric Torres
|
|
.. module:: rbackup.struct.hierarchy
|
|
:synopsis: Classes for creating the backup hierarchy.
|
|
"""
|
|
import json
|
|
import logging
|
|
from os import PathLike
|
|
from pathlib import Path
|
|
|
|
# ========== Logging Setup ===========
|
|
syslog = logging.getLogger(__name__)
|
|
|
|
|
|
# ========== Constants ==========
|
|
METADATA_READ = "r"
|
|
METADATA_WRITE = "w"
|
|
|
|
|
|
# ========== Classes ==========
|
|
class Hierarchy(PathLike):
|
|
"""A class for organizing the backup root hierarchy.
|
|
|
|
Upon creation of a Hierarchy object, it is up to the caller
|
|
to call either shutil.mkdir() or a related method to create
|
|
the directory structure it emulates.
|
|
|
|
Attributes
|
|
----------
|
|
* path
|
|
* name
|
|
* metadata_path
|
|
|
|
Methods
|
|
-------
|
|
* read_metadata - read this Hierarchy's metadata from a file
|
|
and return it
|
|
* write_metadata - write this Hierarchy's metadata to a file
|
|
"""
|
|
|
|
def __init__(self, dest):
|
|
"""Default constructor for the Hierarchy class.
|
|
|
|
>>> hier = Hierarchy('backup')
|
|
>>> hier.path
|
|
PosixPath('backup')
|
|
|
|
:param dest: the root directory of the backup hierarchy
|
|
:type dest: str or path-like object
|
|
"""
|
|
try:
|
|
self._path = Path(dest)
|
|
except TypeError as e:
|
|
raise e
|
|
|
|
def __repr__(self):
|
|
"""Return a string representation of this Hierarchy."""
|
|
return f"{self.__class__.__name__}('{self._path}')"
|
|
|
|
def __fspath__(self):
|
|
return str(self._path)
|
|
|
|
@property
|
|
def path(self):
|
|
"""Return the base directory of this hierarchy.
|
|
|
|
>>> hier = Hierarchy('backup')
|
|
>>> hier.path
|
|
PosixPath('backup')
|
|
|
|
:rtype: path-like object
|
|
"""
|
|
return self._path
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of this hierarchy.
|
|
|
|
>>> hier = Hierarchy('backup/data/snapshot-one')
|
|
>>> hier.name
|
|
'snapshot-one'
|
|
|
|
:rtype: str
|
|
"""
|
|
return self._path.name
|
|
|
|
@property
|
|
def metadata_path(self):
|
|
"""Return the path of this hierarchy's metadata file.
|
|
|
|
>>> hier = Hierarchy('backup')
|
|
>>> hier.metadata_path
|
|
PosixPath('backup/.metadata')
|
|
|
|
:rtype: path-like object
|
|
"""
|
|
return self._path / ".metadata"
|
|
|
|
def read_metadata(self):
|
|
"""Read this repository's metadata from its file and
|
|
then return it.
|
|
|
|
:rtype: type that the data is serialized as
|
|
"""
|
|
syslog.debug(f"Reading metadata from {self.metadata_path}")
|
|
with self.metadata_path.open(mode=METADATA_READ) as mfile:
|
|
return json.load(mfile)
|
|
|
|
def write_metadata(self, attr):
|
|
"""Write this repository's metadata to its file.
|
|
Note that this write operation is atomic to the caller.
|
|
|
|
:param attr: data to write to file
|
|
:type attr: class member to write
|
|
"""
|
|
syslog.debug(f"Writing metadata to {self.metadata_path}")
|
|
|
|
tmpfile = self.metadata_path.with_suffix(".tmp")
|
|
|
|
with tmpfile.open(mode=METADATA_WRITE) as mfile:
|
|
json.dump(attr, mfile)
|
|
|
|
tmpfile.rename(self.metadata_path)
|
|
|
|
|
|
# ========== Functions ==========
|
|
if __name__ == "__main__":
|
|
import doctest
|
|
|
|
doctest.testmod()
|