@@ -37,7 +37,7 @@ def _prompt_s3_bucket() -> str:
3737
3838 Raises ``typer.Exit(1)`` if the user provides an empty value.
3939 """
40- bucket = typer .prompt ("S3 bucket name for backups" ).strip ()
40+ bucket : str = str ( typer .prompt ("S3 bucket name for backups" ) ).strip ()
4141 if not bucket :
4242 output .error ("No S3 bucket name provided." )
4343 raise typer .Exit (1 )
@@ -58,9 +58,7 @@ def _ensure_s3_bucket(backend, *, create_if_missing: bool = False) -> None:
5858 backend ._s3 .head_bucket (Bucket = backend .s3_bucket )
5959 except Exception :
6060 if create_if_missing :
61- output .action (
62- f"Bucket '{ backend .s3_bucket } ' not found — creating..."
63- )
61+ output .action (f"Bucket '{ backend .s3_bucket } ' not found — creating..." )
6462 backend .create_s3_bucket ()
6563 output .success (
6664 f"S3 bucket '{ backend .s3_bucket } ' created "
@@ -92,7 +90,15 @@ def _get_backend_from_env(
9290 backend = DynamoBackend .from_env (allow_missing_bucket = True )
9391
9492 if not backend .s3_bucket :
95- if not get_context ().json_mode and _is_interactive ():
93+ if get_context ().json_mode :
94+ output .emit_json (
95+ {
96+ "error" : "ZEBRA_DAY_S3_BACKUP_BUCKET is required when using "
97+ "DynamoDB backend. Set it to the S3 bucket name for config backups."
98+ }
99+ )
100+ raise typer .Exit (1 )
101+ elif _is_interactive ():
96102 output .warning ("ZEBRA_DAY_S3_BACKUP_BUCKET is not set." )
97103 bucket = _prompt_s3_bucket ()
98104 backend .s3_bucket = bucket
@@ -165,9 +171,7 @@ def _get_backend(
165171
166172 # S3 bucket resolution: flag > config file > env var > interactive prompt
167173 resolved_bucket = (
168- s3_bucket
169- or file_cfg .get ("s3_bucket" )
170- or os .environ .get ("ZEBRA_DAY_S3_BACKUP_BUCKET" )
174+ s3_bucket or file_cfg .get ("s3_bucket" ) or os .environ .get ("ZEBRA_DAY_S3_BACKUP_BUCKET" )
171175 )
172176 if not resolved_bucket :
173177 if _is_interactive ():
@@ -208,6 +212,7 @@ def _get_backend(
208212# init
209213# -----------------------------------------------------------------
210214
215+
211216@dynamo_app .command ("init" )
212217def init_cmd (
213218 table_name : str = typer .Option (
@@ -216,18 +221,14 @@ def init_cmd(
216221 region : str = typer .Option (
217222 None , "--region" , "-r" , help = "AWS region [default: env or us-east-1]"
218223 ),
219- s3_bucket : str = typer .Option (
220- None , "--s3-bucket" , "-b" , help = "S3 bucket for backups"
221- ),
222- s3_prefix : str = typer .Option (
223- "zebra-day/" , "--s3-prefix" , help = "S3 key prefix"
224- ),
224+ s3_bucket : str = typer .Option (None , "--s3-bucket" , "-b" , help = "S3 bucket for backups" ),
225+ s3_prefix : str = typer .Option ("zebra-day/" , "--s3-prefix" , help = "S3 key prefix" ),
225226 s3_config_file : str = typer .Option (
226- None , "--s3-config-file" , help = "JSON file with S3 bucket config (keys: s3_bucket, s3_prefix, region)"
227- ),
228- profile : str = typer .Option (
229- None , "--profile" , "-p" , help = "AWS profile name"
227+ None ,
228+ "--s3-config-file" ,
229+ help = "JSON file with S3 bucket config (keys: s3_bucket, s3_prefix, region)" ,
230230 ),
231+ profile : str = typer .Option (None , "--profile" , "-p" , help = "AWS profile name" ),
231232 cost_center : str = typer .Option (
232233 None , "--cost-center" , help = "lsmc-cost-center tag [default: env or 'global']"
233234 ),
@@ -274,8 +275,7 @@ def init_cmd(
274275
275276 if not perm_result ["all_ok" ]:
276277 output .error (
277- "Permission checks failed. "
278- "Fix the issues above or use --skip-checks to bypass."
278+ "Permission checks failed. Fix the issues above or use --skip-checks to bypass."
279279 )
280280 raise typer .Exit (1 )
281281 output .success ("All permission checks passed" )
@@ -286,7 +286,10 @@ def init_cmd(
286286 backend .create_table ()
287287 output .success (f"Table '{ backend .table_name } ' created and active" )
288288 except Exception as exc :
289- if "ResourceInUseException" in str (type (exc ).__name__ ) or "already exists" in str (exc ).lower ():
289+ if (
290+ "ResourceInUseException" in str (type (exc ).__name__ )
291+ or "already exists" in str (exc ).lower ()
292+ ):
290293 output .warning (f"Table '{ backend .table_name } ' already exists" )
291294 else :
292295 output .error (f"Failed to create table: { exc } " )
@@ -308,7 +311,7 @@ def init_cmd(
308311
309312 # Print env var instructions
310313 output .heading ("Set these environment variables" )
311- output .detail (f "export ZEBRA_DAY_CONFIG_BACKEND=dynamodb" )
314+ output .detail ("export ZEBRA_DAY_CONFIG_BACKEND=dynamodb" )
312315 output .detail (f"export ZEBRA_DAY_DYNAMO_TABLE={ backend .table_name } " )
313316 output .detail (f"export ZEBRA_DAY_DYNAMO_REGION={ backend .region } " )
314317 output .detail (f"export ZEBRA_DAY_S3_BACKUP_BUCKET={ backend .s3_bucket } " )
@@ -319,6 +322,7 @@ def init_cmd(
319322# status
320323# -----------------------------------------------------------------
321324
325+
322326@dynamo_app .command ("status" )
323327def status_cmd (
324328 create_s3_if_missing : bool = typer .Option (
@@ -337,7 +341,9 @@ def status_cmd(
337341 try :
338342 status = backend .get_status ()
339343 except Exception as exc :
340- if "not found" in str (exc ).lower () or "ResourceNotFoundException" in str (type (exc ).__name__ ):
344+ if "not found" in str (exc ).lower () or "ResourceNotFoundException" in str (
345+ type (exc ).__name__
346+ ):
341347 output .error ("Table not found. Run 'zday dynamo init' first." )
342348 raise typer .Exit (1 )
343349 raise
@@ -356,7 +362,12 @@ def status_cmd(
356362 tbl .add_column ("Value" )
357363 tbl .add_row ("Table" , status ["table_name" ])
358364 tbl .add_row ("Region" , status ["region" ])
359- tbl .add_row ("Status" , f"[green]{ status ['table_status' ]} [/green]" if status ["table_status" ] == "ACTIVE" else status ["table_status" ])
365+ tbl .add_row (
366+ "Status" ,
367+ f"[green]{ status ['table_status' ]} [/green]"
368+ if status ["table_status" ] == "ACTIVE"
369+ else status ["table_status" ],
370+ )
360371 tbl .add_row ("Items" , str (status ["item_count" ]))
361372 tbl .add_row ("Templates" , str (template_count ))
362373 tbl .add_row ("Config Version" , str (status ["config_version" ]))
@@ -373,6 +384,7 @@ def status_cmd(
373384# bootstrap
374385# -----------------------------------------------------------------
375386
387+
376388@dynamo_app .command ("bootstrap" )
377389def bootstrap_cmd (
378390 config_file : str = typer .Option (
@@ -447,22 +459,22 @@ def bootstrap_cmd(
447459 # Force backup
448460 prefix = backend .backup_to_s3 (triggered_by = "bootstrap" , force = True )
449461 output .success (f"Backup written to s3://{ backend .s3_bucket } /{ prefix } " )
450- output .success (f"Bootstrap complete: { config_items_written } config + { templates_written } templates" )
451-
462+ output .success (
463+ f"Bootstrap complete: { config_items_written } config + { templates_written } templates"
464+ )
452465
453466
454467# -----------------------------------------------------------------
455468# export
456469# -----------------------------------------------------------------
457470
471+
458472@dynamo_app .command ("export" )
459473def export_cmd (
460474 output_dir : str = typer .Option (
461475 "./zebra-day-export" , "--output-dir" , "-o" , help = "Target directory"
462476 ),
463- fmt : str = typer .Option (
464- "json" , "--format" , "-f" , help = "Config format: json or yaml"
465- ),
477+ fmt : str = typer .Option ("json" , "--format" , "-f" , help = "Config format: json or yaml" ),
466478):
467479 """Pull DynamoDB config and templates to local files."""
468480 try :
@@ -506,6 +518,7 @@ def export_cmd(
506518# backup
507519# -----------------------------------------------------------------
508520
521+
509522@dynamo_app .command ("backup" )
510523def backup_cmd (
511524 create_s3_if_missing : bool = typer .Option (
@@ -533,14 +546,13 @@ def backup_cmd(
533546# restore
534547# -----------------------------------------------------------------
535548
549+
536550@dynamo_app .command ("restore" )
537551def restore_cmd (
538552 s3_key : str = typer .Option (
539553 None , "--s3-key" , "-k" , help = "S3 key prefix of the backup to restore"
540554 ),
541- list_backups : bool = typer .Option (
542- False , "--list" , "-l" , help = "List available backups"
543- ),
555+ list_backups : bool = typer .Option (False , "--list" , "-l" , help = "List available backups" ),
544556 yes : bool = typer .Option (False , "--yes" , "-y" , help = "Skip confirmation prompt" ),
545557):
546558 """Restore DynamoDB from an S3 backup."""
@@ -597,6 +609,7 @@ def restore_cmd(
597609# destroy
598610# -----------------------------------------------------------------
599611
612+
600613@dynamo_app .command ("destroy" )
601614def destroy_cmd (
602615 yes : bool = typer .Option (False , "--yes" , "-y" , help = "Required safety gate" ),
0 commit comments