filebackup 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- filebackup-0.5.3/PKG-INFO +99 -0
- filebackup-0.5.3/README.md +64 -0
- filebackup-0.5.3/pyproject.toml +121 -0
- filebackup-0.5.3/src/FileBackup/CommandLine/CommandLineArguments.py +75 -0
- filebackup-0.5.3/src/FileBackup/CommandLine/EntryPoint.py +53 -0
- filebackup-0.5.3/src/FileBackup/CommandLine/MirrorEntryPoint.py +182 -0
- filebackup-0.5.3/src/FileBackup/CommandLine/OffsiteEntryPoint.py +326 -0
- filebackup-0.5.3/src/FileBackup/CommandLine/__init__.py +0 -0
- filebackup-0.5.3/src/FileBackup/DataStore/FastGlacierDataStore.py +81 -0
- filebackup-0.5.3/src/FileBackup/DataStore/FileSystemDataStore.py +218 -0
- filebackup-0.5.3/src/FileBackup/DataStore/Interfaces/BulkStorageDataStore.py +36 -0
- filebackup-0.5.3/src/FileBackup/DataStore/Interfaces/DataStore.py +37 -0
- filebackup-0.5.3/src/FileBackup/DataStore/Interfaces/FileBasedDataStore.py +157 -0
- filebackup-0.5.3/src/FileBackup/DataStore/Interfaces/__init__.py +0 -0
- filebackup-0.5.3/src/FileBackup/DataStore/S3BrowserDataStore.py +81 -0
- filebackup-0.5.3/src/FileBackup/DataStore/SFTPDataStore.py +355 -0
- filebackup-0.5.3/src/FileBackup/DataStore/__init__.py +0 -0
- filebackup-0.5.3/src/FileBackup/Impl/Common.py +704 -0
- filebackup-0.5.3/src/FileBackup/Impl/__init__.py +0 -0
- filebackup-0.5.3/src/FileBackup/Mirror.py +551 -0
- filebackup-0.5.3/src/FileBackup/Offsite.py +1588 -0
- filebackup-0.5.3/src/FileBackup/Snapshot.py +696 -0
- filebackup-0.5.3/src/FileBackup/__init__.py +6 -0
- filebackup-0.5.3/src/FileBackup/py.typed +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: filebackup
|
|
3
|
+
Version: 0.5.3
|
|
4
|
+
Summary: Tool for creating and restoring file system backups.
|
|
5
|
+
Keywords: backup,mirror,offsite,restore
|
|
6
|
+
Author: David Brownell
|
|
7
|
+
Author-email: David Brownell <github@DavidBrownell.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: System :: Archiving :: Backup
|
|
24
|
+
Classifier: Topic :: System :: Archiving :: Mirroring
|
|
25
|
+
Classifier: Topic :: System :: Systems Administration
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Dist: dbrownell-common>=0.15.0
|
|
28
|
+
Requires-Dist: paramiko>=3.5.1
|
|
29
|
+
Requires-Dist: typer>=0.15.3
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Project-URL: Documentation, https://github.com/davidbrownell/FileBackup
|
|
32
|
+
Project-URL: Homepage, https://github.com/davidbrownell/FileBackup
|
|
33
|
+
Project-URL: Repository, https://github.com/davidbrownell/FileBackup
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
**Project:**
|
|
37
|
+
[](https://github.com/davidbrownell/FileBackup/blob/master/LICENSE)
|
|
38
|
+
|
|
39
|
+
**Package:**
|
|
40
|
+
[](https://pypi.org/project/FileBackup/)
|
|
41
|
+
[](https://pypi.org/project/FileBackup/)
|
|
42
|
+
[](https://pypistats.org/packages/filebackup)
|
|
43
|
+
|
|
44
|
+
**Development:**
|
|
45
|
+
[](https://github.com/astral-sh/uv)
|
|
46
|
+
[](https://github.com/davidbrownell/FileBackup/actions/workflows/CICD.yml)
|
|
47
|
+
[](https://github.com/davidbrownell/FileBackup/actions)
|
|
48
|
+
[](https://github.com/davidbrownell/FileBackup/commits/main/)
|
|
49
|
+
|
|
50
|
+
<!-- Content above this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
51
|
+
|
|
52
|
+
## Contents
|
|
53
|
+
- [Overview](#overview)
|
|
54
|
+
- [Installation](#installation)
|
|
55
|
+
- [Development](#development)
|
|
56
|
+
- [Additional Information](#additional-information)
|
|
57
|
+
- [License](#license)
|
|
58
|
+
|
|
59
|
+
## Overview
|
|
60
|
+
TODO: Complete this section
|
|
61
|
+
|
|
62
|
+
### How to use `FileBackup`
|
|
63
|
+
TODO: Complete this section
|
|
64
|
+
|
|
65
|
+
<!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
| Installation Method | Command |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| Via [uv](https://github.com/astral-sh/uv) | `uv add FileBackup` |
|
|
72
|
+
| Via [pip](https://pip.pypa.io/en/stable/) | `pip install FileBackup` |
|
|
73
|
+
|
|
74
|
+
### Verifying Signed Artifacts
|
|
75
|
+
Artifacts are signed and verified using [py-minisign](https://github.com/x13a/py-minisign) and the public key in the file `./minisign_key.pub`.
|
|
76
|
+
|
|
77
|
+
To verify that an artifact is valid, visit [the latest release](https://github.com/davidbrownell/FileBackup/releases/latest) and download the `.minisign` signature file that corresponds to the artifact, then run the following command, replacing `<filename>` with the name of the artifact to be verified:
|
|
78
|
+
|
|
79
|
+
```shell
|
|
80
|
+
uv run --with py-minisign python -c "import minisign; minisign.PublicKey.from_file('minisign_key.pub').verify_file('<filename>'); print('The file has been verified.')"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Development
|
|
84
|
+
Please visit [Contributing](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) and [Development](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) for information on contributing to this project.
|
|
85
|
+
|
|
86
|
+
## Additional Information
|
|
87
|
+
Additional information can be found at these locations.
|
|
88
|
+
|
|
89
|
+
| Title | Document | Description |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| Code of Conduct | [CODE_OF_CONDUCT.md](https://github.com/davidbrownell/FileBackup/blob/main/CODE_OF_CONDUCT.md) | Information about the norms, rules, and responsibilities we adhere to when participating in this open source community. |
|
|
92
|
+
| Contributing | [CONTRIBUTING.md](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) | Information about contributing to this project. |
|
|
93
|
+
| Development | [DEVELOPMENT.md](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) | Information about development activities involved in making changes to this project. |
|
|
94
|
+
| Governance | [GOVERNANCE.md](https://github.com/davidbrownell/FileBackup/blob/main/GOVERNANCE.md) | Information about how this project is governed. |
|
|
95
|
+
| Maintainers | [MAINTAINERS.md](https://github.com/davidbrownell/FileBackup/blob/main/MAINTAINERS.md) | Information about individuals who maintain this project. |
|
|
96
|
+
| Security | [SECURITY.md](https://github.com/davidbrownell/FileBackup/blob/main/SECURITY.md) | Information about how to privately report security issues associated with this project. |
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
`FileBackup` is licensed under the <a href="https://choosealicense.com/licenses/MIT/" target="_blank">MIT</a> license.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
**Project:**
|
|
2
|
+
[](https://github.com/davidbrownell/FileBackup/blob/master/LICENSE)
|
|
3
|
+
|
|
4
|
+
**Package:**
|
|
5
|
+
[](https://pypi.org/project/FileBackup/)
|
|
6
|
+
[](https://pypi.org/project/FileBackup/)
|
|
7
|
+
[](https://pypistats.org/packages/filebackup)
|
|
8
|
+
|
|
9
|
+
**Development:**
|
|
10
|
+
[](https://github.com/astral-sh/uv)
|
|
11
|
+
[](https://github.com/davidbrownell/FileBackup/actions/workflows/CICD.yml)
|
|
12
|
+
[](https://github.com/davidbrownell/FileBackup/actions)
|
|
13
|
+
[](https://github.com/davidbrownell/FileBackup/commits/main/)
|
|
14
|
+
|
|
15
|
+
<!-- Content above this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
- [Overview](#overview)
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Development](#development)
|
|
21
|
+
- [Additional Information](#additional-information)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
TODO: Complete this section
|
|
26
|
+
|
|
27
|
+
### How to use `FileBackup`
|
|
28
|
+
TODO: Complete this section
|
|
29
|
+
|
|
30
|
+
<!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
| Installation Method | Command |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Via [uv](https://github.com/astral-sh/uv) | `uv add FileBackup` |
|
|
37
|
+
| Via [pip](https://pip.pypa.io/en/stable/) | `pip install FileBackup` |
|
|
38
|
+
|
|
39
|
+
### Verifying Signed Artifacts
|
|
40
|
+
Artifacts are signed and verified using [py-minisign](https://github.com/x13a/py-minisign) and the public key in the file `./minisign_key.pub`.
|
|
41
|
+
|
|
42
|
+
To verify that an artifact is valid, visit [the latest release](https://github.com/davidbrownell/FileBackup/releases/latest) and download the `.minisign` signature file that corresponds to the artifact, then run the following command, replacing `<filename>` with the name of the artifact to be verified:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
uv run --with py-minisign python -c "import minisign; minisign.PublicKey.from_file('minisign_key.pub').verify_file('<filename>'); print('The file has been verified.')"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
Please visit [Contributing](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) and [Development](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) for information on contributing to this project.
|
|
50
|
+
|
|
51
|
+
## Additional Information
|
|
52
|
+
Additional information can be found at these locations.
|
|
53
|
+
|
|
54
|
+
| Title | Document | Description |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| Code of Conduct | [CODE_OF_CONDUCT.md](https://github.com/davidbrownell/FileBackup/blob/main/CODE_OF_CONDUCT.md) | Information about the norms, rules, and responsibilities we adhere to when participating in this open source community. |
|
|
57
|
+
| Contributing | [CONTRIBUTING.md](https://github.com/davidbrownell/FileBackup/blob/main/CONTRIBUTING.md) | Information about contributing to this project. |
|
|
58
|
+
| Development | [DEVELOPMENT.md](https://github.com/davidbrownell/FileBackup/blob/main/DEVELOPMENT.md) | Information about development activities involved in making changes to this project. |
|
|
59
|
+
| Governance | [GOVERNANCE.md](https://github.com/davidbrownell/FileBackup/blob/main/GOVERNANCE.md) | Information about how this project is governed. |
|
|
60
|
+
| Maintainers | [MAINTAINERS.md](https://github.com/davidbrownell/FileBackup/blob/main/MAINTAINERS.md) | Information about individuals who maintain this project. |
|
|
61
|
+
| Security | [SECURITY.md](https://github.com/davidbrownell/FileBackup/blob/main/SECURITY.md) | Information about how to privately report security issues associated with this project. |
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
`FileBackup` is licensed under the <a href="https://choosealicense.com/licenses/MIT/" target="_blank">MIT</a> license.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "FileBackup"
|
|
3
|
+
version = "0.5.3"
|
|
4
|
+
# ^^^^^
|
|
5
|
+
# Wheel names will be generated according to this value. Do not manually modify this value; instead
|
|
6
|
+
# update it according to committed changes by running this command from the root of the repository:
|
|
7
|
+
#
|
|
8
|
+
# uv run python -m AutoGitSemVer.scripts.UpdatePythonVersion ./pyproject.toml ./src
|
|
9
|
+
|
|
10
|
+
description = "Tool for creating and restoring file system backups."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "David Brownell", email = "github@DavidBrownell.com" }
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">= 3.10"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"dbrownell-common>=0.15.0",
|
|
18
|
+
"paramiko>=3.5.1",
|
|
19
|
+
"typer>=0.15.3",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
keywords = [
|
|
23
|
+
"backup",
|
|
24
|
+
"mirror",
|
|
25
|
+
"offsite",
|
|
26
|
+
"restore",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
license = "MIT"
|
|
30
|
+
|
|
31
|
+
classifiers = [
|
|
32
|
+
"Development Status :: 4 - Beta",
|
|
33
|
+
"Environment :: Console",
|
|
34
|
+
"Intended Audience :: Developers",
|
|
35
|
+
"Intended Audience :: End Users/Desktop",
|
|
36
|
+
"Intended Audience :: System Administrators",
|
|
37
|
+
"Natural Language :: English",
|
|
38
|
+
"Operating System :: MacOS",
|
|
39
|
+
"Operating System :: Microsoft :: Windows",
|
|
40
|
+
"Operating System :: POSIX :: Linux",
|
|
41
|
+
"Programming Language :: Python",
|
|
42
|
+
"Programming Language :: Python :: 3.10",
|
|
43
|
+
"Programming Language :: Python :: 3.11",
|
|
44
|
+
"Programming Language :: Python :: 3.12",
|
|
45
|
+
"Programming Language :: Python :: 3.13",
|
|
46
|
+
"Topic :: System :: Archiving :: Backup",
|
|
47
|
+
"Topic :: System :: Archiving :: Mirroring",
|
|
48
|
+
"Topic :: System :: Systems Administration",
|
|
49
|
+
"Topic :: Utilities",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/davidbrownell/FileBackup"
|
|
54
|
+
Documentation = "https://github.com/davidbrownell/FileBackup"
|
|
55
|
+
Repository = "https://github.com/davidbrownell/FileBackup"
|
|
56
|
+
|
|
57
|
+
[project.scripts]
|
|
58
|
+
FileBackup = "FileBackup.CommandLine:EntryPoint.app"
|
|
59
|
+
file_backup = "FileBackup.CommandLine:EntryPoint.app"
|
|
60
|
+
|
|
61
|
+
[build-system]
|
|
62
|
+
requires = ["uv_build>=0.8.15,<0.9.0"]
|
|
63
|
+
build-backend = "uv_build"
|
|
64
|
+
|
|
65
|
+
[dependency-groups]
|
|
66
|
+
dev = [
|
|
67
|
+
"autogitsemver>=0.9.2",
|
|
68
|
+
"cx-freeze>=8.3.0",
|
|
69
|
+
"dbrownell-commitemojis>=0.1.4",
|
|
70
|
+
"pre-commit>=4.2.0",
|
|
71
|
+
"py-minisign>=0.12.0",
|
|
72
|
+
"pytest>=8.4.1",
|
|
73
|
+
"pytest-cov>=6.2.1",
|
|
74
|
+
"ruff>=0.12.3",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
addopts = "--verbose -vv --capture=no --cov=FileBackup --cov-report term --cov-report xml:coverage.xml --cov-fail-under=85.0"
|
|
79
|
+
python_files = [
|
|
80
|
+
"**/*Test.py",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.ruff]
|
|
84
|
+
line-length = 110
|
|
85
|
+
|
|
86
|
+
[tool.ruff.lint]
|
|
87
|
+
exclude = ["tests/**"]
|
|
88
|
+
|
|
89
|
+
ignore = [
|
|
90
|
+
"ANN002", # Missing type annotation for `*args`
|
|
91
|
+
"ANN003", # Missing type annotation for `**kwargs`
|
|
92
|
+
"BLE001", # Do not catch blind exception: `Exception`
|
|
93
|
+
"COM812", # Trailing comma missing
|
|
94
|
+
"D105", # Missing docstring in magic method
|
|
95
|
+
"D107", # Missing docstring in `__init__` method
|
|
96
|
+
"D202", # No blank lines allowed after function docstring
|
|
97
|
+
"E501", # Line too long
|
|
98
|
+
"FIX002", # Line contains TODO, consider resolving the issue
|
|
99
|
+
"I001", # Import block is un-sorted or un-formatted
|
|
100
|
+
"N802", # Function name `xxx` should be lowercase
|
|
101
|
+
"N999", # Invalid module name
|
|
102
|
+
"RSE102", # Unnecessary parentheses on raise exception
|
|
103
|
+
"S101", # Use of assert detected
|
|
104
|
+
"TC006", # Add quotes to type expression in `typing.cast()`
|
|
105
|
+
"TD002", # Missing author in TODO
|
|
106
|
+
"TD003", # Missing issue link for this TODO
|
|
107
|
+
"TRY002", # Create your own exception
|
|
108
|
+
"TRY300", # Consider moving this statement to an `else` block
|
|
109
|
+
"UP032", # Use f-string instead of `format` call
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
[tool.ruff.lint.mccabe]
|
|
113
|
+
max-complexity = 15
|
|
114
|
+
|
|
115
|
+
[tool.ruff.lint.pylint]
|
|
116
|
+
max-args = 10
|
|
117
|
+
max-branches = 20
|
|
118
|
+
max-returns = 20
|
|
119
|
+
|
|
120
|
+
[tool.uv.build-backend]
|
|
121
|
+
module-name = "FileBackup"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------
|
|
2
|
+
# |
|
|
3
|
+
# | CommandLineArguments.py
|
|
4
|
+
# |
|
|
5
|
+
# | David Brownell <db@DavidBrownell.com>
|
|
6
|
+
# | 2024-06-12 13:20:05
|
|
7
|
+
# |
|
|
8
|
+
# ----------------------------------------------------------------------
|
|
9
|
+
# |
|
|
10
|
+
# | Copyright David Brownell 2024
|
|
11
|
+
# | Distributed under the MIT License.
|
|
12
|
+
# |
|
|
13
|
+
# ----------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
from typing import Pattern
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ----------------------------------------------------------------------
|
|
23
|
+
def ToRegex(
|
|
24
|
+
values: list[str],
|
|
25
|
+
) -> list[Pattern]:
|
|
26
|
+
expressions: list[Pattern] = []
|
|
27
|
+
|
|
28
|
+
for value in values:
|
|
29
|
+
try:
|
|
30
|
+
expressions.append(re.compile("^{}$".format(value)))
|
|
31
|
+
except re.error as ex:
|
|
32
|
+
raise typer.BadParameter("The regular expression '{}' is not valid ({}).".format(value, ex))
|
|
33
|
+
|
|
34
|
+
return expressions
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ----------------------------------------------------------------------
|
|
38
|
+
input_filename_or_dirs_argument = typer.Argument(
|
|
39
|
+
..., exists=True, resolve_path=True, help="Input filename or directory."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
ssd_option = typer.Option(
|
|
43
|
+
"--ssd",
|
|
44
|
+
help="Processes tasks in parallel to leverage the capabilities of solid-state-drives.",
|
|
45
|
+
)
|
|
46
|
+
ssd_option_default = False
|
|
47
|
+
|
|
48
|
+
quiet_option = typer.Option("--quiet", help="Reduce the amount of information displayed.")
|
|
49
|
+
quiet_option_default = False
|
|
50
|
+
|
|
51
|
+
force_option = typer.Option(
|
|
52
|
+
"--force",
|
|
53
|
+
help="Ignore previous backup information and overwrite all data in the destination data store.",
|
|
54
|
+
)
|
|
55
|
+
force_option_default = False
|
|
56
|
+
|
|
57
|
+
verbose_option = typer.Option("--verbose", help="Write verbose information to the terminal.")
|
|
58
|
+
verbose_option_default = False
|
|
59
|
+
|
|
60
|
+
debug_option = typer.Option("--debug", help="Write debug information to the terminal.")
|
|
61
|
+
debug_option_default = False
|
|
62
|
+
|
|
63
|
+
file_include_option = typer.Option(
|
|
64
|
+
"--file-include",
|
|
65
|
+
callback=ToRegex,
|
|
66
|
+
help="Regular expression (based on a posix path) used to include files and/or directories when preserving content.",
|
|
67
|
+
)
|
|
68
|
+
file_include_option_default: list[str] = []
|
|
69
|
+
|
|
70
|
+
file_exclude_option = typer.Option(
|
|
71
|
+
"--file-exclude",
|
|
72
|
+
callback=ToRegex,
|
|
73
|
+
help="Regular expression (based on a posix path) used to exclude files and/or directories when preserving content.",
|
|
74
|
+
)
|
|
75
|
+
file_exclude_option_default: list[str] = []
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------
|
|
2
|
+
# |
|
|
3
|
+
# | Copyright (c) 2024 David Brownell
|
|
4
|
+
# | Distributed under the MIT License.
|
|
5
|
+
# |
|
|
6
|
+
# ----------------------------------------------------------------------
|
|
7
|
+
"""Tools to backup and restore files and directories."""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from typer.core import TyperGroup # type: ignore [import-untyped]
|
|
14
|
+
|
|
15
|
+
from FileBackup import __version__
|
|
16
|
+
from FileBackup.CommandLine import MirrorEntryPoint
|
|
17
|
+
from FileBackup.CommandLine import OffsiteEntryPoint
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ----------------------------------------------------------------------
|
|
21
|
+
class NaturalOrderGrouper(TyperGroup):
|
|
22
|
+
# pylint: disable=missing-class-docstring
|
|
23
|
+
# ----------------------------------------------------------------------
|
|
24
|
+
def list_commands(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
25
|
+
return self.commands.keys() # pragma: no cover
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ----------------------------------------------------------------------
|
|
29
|
+
app = typer.Typer(
|
|
30
|
+
cls=NaturalOrderGrouper,
|
|
31
|
+
help=__doc__,
|
|
32
|
+
no_args_is_help=True,
|
|
33
|
+
pretty_exceptions_show_locals=False,
|
|
34
|
+
pretty_exceptions_enable=False,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
app.add_typer(MirrorEntryPoint.app, name="mirror", help=MirrorEntryPoint.__doc__)
|
|
39
|
+
app.add_typer(OffsiteEntryPoint.app, name="offsite", help=OffsiteEntryPoint.__doc__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("version", no_args_is_help=False)
|
|
43
|
+
def Version():
|
|
44
|
+
"""Displays the current version and exits."""
|
|
45
|
+
|
|
46
|
+
sys.stdout.write(f"FileBackup v{__version__}\n")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ----------------------------------------------------------------------
|
|
50
|
+
# ----------------------------------------------------------------------
|
|
51
|
+
# ----------------------------------------------------------------------
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
app() # pragma: no cover
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------
|
|
2
|
+
# |
|
|
3
|
+
# | MirrorEntryPoint.py
|
|
4
|
+
# |
|
|
5
|
+
# | David Brownell <db@DavidBrownell.com>
|
|
6
|
+
# | 2024-06-12 13:16:52
|
|
7
|
+
# |
|
|
8
|
+
# ----------------------------------------------------------------------
|
|
9
|
+
# |
|
|
10
|
+
# | Copyright David Brownell 2024
|
|
11
|
+
# | Distributed under the MIT License.
|
|
12
|
+
# |
|
|
13
|
+
# ----------------------------------------------------------------------
|
|
14
|
+
"""\
|
|
15
|
+
Mirrors backup content: files created locally will be added to the backup data store; files deleted
|
|
16
|
+
locally will be removed from the backup data store; files modified locally will be modified at the
|
|
17
|
+
backup data store.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import datetime
|
|
21
|
+
import textwrap
|
|
22
|
+
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Annotated, cast, Pattern
|
|
25
|
+
|
|
26
|
+
import typer
|
|
27
|
+
|
|
28
|
+
from dbrownell_Common.Streams.DoneManager import DoneManager, Flags as DoneManagerFlags # type: ignore [import-untyped]
|
|
29
|
+
from typer.core import TyperGroup # type: ignore [import-untyped]
|
|
30
|
+
|
|
31
|
+
from FileBackup.CommandLine import CommandLineArguments
|
|
32
|
+
from FileBackup.Impl import Common
|
|
33
|
+
from FileBackup import Mirror
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ----------------------------------------------------------------------
|
|
37
|
+
class NaturalOrderGrouper(TyperGroup):
|
|
38
|
+
# pylint: disable=missing-class-docstring
|
|
39
|
+
# ----------------------------------------------------------------------
|
|
40
|
+
def list_commands(self, *args, **kwargs): # pylint: disable=unused-argument
|
|
41
|
+
return self.commands.keys() # pragma: no cover
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ----------------------------------------------------------------------
|
|
45
|
+
app = typer.Typer(
|
|
46
|
+
cls=NaturalOrderGrouper,
|
|
47
|
+
help=__doc__,
|
|
48
|
+
no_args_is_help=True,
|
|
49
|
+
pretty_exceptions_show_locals=False,
|
|
50
|
+
pretty_exceptions_enable=False,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ----------------------------------------------------------------------
|
|
55
|
+
_destination_argument = typer.Argument(
|
|
56
|
+
...,
|
|
57
|
+
help="Destination data store used when mirroring local content; see the comments below for information on the different data store destination formats.",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ----------------------------------------------------------------------
|
|
62
|
+
@app.command(
|
|
63
|
+
"execute",
|
|
64
|
+
epilog=Common.GetDestinationHelp(),
|
|
65
|
+
no_args_is_help=True,
|
|
66
|
+
)
|
|
67
|
+
def Execute(
|
|
68
|
+
destination: Annotated[str, _destination_argument],
|
|
69
|
+
input_filename_or_dirs: Annotated[
|
|
70
|
+
list[Path],
|
|
71
|
+
CommandLineArguments.input_filename_or_dirs_argument,
|
|
72
|
+
],
|
|
73
|
+
ssd: Annotated[bool, CommandLineArguments.ssd_option] = CommandLineArguments.ssd_option_default,
|
|
74
|
+
force: Annotated[bool, CommandLineArguments.force_option] = CommandLineArguments.force_option_default,
|
|
75
|
+
verbose: Annotated[
|
|
76
|
+
bool, CommandLineArguments.verbose_option
|
|
77
|
+
] = CommandLineArguments.verbose_option_default,
|
|
78
|
+
quiet: Annotated[bool, CommandLineArguments.quiet_option] = CommandLineArguments.quiet_option_default,
|
|
79
|
+
debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
|
|
80
|
+
file_include_params: Annotated[
|
|
81
|
+
list[str],
|
|
82
|
+
CommandLineArguments.file_include_option,
|
|
83
|
+
] = CommandLineArguments.file_include_option_default,
|
|
84
|
+
file_exclude_params: Annotated[
|
|
85
|
+
list[str],
|
|
86
|
+
CommandLineArguments.file_exclude_option,
|
|
87
|
+
] = CommandLineArguments.file_exclude_option_default,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Mirrors content to a backup data store."""
|
|
90
|
+
|
|
91
|
+
file_includes = cast(list[Pattern], file_include_params)
|
|
92
|
+
file_excludes = cast(list[Pattern], file_exclude_params)
|
|
93
|
+
|
|
94
|
+
del file_include_params
|
|
95
|
+
del file_exclude_params
|
|
96
|
+
|
|
97
|
+
with DoneManager.CreateCommandLine(
|
|
98
|
+
flags=DoneManagerFlags.Create(verbose=verbose, debug=debug),
|
|
99
|
+
) as dm:
|
|
100
|
+
dm.WriteVerbose(str(datetime.datetime.now()) + "\n\n")
|
|
101
|
+
|
|
102
|
+
Mirror.Backup(
|
|
103
|
+
dm,
|
|
104
|
+
destination,
|
|
105
|
+
input_filename_or_dirs,
|
|
106
|
+
ssd=ssd,
|
|
107
|
+
force=force,
|
|
108
|
+
quiet=quiet,
|
|
109
|
+
file_includes=file_includes,
|
|
110
|
+
file_excludes=file_excludes,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ----------------------------------------------------------------------
|
|
115
|
+
@app.command(
|
|
116
|
+
"validate",
|
|
117
|
+
no_args_is_help=True,
|
|
118
|
+
epilog=textwrap.dedent(
|
|
119
|
+
"""\
|
|
120
|
+
{}
|
|
121
|
+
Validation Types
|
|
122
|
+
================
|
|
123
|
+
standard: Validates that files and directories at the destination exist and file sizes match the expected values.
|
|
124
|
+
complete: Validates that files and directories at the destination exist and file hashes match the expected values.
|
|
125
|
+
""",
|
|
126
|
+
)
|
|
127
|
+
.replace("\n", "\n\n")
|
|
128
|
+
.format(Common.GetDestinationHelp()),
|
|
129
|
+
)
|
|
130
|
+
def Validate(
|
|
131
|
+
destination: Annotated[str, _destination_argument],
|
|
132
|
+
validate_type: Annotated[
|
|
133
|
+
Mirror.ValidateType,
|
|
134
|
+
typer.Argument(
|
|
135
|
+
case_sensitive=False,
|
|
136
|
+
help="Specifies the type of validation to use; the the comments below for information on the different validation types.",
|
|
137
|
+
),
|
|
138
|
+
] = Mirror.ValidateType.standard,
|
|
139
|
+
ssd: Annotated[bool, CommandLineArguments.ssd_option] = CommandLineArguments.ssd_option_default,
|
|
140
|
+
verbose: Annotated[
|
|
141
|
+
bool, CommandLineArguments.verbose_option
|
|
142
|
+
] = CommandLineArguments.verbose_option_default,
|
|
143
|
+
quiet: Annotated[bool, CommandLineArguments.quiet_option] = CommandLineArguments.quiet_option_default,
|
|
144
|
+
debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Validates previously mirrored content in the backup data store."""
|
|
147
|
+
|
|
148
|
+
with DoneManager.CreateCommandLine(
|
|
149
|
+
flags=DoneManagerFlags.Create(verbose=verbose, debug=debug),
|
|
150
|
+
) as dm:
|
|
151
|
+
dm.WriteVerbose(str(datetime.datetime.now()) + "\n\n")
|
|
152
|
+
|
|
153
|
+
Mirror.Validate(
|
|
154
|
+
dm,
|
|
155
|
+
destination,
|
|
156
|
+
validate_type,
|
|
157
|
+
ssd=ssd,
|
|
158
|
+
quiet=quiet,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ----------------------------------------------------------------------
|
|
163
|
+
@app.command(
|
|
164
|
+
"cleanup",
|
|
165
|
+
epilog=Common.GetDestinationHelp(),
|
|
166
|
+
no_args_is_help=True,
|
|
167
|
+
)
|
|
168
|
+
def Cleanup(
|
|
169
|
+
destination: Annotated[str, _destination_argument],
|
|
170
|
+
verbose: Annotated[
|
|
171
|
+
bool, CommandLineArguments.verbose_option
|
|
172
|
+
] = CommandLineArguments.verbose_option_default,
|
|
173
|
+
debug: Annotated[bool, CommandLineArguments.debug_option] = CommandLineArguments.debug_option_default,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Cleans a backup data store after a mirror execution that was interrupted or failed."""
|
|
176
|
+
|
|
177
|
+
with DoneManager.CreateCommandLine(
|
|
178
|
+
flags=DoneManagerFlags.Create(verbose=verbose, debug=debug),
|
|
179
|
+
) as dm:
|
|
180
|
+
dm.WriteVerbose(str(datetime.datetime.now()) + "\n\n")
|
|
181
|
+
|
|
182
|
+
Mirror.Cleanup(dm, destination)
|