db-sync-tool-kmi 2.11.6__py3-none-any.whl → 3.0.2__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.
Files changed (41) hide show
  1. db_sync_tool/__main__.py +7 -252
  2. db_sync_tool/cli.py +733 -0
  3. db_sync_tool/database/process.py +94 -111
  4. db_sync_tool/database/utility.py +339 -121
  5. db_sync_tool/info.py +1 -1
  6. db_sync_tool/recipes/drupal.py +87 -12
  7. db_sync_tool/recipes/laravel.py +7 -6
  8. db_sync_tool/recipes/parsing.py +102 -0
  9. db_sync_tool/recipes/symfony.py +17 -28
  10. db_sync_tool/recipes/typo3.py +33 -54
  11. db_sync_tool/recipes/wordpress.py +13 -12
  12. db_sync_tool/remote/client.py +206 -71
  13. db_sync_tool/remote/file_transfer.py +303 -0
  14. db_sync_tool/remote/rsync.py +18 -15
  15. db_sync_tool/remote/system.py +2 -3
  16. db_sync_tool/remote/transfer.py +51 -47
  17. db_sync_tool/remote/utility.py +29 -30
  18. db_sync_tool/sync.py +52 -28
  19. db_sync_tool/utility/config.py +367 -0
  20. db_sync_tool/utility/config_resolver.py +573 -0
  21. db_sync_tool/utility/console.py +779 -0
  22. db_sync_tool/utility/exceptions.py +32 -0
  23. db_sync_tool/utility/helper.py +155 -148
  24. db_sync_tool/utility/info.py +53 -20
  25. db_sync_tool/utility/log.py +55 -31
  26. db_sync_tool/utility/logging_config.py +410 -0
  27. db_sync_tool/utility/mode.py +85 -150
  28. db_sync_tool/utility/output.py +122 -51
  29. db_sync_tool/utility/parser.py +33 -53
  30. db_sync_tool/utility/pure.py +93 -0
  31. db_sync_tool/utility/security.py +79 -0
  32. db_sync_tool/utility/system.py +277 -194
  33. db_sync_tool/utility/validation.py +2 -9
  34. db_sync_tool_kmi-3.0.2.dist-info/METADATA +99 -0
  35. db_sync_tool_kmi-3.0.2.dist-info/RECORD +44 -0
  36. {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info}/WHEEL +1 -1
  37. db_sync_tool_kmi-2.11.6.dist-info/METADATA +0 -276
  38. db_sync_tool_kmi-2.11.6.dist-info/RECORD +0 -34
  39. {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info}/entry_points.txt +0 -0
  40. {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info/licenses}/LICENSE +0 -0
  41. {db_sync_tool_kmi-2.11.6.dist-info → db_sync_tool_kmi-3.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,32 @@
1
+ """Custom exception hierarchy for db-sync-tool."""
2
+
3
+
4
+ class DbSyncError(Exception):
5
+ """Base exception for all db-sync-tool errors."""
6
+ pass
7
+
8
+
9
+ class ConfigError(DbSyncError):
10
+ """Configuration and file access errors."""
11
+ pass
12
+
13
+
14
+ class NoConfigFoundError(ConfigError):
15
+ """No configuration found during auto-discovery.
16
+
17
+ This is raised when ConfigResolver cannot find any configuration
18
+ (no project configs, no global hosts, no explicit file).
19
+ This is distinct from ConfigError which indicates a problem with
20
+ an existing config file (parse error, invalid format, etc.).
21
+ """
22
+ pass
23
+
24
+
25
+ class ParsingError(DbSyncError):
26
+ """Framework configuration parsing errors."""
27
+ pass
28
+
29
+
30
+ class ValidationError(DbSyncError):
31
+ """Input validation errors."""
32
+ pass
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: future_fstrings -*-
3
2
 
4
3
  """
5
4
  Helper script
@@ -7,26 +6,33 @@ Helper script
7
6
 
8
7
  import shutil
9
8
  import os
10
- import re
11
9
  from db_sync_tool.utility import mode, system, output
10
+ from db_sync_tool.utility.security import quote_shell_arg # noqa: F401 (re-export)
11
+ from db_sync_tool.utility.pure import ( # noqa: F401 (re-export)
12
+ parse_version, get_file_from_path, remove_surrounding_quotes,
13
+ clean_db_config, dict_to_args, remove_multiple_elements_from_string
14
+ )
12
15
  from db_sync_tool.remote import utility as remote_utility
13
16
 
14
17
 
15
- def clean_up():
18
+ def clean_up() -> None:
16
19
  """
17
- Clean up
18
- :return:
20
+ Clean up temporary files and resources
19
21
  """
20
- if not mode.is_import():
22
+ # Note: MySQL config files are cleaned up in sync.py's finally block
23
+ # to ensure cleanup even on errors
24
+ cfg = system.get_typed_config()
25
+
26
+ # Skip database cleanup for files-only mode or import mode
27
+ if not mode.is_import() and not cfg.files_only:
21
28
  remote_utility.remove_target_database_dump()
22
29
  if mode.get_sync_mode() == mode.SyncMode.PROXY:
23
30
  remove_temporary_data_dir()
24
31
 
25
32
 
26
- def remove_temporary_data_dir():
33
+ def remove_temporary_data_dir() -> None:
27
34
  """
28
35
  Remove temporary data directory for storing database dump files
29
- :return:
30
36
  """
31
37
  if os.path.exists(system.default_local_sync_path):
32
38
  output.message(
@@ -37,31 +43,31 @@ def remove_temporary_data_dir():
37
43
  shutil.rmtree(system.default_local_sync_path)
38
44
 
39
45
 
40
- def clean_up_dump_dir(client, path, num=5):
46
+ def clean_up_dump_dir(client: str, path: str, num: int = 5) -> None:
41
47
  """
42
- Clean up the dump directory from old dump files (only affect .sql and .tar.gz files)
43
- :param client:
44
- :param path:
45
- :param num:
46
- :return:
48
+ Clean up the dump directory from old dump files (only affect .sql and .gz files)
49
+ :param client: Client identifier
50
+ :param path: Path to dump directory
51
+ :param num: Number of files to keep
47
52
  """
48
53
  # Distinguish stat command on os system (Darwin|Linux)
49
54
  if check_os(client).strip() == 'Darwin':
50
55
  _command = get_command(client, 'stat') + ' -f "%Sm %N" ' + path + ' | ' + get_command(
51
56
  client,
52
57
  'sort') + ' -rn | ' + get_command(
53
- client, 'grep') + ' -E ".tar.gz|.sql"'
58
+ client, 'grep') + ' -E "\\.gz$|\\.sql$"'
54
59
  else:
55
60
  _command = get_command(client, 'stat') + ' -c "%y %n" ' + path + ' | ' + \
56
61
  get_command(client,'sort') + ' -rn | ' + get_command(client, 'grep') + \
57
- ' -E ".tar.gz|.sql"'
62
+ ' -E "\\.gz$|\\.sql$"'
58
63
 
59
64
  # List files in directory sorted by change date
60
- _files = mode.run_command(
65
+ _result = mode.run_command(
61
66
  _command,
62
67
  client,
63
68
  True
64
- ).splitlines()
69
+ )
70
+ _files = _result.splitlines() if _result else []
65
71
 
66
72
  for i in range(len(_files)):
67
73
  _filename = _files[i].rsplit(' ', 1)[-1]
@@ -74,218 +80,197 @@ def clean_up_dump_dir(client, path, num=5):
74
80
  )
75
81
 
76
82
 
77
- def check_os(client):
83
+ def check_os(client: str) -> str:
78
84
  """
79
85
  Check which system is running (Linux|Darwin)
80
- :param client:
81
- :return:
86
+ :param client: Client identifier
87
+ :return: OS name
82
88
  """
83
- return mode.run_command(
89
+ result = mode.run_command(
84
90
  get_command(client, 'uname') + ' -s',
85
91
  client,
86
92
  True
87
93
  )
94
+ return result if result else ''
88
95
 
89
96
 
90
- def get_command(client, command):
97
+ def get_command(client: str, command: str) -> str:
91
98
  """
92
99
  Get command helper for overriding default commands on the given client
93
- :param client:
94
- :param command:
100
+ :param client: Client identifier
101
+ :param command: Command name
95
102
  :return: String command
96
103
  """
97
- if 'console' in system.config[client]:
98
- if command in system.config[client]['console']:
99
- return system.config[client]['console'][command]
104
+ cfg = system.get_typed_config()
105
+ client_cfg = cfg.get_client(client)
106
+ if command in client_cfg.console:
107
+ return client_cfg.console[command]
100
108
  return command
101
109
 
102
110
 
103
- def get_dump_dir(client):
111
+ def get_dump_dir(client: str) -> str:
104
112
  """
105
113
  Get database dump directory by client
106
- :param client:
114
+ :param client: Client identifier
107
115
  :return: String path
108
116
  """
109
- if system.config[f'default_{client}_dump_dir']:
117
+ cfg = system.get_typed_config()
118
+ # Check if using default dump dir
119
+ if client == 'origin':
120
+ use_default = cfg.default_origin_dump_dir
121
+ else:
122
+ use_default = cfg.default_target_dump_dir
123
+
124
+ if use_default:
110
125
  return '/tmp/'
111
126
  else:
112
- return system.config[client]['dump_dir']
127
+ return cfg.get_client(client).dump_dir
113
128
 
114
129
 
115
- def check_and_create_dump_dir(client, path):
130
+ def check_and_create_dump_dir(client: str, path: str) -> None:
116
131
  """
117
132
  Check if a path exists on the client system and creates the given path if necessary
118
- :param client:
119
- :param path:
120
- :return:
133
+ :param client: Client identifier
134
+ :param path: Path to check/create
121
135
  """
136
+ _safe_path = quote_shell_arg(path)
122
137
  mode.run_command(
123
- '[ ! -d "' + path + '" ] && mkdir -p "' + path + '"',
138
+ '[ ! -d ' + _safe_path + ' ] && mkdir -p ' + _safe_path,
124
139
  client
125
140
  )
126
141
 
127
142
 
128
- def get_ssh_host_name(client, with_user=False, minimal=False):
143
+ def get_ssh_host_name(client: str, with_user: bool = False, minimal: bool = False) -> str:
129
144
  """
130
145
  Format ssh host name depending on existing client name
131
- :param client:
132
- :param with_user:
133
- :param short:
134
- :return:
146
+ :param client: Client identifier
147
+ :param with_user: Include username in output
148
+ :param minimal: Return minimal format
149
+ :return: Formatted host name
135
150
  """
136
- if not 'user' in system.config[client] and not 'host' in system.config[client]:
151
+ cfg = system.get_typed_config()
152
+ client_cfg = cfg.get_client(client)
153
+
154
+ if not client_cfg.user and not client_cfg.host:
137
155
  return ''
138
156
 
139
157
  if with_user:
140
- _host = system.config[client]['user'] + '@' + system.config[client]['host']
158
+ _host = client_cfg.user + '@' + client_cfg.host
141
159
  else:
142
- _host = system.config[client]['host']
160
+ _host = client_cfg.host
143
161
 
144
- if 'name' in system.config[client]:
162
+ if client_cfg.name:
145
163
  if minimal:
146
- return system.config[client]['name']
164
+ return client_cfg.name
147
165
  else:
148
- return output.CliFormat.BOLD + system.config[client][
149
- 'name'] + output.CliFormat.ENDC + output.CliFormat.BLACK + ' (' + _host + ')' + \
150
- output.CliFormat.ENDC
166
+ return (output.CliFormat.BOLD + client_cfg.name +
167
+ output.CliFormat.ENDC + output.CliFormat.BLACK +
168
+ ' (' + _host + ')' + output.CliFormat.ENDC)
151
169
  else:
152
170
  return _host
153
171
 
154
172
 
155
- def create_local_temporary_data_dir():
173
+ def create_local_temporary_data_dir() -> None:
156
174
  """
157
- Create local temporary data dir
158
- :return:
175
+ Create local temporary data dir with secure permissions
159
176
  """
160
- # @ToDo: Combine with check_and_create_dump_dir()
161
- if not os.path.exists(system.default_local_sync_path):
162
- os.makedirs(system.default_local_sync_path)
163
-
164
-
165
- def dict_to_args(dict):
166
- """
167
- Convert an dictionary to a args list
168
- :param dict: Dictionary
169
- :return: List
170
- """
171
- _args = []
172
- for key, val in dict.items():
173
- if isinstance(val, bool):
174
- if val:
175
- _args.append(f'--{key}')
176
- else:
177
- _args.append(f'--{key}')
178
- _args.append(str(val))
179
- if len(_args) == 0:
180
- return None
181
- return _args
177
+ cfg = system.get_typed_config()
178
+ # Skip secure permissions for user-specified keep_dump directories
179
+ if cfg.keep_dump:
180
+ if not os.path.exists(system.default_local_sync_path):
181
+ os.makedirs(system.default_local_sync_path)
182
+ else:
183
+ # Use secure temp dir creation with 0700 permissions
184
+ system.create_secure_temp_dir(system.default_local_sync_path)
182
185
 
183
186
 
184
- def check_file_exists(client, path):
187
+ def check_file_exists(client: str, path: str) -> bool:
185
188
  """
186
189
  Check if a file exists
187
- :param client: String
188
- :param path: String file path
190
+ :param client: Client identifier
191
+ :param path: File path
189
192
  :return: Boolean
190
193
  """
191
- return mode.run_command(f'[ -f {path} ] && echo "1"', client, True) == '1'
194
+ _safe_path = quote_shell_arg(path)
195
+ return mode.run_command(f'[ -f {_safe_path} ] && echo "1"', client, True) == '1'
192
196
 
193
197
 
194
- def run_script(client=None, script='before'):
198
+ def run_script(client: str | None = None, script: str = 'before') -> None:
195
199
  """
196
200
  Executing script command
197
- :param client: String
198
- :param script: String
199
- :return:
201
+ :param client: Client identifier (or None for global scripts)
202
+ :param script: Script name ('before', 'after', 'error')
200
203
  """
204
+ cfg = system.get_typed_config()
205
+
201
206
  if client is None:
202
- _config = system.config
207
+ # Global scripts
208
+ scripts_dict = cfg.scripts
203
209
  _subject = output.Subject.LOCAL
204
210
  client = mode.Client.LOCAL
205
211
  else:
206
- _config = system.config[client]
212
+ # Client-specific scripts
213
+ client_cfg = cfg.get_client(client)
214
+ scripts_dict = client_cfg.scripts
207
215
  _subject = output.host_to_subject(client)
208
216
 
209
- if not 'scripts' in _config:
210
- return
211
-
212
- if f'{script}' in _config['scripts']:
217
+ if script in scripts_dict:
213
218
  output.message(
214
219
  _subject,
215
220
  f'Running script {client}',
216
221
  True
217
222
  )
218
223
  mode.run_command(
219
- _config['scripts'][script],
224
+ scripts_dict[script],
220
225
  client
221
226
  )
222
227
 
223
228
 
224
- def check_rsync_version():
225
- """
226
- Check rsync version
227
- :return:
229
+ def _check_tool_version(tool: str, version_flag: str = '--version') -> str | None:
228
230
  """
229
- _raw_version = mode.run_command(
230
- 'rsync --version',
231
- mode.Client.LOCAL,
232
- True
233
- )
234
- _version = parse_version(_raw_version)
235
- output.message(
236
- output.Subject.LOCAL,
237
- f'rsync version {_version}'
238
- )
239
-
231
+ Check if a tool is available and return its version.
232
+ DRY helper for version checks.
240
233
 
241
- def check_sshpass_version():
242
- """
243
- Check sshpass version
244
- :return:
234
+ :param tool: Tool name
235
+ :param version_flag: Flag to get version
236
+ :return: Version string or None
245
237
  """
246
- _raw_version = mode.run_command(
247
- 'sshpass -V',
238
+ raw_version = mode.run_command(
239
+ f'{tool} {version_flag}',
248
240
  mode.Client.LOCAL,
249
241
  force_output=True,
250
242
  allow_fail=True
251
243
  )
252
- _version = parse_version(_raw_version)
253
-
254
- if _version:
255
- output.message(
256
- output.Subject.LOCAL,
257
- f'sshpass version {_version}'
258
- )
259
- system.config['use_sshpass'] = True
260
- return True
244
+ return parse_version(raw_version)
261
245
 
262
246
 
263
- def parse_version(output):
247
+ def check_rsync_version() -> bool:
264
248
  """
265
- Parse version out of console output
266
- https://stackoverflow.com/a/60730346
267
- :param output: String
268
- :return:
249
+ Check rsync version and availability.
250
+
251
+ :return: True if rsync is available, False otherwise
269
252
  """
270
- _version_pattern = r'\d+(=?\.(\d+(=?\.(\d+)*)*)*)*'
271
- _regex_matcher = re.compile(_version_pattern)
272
- _version = _regex_matcher.search(output)
273
- if _version:
274
- return _version.group(0)
275
- else:
276
- return None
253
+ version = _check_tool_version('rsync')
254
+ if version:
255
+ output.message(output.Subject.LOCAL, f'rsync version {version}')
256
+ return True
257
+ return False
277
258
 
278
259
 
279
- def get_file_from_path(path):
260
+ def check_sshpass_version() -> bool | None:
280
261
  """
281
- Trims a path string to retrieve the file
282
- :param path:
283
- :return: file
262
+ Check sshpass version
263
+ :return: True if available, None otherwise
284
264
  """
285
- return path.split('/')[-1]
265
+ version = _check_tool_version('sshpass', '-V')
266
+ if version:
267
+ output.message(output.Subject.LOCAL, f'sshpass version {version}')
268
+ system.set_use_sshpass(True)
269
+ return True
270
+ return None
286
271
 
287
272
 
288
- def confirm(prompt=None, resp=False):
273
+ def confirm(prompt: str | None = None, resp: bool = False) -> bool:
289
274
  """
290
275
  https://code.activestate.com/recipes/541096-prompt-the-user-for-confirmation/
291
276
 
@@ -308,18 +293,40 @@ def confirm(prompt=None, resp=False):
308
293
  prompt = 'Confirm'
309
294
 
310
295
  if resp:
311
- prompt = '%s [%s|%s]: ' % (prompt, 'Y', 'n')
296
+ prompt = f'{prompt} [Y|n]: '
312
297
  else:
313
- prompt = '%s [%s|%s]: ' % (prompt, 'y', 'N')
298
+ prompt = f'{prompt} [y|N]: '
314
299
 
315
300
  while True:
316
- ans = input(prompt)
301
+ ans = input(prompt).lower()
317
302
  if not ans:
318
303
  return resp
319
- if ans not in ['y', 'Y', 'n', 'N']:
320
- print('Please enter y or n.')
321
- continue
322
- if ans == 'y' or ans == 'Y':
323
- return True
324
- if ans == 'n' or ans == 'N':
325
- return False
304
+ if ans in ('y', 'n'):
305
+ return ans == 'y'
306
+ print('Please enter y or n.')
307
+
308
+
309
+ def run_sed_command(client: str, command: str) -> str:
310
+ """
311
+ Executes a sed command on the specified client, trying -E first and falling back to -r if -E fails.
312
+
313
+ :param client: The client on which the sed command should be executed.
314
+ :param command: The sed command to execute (excluding the sed options).
315
+ :return: The result of the sed command as a cleaned string (with newlines removed).
316
+ """
317
+ # Check if the client supports -E or -r option for sed
318
+ option = mode.run_command(
319
+ f"echo | {get_command(client, 'sed')} -E '' >/dev/null 2>&1 && echo -E || (echo | {get_command(client, 'sed')} -r '' >/dev/null 2>&1 && echo -r)",
320
+ client,
321
+ True
322
+ )
323
+ # If neither option is supported, default to -E
324
+ if not option:
325
+ option = '-E'
326
+
327
+ result = mode.run_command(
328
+ f"{get_command(client, 'sed')} -n {option} {command}",
329
+ client,
330
+ True
331
+ )
332
+ return result.strip().replace('\n', '') if result else ''
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: future_fstrings -*-
3
2
 
4
3
  """
5
4
 
@@ -11,31 +10,66 @@ from db_sync_tool.utility import mode, system, output
11
10
  from db_sync_tool import info
12
11
 
13
12
 
14
- def print_header(mute):
13
+ def print_header(mute, verbose=0):
15
14
  """
16
15
  Printing console header
17
16
  :param mute: Boolean
17
+ :param verbose: int - 0=compact, 1+=full header
18
18
  :return:
19
19
  """
20
- # pylint: max-line-length=240
21
20
  if mute is False:
22
21
  _colors = get_random_colors()
23
- print(
24
- output.CliFormat.BLACK + '##############################################' + output.CliFormat.ENDC)
25
- print(
26
- output.CliFormat.BLACK + '# #' + output.CliFormat.ENDC)
27
- print(
28
- output.CliFormat.BLACK + '#' + output.CliFormat.ENDC + ' ' + _colors[0] + '⥣ ' + _colors[1] + '⥥ ' + output.CliFormat.ENDC + ' db sync tool ' + output.CliFormat.BLACK + '#' + output.CliFormat.ENDC)
29
- print(
30
- output.CliFormat.BLACK + '# v' + info.__version__ + ' #' + output.CliFormat.ENDC)
31
- print(output.CliFormat.BLACK + '# ' + info.__homepage__ + ' #' + output.CliFormat.ENDC)
32
- print(
33
- output.CliFormat.BLACK + '# #' + output.CliFormat.ENDC)
34
- print(
35
- output.CliFormat.BLACK + '##############################################' + output.CliFormat.ENDC)
22
+ if verbose >= 1:
23
+ # Full header for verbose mode using Rich Panel
24
+ _print_rich_header(_colors)
25
+ else:
26
+ # Compact header for default mode
27
+ print(
28
+ output.CliFormat.BLACK + _colors[0] + '⥣ ' + _colors[1] + '⥥ ' + output.CliFormat.ENDC +
29
+ output.CliFormat.BLACK + 'db-sync-tool v' + info.__version__ + output.CliFormat.ENDC)
36
30
  check_updates()
37
31
 
38
32
 
33
+ def _print_rich_header(_colors):
34
+ """Print header using Rich if available, fallback to ASCII."""
35
+ try:
36
+ from rich.console import Console
37
+ from rich.text import Text
38
+
39
+ console = Console(force_terminal=True)
40
+
41
+ # Build simple header line
42
+ header = Text()
43
+ header.append("⥣ ", style=_color_to_rich(_colors[0]))
44
+ header.append("⥥ ", style=_color_to_rich(_colors[1]))
45
+ header.append("db-sync-tool ", style="bold")
46
+ header.append(f"v{info.__version__}", style="dim")
47
+
48
+ console.print(header)
49
+ console.print() # Empty line after header
50
+ except ImportError:
51
+ # Fallback to simple ASCII header
52
+ print(
53
+ _colors[0] + '⥣ ' + _colors[1] + '⥥ ' + output.CliFormat.ENDC +
54
+ output.CliFormat.BOLD + 'db-sync-tool ' + output.CliFormat.ENDC +
55
+ output.CliFormat.BLACK + 'v' + info.__version__ + output.CliFormat.ENDC
56
+ )
57
+ print()
58
+
59
+
60
+ def _color_to_rich(cli_color):
61
+ """Convert CliFormat color to Rich style."""
62
+ color_map = {
63
+ output.CliFormat.BEIGE: "cyan",
64
+ output.CliFormat.PURPLE: "magenta",
65
+ output.CliFormat.BLUE: "blue",
66
+ output.CliFormat.YELLOW: "yellow",
67
+ output.CliFormat.GREEN: "green",
68
+ output.CliFormat.RED: "red",
69
+ }
70
+ return color_map.get(cli_color, "white")
71
+
72
+
39
73
  def check_updates():
40
74
  """
41
75
  Check for updates of the db_sync_tool
@@ -60,11 +94,10 @@ def print_footer():
60
94
  Printing console footer
61
95
  :return:
62
96
  """
63
- if system.config['dry_run']:
97
+ cfg = system.get_typed_config()
98
+ if cfg.dry_run:
64
99
  _message = 'Successfully executed dry run'
65
- elif not system.config['keep_dump'] and \
66
- not system.config['is_same_client'] and \
67
- not mode.is_import():
100
+ elif not cfg.keep_dump and not cfg.is_same_client and not mode.is_import():
68
101
  _message = 'Successfully synchronized databases'
69
102
  elif mode.is_import():
70
103
  _message = 'Successfully imported database dump'