borgapi 0.6.1__py3-none-any.whl → 0.7.1__py3-none-any.whl
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.
- borgapi/__init__.py +44 -4
- borgapi/borgapi.py +616 -313
- borgapi/capture.py +416 -0
- borgapi/helpers.py +26 -0
- borgapi/options.py +137 -68
- {borgapi-0.6.1.dist-info → borgapi-0.7.1.dist-info}/METADATA +68 -44
- borgapi-0.7.1.dist-info/RECORD +10 -0
- {borgapi-0.6.1.dist-info → borgapi-0.7.1.dist-info}/WHEEL +1 -1
- {borgapi-0.6.1.dist-info → borgapi-0.7.1.dist-info}/top_level.txt +0 -1
- borgapi-0.6.1.dist-info/RECORD +0 -27
- test/__init__.py +0 -0
- test/borgapi/__init__.py +0 -0
- test/borgapi/test_01_borgapi.py +0 -226
- test/borgapi/test_02_init.py +0 -78
- test/borgapi/test_03_create.py +0 -136
- test/borgapi/test_04_extract.py +0 -52
- test/borgapi/test_05_rename.py +0 -32
- test/borgapi/test_06_list.py +0 -59
- test/borgapi/test_07_diff.py +0 -54
- test/borgapi/test_08_delete.py +0 -68
- test/borgapi/test_09_prune.py +0 -48
- test/borgapi/test_10_info.py +0 -50
- test/borgapi/test_11_mount.py +0 -50
- test/borgapi/test_12_key.py +0 -81
- test/borgapi/test_13_export_tar.py +0 -38
- test/borgapi/test_14_config.py +0 -42
- test/borgapi/test_15_benchmark_crud.py +0 -24
- test/borgapi/test_16_compact.py +0 -33
- test/test_00_options.py +0 -70
- {borgapi-0.6.1.dist-info → borgapi-0.7.1.dist-info/licenses}/LICENSE +0 -0
borgapi/borgapi.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
"""Run Borg
|
|
1
|
+
"""Run Borg backups."""
|
|
2
2
|
|
|
3
|
+
import functools
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
|
-
import
|
|
6
|
-
from
|
|
6
|
+
from asyncio import wrap_future
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
from io import StringIO
|
|
7
9
|
from json import decoder, loads
|
|
8
|
-
from typing import Callable,
|
|
10
|
+
from typing import Callable, Optional, Union
|
|
9
11
|
|
|
10
12
|
import borg.archiver
|
|
11
|
-
from borg.logger import JsonFormatter
|
|
12
13
|
from dotenv import dotenv_values, load_dotenv
|
|
13
14
|
|
|
15
|
+
from .capture import LOG_LVL, OutputCapture, OutputOptions
|
|
16
|
+
from .helpers import ENVIRONMENT_DEFAULTS, Options, Output
|
|
14
17
|
from .options import (
|
|
15
18
|
ArchiveInput,
|
|
16
19
|
ArchiveOutput,
|
|
@@ -24,194 +27,116 @@ from .options import (
|
|
|
24
27
|
OptionsBase,
|
|
25
28
|
)
|
|
26
29
|
|
|
27
|
-
__all__ = ["BorgAPI"]
|
|
30
|
+
__all__ = ["BorgAPI", "BorgAPIAsync"]
|
|
28
31
|
|
|
29
|
-
Json = Union[list, dict]
|
|
30
|
-
Output = Union[str, Json, None]
|
|
31
|
-
Options = Union[bool, str, int]
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
class BorgAPIBase:
|
|
34
|
+
"""Automate borg in code.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"""Capture stdout and stderr by redirecting to inmemory streams
|
|
38
|
-
|
|
39
|
-
:param raw: Expecting raw bytes from stdout and stderr
|
|
40
|
-
:type raw: bool
|
|
36
|
+
Base class for the wrapper. Contains all the non-Borg command calls.
|
|
37
|
+
Should not be called by itself. Only really here for readability purposes.
|
|
41
38
|
"""
|
|
42
39
|
|
|
43
|
-
def __init__(
|
|
44
|
-
self,
|
|
45
|
-
raw: bool = False,
|
|
46
|
-
log_json: bool = False,
|
|
47
|
-
log_level: str = LOG_LVL,
|
|
48
|
-
):
|
|
49
|
-
self.raw = raw
|
|
50
|
-
fmt = "%(message)s"
|
|
51
|
-
self.formatter = JsonFormatter(fmt) if log_json else logging.Formatter(fmt)
|
|
52
|
-
self.level = log_level.upper()
|
|
53
|
-
self._init_stdout(raw)
|
|
54
|
-
self._init_stderr()
|
|
55
|
-
|
|
56
|
-
self._init_list_capture()
|
|
57
|
-
self._init_stats_capture()
|
|
58
|
-
self._init_repo_capture()
|
|
59
|
-
|
|
60
|
-
def _init_stdout(self, raw: bool):
|
|
61
|
-
self.stdout = TextIOWrapper(BytesIO()) if raw else StringIO()
|
|
62
|
-
self.stdout_original = sys.stdout
|
|
63
|
-
sys.stdout = self.stdout
|
|
64
|
-
|
|
65
|
-
def _init_stderr(self):
|
|
66
|
-
self.stderr = StringIO()
|
|
67
|
-
self.stderr_original = sys.stderr
|
|
68
|
-
sys.stderr = self.stderr
|
|
69
|
-
|
|
70
|
-
def _init_list_capture(self):
|
|
71
|
-
self.list_handler = logging.StreamHandler(StringIO())
|
|
72
|
-
self.list_handler.setFormatter(self.formatter)
|
|
73
|
-
# self.list_handler.setLevel(self.level)
|
|
74
|
-
self.list_handler.setLevel("INFO")
|
|
75
|
-
|
|
76
|
-
self.list_logger = logging.getLogger("borg.output.list")
|
|
77
|
-
self.list_logger.addHandler(self.list_handler)
|
|
78
|
-
|
|
79
|
-
def _init_stats_capture(self):
|
|
80
|
-
self.stats_handler = logging.StreamHandler(StringIO())
|
|
81
|
-
self.stats_handler.setFormatter(self.formatter)
|
|
82
|
-
# self.stats_handler.setLevel(self.level)
|
|
83
|
-
self.stats_handler.setLevel("INFO")
|
|
84
|
-
|
|
85
|
-
self.stats_logger = logging.getLogger("borg.output.stats")
|
|
86
|
-
self.stats_logger.addHandler(self.stats_handler)
|
|
87
|
-
|
|
88
|
-
def _init_repo_capture(self):
|
|
89
|
-
self.repo_handler = logging.StreamHandler(StringIO())
|
|
90
|
-
self.repo_handler.setFormatter(self.formatter)
|
|
91
|
-
self.repo_handler.setLevel(self.level)
|
|
92
|
-
|
|
93
|
-
self.repo_logger = logging.getLogger("borg.repository")
|
|
94
|
-
self.repo_logger.addHandler(self.repo_handler)
|
|
95
|
-
|
|
96
|
-
# pylint: disable=no-member
|
|
97
|
-
def getvalues(self) -> Union[str, bytes]:
|
|
98
|
-
"""Get the captured values from the redirected stdout and stderr
|
|
99
|
-
|
|
100
|
-
:return: Redirected values from stdout and stderr
|
|
101
|
-
:rtype: Union[str, bytes]
|
|
102
|
-
"""
|
|
103
|
-
stdout_value = stderr_value = None
|
|
104
|
-
if self.raw:
|
|
105
|
-
stdout_value = self.stdout.buffer.getvalue()
|
|
106
|
-
else:
|
|
107
|
-
stdout_value = self.stdout.getvalue().strip()
|
|
108
|
-
stderr_value = self.stderr.getvalue().strip()
|
|
109
|
-
|
|
110
|
-
list_value = self.list_handler.stream.getvalue()
|
|
111
|
-
stats_value = self.stats_handler.stream.getvalue()
|
|
112
|
-
repo_value = self.repo_handler.stream.getvalue()
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
"stdout": stdout_value,
|
|
116
|
-
"stderr": stderr_value,
|
|
117
|
-
"list": list_value,
|
|
118
|
-
"stats": stats_value,
|
|
119
|
-
"repo": repo_value,
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
def close(self):
|
|
123
|
-
"""Close the underlying IO streams and reset stdout and stderr"""
|
|
124
|
-
try:
|
|
125
|
-
self.stdout.close()
|
|
126
|
-
self.stderr.close()
|
|
127
|
-
self.list_handler.stream.close()
|
|
128
|
-
self.list_logger.removeHandler(self.list_handler)
|
|
129
|
-
self.stats_handler.stream.close()
|
|
130
|
-
self.stats_logger.removeHandler(self.stats_handler)
|
|
131
|
-
finally:
|
|
132
|
-
sys.stdout = self.stdout_original
|
|
133
|
-
sys.stderr = self.stderr_original
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# pylint: disable=too-many-public-methods
|
|
137
|
-
class BorgAPI:
|
|
138
|
-
"""Automate borg in code"""
|
|
139
|
-
|
|
140
|
-
# pylint: disable=too-many-arguments,not-callable
|
|
141
40
|
def __init__(
|
|
142
41
|
self,
|
|
143
42
|
defaults: dict = None,
|
|
144
43
|
options: dict = None,
|
|
145
44
|
log_level: str = LOG_LVL,
|
|
146
45
|
log_json: bool = False,
|
|
46
|
+
environ: dict = None,
|
|
147
47
|
):
|
|
48
|
+
"""Set the options to be used across the different command call.
|
|
49
|
+
|
|
50
|
+
:param defaults: options for specific commands to always use, defaults to None
|
|
51
|
+
:type defaults: dict, optional
|
|
52
|
+
:param options: common flags for all commands, defaults to None
|
|
53
|
+
:type options: dict, optional
|
|
54
|
+
:param log_level: level to record logged messages at, defaults to LOG_LVL
|
|
55
|
+
:type log_level: str, optional
|
|
56
|
+
:param log_json: if the output should be in json or string format, defaults to False
|
|
57
|
+
:type log_json: bool, optional
|
|
58
|
+
:param environ: envirnmental variables to set for borg to use (ie BORG_PASSCOMMAND),
|
|
59
|
+
defaults to None
|
|
60
|
+
:type environ: dict, optional
|
|
61
|
+
"""
|
|
148
62
|
self.options = options or {}
|
|
149
63
|
self.optionals = CommandOptions(defaults)
|
|
150
64
|
self.archiver = borg.archiver.Archiver()
|
|
151
65
|
self._previous_dotenv = []
|
|
152
|
-
self.
|
|
66
|
+
self._set_environ_defaults()
|
|
67
|
+
if environ is not None:
|
|
68
|
+
self.set_environ(**environ)
|
|
153
69
|
self.log_level = log_level
|
|
70
|
+
if log_json:
|
|
71
|
+
self.options.set("log_json", log_json)
|
|
154
72
|
|
|
155
|
-
|
|
156
|
-
self.
|
|
157
|
-
self.archiver.log_json = log_json or self.options.get("log_json", False)
|
|
158
|
-
borg.archiver.setup_logging(level=log_level, is_serve=False, json=log_json)
|
|
159
|
-
self.original_stdout = sys.stdout
|
|
73
|
+
self.archiver.log_json = log_json
|
|
74
|
+
borg.archiver.setup_logging(level=self.log_level, is_serve=False, json=log_json)
|
|
160
75
|
logging.getLogger("borgapi")
|
|
161
76
|
self._logger = logging.getLogger(__name__)
|
|
162
77
|
|
|
78
|
+
self.output = OutputCapture()
|
|
79
|
+
|
|
163
80
|
@staticmethod
|
|
164
|
-
def _loads_json_lines(string: str) -> Union[dict, str, None]:
|
|
81
|
+
def _loads_json_lines(string: Union[str, list]) -> Union[dict, str, None]:
|
|
165
82
|
result = None
|
|
166
83
|
try:
|
|
167
|
-
|
|
84
|
+
if type(string) is str:
|
|
85
|
+
result = loads(string)
|
|
86
|
+
elif type(string) is list:
|
|
87
|
+
result = loads(f"[{','.join(string)}]")
|
|
168
88
|
except decoder.JSONDecodeError:
|
|
169
|
-
|
|
89
|
+
if type(string) is str:
|
|
90
|
+
clean = f"[{','.join(string.splitlines())}]"
|
|
91
|
+
elif type(string) is str:
|
|
92
|
+
clean = str(string)
|
|
170
93
|
try:
|
|
171
94
|
result = loads(clean)
|
|
172
95
|
except decoder.JSONDecodeError:
|
|
173
|
-
|
|
96
|
+
try:
|
|
97
|
+
multiline = "[" + string.replace("}{", "},{") + "]"
|
|
98
|
+
result = loads(multiline)
|
|
99
|
+
except decoder.JSONDecodeError:
|
|
100
|
+
result = string or None
|
|
174
101
|
return result
|
|
175
102
|
|
|
176
103
|
@staticmethod
|
|
177
|
-
def _build_result(*results:
|
|
104
|
+
def _build_result(*results: tuple[str, Output], log_json: bool = False) -> Output:
|
|
178
105
|
if not results:
|
|
179
106
|
return None
|
|
180
107
|
if len(results) == 1:
|
|
181
|
-
|
|
108
|
+
result = results[0][1]
|
|
109
|
+
if len(result) == 1:
|
|
110
|
+
return result[0]
|
|
111
|
+
return result
|
|
182
112
|
result = {}
|
|
183
113
|
for name, value in results:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return result or None
|
|
114
|
+
result[name] = value
|
|
115
|
+
return result
|
|
187
116
|
|
|
188
|
-
# pylint: disable=no-member
|
|
189
117
|
def _run(
|
|
190
118
|
self,
|
|
191
|
-
arg_list:
|
|
119
|
+
arg_list: list,
|
|
192
120
|
func: Callable,
|
|
193
|
-
|
|
194
|
-
log_level: str = LOG_LVL,
|
|
121
|
+
output_options: OutputOptions,
|
|
195
122
|
) -> dict:
|
|
196
123
|
self._logger.debug("%s: %s", func.__name__, arg_list)
|
|
197
124
|
arg_list.insert(0, "borgapi")
|
|
198
125
|
arg_list = [str(arg) for arg in arg_list]
|
|
199
|
-
args = self.archiver.get_args(arg_list, os.
|
|
126
|
+
args = self.archiver.get_args(arg_list, os.getenv("SSH_ORIGINAL_COMMAND", None))
|
|
200
127
|
|
|
201
128
|
prev_json = self.archiver.log_json
|
|
202
129
|
log_json = getattr(args, "log_json", prev_json)
|
|
203
130
|
self.archiver.log_json = log_json
|
|
204
131
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
finally:
|
|
214
|
-
capture.close()
|
|
132
|
+
with self.output(output_options):
|
|
133
|
+
try:
|
|
134
|
+
func(args)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
self._logger.error(e)
|
|
137
|
+
raise e
|
|
138
|
+
else:
|
|
139
|
+
capture_result = self.output.getvalues()
|
|
215
140
|
|
|
216
141
|
self.archiver.log_json = prev_json
|
|
217
142
|
|
|
@@ -221,7 +146,7 @@ class BorgAPI:
|
|
|
221
146
|
args = {**self.options, **(value or {})}
|
|
222
147
|
return options_class(**args)
|
|
223
148
|
|
|
224
|
-
def _get_option_list(self, value: dict, options_class: OptionsBase) ->
|
|
149
|
+
def _get_option_list(self, value: dict, options_class: OptionsBase) -> list:
|
|
225
150
|
option = self._get_option(value, options_class)
|
|
226
151
|
return option.parse()
|
|
227
152
|
|
|
@@ -251,13 +176,65 @@ class BorgAPI:
|
|
|
251
176
|
|
|
252
177
|
return lvl
|
|
253
178
|
|
|
179
|
+
def _get_basic_results(self, output: dict, opts: OutputOptions) -> dict:
|
|
180
|
+
result_list = []
|
|
181
|
+
if opts.stats_show:
|
|
182
|
+
if opts.stats_json:
|
|
183
|
+
result_list.append(("stats", self._loads_json_lines(output["stdout"])))
|
|
184
|
+
else:
|
|
185
|
+
result_list.append(("stats", output["stats"]))
|
|
186
|
+
|
|
187
|
+
if opts.list_show:
|
|
188
|
+
if opts.list_json:
|
|
189
|
+
result_list.append(("list", self._loads_json_lines(output["list"])))
|
|
190
|
+
else:
|
|
191
|
+
result_list.append(("list", output["list"]))
|
|
192
|
+
|
|
193
|
+
if opts.prog_show:
|
|
194
|
+
if opts.prog_json:
|
|
195
|
+
result_list.append(("prog", self._loads_json_lines(output["stderr"])))
|
|
196
|
+
else:
|
|
197
|
+
result_list.append(("prog", output["stderr"]))
|
|
198
|
+
|
|
199
|
+
return result_list
|
|
200
|
+
|
|
201
|
+
def _set_environ_defaults(self):
|
|
202
|
+
for key, value in ENVIRONMENT_DEFAULTS.items():
|
|
203
|
+
if os.getenv(key) is None:
|
|
204
|
+
os.environ[key] = value
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class BorgAPI(BorgAPIBase):
|
|
208
|
+
"""Automate borg in code."""
|
|
209
|
+
|
|
210
|
+
def __init__(
|
|
211
|
+
self,
|
|
212
|
+
defaults: dict = None,
|
|
213
|
+
options: dict = None,
|
|
214
|
+
log_level: str = LOG_LVL,
|
|
215
|
+
log_json: bool = False,
|
|
216
|
+
environ: dict = None,
|
|
217
|
+
):
|
|
218
|
+
"""Set the options to be used across the different command call.
|
|
219
|
+
|
|
220
|
+
:param defaults: options for specific commands to always use, defaults to None
|
|
221
|
+
:type defaults: dict, optional
|
|
222
|
+
:param options: common flags for all commands, defaults to None
|
|
223
|
+
:type options: dict, optional
|
|
224
|
+
:param log_level: level to record logged messages at, defaults to LOG_LVL
|
|
225
|
+
:type log_level: str, optional
|
|
226
|
+
:param log_json: if the output should be in json or string format, defaults to False
|
|
227
|
+
:type log_json: bool, optional
|
|
228
|
+
"""
|
|
229
|
+
super().__init__(defaults, options, log_level, log_json, environ)
|
|
230
|
+
|
|
254
231
|
def set_environ(
|
|
255
232
|
self,
|
|
256
233
|
filename: str = None,
|
|
257
234
|
dictionary: dict = None,
|
|
258
235
|
**kwargs: Options,
|
|
259
236
|
) -> None:
|
|
260
|
-
"""Load environment variables from file
|
|
237
|
+
"""Load environment variables from file.
|
|
261
238
|
|
|
262
239
|
If nothing is provided, load_dotenv's default value will be used.
|
|
263
240
|
|
|
@@ -281,15 +258,15 @@ class BorgAPI:
|
|
|
281
258
|
|
|
282
259
|
self._previous_dotenv = variables.keys()
|
|
283
260
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
261
|
+
with StringIO() as config:
|
|
262
|
+
for key, value in variables.items():
|
|
263
|
+
config.write(f"{key}={value}\n")
|
|
264
|
+
config.seek(0)
|
|
265
|
+
load_dotenv(stream=config, override=True)
|
|
266
|
+
config.close()
|
|
290
267
|
|
|
291
268
|
def unset_environ(self, *variable: Optional[str]) -> None:
|
|
292
|
-
"""Remove variables from the environment
|
|
269
|
+
"""Remove variables from the environment.
|
|
293
270
|
|
|
294
271
|
If no variable is provided the values set from the previous call to `set_environ`
|
|
295
272
|
will be removed.
|
|
@@ -309,8 +286,10 @@ class BorgAPI:
|
|
|
309
286
|
encryption: str = "repokey",
|
|
310
287
|
**options: Options,
|
|
311
288
|
) -> Output:
|
|
312
|
-
"""Initialize an empty repository.
|
|
313
|
-
|
|
289
|
+
"""Initialize an empty repository.
|
|
290
|
+
|
|
291
|
+
A repository is a filesystem directory containing the deduplicated data
|
|
292
|
+
from zero or more archives.
|
|
314
293
|
|
|
315
294
|
:param repository: repository to create
|
|
316
295
|
:type repository: str
|
|
@@ -322,15 +301,26 @@ class BorgAPI:
|
|
|
322
301
|
json dict if json flag used, str otherwise
|
|
323
302
|
:rtype: Output
|
|
324
303
|
"""
|
|
304
|
+
common_options = self._get_option(options, CommonOptions)
|
|
305
|
+
init_options = self.optionals.get("init", options)
|
|
306
|
+
|
|
325
307
|
arg_list = []
|
|
326
308
|
arg_list.extend(self._get_option_list(options, CommonOptions))
|
|
327
309
|
arg_list.append("init")
|
|
328
310
|
arg_list.extend(["--encryption", encryption])
|
|
329
|
-
arg_list.extend(
|
|
311
|
+
arg_list.extend(init_options.parse())
|
|
330
312
|
arg_list.append(repository)
|
|
331
313
|
|
|
332
|
-
|
|
333
|
-
|
|
314
|
+
opts = OutputOptions(
|
|
315
|
+
log_lvl=self._get_log_level(options),
|
|
316
|
+
log_json=common_options.log_json,
|
|
317
|
+
prog_show=common_options.progress,
|
|
318
|
+
prog_json=common_options.log_json,
|
|
319
|
+
)
|
|
320
|
+
output = self._run(arg_list, self.archiver.do_init, output_options=opts)
|
|
321
|
+
|
|
322
|
+
result_list = self._get_basic_results(output, opts)
|
|
323
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
334
324
|
|
|
335
325
|
def create(
|
|
336
326
|
self,
|
|
@@ -338,8 +328,7 @@ class BorgAPI:
|
|
|
338
328
|
*paths: str,
|
|
339
329
|
**options: Options,
|
|
340
330
|
) -> Output:
|
|
341
|
-
"""Create a backup archive
|
|
342
|
-
traversing all paths specified.
|
|
331
|
+
"""Create a backup archive of all files found while recursively traversing specified paths.
|
|
343
332
|
|
|
344
333
|
:param archive: name of archive to create (must be also a valid directory name)
|
|
345
334
|
:type archive: str
|
|
@@ -365,21 +354,24 @@ class BorgAPI:
|
|
|
365
354
|
arg_list.append(archive)
|
|
366
355
|
arg_list.extend(paths)
|
|
367
356
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
elif create_options.list and common_options.log_json:
|
|
380
|
-
result_list.append(("list", self._loads_json_lines(output["stderr"])))
|
|
357
|
+
opts = OutputOptions(
|
|
358
|
+
log_lvl=self._get_log_level(options),
|
|
359
|
+
log_json=common_options.log_json,
|
|
360
|
+
stats_show=create_options.stats or create_options.json,
|
|
361
|
+
stats_json=create_options.json,
|
|
362
|
+
list_show=create_options.list,
|
|
363
|
+
list_json=common_options.log_json,
|
|
364
|
+
prog_show=common_options.progress,
|
|
365
|
+
prog_json=common_options.log_json,
|
|
366
|
+
)
|
|
367
|
+
output = self._run(arg_list, self.archiver.do_create, output_options=opts)
|
|
381
368
|
|
|
382
|
-
|
|
369
|
+
result_list = self._get_basic_results(output, opts)
|
|
370
|
+
if opts.list_show:
|
|
371
|
+
if opts.list_json:
|
|
372
|
+
result_list.remove(("list", []))
|
|
373
|
+
result_list.append(("list", self._loads_json_lines(output["stderr"])))
|
|
374
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
383
375
|
|
|
384
376
|
def extract(
|
|
385
377
|
self,
|
|
@@ -411,25 +403,26 @@ class BorgAPI:
|
|
|
411
403
|
arg_list.append(archive)
|
|
412
404
|
arg_list.extend(paths)
|
|
413
405
|
|
|
414
|
-
|
|
415
|
-
|
|
406
|
+
opts = OutputOptions(
|
|
407
|
+
raw_bytes=extract_options.stdout,
|
|
408
|
+
log_lvl=self._get_log_level(options),
|
|
409
|
+
log_json=common_options.log_json,
|
|
410
|
+
list_show=extract_options.list,
|
|
411
|
+
list_json=common_options.log_json,
|
|
412
|
+
prog_show=common_options.progress,
|
|
413
|
+
prog_json=common_options.log_json,
|
|
414
|
+
)
|
|
416
415
|
output = self._run(
|
|
417
416
|
arg_list,
|
|
418
417
|
self.archiver.do_extract,
|
|
419
|
-
|
|
420
|
-
log_level=lvl,
|
|
418
|
+
output_options=opts,
|
|
421
419
|
)
|
|
422
420
|
|
|
423
|
-
result_list =
|
|
424
|
-
if
|
|
425
|
-
result_list.append(("list", output["list"]))
|
|
426
|
-
elif extract_options.list and common_options.log_json:
|
|
427
|
-
result_list.append(("list", self._loads_json_lines(output["list"])))
|
|
428
|
-
|
|
429
|
-
if extract_options.stdout:
|
|
421
|
+
result_list = self._get_basic_results(output, opts)
|
|
422
|
+
if opts.raw_bytes:
|
|
430
423
|
result_list.append(("extract", output["stdout"]))
|
|
431
424
|
|
|
432
|
-
return self._build_result(*result_list)
|
|
425
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
433
426
|
|
|
434
427
|
def check(self, *repository_or_archive: str, **options: Options) -> Output:
|
|
435
428
|
"""Verify the consistency of a repository and the corresponding archives.
|
|
@@ -443,16 +436,26 @@ class BorgAPI:
|
|
|
443
436
|
dict if json flag used, str otherwise
|
|
444
437
|
:rtype: Output
|
|
445
438
|
"""
|
|
439
|
+
common_options = self._get_option(options, CommonOptions)
|
|
440
|
+
check_options = self.optionals.get("check", options)
|
|
441
|
+
|
|
446
442
|
arg_list = []
|
|
447
|
-
arg_list.extend(
|
|
443
|
+
arg_list.extend(common_options.parse())
|
|
448
444
|
arg_list.append("check")
|
|
449
|
-
arg_list.extend(
|
|
445
|
+
arg_list.extend(check_options.parse())
|
|
450
446
|
arg_list.extend(self._get_option_list(options, ArchiveOutput))
|
|
451
447
|
arg_list.extend(repository_or_archive)
|
|
452
448
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
449
|
+
opts = OutputOptions(
|
|
450
|
+
log_lvl=self._get_log_level(options),
|
|
451
|
+
log_json=common_options.log_json,
|
|
452
|
+
prog_show=common_options.progress,
|
|
453
|
+
prog_json=common_options.log_json,
|
|
454
|
+
)
|
|
455
|
+
output = self._run(arg_list, self.archiver.do_check, output_options=opts)
|
|
456
|
+
|
|
457
|
+
result_list = self._get_basic_results(output, opts)
|
|
458
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
456
459
|
|
|
457
460
|
def rename(
|
|
458
461
|
self,
|
|
@@ -473,17 +476,25 @@ class BorgAPI:
|
|
|
473
476
|
dict if json flag used, str otherwise
|
|
474
477
|
:rtype: Output
|
|
475
478
|
"""
|
|
479
|
+
common_options = self._get_option(options, CommonOptions)
|
|
480
|
+
|
|
476
481
|
arg_list = []
|
|
477
|
-
arg_list.extend(
|
|
482
|
+
arg_list.extend(common_options.parse())
|
|
478
483
|
arg_list.append("rename")
|
|
479
484
|
arg_list.append(archive)
|
|
480
485
|
arg_list.append(newname)
|
|
481
486
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
487
|
+
opts = OutputOptions(
|
|
488
|
+
log_lvl=self._get_log_level(options),
|
|
489
|
+
log_json=common_options.log_json,
|
|
490
|
+
prog_show=common_options.progress,
|
|
491
|
+
prog_json=common_options.log_json,
|
|
492
|
+
)
|
|
493
|
+
output = self._run(arg_list, self.archiver.do_rename, output_options=opts)
|
|
494
|
+
|
|
495
|
+
result_list = self._get_basic_results(output, opts)
|
|
496
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
485
497
|
|
|
486
|
-
# pylint: disable=redefined-builtin
|
|
487
498
|
def list(
|
|
488
499
|
self,
|
|
489
500
|
repository_or_archive: str,
|
|
@@ -515,16 +526,26 @@ class BorgAPI:
|
|
|
515
526
|
arg_list.append(repository_or_archive)
|
|
516
527
|
arg_list.extend(paths)
|
|
517
528
|
|
|
518
|
-
|
|
519
|
-
|
|
529
|
+
opts = OutputOptions(
|
|
530
|
+
log_lvl=self._get_log_level(options),
|
|
531
|
+
log_json=common_options.log_json,
|
|
532
|
+
list_show=True,
|
|
533
|
+
list_json=list_options.json_lines or list_options.json,
|
|
534
|
+
prog_show=common_options.progress,
|
|
535
|
+
prog_json=common_options.log_json,
|
|
536
|
+
)
|
|
537
|
+
output = self._run(arg_list, self.archiver.do_list, output_options=opts)
|
|
520
538
|
|
|
521
|
-
result_list =
|
|
522
|
-
if
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
539
|
+
result_list = self._get_basic_results(output, opts)
|
|
540
|
+
if opts.list_show:
|
|
541
|
+
if opts.list_json:
|
|
542
|
+
result_list.remove(("list", []))
|
|
543
|
+
result_list.append(("list", self._loads_json_lines(output["stdout"])))
|
|
544
|
+
else:
|
|
545
|
+
result_list.remove(("list", ""))
|
|
546
|
+
result_list.append(("list", output["stdout"]))
|
|
526
547
|
|
|
527
|
-
return self._build_result(*result_list)
|
|
548
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
528
549
|
|
|
529
550
|
def diff(
|
|
530
551
|
self,
|
|
@@ -560,16 +581,21 @@ class BorgAPI:
|
|
|
560
581
|
arg_list.append(archive_2)
|
|
561
582
|
arg_list.extend(paths)
|
|
562
583
|
|
|
563
|
-
|
|
564
|
-
|
|
584
|
+
opts = OutputOptions(
|
|
585
|
+
log_lvl=self._get_log_level(options),
|
|
586
|
+
log_json=diff_options.json_lines or common_options.log_json,
|
|
587
|
+
prog_show=common_options.progress,
|
|
588
|
+
prog_json=common_options.log_json,
|
|
589
|
+
)
|
|
590
|
+
output = self._run(arg_list, self.archiver.do_diff, output_options=opts)
|
|
565
591
|
|
|
566
|
-
result_list =
|
|
567
|
-
if
|
|
592
|
+
result_list = self._get_basic_results(output, opts)
|
|
593
|
+
if opts.log_json:
|
|
568
594
|
result_list.append(("diff", self._loads_json_lines(output["stdout"])))
|
|
569
595
|
else:
|
|
570
596
|
result_list.append(("diff", output["stdout"]))
|
|
571
597
|
|
|
572
|
-
return self._build_result(*result_list)
|
|
598
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
573
599
|
|
|
574
600
|
def delete(
|
|
575
601
|
self,
|
|
@@ -577,7 +603,7 @@ class BorgAPI:
|
|
|
577
603
|
*archives: Optional[str],
|
|
578
604
|
**options: Options,
|
|
579
605
|
) -> Output:
|
|
580
|
-
"""Delete an archive from the repository or the complete repository
|
|
606
|
+
"""Delete an archive from the repository or the complete repository.
|
|
581
607
|
|
|
582
608
|
:param repository_or_archive: repository or archive to delete
|
|
583
609
|
:type repository_or_archive: str
|
|
@@ -601,20 +627,24 @@ class BorgAPI:
|
|
|
601
627
|
arg_list.append(repository_or_archive)
|
|
602
628
|
arg_list.extend(archives)
|
|
603
629
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
630
|
+
opts = OutputOptions(
|
|
631
|
+
log_lvl=self._get_log_level(options),
|
|
632
|
+
log_json=common_options.log_json,
|
|
633
|
+
# no json option in this command
|
|
634
|
+
stats_show=delete_options.stats, # or delete_options.json,
|
|
635
|
+
# stats_json = delete_options.json,
|
|
636
|
+
list_show=delete_options.list,
|
|
637
|
+
list_json=common_options.log_json,
|
|
638
|
+
prog_show=common_options.progress,
|
|
639
|
+
prog_json=common_options.log_json,
|
|
640
|
+
)
|
|
641
|
+
output = self._run(arg_list, self.archiver.do_delete, output_options=opts)
|
|
612
642
|
|
|
613
|
-
|
|
643
|
+
result_list = self._get_basic_results(output, opts)
|
|
644
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
614
645
|
|
|
615
646
|
def prune(self, repository: str, **options: Options) -> Output:
|
|
616
|
-
"""Prune a repository by deleting all archives not matching
|
|
617
|
-
retention options.
|
|
647
|
+
"""Prune a repository by deleting all archives not matching the specified retention options.
|
|
618
648
|
|
|
619
649
|
:param repository: repository to prune
|
|
620
650
|
:type repository: str
|
|
@@ -635,20 +665,21 @@ class BorgAPI:
|
|
|
635
665
|
arg_list.extend(self._get_option_list(options, ArchivePattern))
|
|
636
666
|
arg_list.append(repository)
|
|
637
667
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
668
|
+
opts = OutputOptions(
|
|
669
|
+
log_lvl=self._get_log_level(options),
|
|
670
|
+
log_json=common_options.log_json,
|
|
671
|
+
# no json option for stats
|
|
672
|
+
stats_show=prune_options.stats, # or prune_options.json,
|
|
673
|
+
# stats_json = prune_options.json,
|
|
674
|
+
list_show=prune_options.list,
|
|
675
|
+
list_json=common_options.log_json,
|
|
676
|
+
prog_show=common_options.progress,
|
|
677
|
+
prog_json=common_options.log_json,
|
|
678
|
+
)
|
|
679
|
+
output = self._run(arg_list, self.archiver.do_prune, output_options=opts)
|
|
650
680
|
|
|
651
|
-
|
|
681
|
+
result_list = self._get_basic_results(output, opts)
|
|
682
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
652
683
|
|
|
653
684
|
def compact(self, repository: str, **options: Options) -> Output:
|
|
654
685
|
"""Compact frees repository space by compacting segments.
|
|
@@ -671,16 +702,23 @@ class BorgAPI:
|
|
|
671
702
|
arg_list.extend(compact_options.parse())
|
|
672
703
|
arg_list.append(repository)
|
|
673
704
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
705
|
+
opts = OutputOptions(
|
|
706
|
+
log_lvl=self._get_log_level(options),
|
|
707
|
+
log_json=common_options.log_json,
|
|
708
|
+
repo_show=common_options.verbose,
|
|
709
|
+
repo_json=common_options.log_json,
|
|
710
|
+
prog_show=common_options.progress,
|
|
711
|
+
prog_json=common_options.log_json,
|
|
712
|
+
)
|
|
713
|
+
output = self._run(arg_list, self.archiver.do_compact, output_options=opts)
|
|
682
714
|
|
|
683
|
-
|
|
715
|
+
result_list = self._get_basic_results(output, opts)
|
|
716
|
+
if opts.repo_show:
|
|
717
|
+
if opts.repo_json:
|
|
718
|
+
result_list.append(("compact", self._loads_json_lines(output["repo"])))
|
|
719
|
+
else:
|
|
720
|
+
result_list.append(("compact", output["repo"]))
|
|
721
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
684
722
|
|
|
685
723
|
def info(self, repository_or_archive: str, **options: Options) -> Output:
|
|
686
724
|
"""Display detailed information about the specified archive or repository.
|
|
@@ -704,16 +742,21 @@ class BorgAPI:
|
|
|
704
742
|
arg_list.extend(self._get_option_list(options, ArchiveOutput))
|
|
705
743
|
arg_list.append(repository_or_archive)
|
|
706
744
|
|
|
707
|
-
|
|
708
|
-
|
|
745
|
+
opts = OutputOptions(
|
|
746
|
+
log_lvl=self._get_log_level(options),
|
|
747
|
+
log_json=info_options.json or common_options.log_json,
|
|
748
|
+
prog_show=common_options.progress,
|
|
749
|
+
prog_json=common_options.log_json,
|
|
750
|
+
)
|
|
751
|
+
output = self._run(arg_list, self.archiver.do_info, output_options=opts)
|
|
709
752
|
|
|
710
|
-
result_list =
|
|
711
|
-
if
|
|
753
|
+
result_list = self._get_basic_results(output, opts)
|
|
754
|
+
if opts.log_json:
|
|
712
755
|
result_list.append(("info", self._loads_json_lines(output["stdout"])))
|
|
713
756
|
else:
|
|
714
757
|
result_list.append(("info", output["stdout"]))
|
|
715
758
|
|
|
716
|
-
return self._build_result(*result_list)
|
|
759
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
717
760
|
|
|
718
761
|
def mount(
|
|
719
762
|
self,
|
|
@@ -737,23 +780,37 @@ class BorgAPI:
|
|
|
737
780
|
dict if json flag used, str otherwise
|
|
738
781
|
:rtype: Output
|
|
739
782
|
"""
|
|
783
|
+
common_options = self._get_option(options, CommonOptions)
|
|
784
|
+
mount_options = self.optionals.get("mount", options)
|
|
785
|
+
|
|
740
786
|
arg_list = []
|
|
741
|
-
arg_list.extend(
|
|
787
|
+
arg_list.extend(common_options.parse())
|
|
742
788
|
arg_list.append("mount")
|
|
743
|
-
arg_list.extend(
|
|
789
|
+
arg_list.extend(mount_options.parse())
|
|
744
790
|
arg_list.extend(self._get_option_list(options, ArchiveOutput))
|
|
745
791
|
arg_list.extend(self._get_option_list(options, ExclusionOutput))
|
|
746
792
|
arg_list.append(repository_or_archive)
|
|
747
793
|
arg_list.append(mountpoint)
|
|
748
794
|
arg_list.extend(paths)
|
|
749
795
|
|
|
796
|
+
opts = OutputOptions(
|
|
797
|
+
log_lvl=self._get_log_level(options),
|
|
798
|
+
log_json=common_options.log_json,
|
|
799
|
+
prog_show=common_options.progress,
|
|
800
|
+
prog_json=common_options.log_json,
|
|
801
|
+
)
|
|
802
|
+
|
|
750
803
|
pid = os.fork()
|
|
751
804
|
# child process, this one does the actual mount (in the foreground)
|
|
752
805
|
if pid == 0:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
806
|
+
output = self._run(arg_list, self.archiver.do_mount, output_options=opts)
|
|
807
|
+
|
|
808
|
+
result_list = self._get_basic_results(output, opts)
|
|
809
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
810
|
+
|
|
811
|
+
result_list = self._get_basic_results({}, opts)
|
|
812
|
+
result_list.append(("mount", {"pid": pid, "cid": os.getpid()}))
|
|
813
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
757
814
|
|
|
758
815
|
def umount(self, mountpoint: str, **options: Options) -> Output:
|
|
759
816
|
"""Un-mount a FUSE filesystem that was mounted with `mount`.
|
|
@@ -767,14 +824,23 @@ class BorgAPI:
|
|
|
767
824
|
dict if json flag used, str otherwise
|
|
768
825
|
:rtype: Output
|
|
769
826
|
"""
|
|
827
|
+
common_options = self._get_option(options, CommonOptions)
|
|
828
|
+
|
|
770
829
|
arg_list = []
|
|
771
|
-
arg_list.extend(
|
|
830
|
+
arg_list.extend(common_options.parse())
|
|
772
831
|
arg_list.append("umount")
|
|
773
832
|
arg_list.append(mountpoint)
|
|
774
833
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
834
|
+
opts = OutputOptions(
|
|
835
|
+
log_lvl=self._get_log_level(options),
|
|
836
|
+
log_json=common_options.log_json,
|
|
837
|
+
prog_show=common_options.progress,
|
|
838
|
+
prog_json=common_options.log_json,
|
|
839
|
+
)
|
|
840
|
+
output = self._run(arg_list, self.archiver.do_umount, output_options=opts)
|
|
841
|
+
|
|
842
|
+
result_list = self._get_basic_results(output, opts)
|
|
843
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
778
844
|
|
|
779
845
|
def key_change_passphrase(self, repository: str, **options: Options) -> Output:
|
|
780
846
|
"""Change the passphrase protecting the repository encryption.
|
|
@@ -788,14 +854,23 @@ class BorgAPI:
|
|
|
788
854
|
dict if json flag used, str otherwise
|
|
789
855
|
:rtype: Output
|
|
790
856
|
"""
|
|
857
|
+
common_options = self._get_option(options, CommonOptions)
|
|
858
|
+
|
|
791
859
|
arg_list = []
|
|
792
|
-
arg_list.extend(
|
|
860
|
+
arg_list.extend(common_options.parse())
|
|
793
861
|
arg_list.extend(["key", "change-passphrase"])
|
|
794
862
|
arg_list.append(repository)
|
|
795
863
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
864
|
+
opts = OutputOptions(
|
|
865
|
+
log_lvl=self._get_log_level(options),
|
|
866
|
+
log_json=common_options.log_json,
|
|
867
|
+
prog_show=common_options.progress,
|
|
868
|
+
prog_json=common_options.log_json,
|
|
869
|
+
)
|
|
870
|
+
output = self._run(arg_list, self.archiver.do_change_passphrase, output_options=opts)
|
|
871
|
+
|
|
872
|
+
result_list = self._get_basic_results(output, opts)
|
|
873
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
799
874
|
|
|
800
875
|
def key_export(
|
|
801
876
|
self,
|
|
@@ -816,16 +891,26 @@ class BorgAPI:
|
|
|
816
891
|
dict if json flag used, str otherwise
|
|
817
892
|
:rtype: Output
|
|
818
893
|
"""
|
|
894
|
+
common_options = self._get_option(options, CommonOptions)
|
|
895
|
+
key_export_options = self.optionals.get("key_export", options)
|
|
896
|
+
|
|
819
897
|
arg_list = []
|
|
820
|
-
arg_list.extend(
|
|
898
|
+
arg_list.extend(common_options.parse())
|
|
821
899
|
arg_list.extend(["key", "export"])
|
|
822
|
-
arg_list.extend(
|
|
900
|
+
arg_list.extend(key_export_options.parse())
|
|
823
901
|
arg_list.append(repository)
|
|
824
902
|
arg_list.append(path)
|
|
825
903
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
904
|
+
opts = OutputOptions(
|
|
905
|
+
log_lvl=self._get_log_level(options),
|
|
906
|
+
log_json=common_options.log_json,
|
|
907
|
+
prog_show=common_options.progress,
|
|
908
|
+
prog_json=common_options.log_json,
|
|
909
|
+
)
|
|
910
|
+
output = self._run(arg_list, self.archiver.do_key_export, output_options=opts)
|
|
911
|
+
|
|
912
|
+
result_list = self._get_basic_results(output, opts)
|
|
913
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
829
914
|
|
|
830
915
|
def key_import(
|
|
831
916
|
self,
|
|
@@ -846,16 +931,26 @@ class BorgAPI:
|
|
|
846
931
|
dict if json flag used, str otherwise
|
|
847
932
|
:rtype: Output
|
|
848
933
|
"""
|
|
934
|
+
common_options = self._get_option(options, CommonOptions)
|
|
935
|
+
key_import_options = self.optionals.get("key_import", options)
|
|
936
|
+
|
|
849
937
|
arg_list = []
|
|
850
|
-
arg_list.extend(
|
|
938
|
+
arg_list.extend(common_options.parse())
|
|
851
939
|
arg_list.extend(["key", "import"])
|
|
852
|
-
arg_list.extend(
|
|
940
|
+
arg_list.extend(key_import_options.parse())
|
|
853
941
|
arg_list.append(repository)
|
|
854
942
|
arg_list.append(path)
|
|
855
943
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
944
|
+
opts = OutputOptions(
|
|
945
|
+
log_lvl=self._get_log_level(options),
|
|
946
|
+
log_json=common_options.log_json,
|
|
947
|
+
prog_show=common_options.progress,
|
|
948
|
+
prog_json=common_options.log_json,
|
|
949
|
+
)
|
|
950
|
+
output = self._run(arg_list, self.archiver.do_key_import, output_options=opts)
|
|
951
|
+
|
|
952
|
+
result_list = self._get_basic_results(output, opts)
|
|
953
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
859
954
|
|
|
860
955
|
def upgrade(self, repository: str, **options: Options) -> Output:
|
|
861
956
|
"""Upgrade an existing, local Borg repository.
|
|
@@ -869,15 +964,115 @@ class BorgAPI:
|
|
|
869
964
|
dict if json flag used, str otherwise
|
|
870
965
|
:rtype: Output
|
|
871
966
|
"""
|
|
967
|
+
common_options = self._get_option(options, CommonOptions)
|
|
968
|
+
upgrade_options = self.optionals.to_list("upgrade", options)
|
|
969
|
+
|
|
872
970
|
arg_list = []
|
|
873
|
-
arg_list.extend(
|
|
971
|
+
arg_list.extend(common_options.parse())
|
|
874
972
|
arg_list.append("upgrade")
|
|
875
|
-
arg_list.extend(
|
|
973
|
+
arg_list.extend(upgrade_options.parse())
|
|
876
974
|
arg_list.append(repository)
|
|
877
975
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
976
|
+
opts = OutputOptions(
|
|
977
|
+
log_lvl=self._get_log_level(options),
|
|
978
|
+
log_json=common_options.log_json,
|
|
979
|
+
prog_show=common_options.progress,
|
|
980
|
+
prog_json=common_options.log_json,
|
|
981
|
+
)
|
|
982
|
+
output = self._run(arg_list, self.archiver.do_upgrade, output_options=opts)
|
|
983
|
+
|
|
984
|
+
result_list = self._get_basic_results(output, opts)
|
|
985
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
986
|
+
|
|
987
|
+
def recreate(
|
|
988
|
+
self,
|
|
989
|
+
repository_or_archive: str,
|
|
990
|
+
*paths: Optional[str],
|
|
991
|
+
**options: Options,
|
|
992
|
+
):
|
|
993
|
+
"""Recreate the contents of existing archives.
|
|
994
|
+
|
|
995
|
+
:param repository_or_archive: repository or archive to recreate
|
|
996
|
+
:type repository_or_archive: str
|
|
997
|
+
:param *paths: paths to recreate; patterns are supported
|
|
998
|
+
:type *paths: Optional[str]
|
|
999
|
+
:param **options: optional arguments specific to `recreate` as well as exclusion and
|
|
1000
|
+
common options; defaults to {}
|
|
1001
|
+
:type **options: Options
|
|
1002
|
+
:return: Output of command, None if no output created,
|
|
1003
|
+
dict if json flag used, str otherwise
|
|
1004
|
+
:rtype: Output
|
|
1005
|
+
"""
|
|
1006
|
+
common_options = self._get_option(options, CommonOptions)
|
|
1007
|
+
recreate_options = self.optionals.get("recreate", options)
|
|
1008
|
+
|
|
1009
|
+
arg_list = []
|
|
1010
|
+
arg_list.extend(common_options.parse())
|
|
1011
|
+
arg_list.append("recreate")
|
|
1012
|
+
arg_list.extend(recreate_options.parse())
|
|
1013
|
+
arg_list.extend(self._get_option_list(options, ExclusionInput))
|
|
1014
|
+
arg_list.extend(self._get_option_list(options, ArchiveInput))
|
|
1015
|
+
arg_list.append(repository_or_archive)
|
|
1016
|
+
arg_list.extend(paths)
|
|
1017
|
+
|
|
1018
|
+
opts = OutputOptions(
|
|
1019
|
+
log_lvl=self._get_log_level(options),
|
|
1020
|
+
log_json=common_options.log_json,
|
|
1021
|
+
stats_show=recreate_options.stats,
|
|
1022
|
+
stats_json=False, # No json flag
|
|
1023
|
+
list_show=recreate_options.list,
|
|
1024
|
+
list_json=common_options.log_json,
|
|
1025
|
+
prog_show=common_options.progress,
|
|
1026
|
+
prog_json=common_options.log_json,
|
|
1027
|
+
)
|
|
1028
|
+
output = self._run(arg_list, self.archiver.do_recreate, output_options=opts)
|
|
1029
|
+
|
|
1030
|
+
result_list = self._get_basic_results(output, opts)
|
|
1031
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
1032
|
+
|
|
1033
|
+
def import_tar(
|
|
1034
|
+
self,
|
|
1035
|
+
archive: str,
|
|
1036
|
+
tarfile: str,
|
|
1037
|
+
**options: Options,
|
|
1038
|
+
):
|
|
1039
|
+
"""Create a backup archive from a tarball.
|
|
1040
|
+
|
|
1041
|
+
:param archive: name of archive to create (must be also a valid directory name)
|
|
1042
|
+
:type archive: str
|
|
1043
|
+
:param tarfile: input tar file. “-” to read from stdin instead.
|
|
1044
|
+
:type tarfile: str
|
|
1045
|
+
:param **options: optional arguments specific to `import_tar` as well as exclusion and
|
|
1046
|
+
common options; defaults to {}
|
|
1047
|
+
:type **options: Options
|
|
1048
|
+
:return: Output of command, None if no output created,
|
|
1049
|
+
dict if json flag used, str otherwise
|
|
1050
|
+
:rtype: Output
|
|
1051
|
+
"""
|
|
1052
|
+
common_options = self._get_option(options, CommonOptions)
|
|
1053
|
+
import_tar_options = self.optionals.get("import_tar", options)
|
|
1054
|
+
|
|
1055
|
+
arg_list = []
|
|
1056
|
+
arg_list.extend(common_options.parse())
|
|
1057
|
+
arg_list.append("import-tar")
|
|
1058
|
+
arg_list.extend(import_tar_options.parse())
|
|
1059
|
+
arg_list.append(archive)
|
|
1060
|
+
arg_list.append(tarfile)
|
|
1061
|
+
|
|
1062
|
+
opts = OutputOptions(
|
|
1063
|
+
log_lvl=self._get_log_level(options),
|
|
1064
|
+
log_json=common_options.log_json,
|
|
1065
|
+
stats_show=import_tar_options.stats or import_tar_options.json,
|
|
1066
|
+
stats_json=import_tar_options.json,
|
|
1067
|
+
list_show=import_tar_options.list,
|
|
1068
|
+
list_json=common_options.log_json,
|
|
1069
|
+
prog_show=common_options.progress,
|
|
1070
|
+
prog_json=common_options.log_json,
|
|
1071
|
+
)
|
|
1072
|
+
output = self._run(arg_list, self.archiver.do_import_tar, output_options=opts)
|
|
1073
|
+
|
|
1074
|
+
result_list = self._get_basic_results(output, opts)
|
|
1075
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
881
1076
|
|
|
882
1077
|
def export_tar(
|
|
883
1078
|
self,
|
|
@@ -913,21 +1108,26 @@ class BorgAPI:
|
|
|
913
1108
|
arg_list.append(file)
|
|
914
1109
|
arg_list.extend(paths)
|
|
915
1110
|
|
|
916
|
-
|
|
1111
|
+
opts = OutputOptions(
|
|
1112
|
+
log_lvl=self._get_log_level(options),
|
|
1113
|
+
log_json=common_options.log_json,
|
|
1114
|
+
raw_bytes=(file == "-"),
|
|
1115
|
+
list_show=export_tar_options.list,
|
|
1116
|
+
list_json=common_options.log_json,
|
|
1117
|
+
prog_show=common_options.progress,
|
|
1118
|
+
prog_json=common_options.log_json,
|
|
1119
|
+
)
|
|
917
1120
|
output = self._run(
|
|
918
1121
|
arg_list,
|
|
919
1122
|
self.archiver.do_export_tar,
|
|
920
|
-
|
|
921
|
-
log_level=lvl,
|
|
1123
|
+
output_options=opts,
|
|
922
1124
|
)
|
|
923
1125
|
|
|
924
|
-
result_list =
|
|
925
|
-
if
|
|
926
|
-
result_list.append(("list", output["list"]))
|
|
927
|
-
if file == "-":
|
|
1126
|
+
result_list = self._get_basic_results(output, opts)
|
|
1127
|
+
if opts.raw_bytes:
|
|
928
1128
|
result_list.append(("tar", output["stdout"]))
|
|
929
1129
|
|
|
930
|
-
return self._build_result(*result_list)
|
|
1130
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
931
1131
|
|
|
932
1132
|
def serve(self, **options: Options) -> Output:
|
|
933
1133
|
"""Start a repository server process. This command is usually not used manually.
|
|
@@ -936,19 +1136,29 @@ class BorgAPI:
|
|
|
936
1136
|
dict if json flag used, str otherwise
|
|
937
1137
|
:rtype: Output
|
|
938
1138
|
"""
|
|
1139
|
+
common_options = self._get_option(options, CommonOptions)
|
|
1140
|
+
serve_options = self.optionals.to_list("serve", options)
|
|
1141
|
+
|
|
939
1142
|
arg_list = []
|
|
940
|
-
arg_list.extend(
|
|
1143
|
+
arg_list.extend(common_options.parse())
|
|
941
1144
|
arg_list.append("serve")
|
|
942
|
-
arg_list.extend(
|
|
1145
|
+
arg_list.extend(serve_options.parse())
|
|
1146
|
+
|
|
1147
|
+
opts = OutputOptions(
|
|
1148
|
+
log_lvl=self._get_log_level(options),
|
|
1149
|
+
log_json=common_options.log_json,
|
|
1150
|
+
prog_show=common_options.progress,
|
|
1151
|
+
prog_json=common_options.log_json,
|
|
1152
|
+
)
|
|
1153
|
+
output = self._run(arg_list, self.archiver.do_serve, output_options=opts)
|
|
943
1154
|
|
|
944
|
-
|
|
945
|
-
self.
|
|
946
|
-
return self._build_result()
|
|
1155
|
+
result_list = self._get_basic_results(output, opts)
|
|
1156
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
947
1157
|
|
|
948
1158
|
def config(
|
|
949
1159
|
self,
|
|
950
1160
|
repository: str,
|
|
951
|
-
*changes: Union[str,
|
|
1161
|
+
*changes: Union[str, tuple[str, str]],
|
|
952
1162
|
**options: Options,
|
|
953
1163
|
) -> Output:
|
|
954
1164
|
"""Get and set options in a local repository or cache config file.
|
|
@@ -956,7 +1166,7 @@ class BorgAPI:
|
|
|
956
1166
|
:param repository: repository to configure
|
|
957
1167
|
:type repository: str
|
|
958
1168
|
:param *changes: config key, new value
|
|
959
|
-
:type *changes: Union[str,
|
|
1169
|
+
:type *changes: Union[str, tuple[str, str]]
|
|
960
1170
|
:param **options: optional arguments specific to `config` as well as
|
|
961
1171
|
common options; defaults to {}
|
|
962
1172
|
:type **options: Options
|
|
@@ -974,25 +1184,36 @@ class BorgAPI:
|
|
|
974
1184
|
arg_list.extend(self._get_option_list(options, ExclusionOutput))
|
|
975
1185
|
arg_list.append(repository)
|
|
976
1186
|
|
|
977
|
-
|
|
1187
|
+
opts = OutputOptions(
|
|
1188
|
+
log_lvl=self._get_log_level(options),
|
|
1189
|
+
log_json=common_options.log_json,
|
|
1190
|
+
list_show=config_options.list,
|
|
1191
|
+
list_json=False,
|
|
1192
|
+
prog_show=common_options.progress,
|
|
1193
|
+
prog_json=common_options.log_json,
|
|
1194
|
+
)
|
|
1195
|
+
|
|
978
1196
|
result_list = []
|
|
979
1197
|
if not changes:
|
|
980
|
-
output = self._run(arg_list, self.archiver.do_config,
|
|
981
|
-
|
|
1198
|
+
output = self._run(arg_list, self.archiver.do_config, output_options=opts)
|
|
1199
|
+
result_list.extend(self._get_basic_results(output, opts))
|
|
1200
|
+
if opts.list_show:
|
|
1201
|
+
result_list.remove(("list", ""))
|
|
982
1202
|
result_list.append(("list", output["stdout"]))
|
|
983
1203
|
|
|
984
1204
|
change_result = []
|
|
985
1205
|
for change in changes:
|
|
1206
|
+
new_args = arg_list
|
|
986
1207
|
if isinstance(change, tuple):
|
|
987
|
-
|
|
988
|
-
self._run(change, self.archiver.do_config, log_level=lvl)
|
|
1208
|
+
new_args.extend([change[0], change[1]])
|
|
989
1209
|
else:
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1210
|
+
new_args.extend([change])
|
|
1211
|
+
output = self._run(new_args, self.archiver.do_config, output_options=opts)
|
|
1212
|
+
change_result.append(output["stdout"].strip())
|
|
1213
|
+
if change_result:
|
|
993
1214
|
result_list.append(("changes", change_result))
|
|
994
1215
|
|
|
995
|
-
return self._build_result(*result_list)
|
|
1216
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
996
1217
|
|
|
997
1218
|
def with_lock(
|
|
998
1219
|
self,
|
|
@@ -1016,16 +1237,25 @@ class BorgAPI:
|
|
|
1016
1237
|
dict if json flag used, str otherwise
|
|
1017
1238
|
:rtype: Output
|
|
1018
1239
|
"""
|
|
1240
|
+
common_options = self._get_option(options, CommonOptions)
|
|
1241
|
+
|
|
1019
1242
|
arg_list = []
|
|
1020
|
-
arg_list.extend(
|
|
1243
|
+
arg_list.extend(common_options.parse())
|
|
1021
1244
|
arg_list.append("with-lock")
|
|
1022
1245
|
arg_list.append(repository)
|
|
1023
1246
|
arg_list.append(command)
|
|
1024
1247
|
arg_list.extend(args)
|
|
1025
1248
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1249
|
+
opts = OutputOptions(
|
|
1250
|
+
log_lvl=self._get_log_level(options),
|
|
1251
|
+
log_json=common_options.log_json,
|
|
1252
|
+
prog_show=common_options.progress,
|
|
1253
|
+
prog_json=common_options.log_json,
|
|
1254
|
+
)
|
|
1255
|
+
output = self._run(arg_list, self.archiver.do_with_lock, output_options=opts)
|
|
1256
|
+
|
|
1257
|
+
result_list = self._get_basic_results(output, opts)
|
|
1258
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
1029
1259
|
|
|
1030
1260
|
def break_lock(self, repository: str, **options: Options) -> Output:
|
|
1031
1261
|
"""Break the repository and cache locks.
|
|
@@ -1039,14 +1269,23 @@ class BorgAPI:
|
|
|
1039
1269
|
dict if json flag used, str otherwise
|
|
1040
1270
|
:rtype: Output
|
|
1041
1271
|
"""
|
|
1272
|
+
common_options = self._get_option(options, CommonOptions)
|
|
1273
|
+
|
|
1042
1274
|
arg_list = []
|
|
1043
|
-
arg_list.extend(
|
|
1275
|
+
arg_list.extend(common_options.parse())
|
|
1044
1276
|
arg_list.append("break-lock")
|
|
1045
1277
|
arg_list.append(repository)
|
|
1046
1278
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1279
|
+
opts = OutputOptions(
|
|
1280
|
+
log_lvl=self._get_log_level(options),
|
|
1281
|
+
log_json=common_options.log_json,
|
|
1282
|
+
prog_show=common_options.progress,
|
|
1283
|
+
prog_json=common_options.log_json,
|
|
1284
|
+
)
|
|
1285
|
+
output = self._run(arg_list, self.archiver.do_break_lock, output_options=opts)
|
|
1286
|
+
|
|
1287
|
+
result_list = self._get_basic_results(output, opts)
|
|
1288
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
1050
1289
|
|
|
1051
1290
|
def benchmark_crud(
|
|
1052
1291
|
self,
|
|
@@ -1075,8 +1314,72 @@ class BorgAPI:
|
|
|
1075
1314
|
arg_list.append(repository)
|
|
1076
1315
|
arg_list.append(path)
|
|
1077
1316
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1317
|
+
opts = OutputOptions(
|
|
1318
|
+
log_lvl=self._get_log_level(options),
|
|
1319
|
+
log_json=common_options.log_json,
|
|
1320
|
+
prog_show=common_options.progress,
|
|
1321
|
+
prog_json=common_options.log_json,
|
|
1322
|
+
)
|
|
1323
|
+
output = self._run(arg_list, self.archiver.do_benchmark_crud, output_options=opts)
|
|
1324
|
+
|
|
1325
|
+
result_list = self._get_basic_results(output, opts)
|
|
1326
|
+
result_list.append(("benchmark", output["stdout"]))
|
|
1327
|
+
return self._build_result(*result_list, log_json=opts.log_json)
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
class BorgAPIAsync(BorgAPI):
|
|
1331
|
+
"""Async version of the :class:`BorgAPI`."""
|
|
1332
|
+
|
|
1333
|
+
CMDS = [
|
|
1334
|
+
"set_environ",
|
|
1335
|
+
"unset_environ",
|
|
1336
|
+
"init",
|
|
1337
|
+
"create",
|
|
1338
|
+
"extract",
|
|
1339
|
+
"check",
|
|
1340
|
+
"rename",
|
|
1341
|
+
"list",
|
|
1342
|
+
"diff",
|
|
1343
|
+
"delete",
|
|
1344
|
+
"prune",
|
|
1345
|
+
"compact",
|
|
1346
|
+
"info",
|
|
1347
|
+
"mount",
|
|
1348
|
+
"umount",
|
|
1349
|
+
"key_change_passphrase",
|
|
1350
|
+
"key_export",
|
|
1351
|
+
"key_import",
|
|
1352
|
+
"upgrade",
|
|
1353
|
+
"recreate",
|
|
1354
|
+
"import_tar",
|
|
1355
|
+
"export_tar",
|
|
1356
|
+
"serve",
|
|
1357
|
+
"config",
|
|
1358
|
+
"with_lock",
|
|
1359
|
+
"break_lock",
|
|
1360
|
+
"benchmark_crud",
|
|
1361
|
+
]
|
|
1362
|
+
|
|
1363
|
+
def __init__(self, *args, **kwargs):
|
|
1364
|
+
"""Turn the commands in `:class:`BorgAPI` into async methods.
|
|
1365
|
+
|
|
1366
|
+
View the `:class:`BorgAPI` for init arguments.
|
|
1367
|
+
"""
|
|
1368
|
+
super().__init__(*args, **kwargs)
|
|
1369
|
+
|
|
1370
|
+
for cmd in self.CMDS:
|
|
1371
|
+
synced = getattr(super(), cmd)
|
|
1372
|
+
wrapped = self._force_async(synced)
|
|
1373
|
+
setattr(self, cmd, wrapped)
|
|
1374
|
+
|
|
1375
|
+
self.pool = ThreadPoolExecutor()
|
|
1376
|
+
|
|
1377
|
+
def _force_async(self, fn):
|
|
1378
|
+
"""Turn a sync function to async function using threads."""
|
|
1379
|
+
|
|
1380
|
+
@functools.wraps(fn)
|
|
1381
|
+
def wrapper(*args, **kwargs):
|
|
1382
|
+
future = self.pool.submit(fn, *args, **kwargs)
|
|
1383
|
+
return wrap_future(future) # make it awaitable
|
|
1080
1384
|
|
|
1081
|
-
|
|
1082
|
-
return self._build_result(*result_list)
|
|
1385
|
+
return wrapper
|