Repository: encode/uvicorn

Issue:
Phase out `debug` in favour of `reload`
Really we ought to phase out the usage of `--debug`/`debug=True`, in favour of...

* `--reload`/`reload=True` (A server-level concern. Reload the server on code changes.)
* `app = Starlette(debug=True)` (An application level concern. Issue nice HTML tracebacks for the "500 Server Error" case.)

Note that the Starlette package has a standalone `ServerErrorMiddleware` that you can use in isolation if you want just use debug tracebacks together with some custom ASGI app.


Discussion:

Resolving patch:
diff --git a/uvicorn/config.py b/uvicorn/config.py
--- a/uvicorn/config.py
+++ b/uvicorn/config.py
@@ -42,7 +42,6 @@
 
 from uvicorn.importer import ImportFromStringError, import_from_string
 from uvicorn.middleware.asgi2 import ASGI2Middleware
-from uvicorn.middleware.debug import DebugMiddleware
 from uvicorn.middleware.message_logger import MessageLoggerMiddleware
 from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
 from uvicorn.middleware.wsgi import WSGIMiddleware
@@ -228,7 +227,6 @@ def __init__(
         access_log: bool = True,
         use_colors: Optional[bool] = None,
         interface: InterfaceType = "auto",
-        debug: bool = False,
         reload: bool = False,
         reload_dirs: Optional[Union[List[str], str]] = None,
         reload_delay: float = 0.25,
@@ -275,7 +273,6 @@ def __init__(
         self.access_log = access_log
         self.use_colors = use_colors
         self.interface = interface
-        self.debug = debug
         self.reload = reload
         self.reload_delay = reload_delay
         self.workers = workers or 1
@@ -511,8 +508,6 @@ def load(self) -> None:
         elif self.interface == "asgi2":
             self.loaded_app = ASGI2Middleware(self.loaded_app)
 
-        if self.debug:
-            self.loaded_app = DebugMiddleware(self.loaded_app)
         if logger.level <= TRACE_LOG_LEVEL:
             self.loaded_app = MessageLoggerMiddleware(self.loaded_app)
         if self.proxy_headers:
@@ -589,4 +584,4 @@ def bind_socket(self) -> socket.socket:
 
     @property
     def should_reload(self) -> bool:
-        return isinstance(self.app, str) and (self.debug or self.reload)
+        return isinstance(self.app, str) and self.reload
diff --git a/uvicorn/main.py b/uvicorn/main.py
--- a/uvicorn/main.py
+++ b/uvicorn/main.py
@@ -79,9 +79,6 @@ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> No
 @click.option(
     "--fd", type=int, default=None, help="Bind to socket from this file descriptor."
 )
-@click.option(
-    "--debug", is_flag=True, default=False, help="Enable debug mode.", hidden=True
-)
 @click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.")
 @click.option(
     "--reload-dir",
@@ -372,7 +369,6 @@ def main(
     ws_per_message_deflate: bool,
     lifespan: LifespanType,
     interface: InterfaceType,
-    debug: bool,
     reload: bool,
     reload_dirs: typing.List[str],
     reload_includes: typing.List[str],
@@ -424,7 +420,6 @@ def main(
         log_level=log_level,
         access_log=access_log,
         interface=interface,
-        debug=debug,
         reload=reload,
         reload_dirs=reload_dirs or None,
         reload_includes=reload_includes or None,
@@ -471,7 +466,6 @@ def run(
     ws_per_message_deflate: bool = True,
     lifespan: LifespanType = "auto",
     interface: InterfaceType = "auto",
-    debug: bool = False,
     reload: bool = False,
     reload_dirs: typing.Optional[typing.Union[typing.List[str], str]] = None,
     reload_includes: typing.Optional[typing.Union[typing.List[str], str]] = None,
@@ -524,7 +518,6 @@ def run(
         ws_per_message_deflate=ws_per_message_deflate,
         lifespan=lifespan,
         interface=interface,
-        debug=debug,
         reload=reload,
         reload_dirs=reload_dirs,
         reload_includes=reload_includes,


Test patch:
diff --git a/tests/middleware/test_debug.py b/tests/middleware/test_debug.py
deleted file mode 100644
--- a/tests/middleware/test_debug.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import httpx
-import pytest
-
-from uvicorn.middleware.debug import DebugMiddleware
-
-
-@pytest.mark.anyio
-async def test_debug_text():
-    async def app(scope, receive, send):
-        raise RuntimeError("Something went wrong")
-
-    app = DebugMiddleware(app)
-    transport = httpx.ASGITransport(
-        app=app,
-        raise_app_exceptions=False,
-    )
-    async with httpx.AsyncClient(
-        transport=transport, base_url="http://testserver"
-    ) as client:
-        response = await client.get("/")
-
-    assert response.status_code == 500
-    assert response.headers["content-type"].startswith("text/plain")
-    assert "RuntimeError" in response.text
-
-
-@pytest.mark.anyio
-async def test_debug_html():
-    async def app(scope, receive, send):
-        raise RuntimeError("Something went wrong")
-
-    app = DebugMiddleware(app)
-    transport = httpx.ASGITransport(
-        app=app,
-        raise_app_exceptions=False,
-    )
-    async with httpx.AsyncClient(
-        transport=transport, base_url="http://testserver"
-    ) as client:
-        response = await client.get("/", headers={"Accept": "text/html, */*"})
-    assert response.status_code == 500
-    assert response.headers["content-type"].startswith("text/html")
-    assert "RuntimeError" in response.text
-
-
-@pytest.mark.anyio
-async def test_debug_after_response_sent():
-    async def app(scope, receive, send):
-        await send({"type": "http.response.start", "status": 204, "headers": []})
-        await send({"type": "http.response.body", "body": b"", "more_body": False})
-        raise RuntimeError("Something went wrong")
-
-    app = DebugMiddleware(app)
-    transport = httpx.ASGITransport(
-        app=app,
-        raise_app_exceptions=False,
-    )
-    async with httpx.AsyncClient(
-        transport=transport, base_url="http://testserver"
-    ) as client:
-        response = await client.get("/")
-    assert response.status_code == 204
-    assert response.content == b""
-
-
-@pytest.mark.anyio
-async def test_debug_not_http():
-    async def app(scope, send, receive):
-        raise RuntimeError("Something went wrong")
-
-    app = DebugMiddleware(app)
-    with pytest.raises(RuntimeError):
-        await app({"type": "websocket"}, None, None)
diff --git a/tests/test_config.py b/tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -14,7 +14,6 @@
 from tests.utils import as_cwd
 from uvicorn._types import Environ, StartResponse
 from uvicorn.config import Config
-from uvicorn.middleware.debug import DebugMiddleware
 from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
 from uvicorn.middleware.wsgi import WSGIMiddleware
 from uvicorn.protocols.http.h11_impl import H11Protocol
@@ -58,14 +57,6 @@ def wsgi_app(environ: Environ, start_response: StartResponse) -> None:
     pass  # pragma: nocover
 
 
-def test_debug_app() -> None:
-    config = Config(app=asgi_app, debug=True, proxy_headers=False)
-    config.load()
-
-    assert config.debug is True
-    assert isinstance(config.loaded_app, DebugMiddleware)
-
-
 @pytest.mark.parametrize(
     "app, expected_should_reload",
     [(asgi_app, False), ("tests.test_config:asgi_app", True)],
@@ -73,13 +64,9 @@ def test_debug_app() -> None:
 def test_config_should_reload_is_set(
     app: "ASGIApplication", expected_should_reload: bool
 ) -> None:
-    config_debug = Config(app=app, debug=True)
-    assert config_debug.debug is True
-    assert config_debug.should_reload is expected_should_reload
-
-    config_reload = Config(app=app, reload=True)
-    assert config_reload.reload is True
-    assert config_reload.should_reload is expected_should_reload
+    config = Config(app=app, reload=True)
+    assert config.reload is True
+    assert config.should_reload is expected_should_reload
 
 
 def test_should_warn_on_invalid_reload_configuration(