1616from enum import Enum
1717from typing import Any , Dict , Iterator , List , Optional
1818
19+ import typer
1920from pydantic import BaseModel , Field , ValidationError
2021from rich .text import Text
2122from snowflake .cli ._plugins .dcm import styles
@@ -152,11 +153,16 @@ class RefreshReporter(Reporter[RefreshRow]):
152153 class Summary :
153154 up_to_date : int = 0
154155 refreshed : int = 0
155- unknown : int = 0
156+ failed : int = 0
157+
158+ @property
159+ def successful (self ) -> int :
160+ """Tables that completed without error (refreshed or already up-to-date)."""
161+ return self .up_to_date + self .refreshed
156162
157163 @property
158164 def total (self ):
159- return self .up_to_date + self .refreshed + self . unknown
165+ return self .successful + self .failed
160166
161167 def __init__ (self , save_output : bool = False ):
162168 super ().__init__ (save_output = save_output )
@@ -187,7 +193,7 @@ def parse_data(self, data: List[RefreshTableResult]) -> Iterator[RefreshRow]:
187193 elif parsed .status == RefreshStatus .REFRESHED :
188194 self ._summary .refreshed += 1
189195 else :
190- self ._summary .unknown += 1
196+ self ._summary .failed += 1
191197 yield parsed
192198
193199 def print_renderables (self , data : Iterator [RefreshRow ]) -> None :
@@ -208,25 +214,44 @@ def print_renderables(self, data: Iterator[RefreshRow]) -> None:
208214 cli_console .styled_message ("\n " )
209215
210216 def _generate_summary_renderables (self ) -> List [Text ]:
211- total = self ._summary .total
212- if total == 0 :
217+ if self ._summary .total == 0 :
213218 return [Text ("No dynamic tables found in the project." )]
214219
215- parts = []
216- if (refreshed := self ._summary .refreshed ) > 0 :
217- parts .append (f"{ refreshed } refreshed" )
218- if (up_to_date := self ._summary .up_to_date ) > 0 :
219- parts .append (f"{ up_to_date } up-to-date" )
220- if (unknown := self ._summary .unknown ) > 0 :
221- parts .append (f"{ unknown } unknown" )
222-
223- summary = ""
224- for i , part in enumerate (parts ):
225- if i > 0 :
226- summary += ", "
227- summary += part
228- summary += "."
229- return [Text (summary )]
220+ renderables : List [Text ] = []
221+ successful = self ._summary .successful
222+ failed = self ._summary .failed
223+
224+ if successful > 0 :
225+ tables_word = "Dynamic Table" if successful == 1 else "Dynamic Tables"
226+ renderables .append (
227+ Text (
228+ f"{ successful } { tables_word } refreshed successfully." ,
229+ styles .PASS_STYLE ,
230+ )
231+ )
232+
233+ if failed > 0 :
234+ refreshes_word = "Refresh" if failed == 1 else "Refreshes"
235+ if renderables :
236+ renderables .append (Text ("\n " ))
237+ renderables .append (
238+ Text (f"{ failed } { refreshes_word } failed." , styles .FAIL_STYLE )
239+ )
240+
241+ return renderables
230242
231243 def _is_success (self ) -> bool :
232- return self ._summary .unknown == 0
244+ # Always treat the run as "success" from the base reporter's perspective
245+ # so that `print_summary()` is always called — we want the styled
246+ # green/red summary rendered above the divider on every run (cf.
247+ # AnalyzeErrorsReporter). Exit code is set separately in
248+ # ``process_payload``.
249+ return True
250+
251+ def process_payload (self , result_json : Dict [str , Any ]) -> None :
252+ super ().process_payload (result_json )
253+ if self ._summary .failed > 0 :
254+ # Failures fail the command, but the styled summary is already on
255+ # screen — exit silently so we don't double-render the message
256+ # inside an "Error" box.
257+ raise typer .Exit (code = 1 )
0 commit comments