Skip to content

Commit 37f956d

Browse files
committed
add models and database
1 parent 301b2c0 commit 37f956d

File tree

10 files changed

+297
-0
lines changed

10 files changed

+297
-0
lines changed

README.md

+40
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,42 @@
11
# flask-api-sqlite-db
22
API for Managing test cases and their execution results across multiple test assets, with data stored in a SQLite database.
3+
4+
## Running the web app
5+
6+
To run the Flask application, follow these steps:
7+
8+
1. **Download the project starter code locally**
9+
10+
```bash
11+
git clone https://github.com/john0isaac/flask-api-sqlite-db.git
12+
cd flask-api-sqlite-db
13+
```
14+
15+
1. **Install, initialize and activate a virtualenv using:**
16+
17+
```bash
18+
pip install virtualenv
19+
python -m virtualenv venv
20+
source venv/bin/activate
21+
```
22+
23+
>**Note** - In Windows, the `venv` does not have a `bin` directory. Therefore, you'd use the analogous command shown below:
24+
25+
```bash
26+
source venv\Scripts\activate
27+
```
28+
29+
1. **Install the dependencies:**
30+
31+
```bash
32+
pip install -r requirements.txt
33+
```
34+
35+
1. **Execute the following command in your terminal to start the flask app**
36+
37+
```bash
38+
export DATABASE_FILENAME=testdb.db
39+
export FLASK_APP=src.app
40+
export FLASK_ENV=development
41+
flask run --reload
42+
```

mock_up_data.sql

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
GRANT ALL PRIVILEGES ON DATABASE testdb TO john;
2+
3+
-- TestCase
4+
INSERT INTO `test_case` VALUES (1, 'First Test');
5+
INSERT INTO `test_case` VALUES (2, 'Second Test');
6+
INSERT INTO `test_case` VALUES (3, 'Third Test');
7+
8+
-- Asset
9+
INSERT INTO `asset` VALUES (1, 'First Asset');
10+
INSERT INTO `asset` VALUES (2, 'Second Asset');
11+
INSERT INTO `asset` VALUES (3, 'Third Asset');
12+
13+
-- Execution
14+
INSERT INTO `execution` VALUES (1, 1, 3, TRUE, 'Success');
15+
INSERT INTO `execution` VALUES (2, 2, 2, FALSE, 'Failure');
16+
INSERT INTO `execution` VALUES (3, 3, 1, FALSE, 'Failure');
17+
INSERT INTO `execution` VALUES (4, 1, 3, TRUE, 'Success');
18+
INSERT INTO `execution` VALUES (5, 2, 2, TRUE, 'Success');

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SQLAlchemy==2.0.27
2+
Flask==3.0.2
3+
Flask-SQLAlchemy==3.1.1

src/__init__.py

Whitespace-only changes.

src/app.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from flask import (
2+
Flask,
3+
request,
4+
abort,
5+
jsonify
6+
)
7+
import os
8+
from src.database.models import (
9+
setup_db,
10+
db_drop_and_create_all,
11+
TestCase,
12+
Asset,
13+
Execution
14+
)
15+
16+
17+
app = Flask(__name__)
18+
19+
# Load configuration for prod vs. dev
20+
is_prod_env = "RUNNING_IN_PRODUCTION" in os.environ
21+
if not is_prod_env:
22+
print("Loading config.development.")
23+
app.config.from_object("src.config.development")
24+
setup_db(app)
25+
# db_drop_and_create_all(app)
26+
else:
27+
print("Loading config.production.")
28+
app.config.from_object("src.config.production")
29+
setup_db(app)
30+
# db_drop_and_create_all(app)
31+
32+
PAGINATION_PER_PAGE = 10
33+
34+
def paginate_results(flask_request, selection):
35+
page = flask_request.args.get('page', 1, type=int)
36+
start = (page - 1) * PAGINATION_PER_PAGE
37+
end = start + PAGINATION_PER_PAGE
38+
39+
results = [result.format() for result in selection]
40+
paginated_results = results[start:end]
41+
42+
return paginated_results
43+
44+
@app.route('/')
45+
def index():
46+
return "Welcome to the Test Case Execution API"
47+
48+
@app.route('/tests')
49+
def get_all_tests():
50+
selection = TestCase.query.order_by(TestCase.id).all()
51+
current_test_cases = paginate_results(request, selection)
52+
if len(current_test_cases) == 0:
53+
abort(404)
54+
return jsonify({
55+
'success': True,
56+
'test_cases': current_test_cases,
57+
'total_test_cases': len(TestCase.query.all())
58+
})
59+
60+
@app.errorhandler(404)
61+
def not_found(error):
62+
return jsonify({
63+
"success": False,
64+
"error": 404,
65+
"message": "Not found"
66+
}), 404
67+
68+
@app.errorhandler(422)
69+
def unprocessable(error):
70+
return jsonify({
71+
"success": False,
72+
"error": 422,
73+
"message": "unprocessable"
74+
}), 422
75+
76+
@app.errorhandler(400)
77+
def bad_request(error):
78+
return jsonify({
79+
"success": False,
80+
"error": 400,
81+
"message": "bad request"
82+
}), 400
83+
84+
@app.errorhandler(405)
85+
def method_not_allowed(error):
86+
return jsonify({
87+
"success": False,
88+
"error": 405,
89+
"message": "method not allowed"
90+
}), 405
91+
92+
if __name__ == '__main__':
93+
app.run()

src/config/__init__.py

Whitespace-only changes.

src/config/development.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import os
2+
from pathlib import Path
3+
4+
DEBUG = True
5+
6+
database_filename = os.environ["DATABASE_FILENAME"]
7+
BASE_DIR = Path(__file__).resolve().parent.parent
8+
database_dir = os.path.join(BASE_DIR, "database")
9+
DATABASE_URI = f"sqlite:///{os.path.join(database_dir, database_filename)}"

src/database/__init__.py

Whitespace-only changes.

src/database/models.py

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""
2+
Models for MySQL
3+
4+
"""
5+
from typing import List
6+
7+
from sqlalchemy import ForeignKey, String, Boolean
8+
from sqlalchemy.orm import Mapped, mapped_column, relationship
9+
from flask_sqlalchemy import SQLAlchemy
10+
from sqlalchemy.orm import DeclarativeBase
11+
12+
class Base(DeclarativeBase):
13+
pass
14+
15+
db = SQLAlchemy(model_class=Base)
16+
17+
'''
18+
setup_db(app)
19+
binds a flask application and a SQLAlchemy service
20+
'''
21+
22+
def setup_db(app):
23+
app.config["SQLALCHEMY_DATABASE_URI"] = app.config.get('DATABASE_URI')
24+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
25+
db.app = app
26+
db.init_app(app)
27+
with app.app_context():
28+
db.create_all()
29+
30+
'''
31+
db_drop_and_create_all()
32+
drops the database tables and starts fresh
33+
can be used to initialize a clean database
34+
'''
35+
36+
def db_drop_and_create_all(app):
37+
with app.app_context():
38+
db.drop_all()
39+
db.create_all()
40+
41+
#----------------------------------------------------------------------------#
42+
# Models.
43+
#----------------------------------------------------------------------------#
44+
45+
class TestCase(db.Model):
46+
__tablename__ = "test_case"
47+
48+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
49+
name: Mapped[str] = mapped_column(String(255), nullable=False)
50+
executions: Mapped[List["Execution"]] = relationship("Execution", back_populates="test_case")
51+
52+
def __init__(self, name):
53+
self.name = name
54+
55+
def insert(self):
56+
db.session.add(self)
57+
db.session.commit()
58+
59+
def update(self):
60+
db.session.commit()
61+
62+
def delete(self):
63+
db.session.delete(self)
64+
db.session.commit()
65+
66+
def format(self):
67+
return {
68+
'id': self.id,
69+
'name': self.name
70+
}
71+
72+
class Asset(db.Model):
73+
__tablename__ = "asset"
74+
75+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
76+
name: Mapped[str] = mapped_column(String(255), nullable=False)
77+
executions: Mapped[List["Execution"]] = relationship("Execution", back_populates="asset")
78+
79+
def __init__(self, name):
80+
self.name = name
81+
82+
def insert(self):
83+
db.session.add(self)
84+
db.session.commit()
85+
86+
def update(self):
87+
db.session.commit()
88+
89+
def delete(self):
90+
db.session.delete(self)
91+
db.session.commit()
92+
93+
def format(self):
94+
return {
95+
'id': self.id,
96+
'name': self.name
97+
}
98+
99+
class Execution(db.Model):
100+
__tablename__ = "execution"
101+
102+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
103+
test_case_id: Mapped[int] = mapped_column(ForeignKey("test_case.id"))
104+
test_case: Mapped["TestCase"] = relationship("TestCase", back_populates="executions")
105+
asset_id: Mapped[int] = mapped_column(ForeignKey("asset.id"))
106+
asset: Mapped["Asset"] = relationship("Asset", back_populates="executions")
107+
passed: Mapped[bool] = mapped_column(Boolean, nullable=False)
108+
details: Mapped[str] = mapped_column(String(500))
109+
110+
def __init__(self, test_case_id, asset_id, passed, details):
111+
self.test_case_id = test_case_id
112+
self.asset_id = asset_id
113+
self.passed = passed
114+
self.details = details
115+
116+
def insert(self):
117+
db.session.add(self)
118+
db.session.commit()
119+
120+
def update(self):
121+
db.session.commit()
122+
123+
def delete(self):
124+
db.session.delete(self)
125+
db.session.commit()
126+
127+
def format(self):
128+
return {
129+
'id': self.id,
130+
'test_case_id': self.test_case_id,
131+
'asset_id': self.asset_id,
132+
'passed': self.passed,
133+
'details' : self.details
134+
}

src/database/testdb.db

16 KB
Binary file not shown.

0 commit comments

Comments
 (0)