fastware v0.1.0 /Server API Reference
On this page

API reference for fastware server: Granian ASGI lifecycle management with foreground and background serving, PID files, port checks, and hot reload.

#Server API Reference

The server module manages the Granian ASGI server lifecycle: PID file management, port availability checks, single-instance enforcement, background and foreground serving, hot reload, and graceful shutdown.

Server symbols are lazily imported from the top-level fastware package to avoid the ~60ms cost of importing Granian when only the routing/response layer is needed.

#src.fastware.server

Granian ASGI server lifecycle management with PID file tracking, port availability checks, foreground and background serve modes, and graceful stop.

#PortInUseError

Raised when the requested port is already in use.

#AlreadyRunningError

Raised when a single-instance server is already running.

#_write_pid

python
def _write_pid(pid_path: Path) -> None

Write the current PID to disk, become process group leader, and register cleanup handlers.

Becoming a process group leader (via os.setpgid(0, 0)) lets the 'stop' subcommand signal our entire process group with os.killpg, so granian worker subprocesses die with us instead of being orphaned.

#_remove_pid

python
def _remove_pid(pid_path: Path) -> None

Remove the PID file if it exists.

#_signal_handler

python
def _signal_handler(signum: int, _frame: object, pid_path: Path) -> None

Handle SIGTERM/SIGINT: forward to our process group, clean up, exit.

#check_already_running

python
def check_already_running(pid_path: Path, name: str='server') -> int | None

Check if another instance is running. Returns the PID if running, None otherwise.

Stale PID files (process dead) are cleaned up automatically.

#ensure_port_available

python
def ensure_port_available(host: str, port: int, name: str='server') -> int

Ensure port is available. If occupied by a previous instance, stop it.

Probes the port. If something responds to GET /health with {"status":"ok"}, it's likely our own stale server -- kill it via the OS. Otherwise, exit with a diagnostic error.

Returns port on success.

#_kill_port_holder

python
def _kill_port_holder(host: str, port: int) -> None

Kill the process holding a port.

#_resolve_target

python
def _resolve_target(target: str | Callable) -> str

Convert a target to a Granian-compatible string.

If target is already a string (e.g. "myapp:app"), return as-is. If target is a callable, register it on a synthetic module so Granian can import it via its string-based loader.

#_find_free_port

python
def _find_free_port(host: str='127.0.0.1') -> int

Find an ephemeral port that is currently free on host.

#_port_file_path

python
def _port_file_path(pid_path: Path) -> Path

Derive the port file path from a PID file path.

E.g. .pixelweaver.pid -> .pixelweaver.port.

#_write_port_file

python
def _write_port_file(pid_path: Path, port: int) -> None

Write the bound port alongside the PID file.

#_remove_port_file

python
def _remove_port_file(port_path: Path) -> None

Remove a port file if it exists.

#read_port_file

python
def read_port_file(pid_path: Path) -> int | None

Read the port stored alongside a PID file. Returns None if missing or unreadable.

#_resolve_host_port

python
def _resolve_host_port(host: str | None, port: int | None, name: str) -> tuple[str, int]

Resolve host and port from explicit args or env vars.

Explicit args override env vars. If neither is provided, raise ValueError.

#_make_server

python
def _make_server(target: str, host: str, port: int) -> Granian

Create a Granian instance bound to host on port.

target is an ASGI module path, e.g. "myapp:app".

#_run_server

python
def _run_server(target: str, host: str, port: int) -> None

Create and run a Granian server. Used as the reload subprocess target.

#_serve_subprocess

python
def _serve_subprocess(target: str, host: str, port: int, pid_path_str: str, name: str) -> None

Entry point for the server subprocess. Runs granian in foreground mode.

#serve_background

python
def serve_background(target: str | Callable, *, host: str, port: int, pid_path: Path, name: str='FASTWARE') -> str

Start the server as an independent background process. Returns the URL.

Unlike serve(foreground=False) which uses a daemon thread (dies with the parent), this spawns a fully detached subprocess that survives the parent exiting. The subprocess writes its own PID and port files.

Parameters


target: ASGI application -- either a module path string (e.g. "myapp:app") or a callable ASGI application object. host: Bind address. port: Bind port. Pass 0 to pick a random free port. pid_path: Path for the PID file. The subprocess writes this, not the caller. name: Application name for log messages.

Returns


str The URL the server is listening on (e.g. "http://127.0.0.1:8000").

Raises


RuntimeError If the server process exits prematurely or fails to start within 10s.

#serve

python
def serve(target: str | Callable, *, foreground: bool, host: str | None=None, port: int | None=None, pid_path: Path | None=None, name: str='FASTWARE', pre_serve: Callable[[], None] | None=None, reload: bool=False, single_instance: bool=True) -> str | None

Start Granian serving the ASGI app.

Parameters


target: ASGI application -- either a module path string (e.g. "myapp:app") or a callable ASGI application object. foreground: Required. When True, blocks the calling thread. When False, spawns a daemon thread and returns the URL string. host: Bind address. Falls back to {NAME}_HOST env var. ValueError if neither. port: Bind port. Falls back to {NAME}_PORT env var. ValueError if neither. pid_path: If given, enables PID file management (detect existing instances, write PID, register cleanup). name: Application name for env var prefix (uppercased) and log messages. Default "FASTWARE". pre_serve: Optional callable invoked synchronously after PID/port checks but before Granian starts. reload: When True, watches .py files in the current working directory and restarts the server on changes. Requires foreground=True. single_instance: When True (default), checks for an existing running instance via the PID file and exits with an error if one is found. When False, skips the PID check (but still writes the PID file if pid_path is given).

Returns


str | None When foreground=False, returns the URL string (e.g. "http://127.0.0.1:8000"). When foreground=True, returns None (blocks until server stops).

#ServerStatus

Result of a status check on a server process.

#_cleanup_pid_and_port

python
def _cleanup_pid_and_port(pid_path: Path) -> None

Remove both the PID file and its companion port file.

#stop

python
def stop(pid_path: Path) -> None

Stop a server by reading its PID file.

Sends SIGTERM, waits up to 10s polling with os.kill(pid, 0), then escalates to SIGKILL. Cleans up the PID file and port file.

Raises FileNotFoundError if PID file does not exist. If the process is already gone (stale PID file), removes the PID file and returns normally.

#status

python
def status(pid_path: Path, health_url: str | None=None) -> ServerStatus

Check the status of a server process.

Parameters


pid_path: Path to the PID file. health_url: Optional URL to probe for health (e.g. "http://127.0.0.1:8000/health"). If provided and the process is running, an HTTP GET is attempted with a short timeout.

Returns


ServerStatus Dataclass with running, pid, and healthy fields.

#ServerStatus

The ServerStatus enum represents the 3 possible states of a fastware server instance: running, stopped, or unknown. It is returned by status() after checking the PID file and probing the process.

ServerStatus
FieldTypeDefaultDescription
runningbool
pidintNone
healthyboolNone

#Error Types

#PortInUseError

Raised by ensure_port_available when the requested port is already in use and cannot be reclaimed. The error message includes the host and port, and suggests using a different port or stopping the process holding it.

Before raising, ensure_port_available attempts to detect whether the port holder is a stale instance of the same server (by probing GET /health). If it is, the stale process is killed automatically and the port is reclaimed without error.

#AlreadyRunningError

Raised by serve (when single_instance=True) if a PID file exists and the corresponding process is still alive. The error message includes the PID of the running instance. This prevents accidentally starting duplicate servers on the same port, which would cause bind failures or silent request splitting.

Stale PID files (where the process has died) are cleaned up automatically and do not trigger this error.

#serve() vs serve_background()

serve() is the primary entry point for starting the Granian ASGI server. It supports both foreground (blocking) and background (daemon thread) modes, with optional PID file management, single-instance enforcement, and hot reload for development workflows:

python
from fastware.server import serve

# Foreground mode -- blocks until the server stops
serve(
    "myapp:app",
    foreground=True,
    host="127.0.0.1",
    port=8000,
    pid_path=Path(".myapp.pid"),
)

# Background mode -- returns the URL, server runs in a daemon thread
url = serve(
    "myapp:app",
    foreground=False,
    host="127.0.0.1",
    port=8000,
)
# url == "http://127.0.0.1:8000"

serve_background() spawns a fully detached subprocess that survives the parent process exiting. Use this when you need the server to outlive the caller (e.g., starting a server from a CLI command):

python
from fastware.server import serve_background

# Detached subprocess -- survives parent exit
url = serve_background(
    "myapp:app",
    host="127.0.0.1",
    port=8000,
    pid_path=Path(".myapp.pid"),
)
serve() vs serve_background()
Featureserve(foreground=False)serve_background()
Process lifetimeDies with parent (daemon thread)Survives parent exit (subprocess)
Use caseDesktop apps, test harnessesCLI start/stop commands
ReturnsURL stringURL string
PID fileOptionalRequired

#Hot Reload

Pass reload=True to serve() for development. This uses watchfiles to monitor all .py files in the project directory and automatically restart the Granian server on changes, providing sub-second feedback during development. Requires foreground=True:

python
serve(
    "myapp:app",
    foreground=True,
    host="127.0.0.1",
    port=8000,
    reload=True,
)