44
55# Import built-in modules
66import argparse
7+ from dataclasses import dataclass
8+ from dataclasses import field
79import logging
810import os
911from pathlib import Path
@@ -185,109 +187,111 @@ def create_parser() -> argparse.ArgumentParser:
185187 return parser
186188
187189
188- def run_server (
189- host : str ,
190- port : int ,
191- plugin_dir : Path ,
192- log_level : str ,
193- * ,
194- disable_docs : bool = False ,
195- workers : int = 1 ,
196- worker_class : str = "uvicorn.workers.UvicornWorker" ,
197- reload : bool = False ,
198- reload_dirs : list [str ] | None = None ,
199- access_log : bool = True ,
200- no_access_log : bool = False ,
201- use_colors : bool = True ,
202- no_use_colors : bool = False ,
203- ssl_keyfile : Path | None = None ,
204- ssl_certfile : Path | None = None ,
205- ssl_ca_certs : Path | None = None ,
206- limit_concurrency : int | None = None ,
207- limit_max_requests : int | None = None ,
208- timeout_keep_alive : int = 5 ,
209- ** kwargs : Any ,
210- ) -> None :
211- """Run the webhook bridge server.
212-
213- Args:
214- host: Host to bind the server to
215- port: Port to bind the server to
216- plugin_dir: Directory containing webhook plugins
217- log_level: Logging level
218- disable_docs: Whether to disable API documentation
219- workers: Number of worker processes
220- worker_class: Worker class to use
221- reload: Enable auto-reload for development
222- reload_dirs: Directories to watch for reload
223- access_log: Enable access log
224- no_access_log: Disable access log (overrides access_log)
225- use_colors: Use colors in log output
226- no_use_colors: Disable colors in log output (overrides use_colors)
227- ssl_keyfile: SSL key file path
228- ssl_certfile: SSL certificate file path
229- ssl_ca_certs: SSL CA certificates file path
230- limit_concurrency: Maximum number of concurrent connections
231- limit_max_requests: Maximum number of requests before restarting worker
232- timeout_keep_alive: Keep-alive timeout in seconds
233- **kwargs: Additional arguments to pass to FastAPI
234- """
235- # Configure logging
190+ @dataclass
191+ class ServerConfig :
192+ """Configuration class for server options."""
193+
194+ host : str
195+ port : int
196+ plugin_dir : Path
197+ log_level : str
198+ disable_docs : bool = False
199+ workers : int = 1
200+ worker_class : str = "uvicorn.workers.UvicornWorker"
201+ reload : bool = False
202+ reload_dirs : list [str ] | None = None
203+ access_log : bool = True
204+ no_access_log : bool = False
205+ use_colors : bool = True
206+ no_use_colors : bool = False
207+ ssl_keyfile : Path | None = None
208+ ssl_certfile : Path | None = None
209+ ssl_ca_certs : Path | None = None
210+ limit_concurrency : int | None = None
211+ limit_max_requests : int | None = None
212+ timeout_keep_alive : int = 5
213+ kwargs : dict [str , Any ] = field (default_factory = dict )
214+
215+
216+ def _configure_logging (log_level : str ) -> None :
217+ """Configure logging for the server."""
236218 logging .basicConfig (
237219 level = getattr (logging , log_level .upper ()),
238220 format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ,
239221 )
240222
241- kwargs ["enable_docs" ] = not disable_docs
242223
243- # Create FastAPI app
244- app = create_app (plugin_dir = str (plugin_dir ), ** kwargs )
245-
246- # Prepare uvicorn configuration
224+ def _build_uvicorn_config (config : ServerConfig ) -> dict [str , Any ]:
225+ """Build uvicorn configuration from server config."""
247226 uvicorn_config = {
248- "app" : app ,
249- "host" : host ,
250- "port" : port ,
251- "log_level" : log_level .lower (),
252- "workers" : workers ,
253- "reload" : reload ,
254- "access_log" : not no_access_log if no_access_log else access_log ,
255- "use_colors" : not no_use_colors if no_use_colors else use_colors ,
256- "timeout_keep_alive" : timeout_keep_alive ,
227+ "host" : config .host ,
228+ "port" : config .port ,
229+ "log_level" : config .log_level .lower (),
230+ "workers" : config .workers ,
231+ "reload" : config .reload ,
232+ "access_log" : not config .no_access_log if config .no_access_log else config .access_log ,
233+ "use_colors" : not config .no_use_colors if config .no_use_colors else config .use_colors ,
234+ "timeout_keep_alive" : config .timeout_keep_alive ,
257235 }
258236
259237 # Add optional configurations
260- if reload_dirs :
261- uvicorn_config ["reload_dirs" ] = reload_dirs
238+ if config . reload_dirs :
239+ uvicorn_config ["reload_dirs" ] = config . reload_dirs
262240
263- if ssl_keyfile :
264- uvicorn_config ["ssl_keyfile" ] = str (ssl_keyfile )
241+ if config . ssl_keyfile :
242+ uvicorn_config ["ssl_keyfile" ] = str (config . ssl_keyfile )
265243
266- if ssl_certfile :
267- uvicorn_config ["ssl_certfile" ] = str (ssl_certfile )
244+ if config . ssl_certfile :
245+ uvicorn_config ["ssl_certfile" ] = str (config . ssl_certfile )
268246
269- if ssl_ca_certs :
270- uvicorn_config ["ssl_ca_certs" ] = str (ssl_ca_certs )
247+ if config . ssl_ca_certs :
248+ uvicorn_config ["ssl_ca_certs" ] = str (config . ssl_ca_certs )
271249
272- if limit_concurrency is not None :
273- uvicorn_config ["limit_concurrency" ] = limit_concurrency
250+ if config . limit_concurrency is not None :
251+ uvicorn_config ["limit_concurrency" ] = config . limit_concurrency
274252
275- if limit_max_requests is not None :
276- uvicorn_config ["limit_max_requests" ] = limit_max_requests
253+ if config .limit_max_requests is not None :
254+ uvicorn_config ["limit_max_requests" ] = config .limit_max_requests
255+
256+ return uvicorn_config
257+
258+
259+ def _setup_multi_worker_env (config : ServerConfig ) -> None :
260+ """Set up environment variables for multi-worker mode."""
261+ os .environ ["WEBHOOK_BRIDGE_PLUGIN_DIR" ] = str (config .plugin_dir )
262+ if "title" in config .kwargs :
263+ os .environ ["WEBHOOK_BRIDGE_TITLE" ] = config .kwargs ["title" ]
264+ if "description" in config .kwargs :
265+ os .environ ["WEBHOOK_BRIDGE_DESCRIPTION" ] = config .kwargs ["description" ]
266+ if "enable_docs" in config .kwargs :
267+ os .environ ["WEBHOOK_BRIDGE_ENABLE_DOCS" ] = str (config .kwargs ["enable_docs" ])
277268
278- # For multiple workers, we need to use a different approach
279- if workers > 1 :
280- # When using multiple workers, we can't pass the app instance directly
281- # Set environment variables for the app factory
282- os .environ ["WEBHOOK_BRIDGE_PLUGIN_DIR" ] = str (plugin_dir )
283- if "title" in kwargs :
284- os .environ ["WEBHOOK_BRIDGE_TITLE" ] = kwargs ["title" ]
285- if "description" in kwargs :
286- os .environ ["WEBHOOK_BRIDGE_DESCRIPTION" ] = kwargs ["description" ]
287- if "enable_docs" in kwargs :
288- os .environ ["WEBHOOK_BRIDGE_ENABLE_DOCS" ] = str (kwargs ["enable_docs" ])
289269
270+ def run_server (config : ServerConfig ) -> None :
271+ """Run the webhook bridge server.
272+
273+ Args:
274+ config: Server configuration object
275+ """
276+ # Configure logging
277+ _configure_logging (config .log_level )
278+
279+ # Prepare kwargs for FastAPI
280+ kwargs = config .kwargs .copy ()
281+ kwargs ["enable_docs" ] = not config .disable_docs
282+
283+ # Build uvicorn configuration
284+ uvicorn_config = _build_uvicorn_config (config )
285+
286+ # For multiple workers, we need to use a different approach
287+ if config .workers > 1 :
288+ # Set up environment variables for the app factory
289+ _setup_multi_worker_env (config )
290290 uvicorn_config ["app" ] = "webhook_bridge.cli:get_app"
291+ else :
292+ # Create FastAPI app for single worker
293+ app = create_app (plugin_dir = str (config .plugin_dir ), ** kwargs )
294+ uvicorn_config ["app" ] = app
291295
292296 # Run server
293297 uvicorn .run (** uvicorn_config )
@@ -325,13 +329,11 @@ def main(argv: Sequence[str] | None = None) -> None:
325329 args = parser .parse_args (argv )
326330
327331 try :
328- run_server (
332+ config = ServerConfig (
329333 host = args .host ,
330334 port = args .port ,
331335 plugin_dir = args .plugin_dir ,
332336 log_level = args .log_level ,
333- title = args .title ,
334- description = args .description ,
335337 disable_docs = args .disable_docs ,
336338 workers = args .workers ,
337339 worker_class = args .worker_class ,
@@ -347,7 +349,12 @@ def main(argv: Sequence[str] | None = None) -> None:
347349 limit_concurrency = args .limit_concurrency ,
348350 limit_max_requests = args .limit_max_requests ,
349351 timeout_keep_alive = args .timeout_keep_alive ,
352+ kwargs = {
353+ "title" : args .title ,
354+ "description" : args .description ,
355+ },
350356 )
357+ run_server (config )
351358 sys .exit (0 )
352359 except Exception as e :
353360 logging .error ("Error running server: %s" , e )
0 commit comments