diff --git a/src/codegate/cli.py b/src/codegate/cli.py index 0cae7601..d189832f 100644 --- a/src/codegate/cli.py +++ b/src/codegate/cli.py @@ -24,6 +24,7 @@ def cli() -> None: """Codegate - A configurable service gateway.""" pass + @cli.command() @click.option( "--port", @@ -90,7 +91,7 @@ def serve( "port": cfg.port, "log_level": cfg.log_level.value, "log_format": cfg.log_format.value, - } + }, ) # TODO: Jakub Implement actual server logic here @@ -103,6 +104,7 @@ def serve( click.echo(f"Error: {e}", err=True) sys.exit(1) + def main() -> None: """Main entry point for the CLI.""" cli() diff --git a/src/codegate/config.py b/src/codegate/config.py index 0af4cbdd..0a27f321 100644 --- a/src/codegate/config.py +++ b/src/codegate/config.py @@ -18,7 +18,7 @@ class LogLevel(str, Enum): DEBUG = "DEBUG" @classmethod - def _missing_(cls, value: str) -> Optional['LogLevel']: + def _missing_(cls, value: str) -> Optional["LogLevel"]: """Handle case-insensitive lookup of enum values.""" try: # Convert to uppercase and look up directly @@ -29,6 +29,7 @@ def _missing_(cls, value: str) -> Optional['LogLevel']: f"Valid levels are: {', '.join(level.value for level in cls)}" ) + class LogFormat(str, Enum): """Valid log formats.""" @@ -36,7 +37,7 @@ class LogFormat(str, Enum): TEXT = "TEXT" @classmethod - def _missing_(cls, value: str) -> Optional['LogFormat']: + def _missing_(cls, value: str) -> Optional["LogFormat"]: """Handle case-insensitive lookup of enum values.""" try: # Convert to uppercase and look up directly @@ -50,10 +51,12 @@ def _missing_(cls, value: str) -> Optional['LogFormat']: class ConfigurationError(Exception): """Raised when there's an error in configuration.""" + def __init__(self, message: str) -> None: super().__init__(message) # You can add additional logging or handling here if needed + @dataclass class Config: """Application configuration with priority resolution.""" @@ -174,6 +177,7 @@ def load( except ConfigurationError as e: # Log warning but continue with defaults import logging + logging.warning(f"Failed to load config file: {e}") # Override with environment variables diff --git a/src/codegate/logging.py b/src/codegate/logging.py index 6a708001..6381c88d 100644 --- a/src/codegate/logging.py +++ b/src/codegate/logging.py @@ -38,10 +38,28 @@ def format(self, record: logging.LogRecord) -> str: extra_attrs = {} for key, value in record.__dict__.items(): if key not in { - "args", "asctime", "created", "exc_info", "exc_text", "filename", - "funcName", "levelname", "levelno", "lineno", "module", "msecs", - "msg", "name", "pathname", "process", "processName", "relativeCreated", - "stack_info", "thread", "threadName", "extra" + "args", + "asctime", + "created", + "exc_info", + "exc_text", + "filename", + "funcName", + "levelname", + "levelno", + "lineno", + "module", + "msecs", + "msg", + "name", + "pathname", + "process", + "processName", + "relativeCreated", + "stack_info", + "thread", + "threadName", + "extra", }: extra_attrs[key] = value @@ -87,13 +105,11 @@ def __init__(self) -> None: """Initialize the text formatter.""" super().__init__( fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%Y-%m-%dT%H:%M:%S.%03dZ" + datefmt="%Y-%m-%dT%H:%M:%S.%03dZ", ) def formatTime( # noqa: N802 - self, - record: logging.LogRecord, - datefmt: Optional[str] = None + self, record: logging.LogRecord, datefmt: Optional[str] = None ) -> str: """Format the time with millisecond precision. @@ -109,8 +125,7 @@ def formatTime( # noqa: N802 def setup_logging( - log_level: Optional[LogLevel] = None, - log_format: Optional[LogFormat] = None + log_level: Optional[LogLevel] = None, log_format: Optional[LogFormat] = None ) -> None: """Configure the logging system. @@ -160,6 +175,6 @@ def setup_logging( extra={ "log_level": log_level.value, "log_format": log_format.value, - "handlers": ["stdout", "stderr"] - } + "handlers": ["stdout", "stderr"], + }, ) diff --git a/tests/conftest.py b/tests/conftest.py index aa7f0478..afbb5956 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ import pytest import yaml + from codegate.config import Config @@ -19,7 +20,7 @@ def temp_config_file(tmp_path: Path) -> Iterator[Path]: "port": 8989, "host": "localhost", "log_level": "DEBUG", - "log_format": "JSON" + "log_format": "JSON", } config_file = tmp_path / "config.yaml" @@ -34,12 +35,14 @@ def env_vars() -> Generator[None, None, None]: """Set up test environment variables.""" original_env = dict(os.environ) - os.environ.update({ - "CODEGATE_APP_PORT": "8989", - "CODEGATE_APP_HOST": "localhost", - "CODEGATE_APP_LOG_LEVEL": "WARNING", - "CODEGATE_LOG_FORMAT": "TEXT" - }) + os.environ.update( + { + "CODEGATE_APP_PORT": "8989", + "CODEGATE_APP_HOST": "localhost", + "CODEGATE_APP_LOG_LEVEL": "WARNING", + "CODEGATE_LOG_FORMAT": "TEXT", + } + ) yield @@ -73,6 +76,7 @@ def capture_logs(tmp_path: Path) -> Iterator[Path]: # Create a file handler import logging + handler = logging.FileHandler(log_file) logger = logging.getLogger() logger.addHandler(handler) diff --git a/tests/test_cli.py b/tests/test_cli.py index a3762dad..bed2dde0 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -6,6 +6,7 @@ import pytest from click.testing import CliRunner + from codegate.cli import cli from codegate.config import LogFormat, LogLevel @@ -44,7 +45,7 @@ def test_serve_default_options(cli_runner: CliRunner, mock_logging: Any) -> None "port": 8989, "log_level": "INFO", "log_format": "JSON", - } + }, ) @@ -56,11 +57,15 @@ def test_serve_custom_options(cli_runner: CliRunner, mock_logging: Any) -> None: cli, [ "serve", - "--port", "8989", - "--host", "localhost", - "--log-level", "DEBUG", - "--log-format", "TEXT" - ] + "--port", + "8989", + "--host", + "localhost", + "--log-level", + "DEBUG", + "--log-format", + "TEXT", + ], ) assert result.exit_code == 0 @@ -72,7 +77,7 @@ def test_serve_custom_options(cli_runner: CliRunner, mock_logging: Any) -> None: "port": 8989, "log_level": "DEBUG", "log_format": "TEXT", - } + }, ) @@ -95,9 +100,7 @@ def test_serve_invalid_log_level(cli_runner: CliRunner) -> None: def test_serve_with_config_file( - cli_runner: CliRunner, - mock_logging: Any, - temp_config_file: Path + cli_runner: CliRunner, mock_logging: Any, temp_config_file: Path ) -> None: """Test serve command with config file.""" with patch("logging.getLogger") as mock_logger: @@ -113,9 +116,10 @@ def test_serve_with_config_file( "port": 8989, "log_level": "DEBUG", "log_format": "JSON", - } + }, ) + def test_serve_with_nonexistent_config_file(cli_runner: CliRunner) -> None: """Test serve command with nonexistent config file.""" result = cli_runner.invoke(cli, ["serve", "--config", "nonexistent.yaml"]) @@ -124,10 +128,7 @@ def test_serve_with_nonexistent_config_file(cli_runner: CliRunner) -> None: def test_serve_priority_resolution( - cli_runner: CliRunner, - mock_logging: Any, - temp_config_file: Path, - env_vars: Any + cli_runner: CliRunner, mock_logging: Any, temp_config_file: Path, env_vars: Any ) -> None: """Test serve command respects configuration priority.""" with patch("logging.getLogger") as mock_logger: @@ -136,12 +137,17 @@ def test_serve_priority_resolution( cli, [ "serve", - "--config", str(temp_config_file), - "--port", "8080", - "--host", "example.com", - "--log-level", "ERROR", - "--log-format", "TEXT" - ] + "--config", + str(temp_config_file), + "--port", + "8080", + "--host", + "example.com", + "--log-level", + "ERROR", + "--log-format", + "TEXT", + ], ) assert result.exit_code == 0 @@ -153,7 +159,7 @@ def test_serve_priority_resolution( "port": 8080, "log_level": "ERROR", "log_format": "TEXT", - } + }, ) @@ -161,5 +167,6 @@ def test_main_function() -> None: """Test main entry point function.""" with patch("codegate.cli.cli") as mock_cli: from codegate.cli import main + main() mock_cli.assert_called_once() diff --git a/tests/test_config.py b/tests/test_config.py index ad5da7ab..68e387fa 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,6 +5,7 @@ import pytest import yaml + from codegate.config import Config, ConfigurationError, LogFormat, LogLevel @@ -58,7 +59,7 @@ def test_config_priority_resolution(temp_config_file: Path, env_vars: None) -> N cli_port=8080, cli_host="example.com", cli_log_level="WARNING", - cli_log_format="TEXT" + cli_log_format="TEXT", ) assert config.port == 8080 assert config.host == "example.com" @@ -122,10 +123,7 @@ def config_file_with_format(tmp_path: Path) -> Path: """Create a config file with log format.""" config_file = tmp_path / "config.yaml" with open(config_file, "w") as f: - yaml.dump({ - "log_format": "TEXT", - "log_level": "DEBUG" - }, f) + yaml.dump({"log_format": "TEXT", "log_level": "DEBUG"}, f) return config_file diff --git a/tests/test_logging.py b/tests/test_logging.py index 03f03b87..eb11f06b 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -14,7 +14,7 @@ def test_json_formatter(): lineno=10, msg="Test message", args=(), - exc_info=None + exc_info=None, ) formatter = JSONFormatter() formatted_log = formatter.format(log_record) @@ -26,6 +26,7 @@ def test_json_formatter(): assert "timestamp" in log_entry assert "extra" in log_entry + def test_text_formatter(): log_record = logging.LogRecord( name="test", @@ -34,7 +35,7 @@ def test_text_formatter(): lineno=10, msg="Test message", args=(), - exc_info=None + exc_info=None, ) formatter = TextFormatter() formatted_log = formatter.format(log_record) @@ -43,6 +44,7 @@ def test_text_formatter(): assert "test" in formatted_log assert "Test message" in formatted_log + def test_setup_logging_json_format(): setup_logging(log_level=LogLevel.DEBUG, log_format=LogFormat.JSON) logger = logging.getLogger("codegate") @@ -58,6 +60,7 @@ def test_setup_logging_json_format(): assert log_entry["level"] == "DEBUG" assert log_entry["message"] == "Debug message" + def test_setup_logging_text_format(): setup_logging(log_level=LogLevel.DEBUG, log_format=LogFormat.TEXT) logger = logging.getLogger("codegate")