Skip to content

Commit 3bb914b

Browse files
feat: adding new sample app for python (#7)
* fix: removed the docker part from readme Signed-off-by: Shashwat79802 <shashwatg79802@gmail.com> * feat: adding new samples app for python Signed-off-by: Shashwat79802 <shashwatg79802@gmail.com> --------- Signed-off-by: Shashwat79802 <shashwatg79802@gmail.com>
1 parent 9789a29 commit 3bb914b

21 files changed

Lines changed: 2789 additions & 0 deletions

fastapi-postgres/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
venv
2+
application/__pycache__

fastapi-postgres/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Use the official Python image as the base image
2+
FROM python:3.11.5-bullseye
3+
4+
# Set the working directory to /app
5+
WORKDIR /app
6+
7+
# Copy the requirements file into the container
8+
COPY requirements.txt .
9+
10+
# Install the dependencies
11+
RUN pip install --no-cache-dir -r requirements.txt
12+
13+
# Copy the rest of the application code into the container
14+
COPY . .
15+
16+
# Set environment variables for PostgreSQL
17+
ENV POSTGRES_USER=postgres \
18+
POSTGRES_PASSWORD=postgres \
19+
POSTGRES_DB=studentdb \
20+
POSTGRES_HOST=0.0.0.0 \
21+
POSTGRES_PORT=5432
22+
23+
# Install PostgreSQL client
24+
RUN apt-get update && apt-get install -y postgresql-client
25+
26+
# Expose port 80 for the FastAPI application
27+
EXPOSE 8000
28+
29+
# Start the FastAPI application
30+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

fastapi-postgres/README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# FastAPI-Postgres CRUD Application
2+
3+
A sample user data CRUD app to test Keploy integration capabilities using [FastAPI](https://fastapi.tiangolo.com/) and [PostgreSQL](https://www.postgresql.org/). <br>
4+
Make the following requests to the respective endpoints -
5+
6+
1. `GET students/` - Get all students.
7+
2. `GET students/{id}` - Get a student by id.
8+
3. `POST students/` - Create a student.
9+
4. `PUT students/{id}` - Update a student by id.
10+
5. `DELETE students/{id}` - Delete a student by id.
11+
12+
## Installation Setup
13+
14+
```bash
15+
git clone https://github.com/keploy/samples-python.git && cd samples-python/fastapi-postgres
16+
pip3 install -r requirements.txt
17+
```
18+
19+
## Installation Keploy
20+
21+
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
22+
23+
**1. AMD Architecture**
24+
25+
```shell
26+
curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz -C /tmp
27+
28+
sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy
29+
```
30+
31+
<details>
32+
<summary> 2. ARM Architecture </summary>
33+
34+
```shell
35+
curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_arm64.tar.gz" | tar xz -C /tmp
36+
37+
sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin && keploy
38+
```
39+
40+
</details>
41+
42+
### Starting the PostgreSQL Instance
43+
44+
```bash
45+
# Start the application
46+
docker-compose up -d
47+
```
48+
49+
### Capture the Testcases
50+
51+
This command will start the recording of API calls using ebpf:-
52+
53+
```shell
54+
sudo -E keploy record -c "uvicorn application.main:app --reload"
55+
```
56+
57+
Make API Calls using Hoppscotch, Postman or cURL command. Keploy with capture those calls to generate the test-suites containing testcases and data mocks.
58+
59+
### Make a POST request
60+
61+
```bash
62+
curl --location 'http://127.0.0.1:8000/students/' \
63+
--header 'Content-Type: application/json' \
64+
--data-raw '{
65+
"name": "Eva White",
66+
"email": "evawhite@example.com",
67+
"password": "evawhite111"
68+
}'
69+
```
70+
71+
```bash
72+
curl --location 'http://127.0.0.1:8000/students/' \
73+
--header 'Content-Type: application/json' \
74+
--data-raw ' {
75+
"name": "John Doe",
76+
"email": "johndoe@example.com",
77+
"password": "johndoe123"
78+
}'
79+
```
80+
81+
### Make a GET request to get all the data
82+
83+
```bash
84+
curl --location 'http://127.0.0.1:8000/students/'
85+
```
86+
87+
This will return all the data saved in the database.
88+
89+
### Make a GET request to get a specific data
90+
91+
```bash
92+
curl --location 'http://127.0.0.1:8000/students/1'
93+
```
94+
95+
### Make a PUT request to update a specific data
96+
97+
```bash
98+
curl --location --request PUT 'http://127.0.0.1:8000/students/2' \
99+
--header 'Content-Type: application/json' \
100+
--data-raw ' {
101+
"name": "John Dow",
102+
"email": "doe.john@example.com",
103+
"password": "johndoe123",
104+
"stream": "Arts"
105+
}'
106+
```
107+
108+
### Make a DELETE request to delete a specific data
109+
110+
```bash
111+
curl --location --request DELETE 'http://127.0.0.1:8000/students/1'
112+
```
113+
114+
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.
115+
116+
## Run the Testcases
117+
118+
Now let's run the application in test mode.
119+
120+
```shell
121+
sudo -E keploy test -c "uvicorn application.main:app --reload" --delay 10
122+
```
123+
124+
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 😄**

fastapi-postgres/application/__init__.py

Whitespace-only changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from sqlalchemy.orm import Session
2+
from . import models, schemas
3+
4+
5+
def get_student(db: Session, student_id: int):
6+
return db.query(models.Student).filter(models.Student.id == student_id).first()
7+
8+
9+
def get_student_byEmail(db: Session, student_email: str):
10+
return db.query(models.Student).filter(models.Student.email == student_email).first()
11+
12+
13+
def get_students(db: Session, skip: int = 0, limit: int = 50):
14+
return db.query(models.Student).offset(skip).limit(limit).all()
15+
16+
17+
def create_student(db: Session, student: schemas.StudentCreate):
18+
student_hashed_password = password_hasher(student.password)
19+
20+
db_student = models.Student(name=student.name, email=student.email, password=student_hashed_password)
21+
db.add(db_student)
22+
db.commit()
23+
db.refresh(db_student)
24+
return db_student
25+
26+
27+
def update_student(db: Session, student: schemas.StudentUpdate, student_id: int):
28+
student_old = db.query(models.Student).filter(models.Student.id == student_id).first()
29+
if student_old is None:
30+
return None
31+
student_old.name = student_old.name if student.name is None else student.name
32+
student_old.email = student_old.email if student.email is None else student.email
33+
student_old.stream = student_old.stream if student.stream is None else student.stream
34+
student_old.password = password_hasher(student.password)
35+
db.commit()
36+
db.refresh(student_old)
37+
return student_old
38+
39+
40+
def delete_student(db: Session, student_id: int):
41+
student = db.query(models.Student).filter(models.Student.id == student_id).first()
42+
if student:
43+
db.delete(student)
44+
db.commit()
45+
return student
46+
47+
48+
def password_hasher(password) -> str:
49+
hashed_password = 's'
50+
for i in range(0, len(password)):
51+
hashed_password += chr(ord(password[i]) + 1)
52+
return hashed_password
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from sqlalchemy import create_engine
2+
from sqlalchemy.ext.declarative import declarative_base
3+
from sqlalchemy.orm import sessionmaker
4+
5+
6+
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@0.0.0.0:5432/studentdb"
7+
8+
engine = create_engine(SQLALCHEMY_DATABASE_URL)
9+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
10+
11+
Base = declarative_base()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from fastapi import Depends, FastAPI, HTTPException
2+
from sqlalchemy.orm import Session
3+
4+
from . import models, crud, schemas
5+
from .database import SessionLocal, engine
6+
7+
models.Base.metadata.create_all(bind=engine)
8+
9+
app = FastAPI()
10+
11+
12+
# Dependency
13+
def get_db():
14+
db = SessionLocal()
15+
try:
16+
yield db
17+
finally:
18+
db.close()
19+
20+
21+
@app.post('/students/', response_model=schemas.Student)
22+
def create_student(student: schemas.StudentCreate, db: Session = Depends(get_db)):
23+
db_student = crud.get_student_byEmail(db, student_email=student.email)
24+
if db_student:
25+
raise HTTPException(status_code=400, detail="Student already registered!!")
26+
data = crud.create_student(db, student=student)
27+
return data
28+
29+
30+
@app.get('/students/', response_model=list[schemas.Student])
31+
def read_students(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
32+
students = crud.get_students(db, skip, limit)
33+
if students == []:
34+
raise HTTPException(404, 'Data not found!!')
35+
return students
36+
37+
38+
@app.get('/students/{student_id}', response_model=schemas.Student)
39+
def read_student(student_id: int, db: Session = Depends(get_db)):
40+
student = crud.get_student(db, student_id=student_id)
41+
if student is None:
42+
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
43+
return student
44+
45+
46+
@app.put('/students/{student_id}', response_model=schemas.Student)
47+
def update_student(student_id: int, student: schemas.StudentUpdate, db: Session = Depends(get_db)):
48+
student = crud.update_student(db, student=student, student_id=student_id)
49+
if student is None:
50+
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
51+
return student
52+
53+
54+
@app.delete('/students/{student_id}', response_model=schemas.Student)
55+
def delete_student(student_id: int, db: Session = Depends(get_db)):
56+
student = crud.delete_student(db, student_id=student_id)
57+
if student is None:
58+
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
59+
return student
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from sqlalchemy import Integer, String, Column
2+
3+
from .database import Base
4+
5+
6+
class Student(Base):
7+
__tablename__="students"
8+
9+
id = Column(Integer, name="ID", primary_key=True, index=True, info="Stores the id of a student", autoincrement="auto")
10+
name = Column(String, name="Name")
11+
email = Column(String, name="Email", index=True)
12+
password = Column(String, name="Hashed Password")
13+
stream = Column(String, name="Subject Stream", default="Mathematics")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pydantic import BaseModel
2+
3+
4+
class StudentBase(BaseModel):
5+
name: str
6+
email: str
7+
8+
9+
class StudentCreate(StudentBase):
10+
password: str
11+
12+
13+
class StudentUpdate(StudentCreate):
14+
stream: str
15+
16+
class Config:
17+
from_attributes = True
18+
19+
20+
class Student(StudentUpdate):
21+
id: int
22+
23+
class Config:
24+
from_attributes = True

fastapi-postgres/data.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[
2+
{
3+
"name": "John Doe",
4+
"email": "johndoe@example.com",
5+
"password": "johndoe123"
6+
},
7+
{
8+
"name": "Alice Smith",
9+
"email": "alicesmith@example.com",
10+
"password": "alicesmith789"
11+
},
12+
{
13+
"name": "Bob Johnson",
14+
"email": "bobjohnson@example.com",
15+
"password": "bobjohnson456"
16+
},
17+
{
18+
"name": "Eva White",
19+
"email": "evawhite@example.com",
20+
"password": "evawhite111"
21+
},
22+
{
23+
"name": "Michael Brown",
24+
"email": "michaelbrown@example.com",
25+
"password": "michaelbrown999"
26+
},
27+
{
28+
"name": "Sophia Lee",
29+
"email": "sophialee@example.com",
30+
"password": "sophialee777"
31+
},
32+
{
33+
"name": "David Hall",
34+
"email": "davidhall@example.com",
35+
"password": "davidhall222"
36+
},
37+
{
38+
"name": "Emma Turner",
39+
"email": "emmaturner@example.com",
40+
"password": "emmaturner654"
41+
},
42+
{
43+
"name": "Oliver Harris",
44+
"email": "oliverharris@example.com",
45+
"password": "oliverharris333"
46+
},
47+
{
48+
"name": "Ava Martinez",
49+
"email": "avamartinez@example.com",
50+
"password": "avamartinez888"
51+
}
52+
]
53+

0 commit comments

Comments
 (0)