A fully functional FastAPI server with RADKit-powered API endpoints. This project demonstrates how to build a web application that connects to a RADKit service using a Direct connection, meaning the RADKit service must be network-reachable, have its RPC port configured, and have a Remote User set up. The FastAPI app authenticates against that service on startup and exposes REST endpoints to interact with the device inventory.
radkit-fastapi/
├── app/
│ ├── __init__.py
│ ├── main.py # App factory & lifespan (RADKit client init)
│ ├── api/
│ │ ├── __init__.py
│ │ └── v1/
│ │ ├── __init__.py
│ │ ├── router.py # V1 router aggregator
│ │ └── routes/
│ │ ├── __init__.py
│ │ └── radkit.py # RADKit API endpoints
│ └── core/
│ ├── __init__.py
│ ├── config.py # App settings (pydantic-settings / .env)
│ └── radkit_client.py # Shared RADKit service state
├── .env # Environment variables (credentials)
├── pyproject.toml # Project metadata & dependencies (uv)
└── README.md
uv is a fast Python package and project manager. Install it once on your machine:
| Platform | Command |
|---|---|
| 🍎🐧 macOS / Linux | curl -LsSf https://astral.sh/uv/install.sh | sh |
| 🪟 Windows (PowerShell) | powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" |
Or via Homebrew on macOS:
brew install uvVerify the installation:
uv --versionFrom the project root, run:
uv syncuv sync reads pyproject.toml, automatically creates a .venv with Python 3.13, and installs all dependencies — including the RADKit packages from https://radkit.cisco.com/pip and the FastAPI stack from PyPI — in a single step.
Copy the example env file and fill in your RADKit credentials:
cp .env.example .env # or create .env manuallyRequired variables:
USER_ID=your@email.com
E2EE_VALIDATION_TOKEN=your-remote-user-token
RADKIT_SERVER_ADDRESS=your-radkit-server.example.com
RPC_PORT=your-radkit-server-port (default is 8181)uv run uvicorn app.main:app --reloadNote: The
--reloadflag watches for file changes and automatically restarts the server, including re-running the RADKit authentication handshake. This means you can update your code or.envcredentials without stopping and redeploying the app manually.
Alternatively, activate the virtual environment first and then run commands directly:
| Platform | Activate command |
|---|---|
| 🍎🐧 macOS / Linux | source .venv/bin/activate |
| 🪟 Windows (cmd) | .venv\Scripts\activate.bat |
| 🪟 Windows (PowerShell) | .venv\Scripts\Activate.ps1 |
# After activation:
uvicorn app.main:app --reloadThe app will be available at:
| Interface | URL |
|---|---|
| API | http://127.0.0.1:8000 |
| Swagger UI | http://127.0.0.1:8000/docs |
| ReDoc | http://127.0.0.1:8000/redoc |
| Method | Path | Description |
|---|---|---|
| GET | / |
Root welcome message |
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/radkit/status |
RADKit service connectivity status and identity |
| GET | /api/v1/radkit/devices |
List all devices in the RADKit inventory |
| POST | /api/v1/radkit/devices/{device_name}/commands |
Execute any CLI command on a device |
| GET | /api/v1/radkit/devices/{device_name}/show |
Execute a show command and return Genie-parsed output |
RADKit is integrated using FastAPI's lifespan handler (app/main.py), which runs setup and teardown logic around the entire application lifecycle. Inside the lifespan, Client.create() opens a RADKit context and immediately establishes a Direct connection to the RADKit service using credentials from the environment. The resulting service object is stored in a module-level variable (app/core/radkit_client.py) so every route can access it without passing it around explicitly. When the app shuts down, the context manager cleans up the connection and resets the global to None.
@asynccontextmanager
async def lifespan(app: FastAPI):
with Client.create() as client:
service = client.service_direct(
username=settings.USER_ID,
host=settings.RADKIT_SERVER_ADDRESS,
port=settings.RPC_PORT,
password=settings.E2EE_VALIDATION_TOKEN
)
radkit_state.radkit_service = service # shared global used by all routes
yield # app runs here
radkit_state.radkit_service = None # cleanup on shutdown
app = FastAPI(lifespan=lifespan)Key points to understand:
Client.create()is synchronous. It spawns a background thread with its own event loop dedicated to RADKit. This thread is separate from FastAPI's async event loop.- All RADKit calls must be synchronous. Route handlers that interact with RADKit must be regular
deffunctions — notasync def. Usingasync deffor RADKit calls will cause deadlocks or unexpected behaviour. - Global state. The
serviceobject is stored inapp/core/radkit_client.pyas a module-level variable and imported by route modules wherever needed. - Single-process only. The ASGI runner (
uvicorn) must not use multiprocessing workers (e.g. avoid--workers NwithN > 1), as each process would have its own RADKit thread and independent authentication state.