Skip to content

[feature]: Add Flask + SQLite Sample App with Keploy Integration #2798 #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
Binary file added flask-sqlite/.coverage.keploy
Binary file not shown.
4 changes: 4 additions & 0 deletions flask-sqlite/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[run]
omit =
/usr/*
sigterm = true
35 changes: 35 additions & 0 deletions flask-sqlite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Python bytecode
__pycache__/
*.py[cod]
*.pyo

# SQLite DB file (do not commit local dev DB)
*.db
instance/

# Keploy reports (tests are OK, reports are not needed)
keploy/reports/

# Coverage reports
coverage.*
*.cover
.hypothesis/

# Keploy traces/cache
*.log
keploy/test-set-*/coverage.*
keploy/test-set-*/mocks/

# Environment files
.env
.venv/
env/
venv/

# IDE files
.vscode/
.idea/

# macOS/Linux system files
.DS_Store
Thumbs.db
126 changes: 126 additions & 0 deletions flask-sqlite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Flask + SQLite Sample App with Keploy Integration

This is a simple **Student Management REST API** built using Python's Flask framework and SQLite for storage. It demonstrates basic **CRUD operations** (Create, Read, Update, Delete) and showcases how to write **API tests with [Keploy](https://keploy.io)** by auto-capturing HTTP calls as test cases.

---

## 🚀 Features

- 🧑 Add, retrieve, update, and delete students
- 💾 Uses SQLite — no external DB setup required
- 🔌 RESTful API with JSON input/output
- ✅ Auto-generate test cases using [Keploy CLI](https://docs.keploy.io)
- 🔄 Replay & validate API responses with Keploy

---

## 📦 Tech Stack

- **Flask** — Lightweight web framework
- **Flask-SQLAlchemy** — ORM for SQLite
- **SQLite** — Built-in relational DB
- **Keploy** — Testing toolkit for API auto-mocking and regression testing

---

## 🛠 Installation

1. **Clone the repository**
```bash
git clone https://github.com/<your-username>/samples-python.git
cd samples-python/flask-sqlite
```

2. Set up virtual environment (optional)
```bash
python3 -m venv venv
source venv/bin/activate
```

3. Install Dependencies

```bash
pip install -r requirements.txt
```
4. Run the Flask app

```bash
python app.py
```
---

## API Endpoints

```bash

| Method | Endpoint | Description |
| ------ | ---------------- | -------------------- |
| GET | `/students` | Get all students |
| POST | `/students` | Add a new student |
| PUT | `/students/<id>` | Update student by ID |
| DELETE | `/students/<id>` | Delete student by ID |

```
---

## Sample Curl Commands

### Add a student

```bash
curl -X POST http://localhost:5000/students \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "age": 21}'

# Get all students
curl http://localhost:5000/students

# Update student
curl -X PUT http://localhost:5000/students/1 \
-H "Content-Type: application/json" \
-d '{"name": "Alice Updated", "age": 22}'

# Delete student
curl -X DELETE http://localhost:5000/students/1

```
---

## Running Keploy Tests

Step 1: Record Tests
Start Keploy in record mode:

```bash
keploy record --command "python app.py"
```
Send some API requests via curl or Postman to generate test cases.

Step 2: Replay Tests
After recording, stop the app and run:

```bash
keploy test --command "python app.py"
```
> Keploy will replay the previously captured requests and validate responses.

## Folder Structure

```bash
flask-sqlite/
├── app.py # Flask app entry point
├── models.py # Student model
├── requirements.txt # Python dependencies
├── keploy.yml # Keploy config file
├── keploy/ # Auto-generated test cases
└── README.md # You are here!
```
---

## Contributing
> Want to improve or add another example (e.g. FastAPI, SQLModel, etc.)? Contributions are welcome! Fork this repo, create your example folder, and submit a PR.

## About Keploy
Keploy is a developer-friendly open-source testing toolkit that auto-generates test cases from API calls in real-time and replays them to catch regressions — without writing any test code.

> Built with ❤️ for the Open Source Community.
80 changes: 80 additions & 0 deletions flask-sqlite/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from flask import Flask, request, jsonify
from models import db, Student

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)

# 🔧 Validation Helper Function
def validate_student_data(data, partial=False):
if not data:
return "Missing JSON body"

if not partial:
if 'name' not in data:
return "Missing 'name'"
if 'age' not in data:
return "Missing 'age'"

if 'name' in data:
if not isinstance(data['name'], str) or not data['name'].strip():
return "Invalid 'name'"

if 'age' in data:
if not isinstance(data['age'], int):
return "Invalid 'age'"

return None # No error

@app.route('/')
def home():
return jsonify({"message": "Flask Student API"}), 200

@app.route('/students', methods=['GET'])
def get_students():
students = Student.query.all()
return jsonify([
{"id": s.id, "name": s.name, "age": s.age} for s in students
])

@app.route('/students', methods=['POST'])
def add_student():
data = request.get_json()
Copy link
Preview

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request payload fields (name and age) are used without validation, which may cause KeyError. Consider validating input and returning a 400 error for missing or invalid fields.

Suggested change
data = request.get_json()
data = request.get_json()
if not data or 'name' not in data or 'age' not in data or not isinstance(data['name'], str) or not isinstance(data['age'], int):
return jsonify({"error": "Invalid input. 'name' must be a string and 'age' must be an integer."}), 400

Copilot uses AI. Check for mistakes.

error = validate_student_data(data)
if error:
return jsonify({"error": error}), 400

student = Student(name=data['name'].strip(), age=data['age'])
db.session.add(student)
db.session.commit()
return jsonify({"id": student.id}), 201

@app.route('/students/<int:id>', methods=['PUT'])
def update_student(id):
student = Student.query.get_or_404(id)
data = request.get_json()
error = validate_student_data(data, partial=True)
if error:
return jsonify({"error": error}), 400

if 'name' in data:
student.name = data['name'].strip()
if 'age' in data:
student.age = data['age']

db.session.commit()
return jsonify({"message": "Updated"}), 200

@app.route('/students/<int:id>', methods=['DELETE'])
def delete_student(id):
student = Student.query.get_or_404(id)
db.session.delete(student)
db.session.commit()
return jsonify({"message": "Deleted"}), 200

if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
67 changes: 67 additions & 0 deletions flask-sqlite/keploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Keploy (2.6.11)
path: ""
appId: 0
appName: flask-sqlite
command: .venv/bin/python app.py
templatize:
testSets: []
port: 0
e2e: false
dnsPort: 26789
proxyPort: 16789
debug: false
disableTele: false
disableANSI: false
containerName: ""
networkName: ""
buildDelay: 30
test:
selectedTests: {}
globalNoise:
global: {}
test-sets: {}
delay: 5
host: ""
port: 0
apiTimeout: 5
skipCoverage: false
coverageReportPath: ""
ignoreOrdering: true
mongoPassword: default@123
language: ""
removeUnusedMocks: false
fallBackOnMiss: false
jacocoAgentPath: ""
basePath: ""
mocking: true
ignoredTests: {}
disableLineCoverage: false
disableMockUpload: true
useLocalMock: false
updateTemplate: false
mustPass: false
maxFailAttempts: 5
maxFlakyChecks: 1
record:
filters: []
basePath: ""
recordTimer: 0s
configPath: ""
bypassRules: []
generateGithubActions: false
keployContainer: keploy-v2
keployNetwork: keploy-network
cmdType: native
contract:
services: []
tests: []
path: ""
download: false
generate: false
driven: consumer
mappings:
servicesMapping: {}
self: s1
inCi: false

# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file.
Copy link
Preview

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the comment: configration should be configuration.

Suggested change
# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file.
# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configuration file.

Copilot uses AI. Check for mistakes.

2 changes: 2 additions & 0 deletions flask-sqlite/keploy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

/reports/
61 changes: 61 additions & 0 deletions flask-sqlite/keploy/test-set-0/tests/test-1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generated by Keploy (2.6.11)
version: api.keploy.io/v1beta1
kind: Http
name: test-1
spec:
metadata: {}
req:
method: GET
proto_major: 1
proto_minor: 1
url: http://localhost:5000/students
header:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Cookie: _ga_9C3YK4FNWZ=GS2.1.s1746835762$o1$g1$t1746835798$j0$l0$h0; _ga=GA1.1.370021656.1746835763; _clck=1e6823p%7C2%7Cfww%7C0%7C1996; __hstc=181257784.e3804237d83ad93b9ef5fdbdd48354d1.1750295949745.1750295949745.1750295949745.1; hubspotutk=e3804237d83ad93b9ef5fdbdd48354d1; __hssrc=1; _clsk=dk2mvr%7C1750295950556%7C1%7C1%7Cl.clarity.ms%2Fcollect; messagesUtk=a1a71bf707fd412b8e4bf788336568d9
Host: localhost:5000
Priority: u=0, i
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: "1"
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0
body: ""
timestamp: 2025-06-19T09:36:00.825424089+05:30
resp:
status_code: 200
header:
Content-Length: "3"
Content-Type: application/json
Date: Thu, 19 Jun 2025 04:06:00 GMT
Server: Werkzeug/3.1.3 Python/3.12.3
body: |
[]
status_message: OK
proto_major: 0
proto_minor: 0
timestamp: 2025-06-19T09:36:02.898108619+05:30
objects: []
assertions:
noise:
header.Date: []
created: 1750305962
curl: |
curl --request GET \
--url http://localhost:5000/students \
--header 'Sec-Fetch-User: ?1' \
--header 'Cookie: _ga_9C3YK4FNWZ=GS2.1.s1746835762$o1$g1$t1746835798$j0$l0$h0; _ga=GA1.1.370021656.1746835763; _clck=1e6823p%7C2%7Cfww%7C0%7C1996; __hstc=181257784.e3804237d83ad93b9ef5fdbdd48354d1.1750295949745.1750295949745.1750295949745.1; hubspotutk=e3804237d83ad93b9ef5fdbdd48354d1; __hssrc=1; _clsk=dk2mvr%7C1750295950556%7C1%7C1%7Cl.clarity.ms%2Fcollect; messagesUtk=a1a71bf707fd412b8e4bf788336568d9' \
--header 'Accept-Language: en-US,en;q=0.5' \
--header 'Sec-Fetch-Dest: document' \
--header 'Sec-Fetch-Mode: navigate' \
--header 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
--header 'Priority: u=0, i' \
--header 'Accept-Encoding: gzip, deflate, br, zstd' \
--header 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:139.0) Gecko/20100101 Firefox/139.0' \
--header 'Upgrade-Insecure-Requests: 1' \
--header 'Sec-Fetch-Site: none' \
--header 'Host: localhost:5000' \
--header 'Connection: keep-alive' \
Loading