| Overview | Intro | Anatomy | Play | Ideation | Sign-Up | Login | Profile | Style | Deploy | Scrum |
Anatomy of Python Flask
A discussion of key elements in a Python Flask backend project. This includes preparing a project for deployment.
Time to Review the README
Open the README of the Flask project and work through it. This is how you will get the backend app running on your localhost. Note, that some familiarity with tools like Python, GitHub, and code editors (e.g., VSCode) is assumed.
Files and Directories in this Project
These are some of the key files and directories in this project. Understanding these files will speed up your development.
-
README.md: This file contains instructions for setting up the necessary tools and cloning the project. A README file is a standard component of all properly set up GitHub projects. -
main.py: This Python source file is used to run the project. Running this file starts a Flask web server locally on localhost. During development, this is the file you use to run, test, and debug the project. -
__init__.py: This file defines the Flask app, loads environment variables (from.env), and sets up security (like CORS) and database connections (SQLite or RDS). It acts as the main configuration and initialization hub for your backend. -
Dockerfileanddocker-compose.yml: These files are used to run and test the project in a Docker container. They allow you to simulate the project’s deployment on a server, such as an AWS EC2 instance. Running these files helps ensure that your tools and dependencies work correctly on a clean machines. -
instances: This directory is the standard location for storing data files that you want to remain on the server. For example, SQLite database files can be stored in this directory. Files stored in this location will persist after web application restart, everyting outside of instances will be recreated at restart. -
api: This directory contains code that receives and responds to requests from external servers. It serves as the interface between the external world and the logic and code in the rest of the project. -
model: This directory holds class definitions for interacting with the database (like SQLAlchemy models). Models can also connect to third-party APIs or machine learning libraries. By keeping data logic inmodel, you make your code reusable and keep theapicode simple and clean. -
requirements.txt: This file lists the dependencies required to turn this Python project into a Flask/Python project. It may also include other backend dependencies, such as dependencies for working with a database. -
.gitignore: This file specifies elements to be excluded from version control. Files are excluded when they are derived and not considered part of the project’s original source. In the VSCode Explorer, you may notice some files appearing dimmed, indicating that they are intentionally excluded from version control based on the rules defined in .gitignore.
Flask Server
As a developer you will create or use a Flask app/server. This lesson will diverge into some interactive steps right now to help you understand the basic code elements in a Flask server with RESTful endpoints.
Note: This minimal, single-file server is just for learning. In real projects, code is split up for:
- Routes (API endpoints)
- Models (data and database)
- Config (settings)
- Logic (functions, helpers)
Later, you’ll see how these parts are organized across multiple files.
Installing dependencies
In a production server, the requirements.txt file lists all the packages you need to install, for example:
flask
flask_cors
flask_restful
Normally, you would run:
pip install -r requirements.txt
But since we’re working in the pages repository and focusing on Flask usage, we’ll install the needed packages manually below.
# The following lines are required packages for Flask execution
!pip install flask
!pip install flask_cors
!pip install flask_restful
Start a Python/Flask Server
This starts on Port 5001 and supports two endpoints / and /api/data.
- The
/api/dataendpoint now uses a model class to store and manage data, following best practices for larger systems. - The API supports both
GET(read all data) andPOST(create new data) requests, matching CRUD naming conventions. - This structure makes it easier to expand with more endpoints and features as your project grows.
%%python --bg
# Refactored: Use CRUD naming (read, create) in InfoModel
from flask import Flask, jsonify, request
from flask_cors import CORS
from flask_restful import Api, Resource
# --- Flask app and CORS setup ---
app = Flask(__name__)
CORS(app, supports_credentials=True, origins='*')
#--- Flask-RESTful API setup ---
api = Api(app)
# --- Model class for InfoDb with CRUD naming ---
class InfoModel:
# Initialize Model with sample data
def __init__(self):
self.data = [
{
"FirstName": "John",
"LastName": "Mortensen",
"DOB": "October 21",
"Residence": "San Diego",
"Email": "jmortensen@powayusd.com",
"Owns_Cars": ["2015-Fusion", "2011-Ranger", "2003-Excursion", "1997-F350", "1969-Cadillac", "2015-Kuboto-3301"]
},
{
"FirstName": "Shane",
"LastName": "Lopez",
"DOB": "February 27",
"Residence": "San Diego",
"Email": "slopez@powayusd.com",
"Owns_Cars": ["2021-Insight"]
}
]
# Read method to get all entries
def read(self):
return self.data
# Create method to add a new entry
def create(self, entry):
self.data.append(entry)
entry['id'] = len(self.data) - 1
return entry
# Instantiate the model
info_model = InfoModel()
# --- API Resource Definition ---
class DataAPI(Resource):
# GET method to retrieve all entries
def get(self):
return jsonify(info_model.read())
# POST method to add a new entry, requires validation
def post(self):
try:
# extract JSON data from raw body of the request
entry = request.get_json()
# sample validation of user input, in production you should do something more robust
if not entry:
return {'message': "No data provided"}, 400
if 'FirstName' not in entry or len(entry['FirstName'].strip()) <= 1:
return {'message': 'FirstName is missing, or is less than 2 characters'}, 400
if 'LastName' not in entry or len(entry['LastName'].strip()) == 0:
return {'message': 'LastName is required'}, 400
# Add entry and return it with ID
return {'message': "Entry added successfully", "body": info_model.create(entry)}, 201
except Exception as e:
# Catch any unexpected server error
return {'message': f"Server error: {str(e)}"}, 500
# Add resource to API to establish endpoint
api.add_resource(DataAPI, '/api/data')
# Wee can use @app.route for HTML endpoints, this will be style for Admin UI
@app.route('/')
def say_hello():
html_content = """
<html>
<head>
<title>Hello</title>
</head>
<body>
<h2>Hello, World!</h2>
</body>
</html>
"""
return html_content
if __name__ == '__main__':
app.run(port=5001)
Test the Python/Flask server using Linux
This script discovers the running flask process on your machine using Linux commands.
- lsof - list open files
lsofandawkreturn the process id, sopscan list details, the vericle bar is called apipe. A pipe flows output from one command to the next.curlis a Linux utiltity that is easiest way to test if web server is responding
%%script bash
# After app.run(), see the the Python open files on port 5001
echo "Python open files on port 5001"
lsof -i :5001
# see the the Python process
echo
echo "Python process"
lsof -i :5001 | awk '/Python/ {print $2}' | xargs ps
# show ontent of the Python server using curl
echo
echo "Content of the Python root endpoint (aka /), using curl",
curl http://localhost:5001/
Python open files on port 5001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Python 67706 johnmortensen 3u IPv4 0x28b12545108683cd 0t0 TCP localhost:commplex-link (LISTEN)
Python process
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Python 67706 johnmortensen 3u IPv4 0x28b12545108683cd 0t0 TCP localhost:commplex-link (LISTEN)
Python process
PID TT STAT TIME COMMAND
67706 ?? S 0:00.10 /opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/Resources/Python.app/Contents/MacOS/Python
Content of the Python root endpoint (aka /), using curl,
PID TT STAT TIME COMMAND
67706 ?? S 0:00.10 /opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/Resources/Python.app/Contents/MacOS/Python
Content of the Python root endpoint (aka /), using curl,
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 134 100 1 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 034 0 0 114k 0 --:--:-- --:--:-- --:--:-- 130k
Dload Upload Total Spent Left Speed
100 134 100 1 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 034 0 0 114k 0 --:--:-- --:--:-- --:--:-- 130k
<html>
<head>
<title>Hello</title>
</head>
<body>
<h2>Hello, World!</h2>
</body>
</html>
</h2>
</body>
</html>
FE GET: Reading data from Flask Server with JavaScript from BE
JavaScript is used to fetch rows of data from backend, after data is received it is formated into the HTML DOM as a table
- HTML is used to setup basics of a table
- The
<script>, has javascript afetchthat passes endpoint (url) and options. The options are critical to communicating request requirements, bad options produce bad results. - Data extracted and is written to the DOM. Headings are static in the code, but rows are dynamically extracted according to the information contained in the server.
%%html
<h1>FE GET method access</h1>
<p>This code extracts data "live" from a local Web Server with JavaScript fetch. Additionally, it formats the data into
a table.</p>
<!-- Head contains information to Support the Document -->
<!-- HTML table fragment for page -->
<table id="demo" class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Residence</th>
</tr>
</thead>
<tbody id="result">
<!-- javascript generated data -->
</tbody>
</table>
<script>
{ // Jupyter Notebook container to avoid variable name conflicts
// prepare HTML result container for new output
const resultContainer = document.getElementById("result");
// prepare URL
const url = "http://localhost:5001/api/data";
// set method to GET, supporting all network options
const options = {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'include', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
},
};
// fetch the API
fetch(url, options)
.then(response => {
if (response.status !== 200) {
throw new Error(`Fetch error: status ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(data); // log data to console for debugging
for (const row of data) {
// tr and td build out for each row
const tr = document.createElement("tr");
const firstname = document.createElement("td");
const lastname = document.createElement("td");
const residence = document.createElement("td");
// data is specific to the API
firstname.innerHTML = row.FirstName;
lastname.innerHTML = row.LastName;
residence.innerHTML = row.Residence;
// this builds each td into tr
tr.appendChild(firstname);
tr.appendChild(lastname);
tr.appendChild(residence);
// add HTML to container
resultContainer.appendChild(tr);
}
})
.catch(error => {
const message = `Network or fetch error: ${error}`;
const p = document.createElement("p");
p.innerHTML = `${message}`;
resultContainer.appendChild(p);
});
}
</script>
FE GET method access
This code extracts data "live" from a local Web Server with JavaScript fetch. Additionally, it formats the data into a table.
| First Name | Last Name | Residence |
|---|
FE POST: Add data from Flask Server with JavaScript to BE
This example allows you to accept input from user and add it to the InfoDB.
- Enter a first name, last name, and residence, then click ‘Add User’ to submit.
- The form sends a POST request to the Flask API and updates the InfoDB in memory.
- Re-run the data table above to see your new entry appear.
- Data is persistent only while the server is running; stopping the server will clear the InfoDB to code defaults.
- For true persistence, a database is needed instead of an in-memory array.
%%html
<h1>FE POST new user</h1>
<form id="addUserForm">
<label for="firstName">First Name:</label>
<input type="text" id="firstName" name="firstName" required><br>
<label for="lastName">Last Name:</label>
<input type="text" id="lastName" name="lastName" required><br>
<label for="residence">Residence:</label>
<input type="text" id="residence" name="residence" required><br>
<button type="submit">Add User</button>
</form>
<div id="addUserResult"></div>
<script>
// Handle form submission action
document.getElementById('addUserForm').addEventListener('submit', function (event) {
event.preventDefault();
// gather form data
const firstName = document.getElementById('firstName').value;
const lastName = document.getElementById('lastName').value;
const residence = document.getElementById('residence').value;
// prepare data object in JSON format
const data = {
FirstName: firstName,
LastName: lastName,
Residence: residence
};
// prepare fetch payload
const url = 'http://localhost:5001/api/data';
const options = {
method: 'POST',
mode: 'cors',
cache: 'default',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
};
// perform fetch to add user
fetch(url, options)
// Always extract JSON, then handle error or success
.then(response => response.json()
.then(json => {
if (!response.ok) {
throw new Error(json.message || json.error || `Fetch error: status ${response.status}`);
}
return json.body; // User details are wrpped in body
})
// on success, show user added through fetch
.then(user => {
const message = `Added ID ${user.id}: ${user.FirstName} ${user.LastName} from ${user.Residence}`;
document.getElementById('addUserResult').innerHTML = `<span style="color:green">${message}</span>`;
}))
// on failure, catch the fetch error and display message
.catch(error => {
document.getElementById('addUserResult').innerHTML = `<span style='color:red'>${error}</span>`;
console.error(error);
});
});
</script>
FE POST new user
Stop the Python/Flask process
This script ends Python/Flask process using pipes to obtain the python process. Then echo the python process to kill -9.
%%script bash
python_ps=$(lsof -i :5001 | awk '/Python/ {print $2}')
echo "Killing python process with PID: $python_ps"
echo $python_ps | xargs kill -9
Killing python process with PID: