diff --git a/docs/introduction/configuration.md b/docs/introduction/configuration.md index 8e9dc59..579740c 100644 --- a/docs/introduction/configuration.md +++ b/docs/introduction/configuration.md @@ -1,5 +1,13 @@ # Configuration +## Debug mode + +`service_debug` defaults to `False`. Debug mode puts tracebacks in error responses, so keep it off in production. + +- FastAPI: when lite-bootstrap builds the app because no `application` was passed, it sets the app's `debug` to `service_debug`. An app you pass in keeps its own `debug`. +- Litestar: the app runs in debug mode when `service_debug=True` or `AppConfig(debug=True)`. Either one turns it on, and neither turns the other off. +- FastStream, FastMCP and the free bootstrapper don't use it. + ## Sentry Sentry integration uses `sentry_sdk` package under the hood. diff --git a/lite_bootstrap/bootstrappers/litestar_bootstrapper.py b/lite_bootstrap/bootstrappers/litestar_bootstrapper.py index 4857abd..b819a54 100644 --- a/lite_bootstrap/bootstrappers/litestar_bootstrapper.py +++ b/lite_bootstrap/bootstrappers/litestar_bootstrapper.py @@ -501,7 +501,7 @@ def __init__(self, bootstrap_config: LitestarConfig) -> None: self._attach_teardown_once(application_config, lambda: self._apply_config(application_config)) def _apply_config(self, application_config: "AppConfig") -> None: - application_config.debug = self.bootstrap_config.service_debug + application_config.debug = application_config.debug or self.bootstrap_config.service_debug # An Empty value reaches layer resolution and 500s every body-reading handler. if application_config.request_max_body_size is Empty: application_config.request_max_body_size = _LITESTAR_DEFAULT_REQUEST_MAX_BODY_SIZE diff --git a/lite_bootstrap/instruments/base.py b/lite_bootstrap/instruments/base.py index 3e619f3..a0991a1 100644 --- a/lite_bootstrap/instruments/base.py +++ b/lite_bootstrap/instruments/base.py @@ -13,7 +13,7 @@ class BaseConfig: service_description: str | None = None service_version: str = "1.0.0" service_environment: str | None = None - service_debug: bool = True + service_debug: bool = False # Deprecated field name -> the field that replaced it. The deprecated field defaults to UNSET. _renamed_fields: typing.ClassVar[Mapping[str, str]] = {} diff --git a/tests/test_config.py b/tests/test_config.py index f680f19..cb5bdf7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -72,7 +72,7 @@ class Source: config = BaseConfig.from_object(Source()) assert config.service_name == "micro-service" assert config.service_version == "1.0.0" - assert config.service_debug is True + assert config.service_debug is False def test_from_object_preserves_falsy_values() -> None: diff --git a/tests/test_fastapi_bootstrap.py b/tests/test_fastapi_bootstrap.py index 36f47ea..8efe798 100644 --- a/tests/test_fastapi_bootstrap.py +++ b/tests/test_fastapi_bootstrap.py @@ -206,6 +206,17 @@ def test_app_property_raises_when_application_unset() -> None: _ = config.app +@pytest.mark.parametrize( + ("config_kwargs", "expected_debug"), + [ + pytest.param({}, False, id="default"), + pytest.param({"service_debug": True}, True, id="service-debug-on"), + ], +) +def test_built_app_debug_follows_service_debug(config_kwargs: dict[str, typing.Any], expected_debug: bool) -> None: + assert FastAPIConfig(**config_kwargs).app.debug is expected_debug + + def test_user_supplied_app_keeps_title_version_debug() -> None: user_app = fastapi.FastAPI(title="user-title", version="9.9.9", debug=False) config = FastAPIConfig( diff --git a/tests/test_litestar_bootstrap.py b/tests/test_litestar_bootstrap.py index 9e3d7e8..74ba797 100644 --- a/tests/test_litestar_bootstrap.py +++ b/tests/test_litestar_bootstrap.py @@ -520,6 +520,24 @@ def test_litestar_bootstrap_keeps_explicit_unlimited_request_max_body_size(lites assert application.request_max_body_size is None +@pytest.mark.parametrize( + ("application_config", "config_kwargs", "expected_debug"), + [ + pytest.param(AppConfig(), {}, False, id="default"), + pytest.param(AppConfig(debug=False), {}, False, id="app-config-debug-off"), + pytest.param(AppConfig(debug=True), {}, True, id="app-config-debug-on"), + pytest.param(AppConfig(), {"service_debug": True}, True, id="service-debug-on"), + ], +) +def test_litestar_debug_follows_app_config_and_service_debug( + application_config: AppConfig, config_kwargs: dict[str, typing.Any], expected_debug: bool +) -> None: + config = LitestarConfig(application_config=application_config, **config_kwargs) + application = LitestarBootstrapper(bootstrap_config=config).bootstrap() + + assert application.debug is expected_debug + + def test_litestar_default_request_max_body_size_matches_litestar() -> None: """Guard: our constant must stay equal to the default Litestar's own __init__ applies.""" litestar_default = inspect.signature(litestar.Litestar.__init__).parameters["request_max_body_size"].default