Prev Next

Python / Uvicorn Fundamentals Interview Questions

1. What is Uvicorn and what problem does it solve for Python web development? 2. What is ASGI and how does it differ from WSGI? 3. How do you install Uvicorn and what is the difference between the minimal and standard installations? 4. How do you run a basic Uvicorn server from the command line? 5. What are Uvicorn's default host, port, and log level settings? 6. What is uvloop and how does it improve Uvicorn's performance? 7. What are httptools and h11, and when would you choose each? 8. How does Uvicorn's --reload flag work and what does it require? 9. How do you run Uvicorn programmatically using uvicorn.run() and uvicorn.Config/Server? 10. What are Uvicorn workers and how do you configure multi-process deployments? 11. How do you deploy Uvicorn with Gunicorn for production? 12. How do you configure SSL/TLS (HTTPS) in Uvicorn? 13. What is the ASGI lifespan protocol and how does Uvicorn support it? 14. How do you configure Uvicorn logging and access logs? 15. What are proxy headers in Uvicorn and how do you configure trusted proxies? 16. How does Uvicorn handle WebSocket connections? 17. What is the --root-path setting in Uvicorn and when do you need it? 18. How do you use Uvicorn with a Unix domain socket instead of a TCP port? 19. What is the --factory flag in Uvicorn and when is it useful? 20. How do you configure Uvicorn with environment variables? 21. What is Uvicorn's --interface flag and what application types does it support? 22. How do you run Uvicorn inside a Docker container? 23. What is the difference between Uvicorn, Hypercorn, and Daphne as ASGI servers? 24. How do you handle graceful shutdown in Uvicorn? 25. What are the key Uvicorn settings for a production-hardened deployment? 26. How do you run Uvicorn with systemd for automatic restarts and log rotation? 27. What is the relationship between Uvicorn and FastAPI? 28. How do you configure Uvicorn's timeout settings? 29. How do you write a minimal ASGI application that works with Uvicorn without any framework? 30. How do you configure Uvicorn's log config file for custom logging formats? 31. What is the --app-dir flag in Uvicorn and when do you need it? 32. How do you test a Uvicorn-served ASGI application without starting the server? 33. What is Granian and how does it compare to Uvicorn as an ASGI server alternative? 34. What is the scope dictionary in the ASGI protocol and what does it contain? 35. What are best practices for deploying Uvicorn in a Kubernetes environment?
Could not find what you were looking for? send us the question and we would be happy to answer your question.

1. What is Uvicorn and what problem does it solve for Python web development?

Uvicorn is a lightning-fast ASGI (Asynchronous Server Gateway Interface) web server for Python. It is the production-grade server that runs modern async Python frameworks like FastAPI and Starlette, bridging the gap between the network and the application code.

Before ASGI, Python web servers used the older WSGI (Web Server Gateway Interface) standard - a synchronous protocol that could not natively support async programming, WebSockets, or long-lived connections. Uvicorn implements ASGI, providing a minimal, high-performance server that unlocks the full power of Python's asyncio ecosystem.

Uvicorn at a glance
PropertyDetail
ProtocolASGI 3.0
HTTP supportHTTP/1.1
WebSocket supportYes - websockets or wsproto
Event loopasyncio (default) or uvloop
HTTP parserh11 (default) or httptools
LicenseBSD
Minimum Python3.10+ (as of uvicorn 0.40.0, December 2025)

Uvicorn is typically used in two ways: directly for development (single process) and as a worker class inside Gunicorn for production multi-process deployments.

What server interface does Uvicorn implement?
What is the minimum Python version required by Uvicorn 0.40.0 (December 2025)?

2. What is ASGI and how does it differ from WSGI?

WSGI (Web Server Gateway Interface) is the original Python standard for web servers and applications. It is synchronous - each request is handled sequentially, blocking while waiting for I/O. This makes it unsuitable for WebSockets, server-sent events, or highly concurrent async applications.

ASGI (Asynchronous Server Gateway Interface) is the modern successor - an async-native interface that supports concurrent connections, WebSockets, HTTP/2, and long-lived connections via Python's asyncio.

WSGI vs ASGI comparison
FeatureWSGIASGI
Execution modelSynchronous (blocking)Asynchronous (non-blocking)
WebSocket supportNoYes
Long-lived connectionsNoYes
Python async/awaitNot nativelyFirst-class support
HTTP/2Not nativelyYes (via compatible servers)
Example serversGunicorn, uWSGIUvicorn, Daphne, Hypercorn
Example frameworksDjango, FlaskFastAPI, Starlette, Django (3.1+)

An ASGI application is an async callable that accepts three arguments: scope (connection metadata), receive (async function to receive messages), and send (async function to send messages). This simple interface enables the server to remain decoupled from the application framework.

Which of the following is a key advantage ASGI has over WSGI?
What are the three arguments an ASGI application callable receives?

3. How do you install Uvicorn and what is the difference between the minimal and standard installations?

Uvicorn is available on PyPI and can be installed with pip. There are two installation variants targeting different use cases.

Installation variants
VariantCommandWhat it includes
Minimal (pure Python)pip install uvicornh11 HTTP parser, asyncio event loop only - no Cython deps
Standard (recommended)pip install uvicorn[standard]Adds uvloop, httptools, websockets, watchfiles for auto-reload
# Minimal install - pure Python, good for restricted environments
pip install uvicorn

# Standard install - Cython-based performance extras
pip install "uvicorn[standard]"

# Pin a specific version for reproducible deployments
pip install "uvicorn[standard]==0.30.1"

# Verify installation
uvicorn --version

# Install in a virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate   # Linux/macOS
.venv\Scripts\activate       # Windows
pip install "uvicorn[standard]"

What the standard extras add:

  • uvloop - a Cython-based replacement for asyncio's event loop, providing a significant throughput boost
  • httptools - a fast Cython-based HTTP parser (replaces the pure-Python h11)
  • websockets - the default WebSocket protocol library
  • watchfiles - enables the --reload file-watching feature for development
What additional library does 'pip install uvicorn[standard]' include that speeds up the event loop?
Which library installed by uvicorn[standard] enables the --reload (auto-reload) feature?

4. How do you run a basic Uvicorn server from the command line?

Uvicorn is started from the command line by specifying the application in module:attribute format. The module is the Python file (without .py), and the attribute is the ASGI application object inside it.

# File: main.py
async def app(scope, receive, send):
    assert scope['type'] == 'http'
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [(b'content-type', b'text/plain')],
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })

# Run from the terminal:
# uvicorn main:app

# Basic run - listens on 127.0.0.1:8000
uvicorn main:app

# Custom host and port
uvicorn main:app --host 0.0.0.0 --port 8080

# Development mode with auto-reload
uvicorn main:app --reload

# FastAPI example
# File: api.py
from fastapi import FastAPI
app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

# Run FastAPI app
uvicorn api:app --reload

# Nested attribute path
# File: mypackage/server.py with app = FastAPI()
uvicorn mypackage.server:app

The application path follows Python import syntax: dots separate packages, the colon separates the module from the attribute. If your ASGI application lives at mypackage/server.py and is named app, you write mypackage.server:app.

What is the correct format for specifying an ASGI application to Uvicorn on the command line?
Which flag enables automatic reloading when source files change in Uvicorn?

5. What are Uvicorn's default host, port, and log level settings?

Uvicorn's defaults are chosen to be safe for local development - binding only to localhost prevents accidental external exposure, while the info log level provides useful startup and access log output without being overwhelming.

Uvicorn defaults
SettingDefault valueNotes
--host127.0.0.1Loopback only - not reachable from other machines
--port8000Standard development port
--log-levelinfoOptions: critical, error, warning, info, debug, trace
--workers1Single process - use WEB_CONCURRENCY env var or --workers for more
--loopautoPrefers uvloop if installed, falls back to asyncio
--httpautoPrefers httptools if installed, falls back to h11
--wsautoPrefers websockets if installed, falls back to wsproto
--lifespanautoEnables lifespan if supported by the application
--proxy-headersenabledTrusts X-Forwarded-* headers from 127.0.0.1
# To make the server externally accessible (development/testing):
uvicorn main:app --host 0.0.0.0

# Production: always specify host explicitly
uvicorn main:app --host 0.0.0.0 --port 8000

# Check what's running:
curl http://127.0.0.1:8000/

# The WEB_CONCURRENCY environment variable sets default worker count
export WEB_CONCURRENCY=4
uvicorn main:app  # will start 4 workers

What is Uvicorn's default host binding?
What is Uvicorn's default port?

6. What is uvloop and how does it improve Uvicorn's performance?

uvloop is a high-performance, drop-in replacement for Python's built-in asyncio event loop. It is implemented in Cython on top of libuv - the same C library used by Node.js - making it significantly faster than the standard asyncio loop for network I/O operations.

asyncio vs uvloop
Aspectasyncio (built-in)uvloop
ImplementationPure Python + CCython / libuv (C)
PerformanceBaseline~2-4x faster on I/O-heavy workloads
InstallationBuilt into Pythonpip install uvloop (included in uvicorn[standard])
Drop-in replacementN/AYes - same API as asyncio
PyPy compatibilityYesNo - requires CPython
# Uvicorn automatically uses uvloop when installed
# Install via standard extras:
pip install "uvicorn[standard]"

# Or force uvloop explicitly:
uvicorn main:app --loop uvloop

# Force asyncio (e.g. for debugging or PyPy):
uvicorn main:app --loop asyncio

# Check which event loop is active (in your app):
import asyncio
print(type(asyncio.get_event_loop()))  # will show uvloop.Loop when active

# Programmatic config:
uvicorn.run("main:app", loop="uvloop")

When NOT to use uvloop: uvloop only works with CPython (not PyPy or alternative runtimes). For PyPy deployments, use uvicorn.workers.UvicornH11Worker instead of the standard worker. Also, some libraries that depend on specific asyncio internals may not be compatible with uvloop.

What underlying C library does uvloop use for its event loop implementation?
In which scenario should you NOT use uvloop with Uvicorn?

7. What are httptools and h11, and when would you choose each?

Uvicorn supports two HTTP parser implementations, selectable via the --http flag. The default auto setting picks httptools if installed (via the standard extras) or falls back to h11.

h11 vs httptools
Featureh11httptools
ImplementationPure Python (h11 library)Cython bindings to llhttp (C parser)
PerformanceModerate~20-30% faster HTTP parsing
PyPy supportYesNo - requires CPython
Standards complianceVery strict - helpful for debuggingProduction-grade
Error messagesMore descriptiveMinimal
InstallAlways available (default)pip install httptools (included in uvicorn[standard])
# Auto - picks httptools if available (recommended for production)
uvicorn main:app --http auto

# Force httptools (fast, production)
uvicorn main:app --http httptools

# Force h11 (pure Python, debugging, PyPy)
uvicorn main:app --http h11

# h11 also has a configurable buffer limit:
# --limit-max-requests sets max request buffer size (h11 only)
uvicorn main:app --http h11 --limit-max-requests 1000

Production recommendation: use httptools for its speed advantage. Use h11 when running on PyPy, in strict compliance testing environments, or when debugging malformed HTTP requests (h11's error messages are more descriptive).

Which HTTP parser implementation is faster and recommended for production Uvicorn deployments?
What is a key advantage of h11 over httptools in Uvicorn?

8. How does Uvicorn's --reload flag work and what does it require?

The --reload flag enables automatic server restart when Python source files change. It is designed for development workflows, saving you from manually restarting the server after every code change.

# Basic auto-reload (restarts when any .py file changes)
uvicorn main:app --reload

# Watch specific directories instead of the entire working directory
uvicorn main:app --reload --reload-dir src/
uvicorn main:app --reload --reload-dir src/ --reload-dir tests/

# Include extra file patterns to watch
uvicorn main:app --reload --reload-include "*.html" --reload-include "*.css"

# Exclude patterns from watching
uvicorn main:app --reload --reload-exclude "*.log"

# IMPORTANT: --reload and --workers are mutually exclusive!
# This will raise an error:
# uvicorn main:app --reload --workers 4  ← WRONG

How it works: Uvicorn uses the watchfiles library (installed by uvicorn[standard]) to monitor the filesystem. When a watched file changes, the server process is restarted. Without watchfiles installed, --reload falls back to a slower polling method.

Key constraint: --reload and --workers are mutually exclusive. Reload mode uses a single process with a reloader wrapper - multiple workers are not compatible. For production, disable reload and use multiple workers instead.

Why are --reload and --workers mutually exclusive in Uvicorn?
Which library does Uvicorn use for file watching when --reload is enabled?

9. How do you run Uvicorn programmatically using uvicorn.run() and uvicorn.Config/Server?

Beyond the CLI, Uvicorn can be configured and launched entirely from Python code. This is useful when you need to customise server startup, integrate it into a larger application, or run it from within an existing async event loop.

import uvicorn

# Method 1: uvicorn.run() - simplest programmatic start
if __name__ == "__main__":
    uvicorn.run(
        "main:app",          # string import path
        host="0.0.0.0",
        port=8000,
        reload=True,         # development only
        log_level="info",
    )

# You can also pass the app object directly (no reload support):
# uvicorn.run(app, host="0.0.0.0", port=8000)

# Method 2: uvicorn.Config + uvicorn.Server - more control
import uvicorn

if __name__ == "__main__":
    config = uvicorn.Config("main:app", port=5000, log_level="info")
    server = uvicorn.Server(config)
    server.run()

# Method 3: Running inside an existing async loop
import asyncio
import uvicorn

async def main():
    config = uvicorn.Config("main:app", port=5000, log_level="info")
    server = uvicorn.Server(config)
    await server.serve()  # use serve() not run() inside async context

if __name__ == "__main__":
    asyncio.run(main())

Important distinction: when passing the app object directly (not a string) to uvicorn.run(), the reload option is not available because the reloader must re-import the module by name. Always use the 'module:attribute' string form when reload=True.

Why must you pass the app as a string ('module:attribute') rather than the object directly when using reload=True with uvicorn.run()?
Which method should you use to start Uvicorn from inside an already-running asyncio event loop?

10. What are Uvicorn workers and how do you configure multi-process deployments?

By default Uvicorn runs as a single process with a single event loop. To utilise multiple CPU cores and handle more concurrent requests, you can run multiple worker processes using the --workers flag.

# Run 4 worker processes
uvicorn main:app --workers 4

# Workers can also be set via environment variable:
export WEB_CONCURRENCY=4
uvicorn main:app

# Common formula for worker count:
# workers = (2 x CPU cores) + 1  for I/O-bound apps
# workers = CPU cores             for CPU-bound apps
import multiprocessing
workers = (2 * multiprocessing.cpu_count()) + 1

# IMPORTANT: --workers and --reload are mutually exclusive

How multi-worker mode works: Uvicorn spawns the specified number of separate OS processes, each running its own event loop and handling requests independently. There is no shared state between workers - each process is completely independent. This is true parallelism (bypasses the GIL) unlike threading.

Single worker vs multiple workers
AspectSingle worker (default)Multiple workers
CPU utilisationOne coreMultiple cores (true parallelism)
Shared stateN/ANot shared - each worker is isolated
Process managementSimpleUse Gunicorn or systemd for production
Auto-reloadSupportedNot compatible with --reload
MemoryLowerEach worker has its own memory space

Production recommendation: for production multi-worker deployments, use Gunicorn with UvicornWorker rather than Uvicorn's built-in --workers. Gunicorn provides superior process lifecycle management, graceful restarts, and health monitoring.

What happens to shared in-memory state (like a global dictionary) when Uvicorn runs with multiple workers?
What formula is commonly recommended for choosing the number of Uvicorn workers for an I/O-bound application?

11. How do you deploy Uvicorn with Gunicorn for production?

For production deployments, the recommended pattern is to use Gunicorn as the process manager with Uvicorn as the worker class. Gunicorn provides mature process management (graceful restarts, signal handling, worker health monitoring) while Uvicorn provides the ASGI event loop inside each worker.

# Install the uvicorn-worker package (the uvicorn.workers module is deprecated)
pip install uvicorn-worker

# Run with Gunicorn + UvicornWorker
gunicorn main:app -w 4 -k uvicorn_worker.UvicornWorker

# Full production command:
gunicorn main:app \
    --workers 4 \
    --worker-class uvicorn_worker.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120 \
    --keep-alive 5 \
    --access-logfile - \
    --error-logfile -

# PyPy-compatible configuration:
gunicorn main:app -w 4 -k uvicorn_worker.UvicornH11Worker

# Using a gunicorn.conf.py file
# gunicorn --config gunicorn.conf.py main:app

# gunicorn.conf.py
import multiprocessing

bind = "0.0.0.0:8000"
workers = (2 * multiprocessing.cpu_count()) + 1
worker_class = "uvicorn_worker.UvicornWorker"
worker_connections = 1000
max_requests = 10000
max_requests_jitter = 1000
timeout = 120
graceful_timeout = 30
keepalive = 5

Note on the deprecated module: the uvicorn.workers module is deprecated and will be removed in a future release. The replacement is the standalone uvicorn-worker package (pip install uvicorn-worker). Update your -k flag from uvicorn.workers.UvicornWorker to uvicorn_worker.UvicornWorker.

What is the recommended production deployment pattern for Uvicorn?
The uvicorn.workers module is deprecated. What should you install instead for Gunicorn + Uvicorn deployments?

12. How do you configure SSL/TLS (HTTPS) in Uvicorn?

Uvicorn supports HTTPS natively by accepting a certificate and private key. This is useful for local development with HTTPS or for simple single-server deployments where a reverse proxy is not involved.

# Run with SSL (requires certificate and private key)
uvicorn main:app \
    --ssl-keyfile ./key.pem \
    --ssl-certfile ./cert.pem \
    --host 0.0.0.0 \
    --port 443

# With a CA bundle (for mutual TLS / client certificates)
uvicorn main:app \
    --ssl-keyfile ./key.pem \
    --ssl-certfile ./cert.pem \
    --ssl-ca-certs ./ca-bundle.pem

# Programmatic SSL config
import ssl
import uvicorn

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        ssl_keyfile="./key.pem",
        ssl_certfile="./cert.pem",
        host="0.0.0.0",
        port=443,
    )

# Local development HTTPS with mkcert:
# 1. Install mkcert: brew install mkcert
# 2. mkcert -install
# 3. mkcert localhost 127.0.0.1
# 4. uvicorn main:app --ssl-keyfile localhost-key.pem --ssl-certfile localhost.pem

Production approach: in most production architectures, SSL termination is handled by a reverse proxy (Nginx, Caddy, or a cloud load balancer) rather than by Uvicorn directly. The reverse proxy decrypts HTTPS traffic and forwards plain HTTP to Uvicorn. This pattern improves performance and simplifies certificate management.

What two files are required to run Uvicorn with HTTPS?
For local HTTPS development, which tool is recommended for generating valid self-signed certificates?

13. What is the ASGI lifespan protocol and how does Uvicorn support it?

The ASGI lifespan protocol provides startup and shutdown events for an ASGI application, allowing it to initialise resources (database connections, caches, background tasks) before serving requests and clean them up on shutdown.

# FastAPI lifespan example (modern approach)
from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: runs before the server begins accepting requests
    print("Starting up...")
    app.state.db = await connect_to_database()
    yield  # server is running here
    # Shutdown: runs after the server stops accepting requests
    print("Shutting down...")
    await app.state.db.close()

app = FastAPI(lifespan=lifespan)

# Pure ASGI lifespan implementation
async def app(scope, receive, send):
    if scope["type"] == "lifespan":
        while True:
            message = await receive()
            if message["type"] == "lifespan.startup":
                # do startup work
                await send({"type": "lifespan.startup.complete"})
            elif message["type"] == "lifespan.shutdown":
                # do cleanup
                await send({"type": "lifespan.shutdown.complete"})
                return
    # ... HTTP handling

Lifespan modes
--lifespan valueBehaviour
auto (default)Enable lifespan if the application supports it; ignore if not
onAlways enable lifespan - fails at startup if the app does not support it
offNever run lifespan events - useful for testing or apps that don't need it

The lifespan protocol is especially important for managing async resources like database connection pools (asyncpg, motor), which must be created inside an async context and properly closed to avoid connection leaks.

What does the ASGI lifespan protocol allow an application to do?
What does setting --lifespan auto (the default) do in Uvicorn?

14. How do you configure Uvicorn logging and access logs?

Uvicorn uses Python's standard logging module with configurable log levels and optional access logging. Log configuration can be provided via CLI flags, environment variables, or a config file.

# Log level options: critical, error, warning, info (default), debug, trace
uvicorn main:app --log-level debug

# Disable access log (reduces CPU by ~10% under high load)
uvicorn main:app --no-access-log

# Disable colour in logs (useful for log aggregators)
uvicorn main:app --no-use-colors

# Use a custom logging config file (JSON, YAML, or INI)
uvicorn main:app --log-config logging.yaml

# Programmatic log config:
uvicorn.run("main:app", log_level="warning", access_log=False)

# Production hardened logging:
uvicorn main:app \
    --log-level warning \
    --no-access-log \
    --no-use-colors

# logging.yaml - custom Uvicorn log config
version: 1
formatters:
  default:
    format: "%(asctime)s %(levelname)s %(message)s"
    use_colors: false
  access:
    format: '%(asctime)s %(client_addr)s - "%(request_line)s" %(status_code)s'
handlers:
  default:
    class: logging.StreamHandler
    formatter: default
  access:
    class: logging.StreamHandler
    formatter: access
loggers:
  uvicorn:
    handlers: [default]
    level: INFO
  uvicorn.access:
    handlers: [access]
    level: INFO
    propagate: false

Performance tip: disabling access logs (--no-access-log) and setting --log-level warning can reduce CPU usage by up to 10-15% under high concurrency, since log formatting and I/O for every request is eliminated. In production, access logging is often handled at the reverse proxy layer (Nginx/Caddy) instead.

What Uvicorn flag disables per-request access logging to reduce CPU overhead?
What log level should you use in production to balance visibility with performance?

15. What are proxy headers in Uvicorn and how do you configure trusted proxies?

When Uvicorn runs behind a reverse proxy (Nginx, Caddy, a load balancer), the proxy forwards requests on behalf of the original client. Without proxy header support, Uvicorn would see the proxy's IP as the client address and the internal scheme (http) rather than the original (https). The --proxy-headers flag tells Uvicorn to trust X-Forwarded-For and X-Forwarded-Proto headers from trusted proxies.

# Enable proxy headers (enabled by default, trusts only 127.0.0.1)
uvicorn main:app --proxy-headers

# Trust a specific proxy IP
uvicorn main:app --proxy-headers --forwarded-allow-ips 10.0.0.1

# Trust multiple proxy IPs or a subnet
uvicorn main:app \
    --proxy-headers \
    --forwarded-allow-ips "10.0.0.0/8,172.16.0.0/12"

# Trust ALL proxies (use ONLY on trusted internal networks!)
uvicorn main:app --proxy-headers --forwarded-allow-ips "*"

# Disable proxy headers (if no reverse proxy in front)
uvicorn main:app --no-proxy-headers

# Set via environment variable:
export FORWARDED_ALLOW_IPS="10.0.0.1,10.0.0.2"
uvicorn main:app --proxy-headers

Security warning: setting --forwarded-allow-ips "*" trusts ALL connecting IPs to set forwarded headers. This means a malicious client could spoof their IP address by sending a fake X-Forwarded-For header. Only trust IPs of known, controlled proxy servers.

Proxy header configuration
SettingWhen to use
--forwarded-allow-ips 127.0.0.1 (default)Reverse proxy on the same machine
--forwarded-allow-ips Known proxy IP (e.g. internal load balancer)
--forwarded-allow-ips "*"Trust everything - only on fully controlled internal networks
--no-proxy-headersUvicorn exposed directly (no reverse proxy)
What is the security risk of setting --forwarded-allow-ips '*' in Uvicorn?
Which environment variable can be used to configure trusted proxy IPs for Uvicorn?

16. How does Uvicorn handle WebSocket connections?

Uvicorn has native WebSocket support, making it suitable for real-time applications like chat, live dashboards, and collaborative tools. WebSocket connections are handled via the websockets or wsproto library.

# Pure ASGI WebSocket application
async def app(scope, receive, send):
    if scope["type"] == "websocket":
        # Accept the WebSocket connection
        await send({"type": "websocket.accept"})
        while True:
            event = await receive()
            if event["type"] == "websocket.receive":
                data = event.get("text") or event.get("bytes")
                # Echo the message back
                await send({"type": "websocket.send", "text": data})
            elif event["type"] == "websocket.disconnect":
                break

# FastAPI WebSocket example
from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Echo: {data}")

WebSocket configuration options
FlagDefaultDescription
--wsautoWebSocket implementation: auto, websockets, websockets-sansio, wsproto
--ws-max-size16777216 (16MB)Maximum WebSocket message size in bytes
--ws-max-queue32Maximum WebSocket message queue length
--ws-ping-interval20.0sInterval between server ping frames
--ws-ping-timeout20.0sTimeout waiting for pong response
--ws-per-message-deflateTrueEnable per-message compression

Note: the WSGI interface (--interface wsgi) always disables WebSocket support because WSGI does not define a WebSocket protocol. Use the ASGI interface for WebSocket-capable applications.

What Uvicorn flag sets the maximum WebSocket message size in bytes?
Why does using --interface wsgi in Uvicorn disable WebSocket support?

17. What is the --root-path setting in Uvicorn and when do you need it?

The --root-path setting tells Uvicorn (and consequently your ASGI application) that it is mounted at a specific URL path prefix. This is needed when your application is served at a sub-path behind a reverse proxy - for example, your app is at https://example.com/api/v1/ rather than https://example.com/.

# Without root-path: app thinks it lives at /
# Nginx config: location /api/v1/ { proxy_pass http://127.0.0.1:8000; }
uvicorn main:app  # scope["root_path"] == ""

# With root-path: app knows it is mounted at /api/v1
uvicorn main:app --root-path /api/v1
# scope["root_path"] == "/api/v1"

# FastAPI uses root_path for generating correct OpenAPI docs URLs
# Without it, /docs links will be wrong when behind a proxy
from fastapi import FastAPI
app = FastAPI(root_path="/api/v1")  # set in the app OR in Uvicorn CLI

# Programmatic:
uvicorn.run("main:app", root_path="/api/v1")

Why it matters in FastAPI: FastAPI uses the ASGI root_path to generate the correct URLs in its OpenAPI (Swagger) documentation. Without setting it, the generated API docs will have incorrect base URLs when the app is served from a sub-path, causing all Try it out requests in Swagger UI to fail.

When do you need to set --root-path in Uvicorn?
What is the effect of an incorrect or missing --root-path setting when using FastAPI behind a reverse proxy at /api/v1?

18. How do you use Uvicorn with a Unix domain socket instead of a TCP port?

Instead of binding to a TCP host:port, Uvicorn can listen on a Unix domain socket (UDS) - a special file on the filesystem used for inter-process communication. This is faster than TCP loopback for communication between processes on the same machine.

# Bind to a Unix domain socket
uvicorn main:app --uds /tmp/uvicorn.sock

# Programmatic:
uvicorn.run("main:app", uds="/tmp/uvicorn.sock")

# Nginx config to proxy to the Unix socket:
# upstream app {
#     server unix:/tmp/uvicorn.sock;
# }
# server {
#     location / {
#         proxy_pass http://app;
#     }
# }

# Bind to a file descriptor (for socket activation, e.g. systemd):
uvicorn main:app --fd 3
# systemd passes the pre-bound socket as fd 3

Binding options in Uvicorn
OptionFlagUse case
TCP host:port--host + --portDefault; works everywhere
Unix domain socket--uds /path/to/file.sockSame-machine reverse proxy; slightly faster than TCP loopback
File descriptor--fd Systemd socket activation; container orchestrators

Performance: Unix domain sockets bypass the TCP/IP stack entirely for same-machine communication, reducing latency. The improvement is measurable but modest - typically a few percent. The main practical benefit is avoiding port number management when running multiple services on the same host.

What is the main advantage of using a Unix domain socket (--uds) over a TCP port in Uvicorn?
Which Uvicorn flag is used for systemd socket activation?

19. What is the --factory flag in Uvicorn and when is it useful?

The --factory flag tells Uvicorn to treat the specified application path as a factory function - a callable that takes no arguments and returns an ASGI application - rather than as the application itself.

# Without --factory: APP is the application object directly
uvicorn main:app

# With --factory: create_app() is called to produce the ASGI app
uvicorn main:create_app --factory

# Example factory function:
# main.py
def create_app():
    from fastapi import FastAPI
    app = FastAPI()

    @app.get("/")
    def root():
        return {"status": "ok"}

    return app

# The factory is called with no arguments and must return the ASGI app

# Programmatic equivalent:
uvicorn.run("main:create_app", factory=True, port=8000)

# Why use a factory?
# - Deferred initialisation (resources created only when workers start)
# - Different config per environment (testing vs production)
# - Integration with dependency injection frameworks
# - Worker-level initialisation (each Gunicorn worker calls the factory separately)

Key use case - Gunicorn + factory: when using Gunicorn with multiple workers, each worker process calls the factory independently. This is important for resources that must be created per-process (like database connection pools) - a factory guarantees each worker initialises its own pool rather than sharing one created before forking.

What is an ASGI application factory in the context of the Uvicorn --factory flag?
Why is the factory pattern particularly useful with Gunicorn multi-worker deployments?

20. How do you configure Uvicorn with environment variables?

Most Uvicorn CLI flags can be set via UVICORN_* prefixed environment variables, making it easy to configure in containerised environments and CI/CD pipelines without changing command-line arguments.

# Environment variable equivalents of CLI flags
export UVICORN_HOST=0.0.0.0
export UVICORN_PORT=8000
export UVICORN_WORKERS=4
export UVICORN_LOG_LEVEL=warning
export UVICORN_LOOP=uvloop
export UVICORN_HTTP=httptools
export UVICORN_RELOAD=false
export UVICORN_NO_ACCESS_LOG=true
export UVICORN_PROXY_HEADERS=true
export UVICORN_FORWARDED_ALLOW_IPS="10.0.0.1"

# Then run with minimal flags:
uvicorn main:app

# Partial override: env vars + CLI flags (CLI takes precedence)
export UVICORN_PORT=8000
uvicorn main:app --port 9000  # uses 9000 (CLI overrides env var)

# WEB_CONCURRENCY is also respected for workers:
export WEB_CONCURRENCY=4
uvicorn main:app

# NOTE: UVICORN_* env vars cannot be set inside --env-file
# --env-file is for configuring your APPLICATION, not Uvicorn itself

Important distinction - --env-file: the --env-file flag loads environment variables into the application process, but UVICORN_* prefixed settings in that file are ignored. The --env-file is intended for your application's configuration (e.g. DATABASE_URL), not for Uvicorn's own settings.

What naming convention do Uvicorn environment variables follow?
What is the purpose of Uvicorn's --env-file flag?

21. What is Uvicorn's --interface flag and what application types does it support?

The --interface flag tells Uvicorn which application protocol to expect. The default auto setting detects the interface automatically, but you can force a specific mode.

--interface options
ValueInterfaceWebSocket supportNotes
auto (default)Auto-detect ASGI3, ASGI2, or WSGIIf ASGIRecommended for most cases
asgi3ASGI 3.0 (current standard)YesExplicit ASGI3
asgi2ASGI 2.0 (legacy double-callable)YesLegacy - rarely needed
wsgiWSGI (synchronous)NoAlways disables WebSocket support
# Auto-detect (default - recommended)
uvicorn main:app --interface auto

# Explicit ASGI3 (FastAPI, Starlette, modern frameworks)
uvicorn main:app --interface asgi3

# WSGI mode (for Django without channels, Flask)
uvicorn main:app --interface wsgi
# WARNING: WSGI mode disables WebSocket support entirely

# Programmatic:
uvicorn.run("main:app", interface="asgi3")

WSGI mode deprecation note: Uvicorn's native WSGI implementation is deprecated. For running WSGI applications through Uvicorn, you should use the a2wsgi adapter (pip install a2wsgi) which wraps your WSGI app in an ASGI-compatible interface. This approach keeps the full ASGI pipeline while supporting legacy WSGI apps.

What happens to WebSocket support when you set --interface wsgi in Uvicorn?
What is the recommended way to run a legacy WSGI app through Uvicorn as an alternative to the deprecated WSGI mode?

22. How do you run Uvicorn inside a Docker container?

Uvicorn is widely used in containerised deployments. The key considerations are binding to the right host, setting the correct number of workers, and choosing between running Uvicorn directly or via Gunicorn.

# Dockerfile - single-container Uvicorn
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Expose the port
EXPOSE 8000

# Run Uvicorn - bind 0.0.0.0 so Docker can forward traffic
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000",
     "--workers", "4", "--loop", "uvloop", "--http", "httptools",
     "--log-level", "info", "--no-access-log"]

# requirements.txt:
# uvicorn[standard]
# fastapi

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - UVICORN_HOST=0.0.0.0
      - UVICORN_PORT=8000
      - UVICORN_WORKERS=4
      - UVICORN_LOG_LEVEL=info
      - WEB_CONCURRENCY=4
    command: uvicorn main:app

# Health check - uvicorn has no built-in health endpoint
# Add one to your application:
# @app.get("/health")
# def health(): return {"status": "ok"}

Key Docker-specific setting: always use --host 0.0.0.0 inside Docker containers. The default 127.0.0.1 binds to the container's loopback interface only, which means Docker's port forwarding cannot reach it - the service will be unreachable from outside the container.

Why must you use --host 0.0.0.0 when running Uvicorn inside a Docker container?
What happens if you leave Uvicorn's default --host 127.0.0.1 inside a Docker container?

23. What is the difference between Uvicorn, Hypercorn, and Daphne as ASGI servers?

Several ASGI server implementations exist. Uvicorn is the most widely used, but Hypercorn and Daphne are also production-ready alternatives with different strengths.

ASGI server comparison
FeatureUvicornHypercornDaphne
HTTP/1.1YesYesYes
HTTP/2NoYesYes
HTTP/3NoYesNo
WebSocketsYesYesYes
Python asyncasyncio / uvloopasyncio / uvloop / trioasyncio
Process managementVia GunicornBuilt-inVia Gunicorn
OriginEncode (Tom Christie)Pallets projectDjango/Channels team
Best forFastAPI, Starlette, general ASGIHTTP/2 needs, Quart appsDjango Channels

When to choose each:

  • Uvicorn - the default choice for FastAPI and Starlette; best performance for HTTP/1.1 + WebSocket workloads; most documentation and community support
  • Hypercorn - choose when you need HTTP/2 or HTTP/3 support natively (without a proxy layer), or if your app uses the Quart framework (Hypercorn was originally Quart's server)
  • Daphne - the original ASGI server; choose for Django Channels applications where it has the best compatibility
Which ASGI server natively supports HTTP/3 in addition to HTTP/1.1 and HTTP/2?
For a new FastAPI application, which ASGI server is the recommended default choice?

24. How do you handle graceful shutdown in Uvicorn?

Uvicorn handles OS signals to shut down gracefully - completing in-flight requests before stopping. Understanding this is important for zero-downtime deployments and container orchestration.

# Uvicorn responds to these OS signals:
# SIGINT  (Ctrl+C)       - graceful shutdown
# SIGTERM (kill default) - graceful shutdown (Kubernetes uses this)
# SIGQUIT                - graceful shutdown with core dump
# SIGKILL (kill -9)      - immediate termination (not catchable)

# Kubernetes sends SIGTERM before SIGKILL:
# Pod termination sequence:
# 1. SIGTERM sent to container
# 2. Uvicorn begins graceful shutdown
# 3. Completes in-flight requests
# 4. SIGKILL sent after terminationGracePeriodSeconds (default 30s)

# Programmatic graceful shutdown:
import asyncio
import signal
import uvicorn

async def main():
    config = uvicorn.Config("main:app", port=8000)
    server = uvicorn.Server(config)

    loop = asyncio.get_running_loop()
    loop.add_signal_handler(
        signal.SIGTERM,
        lambda: asyncio.create_task(server.shutdown())
    )

    await server.serve()

asyncio.run(main())

Lifespan shutdown: during graceful shutdown, Uvicorn sends the lifespan.shutdown event to the application before terminating. This is where you should close database connections, flush caches, and clean up resources. Ensure your lifespan shutdown handler completes quickly - a slow shutdown can cause Kubernetes to send SIGKILL prematurely.

Which OS signal does Kubernetes send to a Uvicorn container to initiate graceful shutdown?
What event does Uvicorn send to the ASGI application during graceful shutdown?

25. What are the key Uvicorn settings for a production-hardened deployment?

Development defaults are convenient but inappropriate for production. A hardened production configuration disables debug features, removes identifying headers, tunes performance, and restricts network exposure.

# Production-hardened Uvicorn command
uvicorn main:app \
    --host 0.0.0.0 \
    --port 8000 \
    --workers 4 \
    --loop uvloop \
    --http httptools \
    --log-level warning \
    --no-access-log \
    --no-server-header \
    --no-date-header \
    --proxy-headers \
    --forwarded-allow-ips "10.0.0.0/8"

# Or using environment variables (recommended for containers):
export UVICORN_HOST=0.0.0.0
export UVICORN_PORT=8000
export UVICORN_WORKERS=4
export UVICORN_LOOP=uvloop
export UVICORN_HTTP=httptools
export UVICORN_LOG_LEVEL=warning
export UVICORN_NO_ACCESS_LOG=true
export UVICORN_NO_SERVER_HEADER=true
export UVICORN_PROXY_HEADERS=true
export UVICORN_FORWARDED_ALLOW_IPS="10.0.0.0/8"
uvicorn main:app

Production settings rationale
SettingReason
--no-server-headerHides 'uvicorn' from Server response header - reduces attack surface
--no-date-headerSlightly reduces response size; date often added by reverse proxy
--log-level warningEliminates per-request log I/O - reduces CPU by ~10% under load
--no-access-logSame reason; access logging often better done at proxy layer
--no-reloadNever use reload in production - it single-processes and slows startup
--loop uvloop2-4x event loop throughput improvement
--http httptoolsFaster HTTP parsing than h11

For maximum reliability in production, pair with Gunicorn (pip install uvicorn-worker) for process supervision, and a reverse proxy (Nginx, Caddy) for SSL termination and rate limiting.

What does --no-server-header do in a production Uvicorn deployment?
Why should --reload never be used in a production Uvicorn deployment?

26. How do you run Uvicorn with systemd for automatic restarts and log rotation?

On Linux servers without Docker, systemd is the standard way to run Uvicorn as a managed service - providing automatic restarts on failure, boot-time startup, and journal-based logging.

# /etc/systemd/system/myapp.service
[Unit]
Description=My FastAPI Application
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
Environment="PATH=/var/www/myapp/.venv/bin"
EnvironmentFile=/var/www/myapp/.env
ExecStart=/var/www/myapp/.venv/bin/uvicorn main:app \
    --host 127.0.0.1 \
    --port 8000 \
    --workers 4 \
    --loop uvloop \
    --log-level warning
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

# Check status and logs
sudo systemctl status myapp
journalctl -u myapp -f      # follow live logs
journalctl -u myapp --since "1 hour ago"

# Reload after config change
sudo systemctl restart myapp

# Graceful restart (sends SIGTERM then restarts)
sudo systemctl reload-or-restart myapp

# Socket activation (advanced - systemd creates the socket)
# myapp.socket + myapp.service - Uvicorn started only when first connection arrives
uvicorn main:app --fd 0  # fd 0 passed by systemd socket activation

Bind Uvicorn to 127.0.0.1 (loopback) in systemd deployments and use Nginx or Caddy as the front-facing reverse proxy. This keeps Uvicorn off the public network and lets the proxy handle SSL, rate limiting, and static files.

In a systemd service file for Uvicorn, what does 'Restart=always' accomplish?
Which host should Uvicorn bind to when running behind Nginx on the same Linux machine?

27. What is the relationship between Uvicorn and FastAPI?

FastAPI and Uvicorn are complementary tools at different layers of the stack. FastAPI is the ASGI application framework; Uvicorn is the ASGI server that runs it. FastAPI generates the HTTP routing, request validation, and response serialisation; Uvicorn handles the network I/O, event loop, and connection lifecycle.

FastAPI vs Uvicorn responsibilities
ResponsibilityFastAPIUvicorn
HTTP routingYesNo
Request validation (Pydantic)YesNo
Response serialisationYesNo
WebSocket routingYesNo
Network I/O (sockets)NoYes
Event loop managementNoYes
Process managementNoPartial (--workers)
SSL terminationNoYes (or reverse proxy)
# main.py - FastAPI application
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

# Uvicorn runs the FastAPI app:
# uvicorn main:app --reload

# FastAPI is itself an ASGI-compliant application:
# It implements: async def __call__(self, scope, receive, send)
# Uvicorn calls this callable for every incoming request

# Development:
# uvicorn main:app --reload --host 127.0.0.1 --port 8000

# Production:
# gunicorn main:app -w 4 -k uvicorn_worker.UvicornWorker

Installation: pip install fastapi uvicorn[standard] gives you both. FastAPI's documentation recommends Uvicorn as the server, and Uvicorn's documentation uses FastAPI in examples - they are tightly coupled in the ecosystem though architecturally independent (any ASGI framework can run on Uvicorn).

What is the correct architectural relationship between FastAPI and Uvicorn?
Which Python interface does FastAPI implement to be compatible with Uvicorn?

28. How do you configure Uvicorn's timeout settings?

Uvicorn provides several timeout parameters that control how long the server waits for client activity and worker health before taking action.

Uvicorn timeout settings
SettingDefaultPurpose
--timeout-keep-alive5sSeconds to wait for a new request on a keep-alive connection before closing
--timeout-notify30sSeconds to notify worker processes during graceful shutdown before sending SIGKILL
--timeout-graceful-shutdownNone (no limit)Maximum seconds for the server to wait for ongoing requests to complete during shutdown
--timeout-worker-healthcheck5sSeconds to wait for a worker to respond to a health check
# Configure keep-alive timeout (how long idle connections stay open)
uvicorn main:app --timeout-keep-alive 10

# Graceful shutdown timeout (force-kill after N seconds if requests don't finish)
uvicorn main:app --timeout-graceful-shutdown 30

# Worker health check timeout
uvicorn main:app --timeout-worker-healthcheck 10

# Programmatic:
uvicorn.run(
    "main:app",
    timeout_keep_alive=10,
    timeout_graceful_shutdown=30,
)

# Gunicorn has its own timeout settings that overlay Uvicorn's:
# gunicorn --timeout 120  (worker timeout for handling a request)
# gunicorn --graceful-timeout 30  (graceful shutdown window)

Keep-alive tuning: the default 5-second keep-alive timeout is appropriate for most use cases. For high-throughput APIs with many short-lived requests, a lower value (2-3s) reduces idle connections. For APIs with slower clients or large payloads, a higher value reduces connection setup overhead.

What does the --timeout-keep-alive setting control in Uvicorn?
What happens when --timeout-graceful-shutdown is reached during Uvicorn shutdown?

29. How do you write a minimal ASGI application that works with Uvicorn without any framework?

Since Uvicorn implements ASGI, any Python callable that follows the ASGI specification can be run with it - no framework required. Understanding the raw ASGI interface deepens your understanding of how frameworks like FastAPI and Starlette work under the hood.

# main.py - minimal HTTP ASGI app
async def app(scope, receive, send):
    assert scope["type"] == "http"

    # Read the request body
    body = b""
    more_body = True
    while more_body:
        message = await receive()
        body += message.get("body", b"")
        more_body = message.get("more_body", False)

    # Send the HTTP response
    response_body = b"Hello, world!"
    await send({
        "type": "http.response.start",
        "status": 200,
        "headers": [
            (b"content-type", b"text/plain"),
            (b"content-length", str(len(response_body)).encode()),
        ],
    })
    await send({
        "type": "http.response.body",
        "body": response_body,
    })

# Run: uvicorn main:app

# Streaming response - send body in multiple chunks
async def streaming_app(scope, receive, send):
    assert scope["type"] == "http"
    await send({
        "type": "http.response.start",
        "status": 200,
        "headers": [(b"content-type", b"text/plain")],
    })
    for chunk in [b"Hello", b", ", b"world!"]:
        await send({
            "type": "http.response.body",
            "body": chunk,
            "more_body": True,  # indicates more chunks are coming
        })
    await send({"type": "http.response.body", "body": b"", "more_body": False})

The ASGI interface separates response start (status code + headers) from response body. This two-message design enables streaming - you can start sending the response before the full body is ready, which is essential for real-time and large-file use cases.

In the raw ASGI protocol, how many 'send' calls are required to complete a basic HTTP response?
What does setting 'more_body': True in an http.response.body ASGI message indicate?

30. How do you configure Uvicorn's log config file for custom logging formats?

Uvicorn's --log-config flag accepts a logging configuration in JSON or YAML format (using Python's dictConfig) or in INI format (fileConfig). This enables structured logging, custom formats, and routing logs to different handlers.

# logging.json - custom log config in dictConfig format
{
  "version": 1,
  "disable_existing_loggers": false,
  "formatters": {
    "default": {
      "()": "uvicorn.logging.DefaultFormatter",
      "fmt": "%(levelprefix)s %(asctime)s %(message)s",
      "use_colors": false
    },
    "access": {
      "()": "uvicorn.logging.AccessFormatter",
      "fmt": '%(levelprefix)s %(asctime)s %(client_addr)s - "%(request_line)s" %(status_code)s',
      "use_colors": false
    }
  },
  "handlers": {
    "default": {
      "formatter": "default",
      "class": "logging.StreamHandler",
      "stream": "ext://sys.stderr"
    },
    "access": {
      "formatter": "access",
      "class": "logging.StreamHandler",
      "stream": "ext://sys.stdout"
    }
  },
  "loggers": {
    "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": false},
    "uvicorn.error": {"level": "INFO"},
    "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": false}
  }
}

# Use the custom log config:
uvicorn main:app --log-config logging.json

# Or YAML format (requires PyYAML - included in uvicorn[standard]):
uvicorn main:app --log-config logging.yaml

# Programmatically:
import logging.config, json
with open("logging.json") as f:
    log_config = json.load(f)
uvicorn.run("main:app", log_config=log_config)

To override colour settings in the log config, set formatters.default.use_colors and formatters.access.use_colors to true or false explicitly. This takes precedence over auto-detection.

What Python logging configuration method does Uvicorn's --log-config JSON/YAML file use?
Which Python package must be installed for Uvicorn to accept a YAML logging config file?

31. What is the --app-dir flag in Uvicorn and when do you need it?

The --app-dir flag adds a specific directory to sys.path before Uvicorn tries to import the application. This is useful when your application module is not in the current working directory or the standard Python path.

# Project structure:
# /project/
#   src/
#     myapp/
#       main.py    ← app lives here

# Without --app-dir: run from /project/src/
cd /project/src
uvicorn myapp.main:app

# With --app-dir: run from /project/
cd /project
uvicorn myapp.main:app --app-dir src
# Equivalent to: PYTHONPATH=/project/src uvicorn myapp.main:app

# Docker example - app in /app/src
CMD ["uvicorn", "myapp.main:app", "--app-dir", "src",
     "--host", "0.0.0.0", "--port", "8000"]

# Programmatic:
uvicorn.run("myapp.main:app", app_dir="src")

# Multiple app directories: use PYTHONPATH env var instead
export PYTHONPATH=/project/src:/project/lib
uvicorn myapp.main:app

Common use case: projects using a src/ layout (where application code lives in src/mypackage/ rather than the project root) need either --app-dir src or a proper package installation (pip install -e .). The --app-dir approach is simpler for containerised deployments where installation may be skipped.

What does the --app-dir flag do in Uvicorn?
For a project with a 'src/' layout where the app is in src/myapp/main.py, which command runs it from the project root?

32. How do you test a Uvicorn-served ASGI application without starting the server?

For unit and integration testing, you don't want to actually start a server and make HTTP requests. The httpx library's ASGITransport (or Starlette's TestClient) allows you to test ASGI applications in-process - no socket, no port, no server startup required.

# Using httpx.AsyncClient with ASGITransport (pure async test)
import pytest
import httpx
from main import app

@pytest.mark.anyio
async def test_homepage():
    async with httpx.AsyncClient(
        transport=httpx.ASGITransport(app=app),
        base_url="http://test"
    ) as client:
        response = await client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

# Using Starlette / FastAPI TestClient (sync interface, uses anyio)
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

# Testing lifespan events with TestClient
def test_with_lifespan():
    with TestClient(app) as client:
        # lifespan startup has run at this point
        response = client.get("/")
        assert response.status_code == 200
    # lifespan shutdown has run after the with block

Why this works: ASGI applications are just async callables - they can be called directly in tests without a server. The transport layer (httpx or Starlette's TestClient) converts Python test calls into ASGI scope/receive/send messages, running the application synchronously in the test process.

What is the main advantage of using httpx.ASGITransport or Starlette's TestClient for ASGI application testing?
When using Starlette's TestClient as a context manager (with TestClient(app) as client:), when do lifespan startup events run?

33. What is Granian and how does it compare to Uvicorn as an ASGI server alternative?

Granian is a newer ASGI-compatible HTTP server written in Rust using Tokio and hyper. It is designed as a high-performance alternative to Uvicorn, with native HTTP/2, TLS, and WebSocket support baked in.

Uvicorn vs Granian
FeatureUvicornGranian
LanguagePython (Cython extensions)Rust
HTTP/1.1YesYes
HTTP/2NoYes (built-in)
TLS/SSLYesYes (native)
WebSocketsYesYes
Process managementVia GunicornBuilt-in (--workers flag)
Python version3.10+3.8+
MaturityVery matureNewer - actively developed
EcosystemHuge - FastAPI, Starlette, etc.ASGI-compatible with same frameworks
# Install Granian
pip install granian

# Run the same FastAPI/Starlette/ASGI app:
granian --interface asgi main:app

# With HTTP/2 + TLS:
granian --interface asgi main:app \
    --host 0.0.0.0 \
    --port 443 \
    --ssl-certificate cert.pem \
    --ssl-keyfile key.pem \
    --http2

# Workers:
granian --interface asgi main:app --workers 4

When to consider Granian: if you need native HTTP/2 without a reverse proxy, or want to explore Rust-speed performance for your ASGI app. For most production use cases, Uvicorn (with Gunicorn) remains the default choice due to its maturity, documentation, and ecosystem support. Granian is noted in Uvicorn's own documentation as a Rust-based ASGI alternative.

What key HTTP feature does Granian support natively that Uvicorn does not?
In which language is Granian implemented, giving it its performance characteristics?

34. What is the scope dictionary in the ASGI protocol and what does it contain?

The scope dictionary is the first argument passed to an ASGI application callable. It contains metadata about the incoming connection - its type (HTTP, WebSocket, or lifespan) and all relevant connection details.

# Inspecting the scope in an ASGI app
async def app(scope, receive, send):
    print(scope["type"])      # "http", "websocket", or "lifespan"

    if scope["type"] == "http":
        print(scope["method"])          # "GET", "POST", etc.
        print(scope["path"])            # "/api/users"
        print(scope["query_string"])    # b"page=1&limit=10"
        print(scope["headers"])         # list of (name, value) byte tuples
        print(scope["client"])          # ("127.0.0.1", 54321) - IP and port
        print(scope["server"])          # ("127.0.0.1", 8000)
        print(scope["scheme"])          # "http" or "https"
        print(scope["root_path"])       # e.g. "/api/v1" if --root-path is set
        print(scope["http_version"])    # "1.1"

    elif scope["type"] == "websocket":
        print(scope["path"])            # WebSocket path
        print(scope["headers"])         # Upgrade headers
        print(scope["subprotocols"])    # Requested sub-protocols

    elif scope["type"] == "lifespan":
        pass  # handle startup and shutdown events

Key HTTP scope fields
FieldTypeExample value
typestr"http"
methodstr"GET"
pathstr'/api/users/42'
query_stringbytesb'page=1&limit=10'
headerslist of tuples[(b'content-type', b'application/json')]
clienttuple or None('127.0.0.1', 54321)
schemestr"https"
root_pathstr'/api/v1'
What are the three possible values of scope['type'] in the ASGI protocol?
In an ASGI scope dictionary, what format are HTTP headers stored in?

35. What are best practices for deploying Uvicorn in a Kubernetes environment?

Kubernetes brings its own deployment patterns that intersect with Uvicorn's configuration. Getting the combination right ensures zero-downtime deployments, proper health checking, and correct signal handling.

Kubernetes + Uvicorn checklist
ConcernRecommendation
Host bindingAlways --host 0.0.0.0 so Kubernetes can route traffic to pods
WorkersSet workers based on pod CPU limits - usually 1-2 per vCPU for I/O-bound apps
Health checksImplement /health and /ready endpoints; Uvicorn has no built-in health endpoint
Graceful shutdownSIGTERM triggers Uvicorn graceful shutdown; set terminationGracePeriodSeconds > timeout-graceful-shutdown
Preemptive SIGKILLIf Uvicorn doesn't finish within terminationGracePeriodSeconds, Kubernetes force-kills
Environment configAll Uvicorn settings via UVICORN_* env vars in ConfigMap/Secret
Liveness vs readinessLiveness: is the server running? Readiness: is it ready to serve traffic?
# Kubernetes Deployment snippet
spec:
  containers:
    - name: api
      image: myapp:latest
      command: ["uvicorn", "main:app",
               "--host", "0.0.0.0",
               "--port", "8000",
               "--workers", "2",
               "--timeout-graceful-shutdown", "25"]
      ports:
        - containerPort: 8000
      env:
        - name: UVICORN_LOG_LEVEL
          value: "warning"
      livenessProbe:
        httpGet:
          path: /health
          port: 8000
        initialDelaySeconds: 5
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /ready
          port: 8000
        initialDelaySeconds: 2
        periodSeconds: 5
      lifecycle:
        preStop:
          exec:
            command: ["sleep", "5"]  # allow load balancer to de-register

Pre-stop hook: adding a preStop sleep of 5 seconds gives Kubernetes time to remove the pod from the service endpoint list before SIGTERM is sent. This prevents new requests from being routed to a pod that is already shutting down.

Why is the Kubernetes preStop sleep hook recommended for Uvicorn pods?
What should the Kubernetes terminationGracePeriodSeconds be set relative to Uvicorn's --timeout-graceful-shutdown?
«
»

Comments & Discussions