11# src/rhosocial/activerecord/backend/impl/postgres/__main__.py
22import argparse
33import asyncio
4- import datetime
5- import decimal
64import logging
7- import json
85import os
96import sys
107
8+ from .backend import PostgresBackend , AsyncPostgresBackend
9+ from .config import PostgresConnectionConfig
10+ from rhosocial .activerecord .backend .errors import ConnectionError , QueryError
11+ from .output import PlainTextOutputProvider
12+
13+ # Attempt to import rich and the RichOutputProvider
1114try :
1215 from rich .console import Console
13- from rich .table import Table
14- from rich .panel import Panel
1516 from rich .logging import RichHandler
16- from rich import box
17+ from . output import RichOutputProvider
1718 RICH_AVAILABLE = True
1819except ImportError :
1920 RICH_AVAILABLE = False
2021
21- from .backend import PostgresBackend , AsyncPostgresBackend
22- from .config import PostgresConnectionConfig
23- from rhosocial .activerecord .backend .errors import ConnectionError , QueryError
24-
25-
2622logger = logging .getLogger (__name__ )
2723
2824
@@ -32,205 +28,114 @@ def parse_args():
3228 formatter_class = argparse .RawTextHelpFormatter
3329 )
3430
35- # Connection parameters with defaults from environment variables
36- parser .add_argument (
37- '--host' ,
38- default = os .getenv ('POSTGRES_HOST' , 'localhost' ),
39- help = 'Database host (default: POSTGRES_HOST environment variable or localhost)'
40- )
41- parser .add_argument (
42- '--port' ,
43- type = int ,
44- default = int (os .getenv ('POSTGRES_PORT' , 5432 )),
45- help = 'Database port (default: POSTGRES_PORT environment variable or 5432)'
46- )
47- parser .add_argument (
48- '--database' ,
49- default = os .getenv ('POSTGRES_DATABASE' ),
50- help = 'Database name (optional, default: POSTGRES_DATABASE environment variable)'
51- )
52- parser .add_argument (
53- '--user' ,
54- default = os .getenv ('POSTGRES_USER' , 'postgres' ),
55- help = 'Database user (default: POSTGRES_USER environment variable or postgres)'
56- )
57- parser .add_argument (
58- '--password' ,
59- default = os .getenv ('POSTGRES_PASSWORD' , '' ),
60- help = 'Database password (default: POSTGRES_PASSWORD environment variable or empty string)'
61- )
62- # Positional argument for the SQL query
63- parser .add_argument (
64- 'query' , # Changed to positional argument
65- help = 'SQL query to execute. Must be enclosed in quotes.'
66- )
31+ # Connection parameters
32+ parser .add_argument ('--host' , default = os .getenv ('POSTGRES_HOST' , 'localhost' ), help = 'Database host' )
33+ parser .add_argument ('--port' , type = int , default = int (os .getenv ('POSTGRES_PORT' , 5432 )), help = 'Database port' )
34+ parser .add_argument ('--database' , default = os .getenv ('POSTGRES_DATABASE' ), help = 'Database name' )
35+ parser .add_argument ('--user' , default = os .getenv ('POSTGRES_USER' , 'postgres' ), help = 'Database user' )
36+ parser .add_argument ('--password' , default = os .getenv ('POSTGRES_PASSWORD' , '' ), help = 'Database password' )
6737
38+ # Execution options
39+ parser .add_argument ('query' , help = 'SQL query to execute. Must be enclosed in quotes.' )
6840 parser .add_argument ('--use-async' , action = 'store_true' , help = 'Use asynchronous backend' )
69- parser .add_argument ('--log-level' , default = 'INFO' , help = 'Set logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)' )
41+
42+ # Output and logging options
43+ parser .add_argument ('--log-level' , default = 'INFO' , help = 'Set logging level (e.g., DEBUG, INFO)' )
44+ parser .add_argument ('--plain' , action = 'store_true' , help = 'Use plain text output even if rich is installed.' )
7045 parser .add_argument ('--rich-ascii' , action = 'store_true' , help = 'Use ASCII characters for rich table borders.' )
7146
7247 return parser .parse_args ()
7348
74- def execute_query_sync (args , backend ):
49+ def execute_query_sync (args , backend , provider ):
7550 try :
7651 backend .connect ()
77- if RICH_AVAILABLE :
78- Console ().print (f"Executing synchronous query: [bold cyan]{ args .query } [/bold cyan]" )
79- else :
80- logger .info (f"Executing synchronous query: { args .query } " )
52+ provider .display_query (args .query , is_async = False )
8153 result = backend .execute (args .query )
82- handle_result (args , result )
54+ handle_result (result , provider , use_ascii = args . rich_ascii )
8355 except ConnectionError as e :
84- if RICH_AVAILABLE :
85- Console ().print (Panel (f"[bold]Database Connection Error[/bold]\n [red]{ e } [/red]" , title = "[bold red]Error[/bold red]" , border_style = "red" ))
86- else :
87- logger .error (f"Database connection error: { e } " )
56+ provider .display_connection_error (e )
8857 sys .exit (1 )
8958 except QueryError as e :
90- if RICH_AVAILABLE :
91- Console ().print (Panel (f"[bold]Database Query Error[/bold]\n [red]{ e } [/red]" , title = "[bold red]Error[/bold red]" , border_style = "red" ))
92- else :
93- logger .error (f"Database query error: { e } " )
59+ provider .display_query_error (e )
9460 sys .exit (1 )
9561 except Exception as e :
96- if RICH_AVAILABLE :
97- Console ().print (Panel (f"[bold]An unexpected error occurred during synchronous execution[/bold]\n [red]{ e } [/red]" , title = "[bold red]Error[/bold red]" , border_style = "red" ))
98- else :
99- logger .error (f"An unexpected error occurred during synchronous execution: { e } " , exc_info = True )
62+ provider .display_unexpected_error (e , is_async = False )
10063 sys .exit (1 )
10164 finally :
10265 if backend ._connection :
10366 backend .disconnect ()
104- if RICH_AVAILABLE :
105- Console ().print ("[dim]Disconnected from database (synchronous).[/dim]" )
106- else :
107- logger .info ("Disconnected from database (synchronous)." )
67+ provider .display_disconnect (is_async = False )
10868
109- async def execute_query_async (args , backend ):
69+ async def execute_query_async (args , backend , provider ):
11070 try :
11171 await backend .connect ()
112- if RICH_AVAILABLE :
113- Console ().print (f"Executing asynchronous query: [bold cyan]{ args .query } [/bold cyan]" )
114- else :
115- logger .info (f"Executing asynchronous query: { args .query } " )
72+ provider .display_query (args .query , is_async = True )
11673 result = await backend .execute (args .query )
117- handle_result (args , result )
74+ handle_result (result , provider , use_ascii = args . rich_ascii )
11875 except ConnectionError as e :
119- if RICH_AVAILABLE :
120- Console ().print (Panel (f"[bold]Database Connection Error[/bold]\n [red]{ e } [/red]" , title = "[bold red]Error[/bold red]" , border_style = "red" ))
121- else :
122- logger .error (f"Database connection error: { e } " )
76+ provider .display_connection_error (e )
12377 sys .exit (1 )
12478 except QueryError as e :
125- if RICH_AVAILABLE :
126- Console ().print (Panel (f"[bold]Database Query Error[/bold]\n [red]{ e } [/red]" , title = "[bold red]Error[/bold red]" , border_style = "red" ))
127- else :
128- logger .error (f"Database query error: { e } " )
79+ provider .display_query_error (e )
12980 sys .exit (1 )
13081 except Exception as e :
131- if RICH_AVAILABLE :
132- Console ().print (Panel (f"[bold]An unexpected error occurred during asynchronous execution[/bold]\n [red]{ e } [/red]" , title = "[bold red]Error[/bold red]" , border_style = "red" ))
133- else :
134- logger .error (f"An unexpected error occurred during asynchronous execution: { e } " , exc_info = True )
82+ provider .display_unexpected_error (e , is_async = True )
13583 sys .exit (1 )
13684 finally :
13785 if backend ._connection :
13886 await backend .disconnect ()
139- if RICH_AVAILABLE :
140- Console ().print ("[dim]Disconnected from database (asynchronous).[/dim]" )
141- else :
142- logger .info ("Disconnected from database (asynchronous)." )
143-
144- def json_serializer (obj ):
145- """Handles serialization of types not supported by default JSON encoder."""
146- if isinstance (obj , (datetime .datetime , datetime .date )):
147- return obj .isoformat ()
148- if isinstance (obj , datetime .timedelta ):
149- return str (obj )
150- if isinstance (obj , decimal .Decimal ):
151- return str (obj )
152- raise TypeError (f"Object of type { obj .__class__ .__name__ } is not JSON serializable" )
153-
154-
155- def handle_result (args , result ):
87+ provider .display_disconnect (is_async = True )
88+
89+
90+ def handle_result (result , provider , ** kwargs ):
15691 if not result :
157- if RICH_AVAILABLE :
158- Console ().print ("[yellow]Query executed, but no result object returned.[/yellow]" )
159- else :
160- logger .info ("Query executed, but no result object returned." )
92+ provider .display_no_result_object ()
16193 return
16294
163- if RICH_AVAILABLE :
164- console = Console ()
165- console .print (f"[bold green]Query executed successfully.[/bold green] "
166- f"Affected rows: [bold cyan]{ result .affected_rows } [/bold cyan], "
167- f"Duration: [bold cyan]{ result .duration :.4f} s[/bold cyan]" )
168- if result .data :
169- # Ensure data is a list of dicts
170- if not isinstance (result .data , list ) or not result .data or not all (isinstance (i , dict ) for i in result .data ):
171- console .print (result .data )
172- return
173-
174- box_style = box .ASCII if args .rich_ascii else box .SQUARE
175- table = Table (show_header = True , header_style = "bold magenta" , box = box_style )
176- headers = result .data [0 ].keys ()
177- for header in headers :
178- table .add_column (header , style = "dim" , overflow = "fold" )
179-
180- for row in result .data :
181- table .add_row (* (str (v ) for v in row .values ()))
182-
183- console .print (table )
184- else :
185- console .print ("[yellow]No data returned.[/yellow]" )
95+ provider .display_success (result .affected_rows , result .duration )
96+ if result .data :
97+ provider .display_results (result .data , ** kwargs )
18698 else :
187- logger .info (f"Query executed successfully. Affected rows: { result .affected_rows } , Duration: { result .duration :.4f} s" )
188- if result .data :
189- logger .info ("Results:" )
190- for row in result .data :
191- if isinstance (row , (dict , list )):
192- print (json .dumps (row , indent = 2 , ensure_ascii = False , default = json_serializer ))
193- else :
194- print (row )
195- else :
196- logger .info ("No data returned." )
99+ provider .display_no_data ()
197100
198101def main ():
199102 args = parse_args ()
200103
201- # Set logging level
104+ # Setup logging
202105 numeric_level = getattr (logging , args .log_level .upper (), None )
203106 if not isinstance (numeric_level , int ):
204107 raise ValueError (f'Invalid log level: { args .log_level } ' )
205108
206- if RICH_AVAILABLE :
109+ # Choose output provider
110+ if RICH_AVAILABLE and not args .plain :
111+ provider = RichOutputProvider (console = Console (), ascii_borders = args .rich_ascii )
207112 logging .basicConfig (
208113 level = numeric_level ,
209114 format = "%(message)s" ,
210115 datefmt = "[%X]" ,
211116 handlers = [RichHandler (rich_tracebacks = True , show_path = False )]
212117 )
213- Console ().print ("[bold green]Rich library detected. Logging and output are beautified.[/bold green]" )
214118 else :
119+ provider = PlainTextOutputProvider ()
215120 logging .basicConfig (level = numeric_level , format = '%(asctime)s - %(levelname)s - %(message)s' )
216- print ("Rich library not found. We recommend installing rich for a more user-friendly output." )
121+
122+ provider .display_greeting ()
217123
124+ # Setup backend
218125 config = PostgresConnectionConfig (
219126 host = args .host ,
220127 port = args .port ,
221128 database = args .database ,
222129 username = args .user ,
223130 password = args .password ,
224-
225-
226131 )
227132
228133 if args .use_async :
229134 backend = AsyncPostgresBackend (connection_config = config )
230- asyncio .run (execute_query_async (args , backend ))
135+ asyncio .run (execute_query_async (args , backend , provider ))
231136 else :
232137 backend = PostgresBackend (connection_config = config )
233- execute_query_sync (args , backend )
138+ execute_query_sync (args , backend , provider )
234139
235140if __name__ == "__main__" :
236141 main ()
0 commit comments