Skip to content

Commit

Permalink
Add FastAPI sample app with postgres and uvicorn
Browse files Browse the repository at this point in the history
  • Loading branch information
Wout Feys committed Nov 26, 2024
1 parent d3e2c7d commit 99bc180
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 0 deletions.
3 changes: 3 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/.env.benchmark
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
AIKIDO_DEBUG=false
AIKIDO_TOKEN="AIK_secret_token"
AIKIDO_BLOCKING=true
5 changes: 5 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
AIKIDO_DEBUG=true
AIKIDO_TOKEN="AIK_secret_token"
AIKIDO_BLOCKING=true
AIKIDO_REALTIME_ENDPOINT="http://host.docker.internal:5000/"
AIKIDO_ENDPOINT="http://host.docker.internal:5000/"
23 changes: 23 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Use an official Python runtime as a parent image
FROM python:3.11

#Copy code base
COPY ./ /tmp

# Set the working directory
WORKDIR /app

# Install dependencies
RUN mv /tmp/sample-apps/fastapi-postgres-uvicorn/requirements.txt ./
RUN pip install -r requirements.txt

# Build and install aikido_zen from source
WORKDIR /tmp
RUN pip install poetry
RUN rm -rf ./dist
RUN make build
RUN mv ./dist/aikido_zen-*.tar.gz ./dist/aikido_zen.tar.gz
RUN pip install ./dist/aikido_zen.tar.gz
RUN pip list

WORKDIR /app
14 changes: 14 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# FastAPI w/ Postgres and Uvicorn Sample app
It runs **multi-threaded** and **async**

## Getting started
Run (with docker-compose installed) :
```bash
docker-compose up --build
```

- You'll be able to access the FastAPI Server at : [localhost:8104](http://localhost:8096)
- To Create a reference test dog use `http://localhost:8104/create/`
- To Create a reference test dog (with executemany) use `http://localhost:8104/create_many/`

- To test a sql injection enter the following dog name : `Malicious dog', TRUE); -- `
86 changes: 86 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from dotenv import load_dotenv
import os
import time
import asyncpg
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi.middleware.cors import CORSMiddleware

load_dotenv()
firewall_disabled = os.getenv("FIREWALL_DISABLED")
if firewall_disabled is not None:
if firewall_disabled.lower() != "1":
import aikido_zen # Aikido package import
aikido_zen.protect()

templates = Jinja2Templates(directory="templates")

app = FastAPI()

# CORS middleware (optional, depending on your needs)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Adjust this as needed
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

async def get_db_connection():
return await asyncpg.connect(
host="host.docker.internal",
database="db",
user="user",
password="password"
)

@app.get("/", response_class=HTMLResponse)
async def homepage(request: Request):
conn = await get_db_connection()
dogs = await conn.fetch("SELECT * FROM dogs")
await conn.close()
return templates.TemplateResponse('index.html', {"request": request, "title": 'Homepage', "dogs": dogs})

@app.get("/dogpage/{dog_id:int}", response_class=HTMLResponse)
async def get_dogpage(request: Request, dog_id: int):
conn = await get_db_connection()
dog = await conn.fetchrow("SELECT * FROM dogs WHERE id = $1", dog_id)
await conn.close()
if dog is None:
raise HTTPException(status_code=404, detail="Dog not found")
return templates.TemplateResponse('dogpage.html', {"request": request, "title": 'Dog', "dog": dog, "isAdmin": "Yes" if dog[2] else "No"})

@app.get("/create", response_class=HTMLResponse)
async def show_create_dog_form(request: Request):
return templates.TemplateResponse('create_dog.html', {"request": request})

@app.post("/create")
async def create_dog(request: Request):
data = await request.form()
dog_name = data.get('dog_name')

if not dog_name:
return JSONResponse({"error": "dog_name is required"}, status_code=400)

conn = await get_db_connection()
try:
await conn.execute("INSERT INTO dogs (dog_name, isAdmin) VALUES ($1, FALSE)", dog_name)
finally:
await conn.close()

return JSONResponse({"message": f'Dog {dog_name} created successfully'}, status_code=201)

@app.get("/just")
async def just():
return JSONResponse({"message": "Empty Page"})

@app.get("/delayed_route")
async def delayed_route():
time.sleep(1/1000) # Note: This will block the event loop; consider using asyncio.sleep instead
return JSONResponse({"message": "Empty Page"})

@app.get("/sync_route")
def sync_route():
data = {"message": "This is a non-async route!"}
return JSONResponse(data)
17 changes: 17 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/docker-compose.benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: "3"
services:
backend_firewall_disabled:
image: sample_fastapi_postgres_uvicorn
command: sh -c "uvicorn app:app --host 0.0.0.0 --port 5000 --workers 4"
restart: always
volumes:
- .:/app
ports:
- "8105:5000"
extra_hosts:
- "app.local.aikido.io:host-gateway"
- "host.docker.internal:host-gateway"
environment:
- FIREWALL_DISABLED=1
backend:
env_file: .env.example
19 changes: 19 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3'
services:
backend:
image: sample_fastapi_postgres_uvicorn
build:
context: ./../../
dockerfile: ./sample-apps/fastapi-postgres-uvicorn/Dockerfile
container_name: fastapi_postgres_uvicorn_backend
command: sh -c "uvicorn app:app --host 0.0.0.0 --port 5000 --workers 4"
restart: always
volumes:
- .:/app
ports:
- "8104:5000"
extra_hosts:
- "app.local.aikido.io:host-gateway"
- "host.docker.internal:host-gateway"
environment:
- FIREWALL_DISABLED=0
5 changes: 5 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS dogs (
id SERIAL PRIMARY KEY,
dog_name VARCHAR(250) NOT NULL,
isadmin BOOLEAN NOT NULL DEFAULT FALSE
);
6 changes: 6 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fastapi
jinja2
asyncpg
cryptography
uvicorn
python-multipart
17 changes: 17 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/templates/create_dog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create Dog</title>
</head>
<body>
<h1>Create a Dog</h1>
<form method="post">
<label for="dog_name">Dog Name:</label>
<input type="text" id="dog_name" name="dog_name" required>
<button type="submit">Create Dog</button>
</form>
</body>
</html>
25 changes: 25 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/templates/dogpage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<style>
body {
font-family: sans-serif;
}
h1 {
font-family: monospace;
text-align: center;
border: 1px solid black;
border-left: none;
border-right: none;
margin: 4px;
}
</style>
</head>
<body>
<h1>{{ title }}</h1>
<p><em>Name :</em> {{dog[1]}}</p>
<p><em>Is admin dog? </em>{{ isAdmin }}</p>
<p><em>ID :</em> {{dog[0]}}</p>
</body>
</html>
48 changes: 48 additions & 0 deletions sample-apps/fastapi-postgres-uvicorn/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<style>
body {
font-family: sans-serif;
}
/* Style for the list */
ul {
list-style-type:disc;
}

/* Style for list items */
li {
margin-bottom: 10px;
}

/* Style for links */
a {
text-decoration: none;
color: #48507f;
}

/* Hover effect for links */
a:hover {
color: #007bff;
}
h1 {
font-family: monospace;
text-align: center;
border: 1px solid black;
border-left: none;
border-right: none;
margin: 4px;
}
</style>
</head>
<body>
<h1>{{ title }}</h1>
<h2>List</h2>
<ul>
{% for dog in dogs %}
<li><a href="/dogpage/{{ dog[0] }}">{{ dog[1] }}</a></li>
{% endfor %}
</ul>
</body>
</html>

0 comments on commit 99bc180

Please sign in to comment.