On this page
#Changelog
#Unreleased
#Fixes
- **
stop()no longer reports a killed server as running.** After escalating to SIGKILL it now waits for the kill to actually land, sostatus(),list_instances()andcheck_already_running()called right after a stop tell the truth instead of seeing the corpse's PID.
#0.6.0
Graceful background-server shutdown, two new documentation guides, and a declared strictcli floor.
Context
The release's headline fix is in the server lifecycle. A server started with serve_background() is a direct child of the caller, so on exit it stayed a zombie in the caller's process table -- and every liveness probe in the module is kill(pid, 0), which succeeds for zombies. stop() therefore never terminated anything gracefully: it polled a server that was already gone for its whole 10-second grace window and then escalated to SIGKILL, every single time. status(), list_instances() and check_already_running() lied in the same way. The probes now reap their own exited children first.
This visit also adopts the stricttest test-isolation floor. The loopback stance is "allow" rather than an exact allowlist because this suite boots real servers on ("127.0.0.1", 0) -- the kernel picks the port at bind time, so there is no host:port pair for an allowlist to name at configuration time. Off-machine egress stays denied. Four tests whose spawned child re-imports its target off the inherited repo cwd carry @pytest.mark.repo_cwd.
#Features
- Two new documentation guides. An observability guide (request IDs, timing middleware, the error log and the audit log) and a tasks-and-features guide (background task lifecycle and feature flags), plus live extras and middleware tables that are generated from the source rather than hand-maintained.
#Fixes
- **The
fastware devCLI works again on current strictcli.** It was pinned three releases behind and hard-errored at registration under 0.36.0; all three commands now declare their effect classification and use the framework's ctx-first handler signature. - **
fastwarenow declaresstrictcli>=0.36.0.** The CLI's effect classification requires it, so an environment resolving an older strictcli used to hard-error at command registration instead of being fixed by the resolver. - **
stop()now shuts a background server down gracefully instead of always killing it.** A server started withserve_background()became a zombie in the caller's process table on exit, and every liveness probe (kill -0) reported it as still running -- sostop()waited out its full 10-second grace window and escalated to SIGKILL every time,status()reported a dead server as running,list_instances()kept returning it, andcheck_already_running()refused to start a replacement.
#0.5.0
Streaming client-disconnect detection, the /__fastware/ runtime namespace (version endpoint, cache headers, blessed service worker, live update channel), an instance registry with presence markers, and the file-driven fastware dev CLI.
Context
This release rounds out fastware's runtime and dev-tooling story.
Streaming handlers previously leaked resources (DB sessions, queues) when a client walked away mid-stream: their finally cleanup never ran. A single receive-owning watcher now cancels abandoned stream generators so cleanup runs, and request.is_disconnected() is reliable in both streaming and non-streaming handlers.
Apps now expose a reserved /__fastware/ runtime namespace: a version endpoint returning an opaque content-hashed build id, immutable cache headers for hashed assets (no-cache for index/SPA fallback), an explicit sw_mode (cache|reset|off) blessed service worker with foreign-worker detection and a mandatory cache -> reset -> off retirement route, and a live update channel (SSE at /__fastware/events plus a dependency-free client that performs one state-preserving reload when the build id changes).
An instance registry lets peers enumerate running servers via list_instances() with automatic stale-entry pruning, plus a lightweight presence-marker API (write_marker/list_markers/remove_marker) for cross-process signals such as window refcounting and focus requests.
Finally, fastware dev run|status|stop drives a file-driven [tool.fastware.dev] Vite + backend development environment with pre-spawn gates, health-gated aux services, topology ordering, process-group supervision, and an optional daemon mode.
#Features
- Version endpoint and static-asset caching. Every app now serves
/__fastware/versionreturning an opaquebuild_id(a content hash of the static assets, stable across mtime changes) plus the app name when configured. Static serving also gained cache headers: hashed Vite-style assets (name-<hash>.ext) getCache-Control: public, max-age=31536000, immutable, while unhashed assets,index.html, and the SPA fallback getno-cache. - Instance registry. Server instances now register a JSON descriptor (PID, port, name) so peers can enumerate running instances via
list_instances(), withregister_instance()/deregister_instance()for lifecycle and automatic stale-entry pruning on read. - Blessed service worker. Apps that serve a frontend now choose an explicit
sw_mode(cache,reset, oroff).cacheserves a generated worker at/__fastware/sw.js(per-build cache name, network-first shell, cache-first hashed assets, never intercepting API/SSE/WebSocket/reserved paths) plus a/__fastware/sw-register.jssnippet with foreign-worker detection;resetserves a self-destruct worker at both the reserved path and legacy registration paths (legacy_sw_paths) to free clients with a stale worker;offdisables it.sw_modeis mandatory with a frontend and forbidden for API-only apps. - Live update channel. Every app now exposes an SSE update channel at
/__fastware/events(each connection primed with the current build id) and a dependency-free/__fastware/client.jsthat reloads the page once when the build id changes, preserving form fields and app-registered state across the reload. - fastware dev CLI. New
fastware dev run|status|stopcommands drive a file-driven[tool.fastware.dev]Vite + backend development environment: pre-spawn gates, health-gated aux services, topology ordering (backend-first/vite-first), process-group supervision with graceful-then-kill teardown, and an optional--daemonmode registered in the instance registry. - Presence-marker registry API. New
write_marker/list_markers/remove_markerhelpers on the instance registry for lightweight per-file cross-process signals (e.g. window refcounting, focus requests) that live beside instance descriptors.
#Fixes
- Streaming disconnect detection. Streaming handlers now detect client disconnects: a single receive-owning watcher cancels abandoned stream generators so their
finallycleanup runs, fixing resource leaks (DB sessions, queues) on streams the client walked away from.request.is_disconnected()is reliable in streaming and non-streaming handlers, and DI-provided resources stay alive for the whole stream body. - Service worker cache-retirement guidance. The
sw_modevalidation error and docs now spell out the mandatorycache -> reset -> offmigration route, warning that switching a cache worker straight tooffstrands clients (a 404'd worker script never unregisters). - dev SW-mode guard notice.
fastware dev runnow prints a one-line stderr notice when it skips the service-worker cache guard for a cmd-form backend (which cannot be introspected forsw_mode), instead of skipping silently. - Granian embed stability. The embedded Granian server is pinned to a compatible API range (
>=2.7,<3.0) to guard against upstream breaking changes, and no longer emits Granian's experimental-API warning at startup.
#0.4.0
Embedded async server for background mode with graceful shutdown
Context
The old foreground=False path ran Granian.serve() in a daemon thread, which had fork-in-thread, sys.exit-in-thread, and no-stop-mechanism bugs. The new implementation uses granian.server.embed.Server, running async workers without forking or signal registration.
#Features
- New feature. Background serve (foreground=False) now uses embedded async server, fixing hang/stop/fork issues. New stop_background(url) function cleanly shuts down background servers.
#0.3.1
Fix CI so the 0.3.1 publish gate can pass: 0.3.0 was blocked because test dependencies (pyjwt, bcrypt, structlog, watchfiles, websockets, mcp, pydantic) were missing from the dev group and CI's uv sync --locked never installed them.
#Infrastructure
- Fix CI so the 0.3.1 publish gate can pass: 0.3.0 was blocked because test dependencies (pyjwt, bcrypt, structlog, watchfiles, websockets, mcp, pydantic) were missing from the dev group and CI's uv sync --locked never installed them.
#0.3.0
Server, testing, and serialization hardening: pinned event loop with loop/workers config on serve(), test-client exception re-raise plus pytest-collection fix, msgspec-native json_as, and a broad sweep of ASGI correctness, auth, middleware, SSE, WebSocket, routing, DI, and static-file fixes.
Context
Granian's default loop auto-selection silently switches the event loop when rloop/uvloop are installed, which is a behavior-change hazard for subprocess-based workloads. serve()/serve_background() now pin asyncio by default and expose explicit loop/workers configuration so the runtime is predictable and opt-in.
This release also finalizes a large hardening pass across the framework (auth, CORS/TrustedHost/Vite middleware, SSE and WebSocket handling, lifespan, dependency injection, static-file traversal, request body limits, and HTTP method semantics) plus internal refactoring that extracted shared scope/header/file-writer helpers. Several defaults changed in security-hardening ways (query-param JWT is now opt-in; the internal ParsedSegment type left the public API), which land as a minor bump under 0.x semver.
#Breaking
check_already_running()no longer accepts the unusednameparameter- Breaking.
fastware.mcpno longer ships built-inROLES/DEFAULT_ROLE;register_tools_for_roleandcreate_mcp_servernow require an explicitrolesmapping, theSA_ROLEenv fallback is gone, and themcppackage is imported lazily (fasterimport fastware.mcp) - **Pinned event loop on
serve()/serve_background().** Newloopparameter ("asyncio"default, or"uvloop"/"rloop") andworkersparameter. The effective default changes from Granian'sauto(which silently switches loops when rloop/uvloop are installed) to a pinnedasyncio, keeping subprocess-based workloads predictable. - **
raise_server_exceptionson the test clients.**AsyncTestClient/TestClientnow re-raise unhandled handler exceptions into the test by default (matching Starlette). Passraise_server_exceptions=Falseto restore the previous behaviour where exceptions become a 500 response. - Query-param JWT acceptance is now opt-in via
allow_query_token(default off); tokens supplied in the query string are rejected unless explicitly enabled. - Breaking. The internal
ParsedSegmenttype is no longer exported from the top-levelfastwarepackage; import it fromfastware.routingif you still need it.
#Features
- **
request.json_asnow decodesmsgspec.Structbodies natively.** When the target type is amsgspec.Structsubclass,json_asdecodes via msgspec (the framework's fast path); other types still use Pydantic. Decode failures raiseHTTPError(422)in both branches. - Dev/Vite proxy improvements:
ViteDevProxystreams HTTP responses (honoringbackend_prefixes) instead of buffering,/wsWebSocket upgrades route to the backend in dev mode via the newly exposedbackend_prefixesoption, and proxy WebSocket failures close with code 1011 instead of being swallowed. - HTTP method handling: unmatched methods return
405with anAllowheader,HEADis served through the matchingGEThandler with an empty body, andallowed_methodsis exposed for 405 responses. - Request bodies are capped at a configurable
max_body_size(returning413when exceeded), and body chunks accumulate without O(n^2) concatenation. - ASGI lifespan support: startup/shutdown events forward to mounted sub-apps with merged lifespan state,
lifespan.startup.failed/lifespan.shutdown.failedare emitted on errors, and the test clients run the full lifespan protocol. - Performance: query strings are parsed once and cached, handler accepted-parameter sets are precomputed at app creation (no per-request
inspect.signature), and single-header lookups use a linear scan instead of building a full header dict. - Error-log SQLite writes are offloaded to a background worker thread, so recording errors no longer blocks the event loop.
#Fixes
serve_backgroundreadiness no longer requires a/healthroute: any HTTP response (including 404) counts as server-ready- **Callable targets now work with
serve_backgroundandserve(reload=True).** Module-level callables are materialized as importable shim modules for the spawned server process; non-importable callables (locals, lambdas,__main__) raise a clear error immediately instead of crashing the child ensure_port_availableno longer kills a port holder based on its/healthresponse body; it only stops a process whose PID matches the server's own PID filestop()now signals the server's whole process group (when the server leads it), so granian workers terminate with the main process- A foreground
serve()after a backgroundserve()in the same process no longer silently loses SIGTERM/SIGINT handling - Single-instance
serve()creates the PID file atomically (O_CREAT|O_EXCL), so two concurrent starts can no longer both pass the already-running check fastware.typesdeclares__all__again --from fastware.types import *no longer leaksAny/Awaitable/Callable/annotations- **No more PytestCollectionWarning from
TestClient.** Both test client classes set__test__ = False, so importingTestClientinto a test module no longer emits a pytest collection warning. - SSE hardening: subscriber queues register on iteration start (no leak), multi-line event data is emitted as repeated
data:lines, and event data is serialized via msgspec. - WebSocket handlers close with the correct codes (1008/1011) on dependency failures, and
accept()handles a client disconnect before the handshake instead of sending a spurious accept. - Auth hardening:
verify_tokencatches onlyjwt.InvalidTokenError,hash_passwordrejects passwords over the bcrypt 72-byte limit, user-store writes are atomic with locked read-modify-write, andrate_limitevicts stale buckets while preserving the handler signature. - CORS middleware rejects a wildcard origin combined with credentials, adds
Vary: Origin, and gates preflight handling onAccess-Control-Request-Method. TrustedHostvalidates the Host header on WebSocket scopes, rejecting disallowed hosts with close code 1008.- Response and disconnect handling: never emit a second
http.response.startafter streaming begins,is_disconnectedno longer drops messages or masks errors, and exception-handler dispatch is guarded whenreceive()fails before a request is bound. - Dependency injection fixes: generator-dependency cleanup errors are logged instead of silently swallowed, dependencies that
yieldmore than once raiseRuntimeError, and factories with only keyword-only parameters are no longer mis-called with a positionalrequest. - Static file serving: fixed a path-traversal hole where sibling-prefix directories escaped the
startswithguard, and file reads (static,FileResponse, SPA) are offloaded viaasyncio.to_thread.
#0.2.0
ASGI sub-app mounting via Router.mount(), WebSocketDisconnect exception, benchmark suite, and a new documentation site.
Context
Router.mount() enables composing routers and ASGI sub-apps under path prefixes; downstream consumers already depend on it and are broken against registry 0.1.0. WebSocketDisconnect adds explicit disconnect detection to WebSocket receive methods. The docs site (guides, API reference, comparisons) is generated by selfdoc and deployed via Cloudflare Pages.
#Features
- New. ASGI sub-app mounting via
Router.mount()for composing multiple routers. - New.
WebSocketDisconnectexception and automatic disconnect detection in WebSocket receive methods. - New. Benchmark suite comparing fastware vs FastAPI (serialization, import time, throughput).
- New. Documentation site (guides, quickstart, API reference, framework comparisons) deployed via Cloudflare Pages.
#0.1.0
Initial release of fastware, a fast batteries-included ASGI framework.
#Features
- Initial release. ASGI micro-framework: Router, Request/Response types, WebSocket, SSE broadcaster, middleware (CORS, RequestID, RequestTiming, TrustedHost, ViteDevProxy), Granian server lifecycle, dependency injection, auth (JWT, passwords, CSRF), structured logging, background tasks, feature flags, MCP server support, and test client utilities.