From 12c9ee389825441058beab38c8f89aada73d0450 Mon Sep 17 00:00:00 2001 From: antonymayi Date: Sat, 13 Apr 2024 13:59:33 +0200 Subject: [PATCH] Allow `ConfigParser` or a `io.IO[Any]` on `log_config` (#1976) * Enabled to pass log_config as ConfigParser instance or a file object * Rebase correctly --------- Co-authored-by: Marcelo Trylesinski --- tests/test_config.py | 12 +++++++++--- uvicorn/config.py | 9 +++++---- uvicorn/main.py | 5 +++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index ca305f6c2..e16cc5d56 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,5 +1,7 @@ from __future__ import annotations +import configparser +import io import json import logging import os @@ -369,14 +371,18 @@ def test_log_config_yaml( mocked_logging_config_module.dictConfig.assert_called_once_with(logging_config) -def test_log_config_file(mocked_logging_config_module: MagicMock) -> None: +@pytest.mark.parametrize("config_file", ["log_config.ini", configparser.ConfigParser(), io.StringIO()]) +def test_log_config_file( + mocked_logging_config_module: MagicMock, + config_file: str | configparser.RawConfigParser | typing.IO[Any], +) -> None: """ Test that one can load a configparser config from disk. """ - config = Config(app=asgi_app, log_config="log_config") + config = Config(app=asgi_app, log_config=config_file) config.load() - mocked_logging_config_module.fileConfig.assert_called_once_with("log_config", disable_existing_loggers=False) + mocked_logging_config_module.fileConfig.assert_called_once_with(config_file, disable_existing_loggers=False) @pytest.fixture(params=[0, 1]) diff --git a/uvicorn/config.py b/uvicorn/config.py index 3cad1d90f..fca8d5fd2 100644 --- a/uvicorn/config.py +++ b/uvicorn/config.py @@ -9,8 +9,9 @@ import socket import ssl import sys +from configparser import RawConfigParser from pathlib import Path -from typing import Any, Awaitable, Callable, Literal +from typing import IO, Any, Awaitable, Callable, Literal import click @@ -189,7 +190,7 @@ def __init__( ws_per_message_deflate: bool = True, lifespan: LifespanType = "auto", env_file: str | os.PathLike[str] | None = None, - log_config: dict[str, Any] | str | None = LOGGING_CONFIG, + log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, log_level: str | int | None = None, access_log: bool = True, use_colors: bool | None = None, @@ -362,11 +363,11 @@ def configure_logging(self) -> None: self.log_config["formatters"]["default"]["use_colors"] = self.use_colors self.log_config["formatters"]["access"]["use_colors"] = self.use_colors logging.config.dictConfig(self.log_config) - elif self.log_config.endswith(".json"): + elif isinstance(self.log_config, str) and self.log_config.endswith(".json"): with open(self.log_config) as file: loaded_config = json.load(file) logging.config.dictConfig(loaded_config) - elif self.log_config.endswith((".yaml", ".yml")): + elif isinstance(self.log_config, str) and self.log_config.endswith((".yaml", ".yml")): # Install the PyYAML package or the uvicorn[standard] optional # dependencies to enable this functionality. import yaml diff --git a/uvicorn/main.py b/uvicorn/main.py index 87bf381ef..b9d5fd1c0 100644 --- a/uvicorn/main.py +++ b/uvicorn/main.py @@ -6,7 +6,8 @@ import platform import ssl import sys -from typing import Any, Callable +from configparser import RawConfigParser +from typing import IO, Any, Callable import click @@ -481,7 +482,7 @@ def run( reload_delay: float = 0.25, workers: int | None = None, env_file: str | os.PathLike[str] | None = None, - log_config: dict[str, Any] | str | None = LOGGING_CONFIG, + log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = LOGGING_CONFIG, log_level: str | int | None = None, access_log: bool = True, proxy_headers: bool = True,