@@ -78,7 +78,9 @@ class ArgResult:
7878
7979 def __init__ (self , exists : bool , value : Any | list [Any ]):
8080 self .exists : bool = exists
81+ """Whether the argument was found or not."""
8182 self .value : Any = value
83+ """The value given with the found argument."""
8284
8385 def __bool__ (self ):
8486 return self .exists
@@ -969,55 +971,39 @@ def __init__(
969971 ):
970972 self .active : bool = False
971973 """Whether the progress bar is currently active (intercepting stdout) or not."""
972- self .min_width : int = max ( 1 , min_width )
974+ self .min_width : int
973975 """The min width of the progress bar in chars."""
974- self .max_width : int = max ( self . min_width , max_width )
976+ self .max_width : int
975977 """The max width of the progress bar in chars."""
976- self .bar_format : str = bar_format
978+ self .bar_format : str
977979 """The format string used to render the progress bar."""
978- self .limited_bar_format : str = limited_bar_format
980+ self .limited_bar_format : str
979981 """The simplified format string used when the console width is too small."""
980- self .chars : tuple [str , ...] = chars
982+ self .chars : tuple [str , ...]
981983 """A tuple of characters ordered from full to empty progress."""
982984
985+ self .set_width (min_width , max_width )
986+ self .set_bar_format (bar_format , limited_bar_format )
987+ self .set_chars (chars )
988+
983989 self ._buffer : list [str ] = []
984990 self ._original_stdout : Optional [TextIO ] = None
985991 self ._current_progress_str : str = ""
986992 self ._last_line_len : int = 0
987993
988- def show_progress (self , current : int , total : int , label : Optional [str ] = None ) -> None :
989- """Show or update the progress bar.\n
990- -----------------------------------------------------------------------------------------
991- - `current` -⠀the current progress value (below 0 or greater than `total` hides the bar)
992- - `total` -⠀the total value representing 100% progress (must be greater than 0)
993- - `label` -⠀an optional label to display alongside the progress bar"""
994- if total <= 0 :
995- raise ValueError ("Total must be greater than 0." )
996-
997- try :
998- if not self .active :
999- self ._start_intercepting ()
1000- self ._flush_buffer ()
1001- self ._draw_progress_bar (current , total , label or "" )
1002- if current < 0 or current > total :
1003- self .hide_progress ()
1004- except Exception :
1005- self ._emergency_cleanup ()
1006- raise
1007-
1008- def hide_progress (self ) -> None :
1009- """Hide the progress bar and restore normal console output."""
1010- if self .active :
1011- self ._clear_progress_line ()
1012- self ._stop_intercepting ()
1013-
1014994 def set_width (self , min_width : Optional [int ] = None , max_width : Optional [int ] = None ) -> None :
1015995 """Set the width of the progress bar.\n
1016996 --------------------------------------------------------------
1017997 - `min_width` -⠀the min width of the progress bar in chars
1018998 - `max_width` -⠀the max width of the progress bar in chars"""
1019- self .min_width = self .min_width if min_width is None else max (1 , min_width )
1020- self .max_width = self .max_width if max_width is None else max (self .min_width , max_width )
999+ if min_width is not None :
1000+ if min_width < 1 :
1001+ raise ValueError ("Minimum width must be at least 1." )
1002+ self .min_width = max (1 , min_width )
1003+ if max_width is not None :
1004+ if max_width < 1 :
1005+ raise ValueError ("Maximum width must be at least 1." )
1006+ self .max_width = max (self .min_width , max_width )
10211007
10221008 def set_bar_format (self , bar_format : Optional [str ] = None , limited_bar_format : Optional [str ] = None ) -> None :
10231009 """Set the format string used to render the progress bar.\n
@@ -1032,17 +1018,79 @@ def set_bar_format(self, bar_format: Optional[str] = None, limited_bar_format: O
10321018 --------------------------------------------------------------------------------------------------
10331019 The bar format (also limited) can additionally be formatted with special formatting codes. For
10341020 more detailed information about formatting codes, see the `format_codes` module documentation."""
1035- self .bar_format = "{l} |{b}| [b]({c})/{t} [dim](([i]({p}%)))" if bar_format is None else bar_format
1036- self .limited_bar_format = "|{b}|" if limited_bar_format is None else limited_bar_format
1037-
1038- def set_chars (self , chars : Optional [tuple [str , ...]] = None ) -> None :
1021+ if bar_format is not None :
1022+ if not _COMPILED ["bar" ].search (bar_format ):
1023+ raise ValueError ("'bar_format' must contain the '{bar}' or '{b}' placeholder." )
1024+ self .bar_format = bar_format
1025+ if limited_bar_format is not None :
1026+ if not _COMPILED ["bar" ].search (limited_bar_format ):
1027+ raise ValueError ("'limited_bar_format' must contain the '{bar}' or '{b}' placeholder." )
1028+ self .limited_bar_format = limited_bar_format
1029+
1030+ def set_chars (self , chars : tuple [str , ...]) -> None :
10391031 """Set the characters used to render the progress bar.\n
10401032 --------------------------------------------------------------------------
10411033 - `chars` -⠀a tuple of characters ordered from full to empty progress<br>
10421034 The first character represents completely filled sections, intermediate
10431035 characters create smooth transitions, and the last character represents
10441036 empty sections. If None, uses default Unicode block characters."""
1045- self .chars = ("█" , "▉" , "▊" , "▋" , "▌" , "▍" , "▎" , "▏" , " " ) if chars is None else chars
1037+ if len (chars ) < 2 :
1038+ raise ValueError ("'chars' must contain at least two characters (full and empty)." )
1039+ if not all (len (c ) == 1 for c in chars if isinstance (c , str )):
1040+ raise ValueError ("All 'chars' items must be single-character strings." )
1041+ self .chars = chars
1042+
1043+ def show_progress (self , current : int , total : int , label : Optional [str ] = None ) -> None :
1044+ """Show or update the progress bar.\n
1045+ -------------------------------------------------------------------------------------------
1046+ - `current` -⠀the current progress value (below `0` or greater than `total` hides the bar)
1047+ - `total` -⠀the total value representing 100% progress (must be greater than `0`)
1048+ - `label` -⠀an optional label which is inserted at the `{label}` or `{l}` placeholder"""
1049+ if total <= 0 :
1050+ raise ValueError ("Total must be greater than 0." )
1051+
1052+ try :
1053+ if not self .active :
1054+ self ._start_intercepting ()
1055+ self ._flush_buffer ()
1056+ self ._draw_progress_bar (current , total , label or "" )
1057+ if current < 0 or current > total :
1058+ self .hide_progress ()
1059+ except Exception :
1060+ self ._emergency_cleanup ()
1061+ raise
1062+
1063+ def hide_progress (self ) -> None :
1064+ """Hide the progress bar and restore normal console output."""
1065+ if self .active :
1066+ self ._clear_progress_line ()
1067+ self ._stop_intercepting ()
1068+
1069+ @contextmanager
1070+ def progress_context (self , total : int , label : Optional [str ] = None ) -> Generator [Callable [[int ], None ], None , None ]:
1071+ """Context manager for automatic cleanup. Returns a function to update progress.\n
1072+ --------------------------------------------------------------------------------------
1073+ - `total` -⠀the total value representing 100% progress (must be greater than `0`)
1074+ - `label` -⠀an optional label which is inserted at the `{label}` or `{l}` placeholder
1075+ --------------------------------------------------------------------------------------
1076+ Example usage:
1077+ ```python
1078+ with ProgressBar().progress_context(500, "Loading") as update_progress:
1079+ for i in range(500):
1080+ # Do some work...
1081+ update_progress(i) # Update progress
1082+ ```"""
1083+ try :
1084+
1085+ def update_progress (current : int ) -> None :
1086+ self .show_progress (current , total , label )
1087+
1088+ yield update_progress
1089+ except Exception :
1090+ self ._emergency_cleanup ()
1091+ raise
1092+ finally :
1093+ self .hide_progress ()
10461094
10471095 def _start_intercepting (self ) -> None :
10481096 self .active = True
@@ -1132,32 +1180,6 @@ def _redraw_progress_bar(self) -> None:
11321180 self ._original_stdout .write (f"{ self ._current_progress_str } " )
11331181 self ._original_stdout .flush ()
11341182
1135- @contextmanager
1136- def progress_context (self , total : int , label : Optional [str ] = None ) -> Generator [Callable [[int ], None ], None , None ]:
1137- """Context manager for automatic cleanup. Returns a function to update progress.\n
1138- -----------------------------------------------------------------------------------
1139- - `total` -⠀the total value representing 100% progress
1140- - `label` -⠀an optional label to display alongside the progress bar
1141- -----------------------------------------------------------------------------------
1142- Example usage:
1143- ```python
1144- with ProgressBar().progress_context(500, "Loading") as update_progress:
1145- for i in range(501):
1146- # Do some work...
1147- update_progress(i) # Update progress
1148- ```"""
1149- try :
1150-
1151- def update_progress (current : int ) -> None :
1152- self .show_progress (current , total , label )
1153-
1154- yield update_progress
1155- except Exception :
1156- self ._emergency_cleanup ()
1157- raise
1158- finally :
1159- self .hide_progress ()
1160-
11611183
11621184class _InterceptedOutput (_io .StringIO ):
11631185 """Custom StringIO that captures output and stores it in the progress bar buffer."""
0 commit comments