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
db_sync_tool/cli.py ADDED
@@ -0,0 +1,733 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Typer-based CLI for db_sync_tool.
5
+
6
+ This module provides a modern CLI using typer with type annotations,
7
+ automatic documentation, and rich help formatting.
8
+ """
9
+
10
+ import argparse
11
+ import logging
12
+ import traceback
13
+ from enum import Enum
14
+ from typing import Annotated
15
+
16
+ import typer
17
+ from rich.console import Console
18
+
19
+ from db_sync_tool import sync
20
+ from db_sync_tool.utility.config_resolver import ConfigResolver
21
+ from db_sync_tool.utility.console import init_output_manager
22
+ from db_sync_tool.utility.exceptions import ConfigError, NoConfigFoundError
23
+ from db_sync_tool.utility.logging_config import init_logging
24
+
25
+
26
+ class OutputFormat(str, Enum):
27
+ """Output format options."""
28
+
29
+ interactive = "interactive"
30
+ ci = "ci"
31
+ json = "json"
32
+ quiet = "quiet"
33
+
34
+
35
+ app = typer.Typer(
36
+ name="db_sync_tool",
37
+ help="A tool for automatic database synchronization from and to host systems.",
38
+ add_completion=True,
39
+ rich_markup_mode="rich",
40
+ )
41
+
42
+
43
+ @app.command()
44
+ def main(
45
+ # === Positional Arguments ===
46
+ origin: Annotated[
47
+ str | None,
48
+ typer.Argument(help="Origin database defined in host file"),
49
+ ] = None,
50
+ target: Annotated[
51
+ str | None,
52
+ typer.Argument(help="Target database defined in host file"),
53
+ ] = None,
54
+ # === Configuration Files ===
55
+ config_file: Annotated[
56
+ str | None,
57
+ typer.Option(
58
+ "--config-file",
59
+ "-f",
60
+ help="Path to configuration file",
61
+ rich_help_panel="Configuration",
62
+ ),
63
+ ] = None,
64
+ host_file: Annotated[
65
+ str | None,
66
+ typer.Option(
67
+ "--host-file",
68
+ "-o",
69
+ help="Using an additional hosts file for merging hosts information",
70
+ rich_help_panel="Configuration",
71
+ ),
72
+ ] = None,
73
+ log_file: Annotated[
74
+ str | None,
75
+ typer.Option(
76
+ "--log-file",
77
+ "-l",
78
+ help="File path for creating an additional log file",
79
+ rich_help_panel="Configuration",
80
+ ),
81
+ ] = None,
82
+ json_log: Annotated[
83
+ bool,
84
+ typer.Option(
85
+ "--json-log",
86
+ "-jl",
87
+ help="Use JSON format for log file output (structured logging)",
88
+ rich_help_panel="Configuration",
89
+ ),
90
+ ] = False,
91
+ # === Output Options ===
92
+ verbose: Annotated[
93
+ int,
94
+ typer.Option(
95
+ "--verbose",
96
+ "-v",
97
+ count=True,
98
+ help="Enable verbose output (-v) or debug output (-vv)",
99
+ rich_help_panel="Output",
100
+ ),
101
+ ] = 0,
102
+ mute: Annotated[
103
+ bool,
104
+ typer.Option(
105
+ "--mute",
106
+ "-m",
107
+ help="Mute console output",
108
+ rich_help_panel="Output",
109
+ ),
110
+ ] = False,
111
+ quiet: Annotated[
112
+ bool,
113
+ typer.Option(
114
+ "--quiet",
115
+ "-q",
116
+ help="Suppress all output except errors (shorthand for --output=quiet)",
117
+ rich_help_panel="Output",
118
+ ),
119
+ ] = False,
120
+ output: Annotated[
121
+ OutputFormat,
122
+ typer.Option(
123
+ "--output",
124
+ help="Output format",
125
+ rich_help_panel="Output",
126
+ ),
127
+ ] = OutputFormat.interactive,
128
+ # === Execution Options ===
129
+ yes: Annotated[
130
+ bool,
131
+ typer.Option(
132
+ "--yes",
133
+ "-y",
134
+ help="Skip user confirmation for database import",
135
+ rich_help_panel="Execution",
136
+ ),
137
+ ] = False,
138
+ dry_run: Annotated[
139
+ bool,
140
+ typer.Option(
141
+ "--dry-run",
142
+ "-dr",
143
+ help="Testing process without running database export, transfer or import",
144
+ rich_help_panel="Execution",
145
+ ),
146
+ ] = False,
147
+ reverse: Annotated[
148
+ bool,
149
+ typer.Option(
150
+ "--reverse",
151
+ "-r",
152
+ help="Reverse origin and target hosts",
153
+ rich_help_panel="Execution",
154
+ ),
155
+ ] = False,
156
+ force_password: Annotated[
157
+ bool,
158
+ typer.Option(
159
+ "--force-password",
160
+ "-fpw",
161
+ help="Force password user query",
162
+ rich_help_panel="Execution",
163
+ ),
164
+ ] = False,
165
+ # === Database Dump Options ===
166
+ import_file: Annotated[
167
+ str | None,
168
+ typer.Option(
169
+ "--import-file",
170
+ "-i",
171
+ help="Import database from a specific file dump",
172
+ rich_help_panel="Database Dump",
173
+ ),
174
+ ] = None,
175
+ dump_name: Annotated[
176
+ str | None,
177
+ typer.Option(
178
+ "--dump-name",
179
+ "-dn",
180
+ help='Set a specific dump file name (default is "_[dbname]_[date]")',
181
+ rich_help_panel="Database Dump",
182
+ ),
183
+ ] = None,
184
+ keep_dump: Annotated[
185
+ str | None,
186
+ typer.Option(
187
+ "--keep-dump",
188
+ "-kd",
189
+ help="Skip target import and save dump file in the given directory",
190
+ rich_help_panel="Database Dump",
191
+ ),
192
+ ] = None,
193
+ clear_database: Annotated[
194
+ bool,
195
+ typer.Option(
196
+ "--clear-database",
197
+ "-cd",
198
+ help="Drop all tables before importing to get a clean database",
199
+ rich_help_panel="Database Dump",
200
+ ),
201
+ ] = False,
202
+ tables: Annotated[
203
+ str | None,
204
+ typer.Option(
205
+ "--tables",
206
+ "-ta",
207
+ help="Define specific tables to export, e.g. --tables=table1,table2",
208
+ rich_help_panel="Database Dump",
209
+ ),
210
+ ] = None,
211
+ where: Annotated[
212
+ str | None,
213
+ typer.Option(
214
+ "--where",
215
+ "-w",
216
+ help="Additional where clause for mysql dump to sync only selected rows",
217
+ rich_help_panel="Database Dump",
218
+ ),
219
+ ] = None,
220
+ additional_mysqldump_options: Annotated[
221
+ str | None,
222
+ typer.Option(
223
+ "--additional-mysqldump-options",
224
+ "-amo",
225
+ help="Additional mysqldump options",
226
+ rich_help_panel="Database Dump",
227
+ ),
228
+ ] = None,
229
+ # === Framework ===
230
+ framework_type: Annotated[
231
+ str | None,
232
+ typer.Option(
233
+ "--type",
234
+ "-t",
235
+ help="Define the framework type [TYPO3, Symfony, Drupal, Wordpress, Laravel]",
236
+ rich_help_panel="Framework",
237
+ ),
238
+ ] = None,
239
+ # === Transfer Options ===
240
+ use_rsync: Annotated[
241
+ bool,
242
+ typer.Option(
243
+ "--use-rsync",
244
+ "-ur",
245
+ help="Use rsync as transfer method",
246
+ rich_help_panel="Transfer",
247
+ ),
248
+ ] = False,
249
+ use_rsync_options: Annotated[
250
+ str | None,
251
+ typer.Option(
252
+ "--use-rsync-options",
253
+ "-uro",
254
+ help="Additional rsync options",
255
+ rich_help_panel="Transfer",
256
+ ),
257
+ ] = None,
258
+ # === File Transfer Options ===
259
+ with_files: Annotated[
260
+ bool,
261
+ typer.Option(
262
+ "--with-files",
263
+ "-wf",
264
+ help="Enable file synchronization (requires 'files' section in config)",
265
+ rich_help_panel="File Transfer",
266
+ ),
267
+ ] = False,
268
+ files_only: Annotated[
269
+ bool,
270
+ typer.Option(
271
+ "--files-only",
272
+ "-fo",
273
+ help="Sync only files, skip database synchronization",
274
+ rich_help_panel="File Transfer",
275
+ ),
276
+ ] = False,
277
+ # === Target Client Options ===
278
+ target_path: Annotated[
279
+ str | None,
280
+ typer.Option(
281
+ "--target-path",
282
+ "-tp",
283
+ help="File path to target database credential file",
284
+ rich_help_panel="Target Client",
285
+ ),
286
+ ] = None,
287
+ target_name: Annotated[
288
+ str | None,
289
+ typer.Option(
290
+ "--target-name",
291
+ "-tn",
292
+ help="Providing a name for the target system",
293
+ rich_help_panel="Target Client",
294
+ ),
295
+ ] = None,
296
+ target_host: Annotated[
297
+ str | None,
298
+ typer.Option(
299
+ "--target-host",
300
+ "-th",
301
+ help="SSH host to target system",
302
+ rich_help_panel="Target Client",
303
+ ),
304
+ ] = None,
305
+ target_user: Annotated[
306
+ str | None,
307
+ typer.Option(
308
+ "--target-user",
309
+ "-tu",
310
+ help="SSH user for target system",
311
+ rich_help_panel="Target Client",
312
+ ),
313
+ ] = None,
314
+ target_password: Annotated[
315
+ str | None,
316
+ typer.Option(
317
+ "--target-password",
318
+ "-tpw",
319
+ help="SSH password for target system",
320
+ rich_help_panel="Target Client",
321
+ ),
322
+ ] = None,
323
+ target_key: Annotated[
324
+ str | None,
325
+ typer.Option(
326
+ "--target-key",
327
+ "-tk",
328
+ help="File path to SSH key for target system",
329
+ rich_help_panel="Target Client",
330
+ ),
331
+ ] = None,
332
+ target_port: Annotated[
333
+ int | None,
334
+ typer.Option(
335
+ "--target-port",
336
+ "-tpo",
337
+ help="SSH port for target system",
338
+ rich_help_panel="Target Client",
339
+ ),
340
+ ] = None,
341
+ target_dump_dir: Annotated[
342
+ str | None,
343
+ typer.Option(
344
+ "--target-dump-dir",
345
+ "-tdd",
346
+ help="Directory path for database dump file on target system",
347
+ rich_help_panel="Target Client",
348
+ ),
349
+ ] = None,
350
+ target_keep_dumps: Annotated[
351
+ int | None,
352
+ typer.Option(
353
+ "--target-keep-dumps",
354
+ "-tkd",
355
+ help="Keep dump file count for target system",
356
+ rich_help_panel="Target Client",
357
+ ),
358
+ ] = None,
359
+ target_db_name: Annotated[
360
+ str | None,
361
+ typer.Option(
362
+ "--target-db-name",
363
+ "-tdn",
364
+ help="Database name for target system",
365
+ rich_help_panel="Target Client - Database",
366
+ ),
367
+ ] = None,
368
+ target_db_host: Annotated[
369
+ str | None,
370
+ typer.Option(
371
+ "--target-db-host",
372
+ "-tdh",
373
+ help="Database host for target system",
374
+ rich_help_panel="Target Client - Database",
375
+ ),
376
+ ] = None,
377
+ target_db_user: Annotated[
378
+ str | None,
379
+ typer.Option(
380
+ "--target-db-user",
381
+ "-tdu",
382
+ help="Database user for target system",
383
+ rich_help_panel="Target Client - Database",
384
+ ),
385
+ ] = None,
386
+ target_db_password: Annotated[
387
+ str | None,
388
+ typer.Option(
389
+ "--target-db-password",
390
+ "-tdpw",
391
+ help="Database password for target system",
392
+ rich_help_panel="Target Client - Database",
393
+ ),
394
+ ] = None,
395
+ target_db_port: Annotated[
396
+ int | None,
397
+ typer.Option(
398
+ "--target-db-port",
399
+ "-tdpo",
400
+ help="Database port for target system",
401
+ rich_help_panel="Target Client - Database",
402
+ ),
403
+ ] = None,
404
+ target_after_dump: Annotated[
405
+ str | None,
406
+ typer.Option(
407
+ "--target-after-dump",
408
+ "-tad",
409
+ help="Additional dump file to insert after regular database import",
410
+ rich_help_panel="Target Client",
411
+ ),
412
+ ] = None,
413
+ # === Origin Client Options ===
414
+ origin_path: Annotated[
415
+ str | None,
416
+ typer.Option(
417
+ "--origin-path",
418
+ "-op",
419
+ help="File path to origin database credential file",
420
+ rich_help_panel="Origin Client",
421
+ ),
422
+ ] = None,
423
+ origin_name: Annotated[
424
+ str | None,
425
+ typer.Option(
426
+ "--origin-name",
427
+ "-on",
428
+ help="Providing a name for the origin system",
429
+ rich_help_panel="Origin Client",
430
+ ),
431
+ ] = None,
432
+ origin_host: Annotated[
433
+ str | None,
434
+ typer.Option(
435
+ "--origin-host",
436
+ "-oh",
437
+ help="SSH host to origin system",
438
+ rich_help_panel="Origin Client",
439
+ ),
440
+ ] = None,
441
+ origin_user: Annotated[
442
+ str | None,
443
+ typer.Option(
444
+ "--origin-user",
445
+ "-ou",
446
+ help="SSH user for origin system",
447
+ rich_help_panel="Origin Client",
448
+ ),
449
+ ] = None,
450
+ origin_password: Annotated[
451
+ str | None,
452
+ typer.Option(
453
+ "--origin-password",
454
+ "-opw",
455
+ help="SSH password for origin system",
456
+ rich_help_panel="Origin Client",
457
+ ),
458
+ ] = None,
459
+ origin_key: Annotated[
460
+ str | None,
461
+ typer.Option(
462
+ "--origin-key",
463
+ "-ok",
464
+ help="File path to SSH key for origin system",
465
+ rich_help_panel="Origin Client",
466
+ ),
467
+ ] = None,
468
+ origin_port: Annotated[
469
+ int | None,
470
+ typer.Option(
471
+ "--origin-port",
472
+ "-opo",
473
+ help="SSH port for origin system",
474
+ rich_help_panel="Origin Client",
475
+ ),
476
+ ] = None,
477
+ origin_dump_dir: Annotated[
478
+ str | None,
479
+ typer.Option(
480
+ "--origin-dump-dir",
481
+ "-odd",
482
+ help="Directory path for database dump file on origin system",
483
+ rich_help_panel="Origin Client",
484
+ ),
485
+ ] = None,
486
+ origin_keep_dumps: Annotated[
487
+ int | None,
488
+ typer.Option(
489
+ "--origin-keep-dumps",
490
+ "-okd",
491
+ help="Keep dump file count for origin system",
492
+ rich_help_panel="Origin Client",
493
+ ),
494
+ ] = None,
495
+ origin_db_name: Annotated[
496
+ str | None,
497
+ typer.Option(
498
+ "--origin-db-name",
499
+ "-odn",
500
+ help="Database name for origin system",
501
+ rich_help_panel="Origin Client - Database",
502
+ ),
503
+ ] = None,
504
+ origin_db_host: Annotated[
505
+ str | None,
506
+ typer.Option(
507
+ "--origin-db-host",
508
+ "-odh",
509
+ help="Database host for origin system",
510
+ rich_help_panel="Origin Client - Database",
511
+ ),
512
+ ] = None,
513
+ origin_db_user: Annotated[
514
+ str | None,
515
+ typer.Option(
516
+ "--origin-db-user",
517
+ "-odu",
518
+ help="Database user for origin system",
519
+ rich_help_panel="Origin Client - Database",
520
+ ),
521
+ ] = None,
522
+ origin_db_password: Annotated[
523
+ str | None,
524
+ typer.Option(
525
+ "--origin-db-password",
526
+ "-odpw",
527
+ help="Database password for origin system",
528
+ rich_help_panel="Origin Client - Database",
529
+ ),
530
+ ] = None,
531
+ origin_db_port: Annotated[
532
+ int | None,
533
+ typer.Option(
534
+ "--origin-db-port",
535
+ "-odpo",
536
+ help="Database port for origin system",
537
+ rich_help_panel="Origin Client - Database",
538
+ ),
539
+ ] = None,
540
+ ) -> None:
541
+ """
542
+ Synchronize a database from origin to target system.
543
+
544
+ Examples:
545
+ db_sync_tool -f config.yaml
546
+ db_sync_tool production local -o hosts.yaml
547
+ db_sync_tool -f config.yaml -v -y
548
+ """
549
+ # Initialize output manager first (needed for config resolver output)
550
+ output_format = "quiet" if quiet else output.value
551
+ init_output_manager(format=output_format, verbose=verbose, mute=mute or quiet)
552
+
553
+ # Config resolution: use ConfigResolver if no explicit config file or host file
554
+ resolved_config = None
555
+ resolved_origin = origin
556
+ resolved_target = target
557
+
558
+ if config_file is None and import_file is None and host_file is None:
559
+ # Use ConfigResolver for auto-discovery
560
+ # Skip if host_file is provided (use original host linking mechanism)
561
+ console = Console()
562
+ resolver = ConfigResolver(console=console)
563
+
564
+ # Check if we should use auto-discovery
565
+ # Allow interactive mode only if running in a TTY and not in quiet/mute mode
566
+ interactive = console.is_terminal and not (quiet or mute)
567
+
568
+ try:
569
+ resolved = resolver.resolve(
570
+ config_file=None,
571
+ origin=origin,
572
+ target=target,
573
+ interactive=interactive,
574
+ )
575
+
576
+ if resolved.config_file:
577
+ config_file = str(resolved.config_file)
578
+
579
+ # Store resolved configs for later merging
580
+ if resolved.origin_config or resolved.target_config:
581
+ resolved_config = resolved
582
+ # Clear origin/target args since we're using resolved configs
583
+ resolved_origin = None
584
+ resolved_target = None
585
+
586
+ except NoConfigFoundError:
587
+ # No auto-discovery config found, fall through to original behavior
588
+ # This is expected when no .db-sync-tool/ or ~/.db-sync-tool/ exists
589
+ pass
590
+ except ConfigError:
591
+ # Config was found but has errors (invalid YAML, missing host, etc.)
592
+ # Re-raise to let the user know about the problem
593
+ raise
594
+ except Exception as e:
595
+ # Unexpected error during config resolution
596
+ # Log with details in verbose mode, then re-raise
597
+ logger = logging.getLogger('db_sync_tool')
598
+ error_msg = f"Unexpected error during config resolution: {e}"
599
+ if verbose >= 2:
600
+ logger.error(f"{error_msg}\n{traceback.format_exc()}")
601
+ elif verbose >= 1:
602
+ logger.error(error_msg)
603
+ raise
604
+
605
+ # Build args namespace for compatibility with existing code
606
+ args = _build_args_namespace(
607
+ origin=resolved_origin,
608
+ target=resolved_target,
609
+ config_file=config_file,
610
+ host_file=host_file,
611
+ log_file=log_file,
612
+ json_log=json_log,
613
+ verbose=verbose,
614
+ mute=mute,
615
+ quiet=quiet,
616
+ output=output,
617
+ yes=yes,
618
+ dry_run=dry_run,
619
+ reverse=reverse,
620
+ force_password=force_password,
621
+ import_file=import_file,
622
+ dump_name=dump_name,
623
+ keep_dump=keep_dump,
624
+ clear_database=clear_database,
625
+ tables=tables,
626
+ where=where,
627
+ additional_mysqldump_options=additional_mysqldump_options,
628
+ framework_type=framework_type,
629
+ use_rsync=use_rsync,
630
+ use_rsync_options=use_rsync_options,
631
+ with_files=with_files,
632
+ files_only=files_only,
633
+ target_path=target_path,
634
+ target_name=target_name,
635
+ target_host=target_host,
636
+ target_user=target_user,
637
+ target_password=target_password,
638
+ target_key=target_key,
639
+ target_port=target_port,
640
+ target_dump_dir=target_dump_dir,
641
+ target_keep_dumps=target_keep_dumps,
642
+ target_db_name=target_db_name,
643
+ target_db_host=target_db_host,
644
+ target_db_user=target_db_user,
645
+ target_db_password=target_db_password,
646
+ target_db_port=target_db_port,
647
+ target_after_dump=target_after_dump,
648
+ origin_path=origin_path,
649
+ origin_name=origin_name,
650
+ origin_host=origin_host,
651
+ origin_user=origin_user,
652
+ origin_password=origin_password,
653
+ origin_key=origin_key,
654
+ origin_port=origin_port,
655
+ origin_dump_dir=origin_dump_dir,
656
+ origin_keep_dumps=origin_keep_dumps,
657
+ origin_db_name=origin_db_name,
658
+ origin_db_host=origin_db_host,
659
+ origin_db_user=origin_db_user,
660
+ origin_db_password=origin_db_password,
661
+ origin_db_port=origin_db_port,
662
+ resolved_config=resolved_config,
663
+ )
664
+
665
+ # Store json_log in system.config for use by log.py and other modules
666
+ # Import here to avoid circular imports at module level
667
+ from db_sync_tool.utility import system as sys_module
668
+ sys_module.config['json_log'] = json_log
669
+
670
+ # Initialize structured logging
671
+ init_logging(
672
+ verbose=verbose,
673
+ mute=mute or quiet,
674
+ log_file=log_file,
675
+ json_logging=json_log,
676
+ )
677
+
678
+ # Call sync with typed args
679
+ sync.Sync(
680
+ config_file=config_file,
681
+ verbose=verbose,
682
+ yes=yes,
683
+ mute=mute or quiet,
684
+ dry_run=dry_run,
685
+ import_file=import_file,
686
+ dump_name=dump_name,
687
+ keep_dump=keep_dump,
688
+ host_file=host_file,
689
+ clear=clear_database,
690
+ force_password=force_password,
691
+ use_rsync=use_rsync,
692
+ use_rsync_options=use_rsync_options,
693
+ reverse=reverse,
694
+ with_files=with_files,
695
+ files_only=files_only,
696
+ args=args,
697
+ )
698
+
699
+
700
+ def _build_args_namespace(**kwargs: object) -> argparse.Namespace:
701
+ """
702
+ Build an argparse-compatible namespace from typer arguments.
703
+
704
+ This enables backward compatibility with existing code that
705
+ expects an argparse.Namespace object.
706
+ """
707
+ args = argparse.Namespace()
708
+
709
+ # Map typer parameter names to argparse attribute names
710
+ name_mapping = {
711
+ "framework_type": "type",
712
+ }
713
+
714
+ for key, value in kwargs.items():
715
+ # Map to argparse name if different
716
+ attr_name = name_mapping.get(key, key)
717
+
718
+ # Handle enum conversion
719
+ if hasattr(value, "value"):
720
+ setattr(args, attr_name, value.value)
721
+ else:
722
+ setattr(args, attr_name, value)
723
+
724
+ return args
725
+
726
+
727
+ def run() -> None:
728
+ """Entry point for typer CLI."""
729
+ app()
730
+
731
+
732
+ if __name__ == "__main__":
733
+ run()