Skip to content

Commit 807a80e

Browse files
committed
refactor: fix code complexity and argument count issues
- Refactor run_server function to reduce complexity - Convert ServerConfig to dataclass to simplify parameter handling - Split complex logic into smaller helper functions: - _configure_logging: Handle logging configuration - _build_uvicorn_config: Build uvicorn configuration - _setup_multi_worker_env: Set up environment for multi-worker mode - Update tests to use new ServerConfig structure - Fix all linting issues (C901, PLR0913) - Maintain backward compatibility and functionality
1 parent ecfab71 commit 807a80e

File tree

2 files changed

+112
-99
lines changed

2 files changed

+112
-99
lines changed

tests/test_cli.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import pytest
1212

1313
# Import local modules
14+
from webhook_bridge.cli import ServerConfig
1415
from webhook_bridge.cli import create_parser
1516
from webhook_bridge.cli import main
1617
from webhook_bridge.cli import run_server
@@ -53,15 +54,18 @@ def test_create_parser_custom_values() -> None:
5354
@patch("webhook_bridge.cli.uvicorn.run")
5455
def test_run_server_with_api_config(mock_run: MagicMock) -> None:
5556
"""Test server running with API configuration."""
56-
plugin_dir = str(Path("/plugins").absolute())
57-
run_server(
57+
plugin_dir = Path("/plugins").absolute()
58+
config = ServerConfig(
5859
host="localhost",
5960
port=9000,
6061
plugin_dir=plugin_dir,
6162
log_level="DEBUG",
62-
title="Custom API",
63-
description="Custom Description",
63+
kwargs={
64+
"title": "Custom API",
65+
"description": "Custom Description",
66+
},
6467
)
68+
run_server(config)
6569

6670
mock_run.assert_called_once()
6771

@@ -94,8 +98,8 @@ def test_main_with_all_options(mock_run: MagicMock) -> None:
9498
@patch("webhook_bridge.cli.uvicorn.run")
9599
def test_run_server_with_workers(mock_run: MagicMock) -> None:
96100
"""Test server running with multiple workers."""
97-
plugin_dir = str(Path("/plugins").absolute())
98-
run_server(
101+
plugin_dir = Path("/plugins").absolute()
102+
config = ServerConfig(
99103
host="localhost",
100104
port=9000,
101105
plugin_dir=plugin_dir,
@@ -109,6 +113,7 @@ def test_run_server_with_workers(mock_run: MagicMock) -> None:
109113
no_use_colors=True,
110114
timeout_keep_alive=10,
111115
)
116+
run_server(config)
112117

113118
mock_run.assert_called_once()
114119
call_args = mock_run.call_args[1]
@@ -121,18 +126,19 @@ def test_run_server_with_workers(mock_run: MagicMock) -> None:
121126
@patch("webhook_bridge.cli.uvicorn.run")
122127
def test_run_server_with_ssl(mock_run: MagicMock) -> None:
123128
"""Test server running with SSL configuration."""
124-
plugin_dir = str(Path("/plugins").absolute())
129+
plugin_dir = Path("/plugins").absolute()
125130
ssl_keyfile = Path("/path/to/key.pem")
126131
ssl_certfile = Path("/path/to/cert.pem")
127132

128-
run_server(
133+
config = ServerConfig(
129134
host="localhost",
130135
port=9000,
131136
plugin_dir=plugin_dir,
132137
log_level="INFO",
133138
ssl_keyfile=ssl_keyfile,
134139
ssl_certfile=ssl_certfile,
135140
)
141+
run_server(config)
136142

137143
mock_run.assert_called_once()
138144
call_args = mock_run.call_args[1]
@@ -143,16 +149,16 @@ def test_run_server_with_ssl(mock_run: MagicMock) -> None:
143149
@patch("webhook_bridge.cli.uvicorn.run")
144150
def test_run_server_with_performance_limits(mock_run: MagicMock) -> None:
145151
"""Test server running with performance limits."""
146-
plugin_dir = str(Path("/plugins").absolute())
147-
148-
run_server(
152+
plugin_dir = Path("/plugins").absolute()
153+
config = ServerConfig(
149154
host="localhost",
150155
port=9000,
151156
plugin_dir=plugin_dir,
152157
log_level="INFO",
153158
limit_concurrency=100,
154159
limit_max_requests=1000,
155160
)
161+
run_server(config)
156162

157163
mock_run.assert_called_once()
158164
call_args = mock_run.call_args[1]

webhook_bridge/cli.py

Lines changed: 95 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
# Import built-in modules
66
import argparse
7+
from dataclasses import dataclass
8+
from dataclasses import field
79
import logging
810
import os
911
from 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

Comments
 (0)