22import time
33import sys
44import itertools
5+ import os
56
67
78class LoadingIndicator :
89 """
910 A utility class for displaying a loading animation in the console.
1011 """
12+
1113 def __init__ (self , message = "Loading" , animation_type = "spinner" ):
1214 """
1315 Initialize the loading indicator.
@@ -19,41 +21,68 @@ def __init__(self, message="Loading", animation_type="spinner"):
1921 self .animation_type = animation_type
2022 self .is_running = False
2123 self .thread = None
22-
24+
2325 # Define different animation styles
2426 self .animations = {
2527 "spinner" : itertools .cycle (['⠋' , '⠙' , '⠹' , '⠸' , '⠼' , '⠴' , '⠦' , '⠧' , '⠇' , '⠏' ]),
2628 "dots" : itertools .cycle (['. ' , '.. ' , '...' , ' ' ]),
2729 "bar" : itertools .cycle (['[= ]' , '[== ]' , '[===]' , '[ ==]' , '[ =]' , '[ ]' ]),
2830 "thinking" : itertools .cycle (['🧠 ' , '🧠. ' , '🧠..' , '🧠...' ])
2931 }
30-
32+
3133 # Use the specified animation or default to spinner
3234 self .animation = self .animations .get (animation_type , self .animations ["spinner" ])
33-
35+
36+ # Check if we're in a Docker environment
37+ self .use_threading = not (os .environ .get ("DOCKER_CONTAINER" , "" ).lower () in ("true" , "1" ))
38+
3439 def _animate (self ):
3540 """The animation function that runs in a separate thread."""
3641 while self .is_running :
3742 char = next (self .animation )
3843 sys .stdout .write (f'\r { char } { self .message } ' )
3944 sys .stdout .flush ()
4045 time .sleep (0.1 )
41-
46+
4247 # Clear the line when animation stops
4348 sys .stdout .write ('\r ' + ' ' * (len (self .message ) + 10 ) + '\r ' )
4449 sys .stdout .flush ()
45-
50+
51+ def _animate_simple (self ):
52+ """A simpler animation that doesn't use threading."""
53+ char = next (self .animation )
54+ sys .stdout .write (f'\r { char } { self .message } ' )
55+ sys .stdout .flush ()
56+
4657 def start (self ):
4758 """Start the loading animation."""
4859 if not self .is_running :
4960 self .is_running = True
50- self .thread = threading .Thread (target = self ._animate )
51- self .thread .daemon = True
52- self .thread .start ()
53-
61+
62+ # Use threading only if it's enabled
63+ if self .use_threading :
64+ try :
65+ self .thread = threading .Thread (target = self ._animate )
66+ self .thread .daemon = True
67+ self .thread .start ()
68+ except (RuntimeError , ThreadError ):
69+ # If thread creation fails, fall back to simple mode
70+ self ._animate_simple ()
71+ else :
72+ # Just show a static indicator without animation
73+ sys .stdout .write (f'\r ⏳ { self .message } ' )
74+ sys .stdout .flush ()
75+
5476 def stop (self ):
5577 """Stop the loading animation."""
5678 if self .is_running :
5779 self .is_running = False
58- if self .thread :
59- self .thread .join ()
80+ if self .thread and self .use_threading :
81+ try :
82+ self .thread .join (timeout = 0.5 ) # Add timeout to prevent hanging
83+ except Exception :
84+ pass # Ignore any errors when stopping
85+
86+ # Clear the line
87+ sys .stdout .write ('\r ' + ' ' * (len (self .message ) + 10 ) + '\r ' )
88+ sys .stdout .flush ()
0 commit comments