Static Files
Serving static files is an HTTP concern — it belongs in the adapter. EVOID’s runtime only handles intents, so you mount static file serving alongside your EVOID routes.
Mounting Static Files in Starlette
Use Starlette’s StaticFiles to serve a directory:
from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.staticfiles import StaticFiles
from starlette.responses import JSONResponse
from evoid.core import Intent, execute
async def handle(request):
intent = Intent(
name=f"{request.method}:{request.url.path}",
level=Level.STANDARD,
metadata={"method": request.method, "path": request.url.path},
)
result = await execute(intent)
if result.success:
return JSONResponse(result.value)
return JSONResponse({"error": str(result.error)}, status_code=500)
app = Starlette(
routes=[
# EVOID intent routes
Route("/api/{path:path}", handle, methods=["GET", "POST", "PUT", "DELETE"]),
# Static files — mounted at /static
Mount("/static", app=StaticFiles(directory="public"), name="static"),
],
)
Path Ordering Matters
Starlette matches routes in order. Put specific routes before catch-all patterns:
app = Starlette(
routes=[
# Specific API routes first
Route("/api/health", health_endpoint, methods=["GET"]),
Route("/api/users", users_endpoint, methods=["GET", "POST"]),
# Static files
Mount("/static", app=StaticFiles(directory="public"), name="static"),
# Catch-all EVOID route last
Route("/api/{path:path}", handle, methods=["GET", "POST", "PUT", "DELETE"]),
],
)
Serving from Multiple Directories
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
app = Starlette(
routes=[
# CSS/JS assets
Mount("/assets", app=StaticFiles(directory="dist/assets"), name="assets"),
# Uploaded files
Mount("/uploads", app=StaticFiles(directory="uploads"), name="uploads"),
# EVOID routes
Route("/api/{path:path}", handle, methods=["GET", "POST", "PUT", "DELETE"]),
],
)
Custom Adapter with Static Files
Here is a complete adapter that serves static files alongside EVOID:
import json
from pathlib import Path
from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.staticfiles import StaticFiles
from starlette.requests import Request
from starlette.responses import JSONResponse
from evoid.core import Intent, Level, execute
PUBLIC_DIR = Path("public")
PUBLIC_DIR.mkdir(exist_ok=True)
async def handle_intent(request: Request) -> JSONResponse:
method = request.method
path = request.url.path
body = await request.body() if method in ("POST", "PUT", "PATCH") else b""
data = {}
if body:
try:
data = json.loads(body)
except json.JSONDecodeError:
data = {}
intent = Intent(
name=f"{method.lower()}:{path}",
level=Level.STANDARD,
metadata={"method": method, "path": path, "body": data, "headers": dict(request.headers)},
)
result = await execute(intent)
if result.success:
return JSONResponse(result.value)
return JSONResponse({"error": str(result.error)}, status_code=500)
app = Starlette(
routes=[
Mount("/static", app=StaticFiles(directory=str(PUBLIC_DIR)), name="static"),
Route("/{path:path}", handle_intent, methods=["GET", "POST", "PUT", "DELETE"]),
],
)
@route Style
from evoid.web.route import Service, get, run
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
app = Service("my-api")
@app.get("/api/data")
async def get_data() -> dict:
return {"data": [1, 2, 3]}
# The run function creates the ASGI app — you can extend it
# by importing create_app and adding routes directly
@controller Style
from evoid.web.controller import Service, Controller, GET
app = Service("my-api")
@Controller("/api")
class DataController:
@GET("/data")
async def get_data(self) -> dict:
return {"data": [1, 2, 3]}
Native IOP Style
from evoid import Intent, Level, add_intent
GET_DATA = Intent(
name="get:data",
level=Level.STANDARD,
)
async def handle_get_data(intent: Intent) -> dict:
return {"data": [1, 2, 3]}
add_intent(GET_DATA, handle_get_data)