Crash Course into Python Flask
A discussion of key elements in a Python Flask backend project. This includes preparing a project for deployment and interaction with a frontend.
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 machine. -
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 a web application restart. Everything 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 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 include some interactive steps 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 a pages notebook 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
Creating a 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
app = Flask(__name__)
CORS(app, supports_credentials=True, origins='*')
api = Api(app)
# --- Model class for InfoDb with CRUD naming ---
class InfoModel:
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"]
}
]
def read(self):
return self.data
def create(self, entry):
self.data.append(entry)
# Instantiate the model
info_model = InfoModel()
# --- API Resource ---
class DataAPI(Resource):
def get(self):
return jsonify(info_model.read())
def post(self):
# Add a new entry to InfoDb
entry = request.get_json()
if not entry:
return {"error": "No data provided"}, 400
info_model.create(entry)
return {"message": "Entry added successfully", "entry": entry}, 201
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)
Explore the Python/Flask app with 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/
Accessing Flask Server with JavaScript
JavaScript is used to fetch data from the backend. After data is received, it is formatted into the HTML DOM.
- HTML is used to set up the basics of a table.
- The
<script>contains JavaScript with afetchthat passes the endpoint (URL) and options. The options are critical to communicating request requirements; bad options produce bad results. - Data is extracted and 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>Flask app access using JavaScript</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
url = "http://localhost:5001/api/data";
// set options for cross origin header request
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)
// response is a RESTful "promise" on any successful fetch
.then(response => {
// check for response errors and display
if (response.status !== 200) {
console.error(response.status);
return;
}
// valid response will contain json data
response.json().then(data => {
console.log(data);
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);
}
})
})
}
</script>
Flask app access using JavaScript
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 |
|---|
Add to the InfoDB Array
This example allows you to accept input from the 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
<h3>Add a New User to InfoDB</h3>
<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>
document.getElementById('addUserForm').addEventListener('submit', async function(event) {
event.preventDefault();
const firstName = document.getElementById('firstName').value;
const lastName = document.getElementById('lastName').value;
const residence = document.getElementById('residence').value;
const data = {
FirstName: firstName,
LastName: lastName,
Residence: residence
};
try {
const response = await fetch('http://localhost:5001/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (response.ok) {
document.getElementById('addUserResult').innerHTML = '<span style="color:green">User added!</span>';
document.getElementById('addUserForm').reset();
} else {
document.getElementById('addUserResult').innerHTML = '<span style="color:red">' + (result.error || 'Error adding user') + '</span>';
}
} catch (err) {
document.getElementById('addUserResult').innerHTML = '<span style="color:red">Network error</span>';
}
});
</script>
Add a New User to InfoDB
Stop the Python/Flask process
This script ends the 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: