Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions fastapi-postgres/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
venv
application/__pycache__
30 changes: 30 additions & 0 deletions fastapi-postgres/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Use the official Python image as the base image
FROM python:3.11.5-bullseye

# Set the working directory to /app
WORKDIR /app

# Copy the requirements file into the container
COPY requirements.txt .

# Install the dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code into the container
COPY . .

# Set environment variables for PostgreSQL
ENV POSTGRES_USER=postgres \
POSTGRES_PASSWORD=postgres \
POSTGRES_DB=studentdb \
POSTGRES_HOST=0.0.0.0 \
POSTGRES_PORT=5432

# Install PostgreSQL client
RUN apt-get update && apt-get install -y postgresql-client

# Expose port 80 for the FastAPI application
EXPOSE 8000

# Start the FastAPI application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
124 changes: 124 additions & 0 deletions fastapi-postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# FastAPI-Postgres CRUD Application

A sample user data CRUD app to test Keploy integration capabilities using [FastAPI](https://fastapi.tiangolo.com/) and [PostgreSQL](https://www.postgresql.org/). <br>
Make the following requests to the respective endpoints -

1. `GET students/` - Get all students.
2. `GET students/{id}` - Get a student by id.
3. `POST students/` - Create a student.
4. `PUT students/{id}` - Update a student by id.
5. `DELETE students/{id}` - Delete a student by id.

## Installation Setup

```bash
git clone https://github.com/keploy/samples-python.git && cd samples-python/fastapi-postgres
pip3 install -r requirements.txt
```

## Installation Keploy

Keploy can be installed on Linux directly and on Windows with the help of WSL. Based on your system architecture, install the keploy latest binary release

**1. AMD Architecture**

```shell
curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp

sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy
```

<details>
<summary> 2. ARM Architecture </summary>

```shell
curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" | tar xz -C /tmp

sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy
```

</details>

### Starting the PostgreSQL Instance

```bash
# Start the application
docker-compose up -d
```

### Capture the Testcases

This command will start the recording of API calls using ebpf:-

```shell
sudo -E keploy record -c "uvicorn application.main:app --reload"
```

Make API Calls using Hoppscotch, Postman or cURL command. Keploy with capture those calls to generate the test-suites containing testcases and data mocks.

### Make a POST request

```bash
curl --location 'http://127.0.0.1:8000/students/' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Eva White",
"email": "[email protected]",
"password": "evawhite111"
}'
```

```bash
curl --location 'http://127.0.0.1:8000/students/' \
--header 'Content-Type: application/json' \
--data-raw ' {
"name": "John Doe",
"email": "[email protected]",
"password": "johndoe123"
}'
```

### Make a GET request to get all the data

```bash
curl --location 'http://127.0.0.1:8000/students/'
```

This will return all the data saved in the database.

### Make a GET request to get a specific data

```bash
curl --location 'http://127.0.0.1:8000/students/1'
```

### Make a PUT request to update a specific data

```bash
curl --location --request PUT 'http://127.0.0.1:8000/students/2' \
--header 'Content-Type: application/json' \
--data-raw ' {
"name": "John Dow",
"email": "[email protected]",
"password": "johndoe123",
"stream": "Arts"
}'
```

### Make a DELETE request to delete a specific data

```bash
curl --location --request DELETE 'http://127.0.0.1:8000/students/1'
```

Now all these API calls were captured as **editable** testcases and written to `keploy/tests` folder. The keploy directory would also have `mocks` file that contains all the outputs of postgres operations.

## Run the Testcases

Now let's run the application in test mode.

```shell
sudo -E keploy test -c "uvicorn application.main:app --reload" --delay 10
```

So, no need to setup fake database/apis like Postgres or write mocks for them. Keploy automatically mocks them and, **The application thinks it's talking to Postgres 😄**
Empty file.
52 changes: 52 additions & 0 deletions fastapi-postgres/application/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from sqlalchemy.orm import Session
from . import models, schemas


def get_student(db: Session, student_id: int):
return db.query(models.Student).filter(models.Student.id == student_id).first()


def get_student_byEmail(db: Session, student_email: str):
return db.query(models.Student).filter(models.Student.email == student_email).first()


def get_students(db: Session, skip: int = 0, limit: int = 50):
return db.query(models.Student).offset(skip).limit(limit).all()


def create_student(db: Session, student: schemas.StudentCreate):
student_hashed_password = password_hasher(student.password)

db_student = models.Student(name=student.name, email=student.email, password=student_hashed_password)
db.add(db_student)
db.commit()
db.refresh(db_student)
return db_student


def update_student(db: Session, student: schemas.StudentUpdate, student_id: int):
student_old = db.query(models.Student).filter(models.Student.id == student_id).first()
if student_old is None:
return None
student_old.name = student_old.name if student.name is None else student.name
student_old.email = student_old.email if student.email is None else student.email
student_old.stream = student_old.stream if student.stream is None else student.stream
student_old.password = password_hasher(student.password)
db.commit()
db.refresh(student_old)
return student_old


def delete_student(db: Session, student_id: int):
student = db.query(models.Student).filter(models.Student.id == student_id).first()
if student:
db.delete(student)
db.commit()
return student


def password_hasher(password) -> str:
hashed_password = 's'
for i in range(0, len(password)):
hashed_password += chr(ord(password[i]) + 1)
return hashed_password
11 changes: 11 additions & 0 deletions fastapi-postgres/application/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


SQLALCHEMY_DATABASE_URL = "postgresql://postgres:[email protected]:5432/studentdb"

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
59 changes: 59 additions & 0 deletions fastapi-postgres/application/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session

from . import models, crud, schemas
from .database import SessionLocal, engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()


# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()


@app.post('/students/', response_model=schemas.Student)
def create_student(student: schemas.StudentCreate, db: Session = Depends(get_db)):
db_student = crud.get_student_byEmail(db, student_email=student.email)
if db_student:
raise HTTPException(status_code=400, detail="Student already registered!!")
data = crud.create_student(db, student=student)
return data


@app.get('/students/', response_model=list[schemas.Student])
def read_students(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
students = crud.get_students(db, skip, limit)
if students == []:
raise HTTPException(404, 'Data not found!!')
return students


@app.get('/students/{student_id}', response_model=schemas.Student)
def read_student(student_id: int, db: Session = Depends(get_db)):
student = crud.get_student(db, student_id=student_id)
if student is None:
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
return student


@app.put('/students/{student_id}', response_model=schemas.Student)
def update_student(student_id: int, student: schemas.StudentUpdate, db: Session = Depends(get_db)):
student = crud.update_student(db, student=student, student_id=student_id)
if student is None:
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
return student


@app.delete('/students/{student_id}', response_model=schemas.Student)
def delete_student(student_id: int, db: Session = Depends(get_db)):
student = crud.delete_student(db, student_id=student_id)
if student is None:
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
return student
13 changes: 13 additions & 0 deletions fastapi-postgres/application/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sqlalchemy import Integer, String, Column

from .database import Base


class Student(Base):
__tablename__="students"

id = Column(Integer, name="ID", primary_key=True, index=True, info="Stores the id of a student", autoincrement="auto")
name = Column(String, name="Name")
email = Column(String, name="Email", index=True)
password = Column(String, name="Hashed Password")
stream = Column(String, name="Subject Stream", default="Mathematics")
24 changes: 24 additions & 0 deletions fastapi-postgres/application/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from pydantic import BaseModel


class StudentBase(BaseModel):
name: str
email: str


class StudentCreate(StudentBase):
password: str


class StudentUpdate(StudentCreate):
stream: str

class Config:
from_attributes = True


class Student(StudentUpdate):
id: int

class Config:
from_attributes = True
53 changes: 53 additions & 0 deletions fastapi-postgres/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[
{
"name": "John Doe",
"email": "[email protected]",
"password": "johndoe123"
},
{
"name": "Alice Smith",
"email": "[email protected]",
"password": "alicesmith789"
},
{
"name": "Bob Johnson",
"email": "[email protected]",
"password": "bobjohnson456"
},
{
"name": "Eva White",
"email": "[email protected]",
"password": "evawhite111"
},
{
"name": "Michael Brown",
"email": "[email protected]",
"password": "michaelbrown999"
},
{
"name": "Sophia Lee",
"email": "[email protected]",
"password": "sophialee777"
},
{
"name": "David Hall",
"email": "[email protected]",
"password": "davidhall222"
},
{
"name": "Emma Turner",
"email": "[email protected]",
"password": "emmaturner654"
},
{
"name": "Oliver Harris",
"email": "[email protected]",
"password": "oliverharris333"
},
{
"name": "Ava Martinez",
"email": "[email protected]",
"password": "avamartinez888"
}
]

13 changes: 13 additions & 0 deletions fastapi-postgres/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3'
services:

postgres:
image: postgres:latest
environment:
POSTGRES_DB: studentdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432" # Map the PostgreSQL port to the host machine
volumes:
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
Loading