22
33import asyncio
44import signal
5- from typing import Dict , List , Any , Optional , AsyncIterator
6- from dataclasses import dataclass , field
75import logging
6+ from typing import Dict , List , Any , Optional , AsyncIterator , Callable , Awaitable
7+ from dataclasses import dataclass , field
88
99from ..exceptions import DialogChainError , ConfigurationError
10- from .routes import Route , RouteConfig
11- from .tasks import TaskManager
12- from ..connectors import Source , Destination
13- from ..processors import Processor
10+ from .route import Route
11+ from .connector import ConnectorManager
12+ from .processor import MessageProcessor
1413
1514logger = logging .getLogger (__name__ )
1615
17- @ dataclass
16+
1817class DialogChainEngine :
1918 """Main engine class for DialogChain processing."""
2019
21- config : Dict [str , Any ]
22- routes : List [Route ] = field (default_factory = list )
23- tasks : List [asyncio .Task ] = field (default_factory = list )
24- _is_running : bool = False
25- verbose : bool = False
26-
27- def __post_init__ (self ):
28- """Initialize the engine with configuration."""
29- self .task_manager = TaskManager ()
20+ def __init__ (
21+ self ,
22+ config : Dict [str , Any ],
23+ verbose : bool = False ,
24+ connector_manager : Optional [ConnectorManager ] = None ,
25+ processor_factory : Optional [Callable [[Dict [str , Any ]], Awaitable [Any ]]] = None
26+ ):
27+ """Initialize the DialogChain engine.
28+
29+ Args:
30+ config: Engine configuration dictionary
31+ verbose: Enable verbose logging
32+ connector_manager: Optional pre-configured connector manager
33+ processor_factory: Optional custom processor factory function
34+ """
35+ self .config = config
36+ self .verbose = verbose
37+ self ._is_running = False
38+ self ._routes : List [Route ] = []
39+ self ._tasks : List [asyncio .Task ] = []
40+
41+ # Set up logging
3042 self ._setup_logging ()
43+
44+ # Initialize components
45+ self .connector_manager = connector_manager or ConnectorManager ()
46+ self .processor_factory = processor_factory or self ._create_processor
47+
48+ # Load and validate configuration
3149 self ._load_config ()
3250
3351 def _setup_logging (self ):
@@ -37,6 +55,7 @@ def _setup_logging(self):
3755 level = log_level ,
3856 format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
3957 )
58+ logger .setLevel (log_level )
4059
4160 def _load_config (self ):
4261 """Load and validate configuration."""
@@ -46,13 +65,32 @@ def _load_config(self):
4665 # Load routes from config
4766 for route_config in self .config .get ('routes' , []):
4867 try :
49- route = Route . from_config (route_config )
50- self .routes .append (route )
68+ route = self . _create_route (route_config )
69+ self ._routes .append (route )
5170 logger .info (f"Loaded route: { route .name } " )
5271 except Exception as e :
53- logger .error (f"Failed to load route: { e } " )
72+ logger .error (f"Failed to load route: { e } " , exc_info = True )
5473 if self .verbose :
55- logger .exception ("Route loading error" )
74+ raise
75+
76+ async def _create_processor (self , config : Dict [str , Any ]) -> Any :
77+ """Create a processor from configuration.
78+
79+ This is a default implementation that can be overridden by providing
80+ a custom processor_factory to the engine constructor.
81+ """
82+ # Import here to avoid circular imports
83+ from ..processors import create_processor
84+ return create_processor (config )
85+
86+ def _create_route (self , config : Dict [str , Any ]) -> Route :
87+ """Create a route from configuration."""
88+ return Route .from_config (
89+ config ,
90+ create_source = self .connector_manager .create_source ,
91+ create_processor = self .processor_factory ,
92+ create_destination = self .connector_manager .create_destination
93+ )
5694
5795 async def start (self ):
5896 """Start the engine and all routes."""
@@ -63,16 +101,23 @@ async def start(self):
63101 self ._is_running = True
64102 logger .info ("Starting DialogChain engine..." )
65103
66- try :
67- for route in self .routes :
68- task = asyncio .create_task (self ._run_route (route ))
69- self .tasks .append (task )
70-
71- logger .info ("Engine started successfully" )
72- except Exception as e :
73- logger .error (f"Failed to start engine: { e } " )
74- await self .stop ()
75- raise
104+ # Set up signal handlers for graceful shutdown
105+ loop = asyncio .get_running_loop ()
106+ for sig in (signal .SIGINT , signal .SIGTERM ):
107+ loop .add_signal_handler (sig , lambda : asyncio .create_task (self .stop ()))
108+
109+ # Start all routes
110+ for route in self ._routes :
111+ try :
112+ await route .start ()
113+ self ._tasks .append (asyncio .create_task (route ._run_loop ()))
114+ logger .info (f"Started route: { route .name } " )
115+ except Exception as e :
116+ logger .error (f"Failed to start route { route .name } : { e } " , exc_info = True )
117+ if self .verbose :
118+ raise
119+
120+ logger .info ("DialogChain engine started successfully" )
76121
77122 async def stop (self ):
78123 """Stop the engine and clean up resources."""
@@ -83,15 +128,21 @@ async def stop(self):
83128 self ._is_running = False
84129
85130 # Cancel all running tasks
86- for task in self .tasks :
131+ for task in self ._tasks :
87132 if not task .done ():
88133 task .cancel ()
89134
90- if self .tasks :
91- await asyncio .gather (* self .tasks , return_exceptions = True )
135+ # Stop all routes
136+ stop_tasks = [route .stop () for route in self ._routes ]
137+ if stop_tasks :
138+ await asyncio .gather (* stop_tasks , return_exceptions = True )
92139
93- self .tasks .clear ()
94- logger .info ("Engine stopped" )
140+ # Wait for tasks to complete
141+ if self ._tasks :
142+ await asyncio .gather (* self ._tasks , return_exceptions = True )
143+
144+ self ._tasks .clear ()
145+ logger .info ("DialogChain engine stopped" )
95146
96147 async def _run_route (self , route : Route ):
97148 """Run a single route."""
0 commit comments