CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-hypercorn

A high-performance ASGI and WSGI web server implementation that provides comprehensive support for modern web protocols including HTTP/1, HTTP/2, WebSockets, and experimental HTTP/3

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

utilities.mddocs/

Utilities

Utility functions for application loading, file monitoring, address parsing, header processing, and other common server operations. These functions provide essential infrastructure for Hypercorn's operation and can be used for custom server implementations.

Capabilities

Application Loading and Wrapping

Functions for loading applications from module paths and wrapping them with appropriate adapters.

def load_application(path: str, wsgi_max_body_size: int):
    """
    Load application from module path.
    
    Dynamically imports and loads an ASGI or WSGI application from
    a module path specification. Handles both module and attribute
    loading with proper error handling.
    
    Args:
        path: Module path in format "module:attribute" or "module"
              Examples: "myapp:app", "myapp.wsgi:application"
        wsgi_max_body_size: Maximum body size for WSGI applications
        
    Returns:
        Loaded application object (ASGI or WSGI)
        
    Raises:
        ImportError: If module cannot be imported
        AttributeError: If attribute doesn't exist in module
        NoAppError: If loaded object is not a valid application
    """

def wrap_app(app, wsgi_max_body_size: int, mode: str | None = None):
    """
    Wrap application in appropriate wrapper.
    
    Automatically detects application type (ASGI/WSGI) and wraps
    it with the appropriate wrapper class for use with Hypercorn.
    
    Args:
        app: Application object to wrap
        wsgi_max_body_size: Maximum body size for WSGI applications
        mode: Optional mode specification ("asgi" or "wsgi")
              If None, auto-detects based on application signature
              
    Returns:
        Wrapped application (ASGIWrapper or WSGIWrapper instance)
        
    Raises:
        ValueError: If mode is specified but doesn't match application
        TypeError: If application is neither ASGI nor WSGI compatible
    """

def is_asgi(app) -> bool:
    """
    Check if application is ASGI-compatible.
    
    Examines application signature to determine if it follows
    the ASGI interface specification.
    
    Args:
        app: Application object to check
        
    Returns:
        True if application is ASGI-compatible, False otherwise
        
    Checks for:
        - Callable with 3 parameters (scope, receive, send)
        - Async function or method
        - Proper ASGI signature patterns
    """

File Monitoring

Functions for file watching and change detection, used for development auto-reload functionality.

def files_to_watch() -> dict[Path, float]:
    """
    Get list of files to watch for changes.
    
    Discovers Python modules and other files that should be
    monitored for changes to trigger server reloads during
    development.
    
    Returns:
        Dictionary mapping file paths to modification times
        
    Includes:
        - All imported Python modules
        - Configuration files
        - Template files (if applicable)
        - Static files (if applicable)
    """

def check_for_updates(files: dict[Path, float]) -> bool:
    """
    Check if watched files have been modified.
    
    Examines file modification times to determine if any
    of the watched files have changed since last check.
    
    Args:
        files: Dictionary mapping file paths to last known modification times
        
    Returns:
        True if any files have been modified, False otherwise
        
    Used by auto-reload functionality to trigger server
    restarts when source code changes during development.
    """

Process Management

Functions for process identification and management.

def write_pid_file(pid_path: str):
    """
    Write process PID to file.
    
    Creates a PID file containing the current process ID,
    used for process management and monitoring.
    
    Args:
        pid_path: Path where PID file should be written
        
    Raises:
        IOError: If PID file cannot be written
        PermissionError: If insufficient permissions for file location
        
    The PID file is typically used by process managers,
    monitoring systems, and shutdown scripts.
    """

Network Address Handling

Functions for parsing and formatting network socket addresses.

def parse_socket_addr(family: int, address: tuple):
    """
    Parse socket address based on family.
    
    Parses socket addresses for different address families,
    handling IPv4, IPv6, and Unix socket addresses.
    
    Args:
        family: Socket family (socket.AF_INET, socket.AF_INET6, socket.AF_UNIX)
        address: Address tuple from socket operations
        
    Returns:
        Parsed address information (varies by family)
        
    Address formats:
        - AF_INET: (host, port)
        - AF_INET6: (host, port, flowinfo, scopeid)  
        - AF_UNIX: path string
    """

def repr_socket_addr(family: int, address: tuple) -> str:
    """
    Create string representation of socket address.
    
    Formats socket addresses as human-readable strings
    for logging and display purposes.
    
    Args:
        family: Socket family constant
        address: Address tuple
        
    Returns:
        String representation of address
        
    Examples:
        - IPv4: "192.168.1.100:8000"
        - IPv6: "[::1]:8000"
        - Unix: "/tmp/socket"
    """

HTTP Processing

Functions for HTTP header and request processing.

def build_and_validate_headers(headers) -> list[tuple[bytes, bytes]]:
    """
    Build and validate HTTP headers.
    
    Processes and validates HTTP headers, ensuring proper
    formatting and compliance with HTTP specifications.
    
    Args:
        headers: Headers in various formats (list, dict, etc.)
        
    Returns:
        List of (name, value) byte tuples
        
    Raises:
        ValueError: If headers are malformed
        TypeError: If headers are wrong type
        
    Performs validation:
        - Header name format and characters
        - Header value format and encoding
        - Prohibited headers (e.g., connection-specific)
    """

def filter_pseudo_headers(headers: list[tuple[bytes, bytes]]) -> list[tuple[bytes, bytes]]:
    """
    Filter out HTTP/2 pseudo-headers.
    
    Removes HTTP/2 pseudo-headers (starting with ':') from
    header list, typically when converting from HTTP/2 to HTTP/1.
    
    Args:
        headers: List of (name, value) header tuples
        
    Returns:
        Filtered headers list without pseudo-headers
        
    HTTP/2 pseudo-headers include:
        - :method, :path, :scheme, :authority (request)
        - :status (response)
    """

def suppress_body(method: str, status_code: int) -> bool:
    """
    Check if response body should be suppressed.
    
    Determines whether an HTTP response should include a body
    based on the request method and response status code.
    
    Args:
        method: HTTP request method (GET, HEAD, POST, etc.)
        status_code: HTTP response status code
        
    Returns:
        True if body should be suppressed, False otherwise
        
    Bodies are suppressed for:
        - HEAD requests (always)
        - 1xx informational responses
        - 204 No Content responses
        - 304 Not Modified responses
    """

Server Validation

Functions for validating server configuration and requests.

def valid_server_name(config: Config, request) -> bool:
    """
    Validate server name against configuration.
    
    Checks if the server name from request matches the
    configured valid server names in the server configuration.
    
    Args:
        config: Server configuration object
        request: HTTP request object containing Host header
        
    Returns:
        True if server name is valid, False otherwise
        
    Validation includes:
        - Exact hostname matches
        - Wildcard pattern matching
        - Port number handling
        - Default server name fallback
    """

Async Utilities

Async utility functions for server operation and shutdown handling.

async def raise_shutdown(shutdown_event: Callable[..., Awaitable]) -> None:
    """
    Raise shutdown signal by awaiting the shutdown event.
    
    Waits for the shutdown event to complete and then raises
    ShutdownError to trigger server shutdown sequence.
    
    Args:
        shutdown_event: Async callable that when awaited triggers shutdown
        
    Raises:
        ShutdownError: Always raised after shutdown event completes
    """

async def check_multiprocess_shutdown_event(
    shutdown_event: EventType, 
    sleep: Callable[[float], Awaitable[Any]]
) -> None:
    """
    Check for multiprocess shutdown events.
    
    Periodically checks for shutdown events in multiprocess
    environments, using the provided sleep function for timing.
    
    Args:
        shutdown_event: Multiprocess event object to check
        sleep: Async sleep function for periodic checking
        
    Used in multiprocess worker implementations to detect
    when the master process signals shutdown.
    """

Exception Classes

Utility-related exceptions for error handling.

class NoAppError(Exception):
    """
    Raised when application cannot be loaded.
    
    This exception occurs when the application loading
    process fails due to import errors, missing attributes,
    or invalid application objects.
    """

class ShutdownError(Exception):
    """
    Raised during server shutdown process.
    
    This exception indicates errors that occur during
    the server shutdown sequence, such as timeout issues
    or resource cleanup failures.
    """

class LifespanTimeoutError(Exception):
    """
    Raised when ASGI lifespan events timeout.
    
    This exception occurs when ASGI application lifespan
    startup or shutdown events take longer than the
    configured timeout period.
    """
    
    def __init__(self, stage: str) -> None:
        """
        Initialize lifespan timeout error.
        
        Args:
            stage: Lifespan stage that timed out ("startup" or "shutdown")
        """

class LifespanFailureError(Exception):
    """
    Raised when ASGI lifespan events fail.
    
    This exception indicates that an ASGI application's
    lifespan startup or shutdown event completed with
    an error or unexpected state.
    """
    
    def __init__(self, stage: str, message: str) -> None:
        """
        Initialize lifespan failure error.
        
        Args:
            stage: Lifespan stage that failed ("startup" or "shutdown")
            message: Error message describing the failure
        """

class UnexpectedMessageError(Exception):
    """
    Raised for unexpected ASGI messages.
    
    This exception occurs when an ASGI application sends
    messages that don't conform to the expected protocol
    sequence or message format.
    """
    
    def __init__(self, state: Enum, message_type: str) -> None:
        """
        Initialize unexpected message error.
        
        Args:
            state: Current protocol state when error occurred
            message_type: Type of unexpected message received
        """

class FrameTooLargeError(Exception):
    """
    Raised when protocol frame size exceeds limits.
    
    This exception indicates that a protocol frame (HTTP/2,
    WebSocket, etc.) exceeds the configured maximum size
    limits, potentially indicating a malicious request.
    """

Usage Examples

Application Loading

from hypercorn.utils import load_application, wrap_app

# Load application from module path
try:
    app = load_application("myproject.wsgi:application", wsgi_max_body_size=16*1024*1024)
    print(f"Loaded application: {app}")
except Exception as e:
    print(f"Failed to load application: {e}")

# Wrap application automatically
wrapped_app = wrap_app(app, wsgi_max_body_size=16*1024*1024)
print(f"Wrapped application: {wrapped_app}")

# Check application type
from hypercorn.utils import is_asgi
if is_asgi(app):
    print("Application is ASGI-compatible")
else:
    print("Application is WSGI-compatible")

File Monitoring for Development

import time
from hypercorn.utils import files_to_watch, check_for_updates

# Get files to monitor
watch_files = files_to_watch()
print(f"Monitoring {len(watch_files)} files for changes")

# Check for changes periodically
while True:
    if check_for_updates(watch_files):
        print("Files changed - restarting server")
        # Trigger server restart
        break
    time.sleep(1)

Process Management

import os
from hypercorn.utils import write_pid_file

# Write PID file for process management
try:
    write_pid_file("/var/run/hypercorn.pid")
    print(f"PID {os.getpid()} written to file")
except Exception as e:
    print(f"Failed to write PID file: {e}")

Address Parsing

import socket
from hypercorn.utils import parse_socket_addr, repr_socket_addr

# Parse different address types
ipv4_addr = ("192.168.1.100", 8000)
ipv6_addr = ("::1", 8000, 0, 0)

parsed_ipv4 = parse_socket_addr(socket.AF_INET, ipv4_addr)
parsed_ipv6 = parse_socket_addr(socket.AF_INET6, ipv6_addr)

# Create string representations
ipv4_str = repr_socket_addr(socket.AF_INET, ipv4_addr)
ipv6_str = repr_socket_addr(socket.AF_INET6, ipv6_addr)

print(f"IPv4: {ipv4_str}")  # "192.168.1.100:8000"
print(f"IPv6: {ipv6_str}")  # "[::1]:8000"

Header Processing

from hypercorn.utils import build_and_validate_headers, filter_pseudo_headers

# Build headers from various formats
headers_dict = {"content-type": "text/html", "server": "hypercorn"}
headers_list = [("content-type", "text/html"), ("server", "hypercorn")]

validated_headers = build_and_validate_headers(headers_dict)
print(f"Validated headers: {validated_headers}")

# Filter HTTP/2 pseudo-headers
http2_headers = [
    (b":method", b"GET"),
    (b":path", b"/test"), 
    (b"user-agent", b"test-client"),
    (b":authority", b"example.com")
]

filtered = filter_pseudo_headers(http2_headers)
print(f"Filtered headers: {filtered}")  # Only user-agent remains

Response Body Suppression

from hypercorn.utils import suppress_body

# Check if body should be suppressed
cases = [
    ("HEAD", 200),    # HEAD requests - suppress
    ("GET", 204),     # No Content - suppress  
    ("GET", 304),     # Not Modified - suppress
    ("GET", 200),     # Normal GET - don't suppress
    ("POST", 201),    # Normal POST - don't suppress
]

for method, status in cases:
    should_suppress = suppress_body(method, status)
    print(f"{method} {status}: {'suppress' if should_suppress else 'include'} body")

Server Name Validation

from hypercorn.config import Config
from hypercorn.utils import valid_server_name

# Configure server names
config = Config()
config.server_names = ["example.com", "*.example.com", "api.service.local"]

# Mock request object (simplified)
class MockRequest:
    def __init__(self, host):
        self.headers = {"host": host}

# Validate different server names
test_hosts = ["example.com", "api.example.com", "invalid.com", "api.service.local"]

for host in test_hosts:
    request = MockRequest(host)
    is_valid = valid_server_name(config, request)
    print(f"{host}: {'valid' if is_valid else 'invalid'}")

Install with Tessl CLI

npx tessl i tessl/pypi-hypercorn

docs

application-wrappers.md

async-integration.md

configuration.md

events.md

index.md

logging.md

middleware.md

server-execution.md

types.md

utilities.md

tile.json