55import importlib
66import logging
77import time
8- import traceback
98from concurrent .futures import ThreadPoolExecutor , as_completed
109from timeit import default_timer as timer
11- from typing import TYPE_CHECKING , Any
10+ from typing import TYPE_CHECKING , Any , ClassVar
1211
1312import voluptuous as vol
1413from voluptuous .humanize import humanize_error
3029from viseron .watchdog .thread_watchdog import RestartableThread
3130
3231if TYPE_CHECKING :
32+ from types import ModuleType
33+
3334 from viseron import Viseron
3435 from viseron .domains import OptionalDomain , RequireDomain
3536 from viseron .viseron_types import SupportedDomains
5152class Component :
5253 """Represents a Viseron component."""
5354
54- retry_timers : dict [str , NamedTimer ] = {}
55+ retry_timers : ClassVar [ dict [str , NamedTimer ] ] = {}
5556
5657 def __init__ (
5758 self ,
@@ -79,7 +80,7 @@ def path(self) -> str:
7980 """Return component path."""
8081 return self ._path
8182
82- def get_component (self ):
83+ def get_component (self ) -> ModuleType :
8384 """Return component module."""
8485 return importlib .import_module (self ._path )
8586
@@ -92,20 +93,21 @@ def validate_component_config(self) -> dict | bool | None:
9293 except vol .Invalid as ex :
9394 LOGGER .exception (
9495 f"Error validating config for component { self .name } : "
95- f"{ humanize_error (self ._config , ex )} "
96+ f"{ humanize_error (self ._config , ex )} " # noqa: TRY401
9697 )
9798 return None
9899 except Exception : # pylint: disable=broad-except
99100 LOGGER .exception ("Unknown error calling %s CONFIG_SCHEMA" , self .name )
100101 return None
101102 return True
102103
103- def setup_component (self , tries : int = 1 , domains_only = False ) -> bool :
104+ def setup_component (self , tries : int = 1 , * , domains_only : bool = False ) -> bool :
104105 """Set up component."""
105106 LOGGER .info (
106- "Setting up component %s%s" ,
107+ "Setting up component %s%s%s " ,
107108 self .name ,
108109 (f", attempt { tries } " if tries > 1 else "" ),
110+ (" (domains only)" if domains_only else "" ),
109111 )
110112 slow_setup_warning = NamedTimer (
111113 SLOW_SETUP_WARNING ,
@@ -131,15 +133,13 @@ def setup_component(self, tries: int = 1, domains_only=False) -> bool:
131133 # setup() is optional for stateless components
132134 if hasattr (component_module , "setup" ) and not domains_only :
133135 result = component_module .setup (self ._vis , config )
136+ # No setup function, assume success if setup_domains exists
137+ elif hasattr (component_module , "setup_domains" ):
138+ result = True
134139 else :
135- # No setup function, assume success if setup_domains exists
136- if hasattr (component_module , "setup_domains" ):
137- result = True
138- else :
139- LOGGER .error (
140- f"Component { self .name } has neither setup() "
141- "nor setup_domains()"
142- )
140+ LOGGER .error (
141+ f"Component { self .name } has neither setup() nor setup_domains()"
142+ )
143143 except ComponentNotReady as error :
144144 if self ._vis .shutdown_event .is_set ():
145145 LOGGER .warning (
@@ -153,7 +153,7 @@ def setup_component(self, tries: int = 1, domains_only=False) -> bool:
153153 LOGGER .error (
154154 f"Component { self .name } is not ready. "
155155 f"Retrying in { wait_time } seconds in the background. "
156- f"Error: { str ( error ) } "
156+ f"Error: { error !s } "
157157 )
158158 retry_timer = NamedTimer (
159159 wait_time ,
@@ -178,11 +178,8 @@ def cancel_retry_timer() -> None:
178178 VISERON_SIGNAL_SHUTDOWN , cancel_retry_timer
179179 )
180180 retry_timer .start ()
181- except Exception as ex : # pylint: disable=broad-except
182- LOGGER .error (
183- f"Uncaught exception setting up component { self .name } : { ex } \n "
184- f"{ traceback .format_exc ()} "
185- )
181+ except Exception : # pylint: disable=broad-except
182+ LOGGER .exception (f"Uncaught exception setting up component { self .name } " )
186183 finally :
187184 slow_setup_warning .cancel ()
188185
@@ -192,10 +189,9 @@ def cancel_retry_timer() -> None:
192189 if hasattr (component_module , "setup_domains" ):
193190 try :
194191 component_module .setup_domains (self ._vis , config )
195- except Exception as ex : # pylint: disable=broad-except
196- LOGGER .error (
197- f"Uncaught exception in setup_domains for component "
198- f"{ self .name } : { ex } \n { traceback .format_exc ()} "
192+ except Exception : # pylint: disable=broad-except
193+ LOGGER .exception (
194+ f"Uncaught exception in setup_domains for component { self .name } "
199195 )
200196 return False
201197 LOGGER .info (
@@ -238,11 +234,14 @@ def add_domain_to_setup(
238234 # Check if already registered (any state)
239235 existing = registry .get (domain , identifier )
240236 if existing :
241- LOGGER .warning (
242- f"Domain { domain } with identifier { identifier } already in setup queue. "
243- f"Skipping setup of domain { domain } with identifier { identifier } for "
244- f"component { self .name } " ,
245- )
237+ if existing .state == DomainState .PENDING :
238+ LOGGER .warning (
239+ f"Domain { domain } with identifier { identifier } "
240+ "already pending setup. "
241+ f"Skipping setup of domain { domain } "
242+ f"with identifier { identifier } for "
243+ f"component { self .name } " ,
244+ )
246245 return None
247246
248247 return registry .register (
@@ -274,7 +273,7 @@ def get_component(
274273
275274
276275def setup_component (
277- vis : Viseron , component : Component , tries : int = 1 , domains_only = False
276+ vis : Viseron , component : Component , tries : int = 1 , * , domains_only : bool = False
278277) -> None :
279278 """Set up single component."""
280279 # When tries is larger than one, it means we are in a retry loop.
@@ -340,9 +339,9 @@ def unload_component(vis: Viseron, component: str) -> set[str] | None:
340339 component_module = component_instance .get_component ()
341340 if hasattr (component_module , "unload" ):
342341 try :
343- component_module .unload ()
344- except Exception as ex : # pylint: disable=broad-except
345- LOGGER .error (f"Error unloading component { component } : { ex } " )
342+ component_module .unload (vis )
343+ except Exception : # pylint: disable=broad-except
344+ LOGGER .exception (f"Error unloading component { component } " )
346345 else :
347346 LOGGER .debug (f"Component { component } has no unload method" )
348347
@@ -359,7 +358,7 @@ class CriticalComponentsConfigStore:
359358 Used to store the last known good config for critical components.
360359 """
361360
362- def __init__ (self , vis ) -> None :
361+ def __init__ (self , vis : Viseron ) -> None :
363362 self ._vis = vis
364363 self ._store = Storage (vis , STORAGE_KEY )
365364
@@ -448,27 +447,26 @@ def setup_components(
448447 return
449448
450449 # Setup components in parallel
451- setup_threads = []
452- for component in (
453- components_to_setup
454- - set (LOGGING_COMPONENTS )
455- - set (CORE_COMPONENTS )
456- - set (DEFAULT_COMPONENTS )
457- ):
458- setup_threads .append (
459- RestartableThread (
460- target = setup_component ,
461- args = (vis , get_component (vis , component , config )),
462- kwargs = {"domains_only" : domains_only },
463- name = f"{ component } _setup" ,
464- daemon = True ,
465- register = False ,
466- )
450+ setup_threads = [
451+ RestartableThread (
452+ target = setup_component ,
453+ args = (vis , get_component (vis , component , config )),
454+ kwargs = {"domains_only" : domains_only },
455+ name = f"{ component } _setup" ,
456+ daemon = True ,
457+ register = False ,
458+ )
459+ for component in (
460+ components_to_setup
461+ - set (LOGGING_COMPONENTS )
462+ - set (CORE_COMPONENTS )
463+ - set (DEFAULT_COMPONENTS )
467464 )
465+ ]
468466 for thread in setup_threads :
469467 thread .start ()
470468
471- def join (thread ) -> None :
469+ def join (thread : RestartableThread ) -> None :
472470 thread .join (timeout = 30 )
473471 time .sleep (0.5 ) # Wait for thread to exit properly
474472 if thread .is_alive ():
0 commit comments