Web App
Service lifecycle framework with FastAPI background services, dependency management, and optional desktop GUI launcher.
Overview
vcti-webapp provides a Service base class for managing background services
with ordered dependencies and thread-safe lifecycle control. FastAPIService
extends this to run FastAPI applications via uvicorn in background threads
with automatic port discovery. For desktop applications, the optional GUI
module adds AppLauncher (pywebview window), StaticDirApp (static file
serving), and ZippedApp (serve from ZIP archives).
Installation
Core (services only):
pip install vcti-webapp>=1.0.0
With desktop GUI support:
pip install vcti-webapp[gui]>=1.0.0
With ZIP archive support (includes GUI + vcti-session):
pip install vcti-webapp[zip]>=1.0.0
Quick Start
Background FastAPI service
from fastapi import FastAPI
from vcti.webapp import FastAPIService
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
# Start in background thread with auto-discovered port
with FastAPIService(app, name="api") as svc:
print(f"Running on http://localhost:{svc.port}")
# ... do work ...
# Server stops automatically on exit
Service dependencies
from vcti.webapp import Service, FastAPIService, service_dependency
class DatabaseService(Service):
def _start_service(self):
self.connection = connect_to_db()
def _stop_service(self):
self.connection.close()
db = DatabaseService(name="database")
api = FastAPIService(app, name="api")
api.add_dependency(db)
api.start() # starts db first, then api
api.stop() # stops api first, then db (reverse order)
Error recovery
from vcti.webapp import CyclicDependencyError
# Circular dependencies are caught at registration time
try:
svc_a.add_dependency(svc_b)
except CyclicDependencyError:
print("Would create a cycle — dependency not added")
# Start failures roll back dependencies automatically
try:
svc.start()
except RuntimeError:
# All already-started dependencies have been stopped
print("Service failed to start, deps cleaned up")
Desktop GUI (requires pywebview)
from vcti.webapp.app_launcher import AppLauncher
launcher = AppLauncher(app, window_title="My App", window_size=(1280, 720))
launcher.start() # opens desktop window, blocks until closed
Core API
Service (base)
| Method | Description |
|---|---|
start() |
Start service and dependencies (thread-safe, safe to call from multiple threads) |
stop() |
Stop service and optionally dependencies |
add_dependency(service) |
Register a dependency (returns self, raises CyclicDependencyError on cycle) |
is_running() |
Check if running |
FastAPIService
| Parameter | Default | Description |
|---|---|---|
app |
(required) | The FastAPI application |
host |
"127.0.0.1" |
Bind address (loopback only by default for security) |
port |
None |
TCP port (None = auto-discover a free port) |
ready_timeout |
10.0 |
Seconds to wait for server readiness; 0 to skip |
ready_poll_interval |
0.05 |
Seconds between readiness polls |
uvicorn_config |
None |
Extra kwargs forwarded to uvicorn.Config |
GUI classes (optional — vcti-webapp[gui])
| Class | Purpose |
|---|---|
AppLauncher |
Desktop window via pywebview |
StaticDirApp |
Serve static files from a directory |
ZippedApp |
Extract and serve from ZIP archive |
Advanced Usage
Custom uvicorn configuration
# Suppress noisy startup logs
svc = FastAPIService(app, uvicorn_config={"log_level": "warning", "access_log": False})
# TLS termination at uvicorn
svc = FastAPIService(app, uvicorn_config={
"ssl_keyfile": "key.pem",
"ssl_certfile": "cert.pem",
})
Slow CI environments
# Increase readiness timeout on slow machines
svc = FastAPIService(app, ready_timeout=30.0, ready_poll_interval=0.1)
Shared dependencies
When multiple services share a dependency, use stop_dependencies=False
to prevent the first service to stop from tearing down the shared resource:
db = DatabaseService(name="db")
api = FastAPIService(app, name="api", stop_dependencies=False)
api.add_dependency(db)
worker = WorkerService(name="worker", stop_dependencies=False)
worker.add_dependency(db)
# api.stop() leaves db running — worker still needs it
Static file serving without GUI
StaticDirApp inherits from AppLauncher (which requires pywebview),
but you can serve static files without a GUI using plain FastAPIService:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from vcti.webapp import FastAPIService
app = FastAPI()
app.mount("/static", StaticFiles(directory="./dist"), name="static")
with FastAPIService(app, name="docs-server") as svc:
print(f"Serving at http://localhost:{svc.port}/static/index.html")
Root redirect (show_index_on_root)
StaticDirApp and ZippedApp redirect GET / to
{static_route}/{default_page} by default. Set show_index_on_root=False
if you define your own root route or want / to return 404:
StaticDirApp(Path("./dist"), show_index_on_root=False)
Threading
start() and stop() are thread-safe. Calling start() from multiple
threads simultaneously is safe — only one thread runs startup; others
return immediately. See docs/design.md for details on
lock ordering and concurrency guarantees.
Dependencies
- fastapi (>=0.100) — required
- uvicorn (>=0.20) — required
- pywebview (>=4.0) — optional, for GUI
- vcti-session (>=1.0.0) — optional, for ZippedApp
Metadata
Release files for vcti-webapp 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vcti_webapp-1.0.0.tar.gz | 18.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vcti_webapp-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.5 kB
Release files / vcti_webapp-1.0.0.tar.gz
| Download URL | vcti_webapp-1.0.0.tar.gz |
|---|---|
| Size | 18.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
af1f6a1aa85870da1746e186ab7ec1304fb7a5a58e3a68588af84f0437d76260
|
|
BLAKE2b-256 checksum How to use checksums |
16a03356626e70e377e8c1a6612728252900b37f6fbe45a27d48836eca04d566
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 29, 2026.
Transparency logRelease files / vcti_webapp-1.0.0-py3-none-any.whl
| Download URL | vcti_webapp-1.0.0-py3-none-any.whl |
|---|---|
| Size | 13.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5c3ee45fc9908f2f8396a71acf57b1a6ac748698b5f840979a67b8e8866fd26a
|
|
BLAKE2b-256 checksum How to use checksums |
c0f2e86255f83aba85a98763709f91c35ed071616e4b74ab4c1112f132c16223
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 29, 2026.
Transparency log