starrocks-br 0.5.0__py3-none-any.whl → 0.5.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.
starrocks_br/cli.py CHANGED
@@ -125,11 +125,14 @@ def init(config):
125
125
  " starrocks-br backup incremental --group my_daily_incremental --config config.yaml"
126
126
  )
127
127
 
128
- except FileNotFoundError as e:
129
- logger.error(f"Config file not found: {e}")
128
+ except exceptions.ConfigFileNotFoundError as e:
129
+ error_handler.handle_config_file_not_found_error(e)
130
130
  sys.exit(1)
131
- except ValueError as e:
132
- logger.error(f"Configuration error: {e}")
131
+ except exceptions.ConfigValidationError as e:
132
+ error_handler.handle_config_validation_error(e, config)
133
+ sys.exit(1)
134
+ except FileNotFoundError as e:
135
+ error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
133
136
  sys.exit(1)
134
137
  except Exception as e:
135
138
  logger.error(f"Failed to initialize schema: {e}")
@@ -275,14 +278,26 @@ def backup_incremental(config, baseline_backup, group, name):
275
278
  logger.error(f"{result['error_message']}")
276
279
  sys.exit(1)
277
280
 
281
+ except exceptions.ConcurrencyConflictError as e:
282
+ error_handler.handle_concurrency_conflict_error(e, config)
283
+ sys.exit(1)
284
+ except exceptions.BackupLabelNotFoundError as e:
285
+ error_handler.handle_backup_label_not_found_error(e, config)
286
+ sys.exit(1)
287
+ except exceptions.NoFullBackupFoundError as e:
288
+ error_handler.handle_no_full_backup_found_error(e, config, group)
289
+ sys.exit(1)
290
+ except exceptions.ConfigFileNotFoundError as e:
291
+ error_handler.handle_config_file_not_found_error(e)
292
+ sys.exit(1)
293
+ except exceptions.ConfigValidationError as e:
294
+ error_handler.handle_config_validation_error(e, config)
295
+ sys.exit(1)
278
296
  except FileNotFoundError as e:
279
- logger.error(f"Config file not found: {e}")
297
+ error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
280
298
  sys.exit(1)
281
299
  except ValueError as e:
282
- logger.error(f"Configuration error: {e}")
283
- sys.exit(1)
284
- except RuntimeError as e:
285
- logger.error(f"{e}")
300
+ logger.error(f"Error: {e}")
286
301
  sys.exit(1)
287
302
  except Exception as e:
288
303
  logger.error(f"Unexpected error: {e}")
@@ -394,15 +409,23 @@ def backup_full(config, group, name):
394
409
  logger.error(f"{result['error_message']}")
395
410
  sys.exit(1)
396
411
 
397
- except (FileNotFoundError, ValueError, RuntimeError, Exception) as e:
398
- if isinstance(e, FileNotFoundError):
399
- logger.error(f"Config file not found: {e}")
400
- elif isinstance(e, ValueError):
401
- logger.error(f"Configuration error: {e}")
402
- elif isinstance(e, RuntimeError):
403
- logger.error(f"{e}")
404
- else:
405
- logger.error(f"Unexpected error: {e}")
412
+ except exceptions.ConcurrencyConflictError as e:
413
+ error_handler.handle_concurrency_conflict_error(e, config)
414
+ sys.exit(1)
415
+ except exceptions.ConfigFileNotFoundError as e:
416
+ error_handler.handle_config_file_not_found_error(e)
417
+ sys.exit(1)
418
+ except exceptions.ConfigValidationError as e:
419
+ error_handler.handle_config_validation_error(e, config)
420
+ sys.exit(1)
421
+ except FileNotFoundError as e:
422
+ error_handler.handle_config_file_not_found_error(exceptions.ConfigFileNotFoundError(str(e)))
423
+ sys.exit(1)
424
+ except ValueError as e:
425
+ logger.error(f"Error: {e}")
426
+ sys.exit(1)
427
+ except Exception as e:
428
+ logger.error(f"Unexpected error: {e}")
406
429
  sys.exit(1)
407
430
 
408
431
 
@@ -559,8 +582,8 @@ def restore_command(config, target_label, group, table, rename_suffix, yes):
559
582
  exceptions.ConfigValidationError(str(e)), config
560
583
  )
561
584
  sys.exit(1)
562
- except RuntimeError as e:
563
- logger.error(f"{e}")
585
+ except exceptions.ConcurrencyConflictError as e:
586
+ error_handler.handle_concurrency_conflict_error(e, config)
564
587
  sys.exit(1)
565
588
  except Exception as e:
566
589
  logger.error(f"Unexpected error: {e}")
@@ -1,6 +1,6 @@
1
1
  from typing import Literal
2
2
 
3
- from . import logger, utils
3
+ from . import exceptions, logger, utils
4
4
 
5
5
 
6
6
  def reserve_job_slot(db, scope: str, label: str) -> None:
@@ -46,14 +46,7 @@ def _can_heal_stale_job(scope: str, label: str, db) -> bool:
46
46
 
47
47
  def _raise_concurrency_conflict(scope: str, active_jobs: list[tuple[str, str, str]]) -> None:
48
48
  """Raise a concurrency conflict error with helpful message."""
49
- active_job_strings = [f"{job[0]}:{job[1]}" for job in active_jobs]
50
- active_labels = [job[1] for job in active_jobs]
51
-
52
- raise RuntimeError(
53
- f"Concurrency conflict: Another '{scope}' job is already ACTIVE: {', '.join(active_job_strings)}. "
54
- f"Wait for it to complete or cancel it via: UPDATE ops.run_status SET state='CANCELLED' "
55
- f"WHERE label='{active_labels[0]}' AND state='ACTIVE'"
56
- )
49
+ raise exceptions.ConcurrencyConflictError(scope, active_jobs)
57
50
 
58
51
 
59
52
  def _insert_new_job(db, scope: str, label: str) -> None:
starrocks_br/config.py CHANGED
@@ -2,6 +2,8 @@ from typing import Any
2
2
 
3
3
  import yaml
4
4
 
5
+ from . import exceptions
6
+
5
7
 
6
8
  def load_config(config_path: str) -> dict[str, Any]:
7
9
  """Load and parse YAML configuration file.
@@ -20,7 +22,7 @@ def load_config(config_path: str) -> dict[str, Any]:
20
22
  config = yaml.safe_load(f)
21
23
 
22
24
  if not isinstance(config, dict):
23
- raise ValueError("Config must be a dictionary")
25
+ raise exceptions.ConfigValidationError("Config must be a dictionary")
24
26
 
25
27
  return config
26
28
 
@@ -32,13 +34,13 @@ def validate_config(config: dict[str, Any]) -> None:
32
34
  config: Configuration dictionary
33
35
 
34
36
  Raises:
35
- ValueError: If required fields are missing
37
+ ConfigValidationError: If required fields are missing
36
38
  """
37
39
  required_fields = ["host", "port", "user", "database", "repository"]
38
40
 
39
41
  for field in required_fields:
40
42
  if field not in config:
41
- raise ValueError(f"Missing required config field: {field}")
43
+ raise exceptions.ConfigValidationError(f"Missing required config field: {field}")
42
44
 
43
45
  _validate_tls_section(config.get("tls"))
44
46
 
@@ -48,17 +50,19 @@ def _validate_tls_section(tls_config) -> None:
48
50
  return
49
51
 
50
52
  if not isinstance(tls_config, dict):
51
- raise ValueError("TLS configuration must be a dictionary")
53
+ raise exceptions.ConfigValidationError("TLS configuration must be a dictionary")
52
54
 
53
55
  enabled = bool(tls_config.get("enabled", False))
54
56
 
55
57
  if enabled and not tls_config.get("ca_cert"):
56
- raise ValueError("TLS configuration requires 'ca_cert' when 'enabled' is true")
58
+ raise exceptions.ConfigValidationError(
59
+ "TLS configuration requires 'ca_cert' when 'enabled' is true"
60
+ )
57
61
 
58
62
  if "verify_server_cert" in tls_config and not isinstance(
59
63
  tls_config["verify_server_cert"], bool
60
64
  ):
61
- raise ValueError(
65
+ raise exceptions.ConfigValidationError(
62
66
  "TLS configuration field 'verify_server_cert' must be a boolean if provided"
63
67
  )
64
68
 
@@ -67,6 +71,6 @@ def _validate_tls_section(tls_config) -> None:
67
71
  if not isinstance(tls_versions, list) or not all(
68
72
  isinstance(version, str) for version in tls_versions
69
73
  ):
70
- raise ValueError(
74
+ raise exceptions.ConfigValidationError(
71
75
  "TLS configuration field 'tls_versions' must be a list of strings if provided"
72
76
  )
@@ -263,3 +263,45 @@ def handle_restore_operation_cancelled_error() -> None:
263
263
  ],
264
264
  help_links=["starrocks-br restore --help"],
265
265
  )
266
+
267
+
268
+ def handle_concurrency_conflict_error(
269
+ exc: exceptions.ConcurrencyConflictError, config: str = None
270
+ ) -> None:
271
+ active_job_strings = [f"{job[0]}:{job[1]}" for job in exc.active_jobs]
272
+ first_label = exc.active_labels[0] if exc.active_labels else "unknown"
273
+
274
+ display_structured_error(
275
+ title="CONCURRENCY CONFLICT",
276
+ reason=f"Another '{exc.scope}' job is already running.\nOnly one job of the same type can run at a time to prevent conflicts.",
277
+ what_to_do=[
278
+ f"Wait for the active job to complete: {', '.join(active_job_strings)}",
279
+ f"Check the job status in ops.run_status:\n SELECT * FROM ops.run_status WHERE label = '{first_label}' AND state = 'ACTIVE';",
280
+ f"If the job is stuck, cancel it manually:\n UPDATE ops.run_status SET state = 'CANCELLED' WHERE label = '{first_label}' AND state = 'ACTIVE';",
281
+ "Verify the job is not actually running in StarRocks before cancelling it",
282
+ ],
283
+ inputs={
284
+ "--config": config,
285
+ "Scope": exc.scope,
286
+ "Active jobs": ", ".join(active_job_strings),
287
+ },
288
+ help_links=["Check ops.run_status table for job status"],
289
+ )
290
+
291
+
292
+ def handle_no_full_backup_found_error(
293
+ exc: exceptions.NoFullBackupFoundError, config: str = None, group: str = None
294
+ ) -> None:
295
+ display_structured_error(
296
+ title="NO FULL BACKUP FOUND",
297
+ reason=f"No successful full backup was found for database '{exc.database}'.\nIncremental backups require a baseline full backup to compare against.",
298
+ what_to_do=[
299
+ "Run a full backup first:\n starrocks-br backup full --config "
300
+ + (config if config else "<config.yaml>")
301
+ + f" --group {group if group else '<group_name>'}",
302
+ f"Verify no full backups exist for this database:\n SELECT label, backup_type, status, finished_at FROM ops.backup_history WHERE backup_type = 'full' AND label LIKE '{exc.database}_%' ORDER BY finished_at DESC;",
303
+ "After the full backup completes successfully, retry the incremental backup",
304
+ ],
305
+ inputs={"Database": exc.database, "--config": config, "--group": group},
306
+ help_links=["starrocks-br backup full --help"],
307
+ )
@@ -91,3 +91,19 @@ class NoTablesFoundError(StarRocksBRError):
91
91
  class RestoreOperationCancelledError(StarRocksBRError):
92
92
  def __init__(self):
93
93
  super().__init__("Restore operation cancelled by user")
94
+
95
+
96
+ class ConcurrencyConflictError(StarRocksBRError):
97
+ def __init__(self, scope: str, active_jobs: list[tuple[str, str, str]]):
98
+ self.scope = scope
99
+ self.active_jobs = active_jobs
100
+ self.active_labels = [job[1] for job in active_jobs]
101
+ super().__init__(
102
+ f"Concurrency conflict: Another '{scope}' job is already active: {', '.join(f'{job[0]}:{job[1]}' for job in active_jobs)}"
103
+ )
104
+
105
+
106
+ class NoFullBackupFoundError(StarRocksBRError):
107
+ def __init__(self, database: str):
108
+ self.database = database
109
+ super().__init__(f"No successful full backup found for database '{database}'")
starrocks_br/planner.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  import hashlib
3
3
 
4
- from starrocks_br import logger, timezone, utils
4
+ from starrocks_br import exceptions, logger, timezone, utils
5
5
 
6
6
 
7
7
  def find_latest_full_backup(db, database: str) -> dict[str, str] | None:
@@ -83,16 +83,12 @@ def find_recent_partitions(
83
83
  """
84
84
  baseline_rows = db.query(baseline_query)
85
85
  if not baseline_rows:
86
- raise ValueError(
87
- f"Baseline backup '{baseline_backup_label}' not found or not successful"
88
- )
86
+ raise exceptions.BackupLabelNotFoundError(baseline_backup_label)
89
87
  baseline_time_raw = baseline_rows[0][0]
90
88
  else:
91
89
  latest_backup = find_latest_full_backup(db, database)
92
90
  if not latest_backup:
93
- raise ValueError(
94
- f"No successful full backup found for database '{database}'. Run a full database backup first."
95
- )
91
+ raise exceptions.NoFullBackupFoundError(database)
96
92
  baseline_time_raw = latest_backup["finished_at"]
97
93
 
98
94
  if isinstance(baseline_time_raw, datetime.datetime):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: starrocks-br
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: StarRocks Backup and Restore automation tool
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,23 +1,23 @@
1
1
  starrocks_br/__init__.py,sha256=i1m0FIl2IAXaVyNoya0ZNAx3WfhIp9I6VLhTz06qNFY,28
2
- starrocks_br/cli.py,sha256=fMtTLGFgEfM1HkXX5y0IVmTC6yCcGLfYnoA8G-qPWCs,21686
3
- starrocks_br/concurrency.py,sha256=N0LD4VHTAFNhD4YslrkOCDSx5cnR5rCEkNH9MkODxv8,5903
4
- starrocks_br/config.py,sha256=APqOZcJuUzYmGNHoJRlsu4l3sWl_4SS1kRLKjKm2Oag,2059
2
+ starrocks_br/cli.py,sha256=rTqKP4gSkMhX4rgAOhvDFZfwJQB7USRV9uYrDvJ-58c,22924
3
+ starrocks_br/concurrency.py,sha256=uj5s5wR5ixa3XIdrwLiy7ootc71mMg3F1Uw-t0w16Ik,5558
4
+ starrocks_br/config.py,sha256=VrNXy_YxqiRJ3vDUGzRStZFM5ttK-q5EN_JdNiDiNJ0,2250
5
5
  starrocks_br/db.py,sha256=47ynDQ9kdykJRj_nrHxX020b9njozzQxiZBI9lFdS7A,4946
6
- starrocks_br/error_handler.py,sha256=qqN3Ht2YCHMzvnP_snPIPJuZPKxvgrHSt2qlVfItBY8,10830
7
- starrocks_br/exceptions.py,sha256=vStzFWxDpO6krg1l-_6IxrXNKI8jc0aYSs7GiVsDze8,3273
6
+ starrocks_br/error_handler.py,sha256=j9UW4vh7PaQW5tSi1YP-SqGuD420cCWo2-eFrkewCoU,13085
7
+ starrocks_br/exceptions.py,sha256=0KFwLKrNS4UGGvhc-54HncL6BX-wc6XnNVBvl8nYEus,3909
8
8
  starrocks_br/executor.py,sha256=YE12jiU-4tru2D7BAe8Y0Fom72LHjGz04obN4FcAWhA,11345
9
9
  starrocks_br/health.py,sha256=rmkgNYf6kk3VDZx-PmnAG3lzmtvnJcUPG7Ppb6BA7IU,1021
10
10
  starrocks_br/history.py,sha256=ewXMVUHJvpWjvPndYUdz9xPh24HDPiUAuJgIALuWays,2964
11
11
  starrocks_br/labels.py,sha256=07UFd8BMyyV2MQwf7NaLviuu37lMLOOFX3DCbf_XqOE,1662
12
12
  starrocks_br/logger.py,sha256=8F7ZnqCOVFJDt6-rZevh94udGbhZhDLrBw8W3RZbM-4,1432
13
- starrocks_br/planner.py,sha256=wbOTKZvuWAFaGWXcciKOIveLzfYWsnGXK1ZI4lr7MVU,10892
13
+ starrocks_br/planner.py,sha256=qk-T6SZmNzztrXwyS1jjwL1kSS_l6MRo3TNx-U-ECRw,10754
14
14
  starrocks_br/repository.py,sha256=gZgT0mAjs-AAdESXPF8Syv0bE8m5njya5leTageElQ8,1251
15
15
  starrocks_br/restore.py,sha256=7_VcrGt0KVqhWe9f3JgQYDMNuM6_EjBqCi63BiSa2WY,20105
16
16
  starrocks_br/schema.py,sha256=FSJjcz4q3SU_rHLptsSzrlm-o0dcvIu6LbpT-Z5GyZA,6199
17
17
  starrocks_br/timezone.py,sha256=WlB_gkgI4AjQzqHVA1eG9CY_9QiX5cYpVKjQLvSrd4Y,3578
18
18
  starrocks_br/utils.py,sha256=LF3uBdaNMeslE4UHl_wwv4QErCS48ISxpPqYX8dbrc8,2176
19
- starrocks_br-0.5.0.dist-info/METADATA,sha256=kfTP9BWHOUoF7gvh8tgH2TufIRmWQasVGZgXw4GH5Ec,5728
20
- starrocks_br-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- starrocks_br-0.5.0.dist-info/entry_points.txt,sha256=AKUt01G2MAlh85s1Q9kNQDOUio14kaTnT3dmg9gjdNg,54
22
- starrocks_br-0.5.0.dist-info/top_level.txt,sha256=CU1tGVo0kjulhDr761Sndg-oTeRKsisDnWm8UG95aBE,13
23
- starrocks_br-0.5.0.dist-info/RECORD,,
19
+ starrocks_br-0.5.1.dist-info/METADATA,sha256=0n2LiJNNG8p_Es5zV8JRQhdSTcV8fkE2eCiwiSYTRas,5728
20
+ starrocks_br-0.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ starrocks_br-0.5.1.dist-info/entry_points.txt,sha256=AKUt01G2MAlh85s1Q9kNQDOUio14kaTnT3dmg9gjdNg,54
22
+ starrocks_br-0.5.1.dist-info/top_level.txt,sha256=CU1tGVo0kjulhDr761Sndg-oTeRKsisDnWm8UG95aBE,13
23
+ starrocks_br-0.5.1.dist-info/RECORD,,