99from __future__ import annotations
1010
1111import difflib
12+ import functools
13+ import inspect
1214import shutil
1315import sys
1416import time
15- from collections .abc import Iterator , Sequence
17+ from collections .abc import Callable , Iterator , Sequence
1618from contextlib import contextmanager
1719from pathlib import Path
1820from typing import Annotated , NoReturn
@@ -205,26 +207,46 @@ def _reporting_errors() -> Iterator[None]:
205207 _fail (str (error ))
206208
207209
208- def _options (
209- output_format : OutputFormat ,
210- null : str ,
211- truncate : int | None ,
212- page : int | None ,
213- page_size : int | None ,
214- pager : bool ,
215- color : bool | None ,
216- width : int | None ,
217- ) -> OutputOptions :
218- return OutputOptions (
219- format = output_format ,
220- null = null ,
221- truncate = truncate ,
222- page = page ,
223- page_size = page_size ,
224- pager = pager ,
225- color = color ,
226- width = width ,
227- )
210+ OUTPUT_PARAMETERS : dict [str , tuple [object , object ]] = {
211+ "output_format" : (FormatOption , OutputFormat .TABLE ),
212+ "null" : (NullOption , "NULL" ),
213+ "truncate" : (TruncateOption , None ),
214+ "page" : (PageOption , None ),
215+ "page_size" : (PageSizeOption , None ),
216+ "pager" : (PagerOption , False ),
217+ "color" : (ColorOption , None ),
218+ "width" : (WidthOption , None ),
219+ }
220+
221+
222+ def with_output_options (command : Callable [..., None ]) -> Callable [..., None ]:
223+ """Expose the shared output options on *command* and hand them over as ``options``.
224+
225+ Typer has no option groups, so instead of repeating the eight output
226+ parameters in every command this decorator appends them to the signature
227+ Typer inspects, and packs the values it receives into an
228+ :class:`OutputOptions` passed to the command as its ``options`` argument.
229+ """
230+ signature = inspect .signature (command , eval_str = True )
231+ own = [parameter for parameter in signature .parameters .values () if parameter .name != "options" ]
232+ shared = [
233+ inspect .Parameter (
234+ name , inspect .Parameter .KEYWORD_ONLY , default = default , annotation = annotation
235+ )
236+ for name , (annotation , default ) in OUTPUT_PARAMETERS .items ()
237+ ]
238+
239+ @functools .wraps (command )
240+ def wrapper (** arguments : object ) -> None :
241+ values = {name : arguments .pop (name ) for name in OUTPUT_PARAMETERS }
242+ values ["format" ] = values .pop ("output_format" )
243+ command (options = OutputOptions (** values ), ** arguments )
244+
245+ wrapper .__signature__ = signature .replace (parameters = [* own , * shared ]) # type: ignore[attr-defined]
246+ wrapper .__annotations__ = {
247+ parameter .name : parameter .annotation for parameter in [* own , * shared ]
248+ }
249+ return wrapper
228250
229251
230252def _pairs (values : Sequence [str ] | None , * , option : str ) -> list [tuple [str , str ]]:
@@ -274,20 +296,14 @@ def _split_list(value: str | None) -> list[str] | None:
274296
275297
276298@app .command ()
299+ @with_output_options
277300def tables (
278301 database : DatabaseArg ,
279302 include_internal : AllOption = False ,
280- output_format : FormatOption = OutputFormat .TABLE ,
281- null : NullOption = "NULL" ,
282- truncate : TruncateOption = None ,
283- page : PageOption = None ,
284- page_size : PageSizeOption = None ,
285- pager : PagerOption = False ,
286- color : ColorOption = None ,
287- width : WidthOption = None ,
303+ * ,
304+ options : OutputOptions ,
288305) -> None :
289306 """List the tables and views of the database with their row counts."""
290- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
291307 with _reporting_errors (), open_database (database ) as db :
292308 emit (db .tables (include_internal = include_internal ), options , empty = "(no tables)" )
293309
@@ -315,84 +331,61 @@ def schema(
315331
316332
317333@app .command ()
334+ @with_output_options
318335def describe (
319336 database : DatabaseArg ,
320337 table : TableArg ,
321- output_format : FormatOption = OutputFormat .TABLE ,
322- null : NullOption = "NULL" ,
323- truncate : TruncateOption = None ,
324- page : PageOption = None ,
325- page_size : PageSizeOption = None ,
326- pager : PagerOption = False ,
327- color : ColorOption = None ,
328- width : WidthOption = None ,
338+ * ,
339+ options : OutputOptions ,
329340) -> None :
330341 """Show the columns of a table or view: type, NOT NULL, default and primary key."""
331- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
332342 with _reporting_errors (), open_database (database ) as db :
333343 emit (db .columns (table ), options )
334344
335345
336346@app .command ()
347+ @with_output_options
337348def indexes (
338349 database : DatabaseArg ,
339350 table : OptionalTableArg = None ,
340- output_format : FormatOption = OutputFormat .TABLE ,
341- null : NullOption = "NULL" ,
342- truncate : TruncateOption = None ,
343- page : PageOption = None ,
344- page_size : PageSizeOption = None ,
345- pager : PagerOption = False ,
346- color : ColorOption = None ,
347- width : WidthOption = None ,
351+ * ,
352+ options : OutputOptions ,
348353) -> None :
349354 """List the indexes of a table, or of every table, with the columns they cover."""
350- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
351355 with _reporting_errors (), open_database (database ) as db :
352356 emit (db .indexes (table ), options , empty = "(no indexes)" )
353357
354358
355359@app .command ()
360+ @with_output_options
356361def foreign_keys (
357362 database : DatabaseArg ,
358363 table : OptionalTableArg = None ,
359- output_format : FormatOption = OutputFormat .TABLE ,
360- null : NullOption = "NULL" ,
361- truncate : TruncateOption = None ,
362- page : PageOption = None ,
363- page_size : PageSizeOption = None ,
364- pager : PagerOption = False ,
365- color : ColorOption = None ,
366- width : WidthOption = None ,
364+ * ,
365+ options : OutputOptions ,
367366) -> None :
368367 """List the foreign keys declared by a table, or by every table."""
369- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
370368 with _reporting_errors (), open_database (database ) as db :
371369 emit (db .foreign_keys (table ), options , empty = "(no foreign keys)" )
372370
373371
374372@app .command ()
373+ @with_output_options
375374def info (
376375 database : DatabaseArg ,
377376 check : Annotated [
378377 bool , typer .Option ("--check" , help = "Run PRAGMA integrity_check (slow on big files)." )
379378 ] = False ,
380- output_format : FormatOption = OutputFormat .TABLE ,
381- null : NullOption = "NULL" ,
382- truncate : TruncateOption = None ,
383- page : PageOption = None ,
384- page_size : PageSizeOption = None ,
385- pager : PagerOption = False ,
386- color : ColorOption = None ,
387- width : WidthOption = None ,
379+ * ,
380+ options : OutputOptions ,
388381) -> None :
389382 """Show facts about the database file: size, pragmas and object counts."""
390- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
391383 with _reporting_errors (), open_database (database ) as db :
392384 emit (db .info (check = check ), options )
393385
394386
395387@app .command ()
388+ @with_output_options
396389def show (
397390 database : DatabaseArg ,
398391 table : TableArg ,
@@ -411,17 +404,10 @@ def show(
411404 typer .Option ("--limit" , "-n" , min = 0 , help = "Maximum number of rows to print." ),
412405 ] = None ,
413406 offset : Annotated [int , typer .Option ("--offset" , min = 0 , help = "Number of rows to skip." )] = 0 ,
414- output_format : FormatOption = OutputFormat .TABLE ,
415- null : NullOption = "NULL" ,
416- truncate : TruncateOption = None ,
417- page : PageOption = None ,
418- page_size : PageSizeOption = None ,
419- pager : PagerOption = False ,
420- color : ColorOption = None ,
421- width : WidthOption = None ,
407+ * ,
408+ options : OutputOptions ,
422409) -> None :
423410 """Print the rows of a table or view."""
424- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
425411 with _reporting_errors (), open_database (database ) as db :
426412 result = db .rows (
427413 table ,
@@ -436,28 +422,23 @@ def show(
436422
437423
438424@app .command ()
425+ @with_output_options
439426def stats (
440427 database : DatabaseArg ,
441428 table : TableArg ,
442429 top : Annotated [
443430 int , typer .Option ("--top" , min = 0 , help = "How many frequent values to list per column." )
444431 ] = 3 ,
445- output_format : FormatOption = OutputFormat .TABLE ,
446- null : NullOption = "NULL" ,
447- truncate : TruncateOption = None ,
448- page : PageOption = None ,
449- page_size : PageSizeOption = None ,
450- pager : PagerOption = False ,
451- color : ColorOption = None ,
452- width : WidthOption = None ,
432+ * ,
433+ options : OutputOptions ,
453434) -> None :
454435 """Per-column statistics: nulls, distinct values, min, max and most frequent values."""
455- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
456436 with _reporting_errors (), open_database (database ) as db :
457437 emit (db .stats (table , top = top ), options )
458438
459439
460440@app .command ()
441+ @with_output_options
461442def search (
462443 database : DatabaseArg ,
463444 text : Annotated [
@@ -475,17 +456,10 @@ def search(
475456 limit : Annotated [
476457 int | None , typer .Option ("--limit" , "-n" , min = 1 , help = "Stop after N matches." )
477458 ] = None ,
478- output_format : FormatOption = OutputFormat .TABLE ,
479- null : NullOption = "NULL" ,
480- truncate : TruncateOption = None ,
481- page : PageOption = None ,
482- page_size : PageSizeOption = None ,
483- pager : PagerOption = False ,
484- color : ColorOption = None ,
485- width : WidthOption = None ,
459+ * ,
460+ options : OutputOptions ,
486461) -> None :
487462 """Find a text in every column of every table."""
488- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
489463 with _reporting_errors (), open_database (database ) as db :
490464 emit (db .search (text , tables = table , limit = limit ), options , empty = "(no matches)" )
491465
@@ -494,6 +468,7 @@ def search(
494468
495469
496470@app .command ()
471+ @with_output_options
497472def query (
498473 database : DatabaseArg ,
499474 sql : Annotated [
@@ -524,21 +499,14 @@ def query(
524499 float | None ,
525500 typer .Option ("--watch" , min = 0.1 , help = "Re-run every N seconds until Ctrl-C." ),
526501 ] = None ,
527- output_format : FormatOption = OutputFormat .TABLE ,
528- null : NullOption = "NULL" ,
529- truncate : TruncateOption = None ,
530- page : PageOption = None ,
531- page_size : PageSizeOption = None ,
532- pager : PagerOption = False ,
533- color : ColorOption = None ,
534- width : WidthOption = None ,
502+ * ,
503+ options : OutputOptions ,
535504) -> None :
536505 """Run SQL statements and print their results."""
537506 statements = _read_sql (sql , file )
538- if watch is not None and (pager or write ):
507+ if watch is not None and (options . pager or write ):
539508 _fail ("--watch cannot be combined with --pager or --write" )
540509 parameters = _parameters (params )
541- options = _options (output_format , null , truncate , page , page_size , pager , color , width )
542510 with _reporting_errors (), open_database (database , write = write ) as db :
543511 _attach_all (db , attach , write = write )
544512 try :
0 commit comments