Flask is a popular web development framework using Python for creating dynamic web apps. This project shows you how to run a Flask server on a Pi 4 that can host a dynamic website running on your home, school or office network.
As an example app, the project obtains historical climate statistics from the Met Office web service in the UK. It allows the user to choose a particular Weather Station to obtain climate statistics from and then filters the data to show the temperature and rainfall information for the same month in each year using the historical record.
The web app runs in a browser or any mobile device connected to the network.
Python 3 and the Flask server are installed by default in the standard Raspberry Pi operating system, Raspbian.
Flask apps are based around Python functions that handle the dynamic elements of your app. They can be developed and tested separately and then incorporated into the main app.
import requests
import datetime
base = 'https://www.metoffice.gov.uk/pub/data/weather/uk/climate/stationdata/'
stations = {'Aberporth': 'aberporth', 'Oxford': 'oxford',
'Heathrow': 'heathrow', 'Wick Airport': 'wickairport'}
months = {'January': 1, 'February': 2, 'March': 3, 'April': 4, 'May': 5,
'June': 6, 'July': 7, 'August': 8, 'September': 9, 'October': 10,
'November': 11, 'December': 12}
def get_url(station) -> str:
"""Return the station URL from the name"""
url = base + station + 'data.txt'
return url
def get_data(from_year=2000, for_month=1, station='heathrow') -> list:
"""Return a filtered list of data from the station, for the month, from each year
upto current year"""
current_year = datetime.datetime.now().year
# Get all station data
data_str = requests.get(get_url(station)).text
# Convert into list where each item is list by year and month
data_lst = [d.split() for d in data_str.splitlines()]
# Filter list by year and month
filter_lst = [data_mth for data_mth in data_lst for year in range(from_year, current_year + 1)
if data_mth[0] == str(year) and data_mth[1] == str(for_month)]
return filter_lst
if __name__ == '__main__':
data = get_data(2018, 1)
for d in data:
print(d)
“stations.py” is a Python module which contains the definition of data structures and functions to help manage the weather station information and convert month names into an index for each month.
The app gets its data from the Met Office website. Each weather station has a different URL so there’s a function get_url() that generates the correct URL from the name of the station.
The get_data() function retrieves data from the weather station URL and processes it. It uses Python’s powerful text processing capabilities to convert the text based response into a Python list. The list itself contains values for the same month of the year, starting from the selected year, up until the present one. It’s this list of climate data that will be displayed as a table in the web app.
Flask uses Templates to define HTML code for the look and feel of the web app. Templates are the mechanism by which static and dynamic elements of the web app are combined.
<!doctype html>
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="static/stylesheet.css"/>
</head>
<body style="background-image: url({{url_for('static', filename='background.jpg')}})">
{% block body %}
{% endblock %}
</body>
</html>
The starting page for the web app is an entry form that allows the user to enter the starting year for the data, the month to be filtered for each year and the weather station location. The form uses another template to define these HTML elements and allows the selections to be used in the Python code that follows.
{% extends 'base.html' %}
{% block body %}
<h2>{{ title }}</h2>
<form method='POST' action='/search'>
<table>
<p>Use this form to submit a search request:</p>
<tr><td>Starting Year:</td><td><input name='from_year' type='TEXT' value='2000'></td></tr>
<tr><td>Month</td><td><select name='month' type='TEXT'>
{% for month in months %}
<option value="{{ month }}" SELECTED>{{ month }}</option>"
{% endfor %}
</select></td></tr>
<tr><td>Weather station</td><td><select name='station'>
{% for station in stations %}
<option value= "{{ station }}" SELECTED>{{ station }}</option>"
{% endfor %}
</select></td></tr>
</table>
<p><input value='Search' type='SUBMIT'></p>
</form>
{% endblock %}
The data that’s returned after clicking the Search Button in the Entry Form is displayed in another HTML page. This has a table displaying the search terms used and then builds a table for each row of the data fetched by the get_data() function.
{% extends 'base.html' %}
{% block body %}
<h2>Monthly climate results from {{ station }} weather station</h2>
<p>You submitted the following data:</p>
<table>
<tr><td>From Year</td><td>{{ year }}</td></tr>
<tr><td>Month</td><td>{{ month }}</td></tr>
<tr><td>Weather station</td><td>{{ station }}</td></tr>
</table>
<p>When the year is "{{ year }}" and month is "{{ month }}", the following
results are returned from "{{ station }}" weather station:</p>
<table>
<tr><td>Year</td><td>Month</td><td>Max Temp (C)<td>Min Temp (C)</td><td>Frost (Days)</td><td>Rain (mm)</td><td>Sun (Hr)</td></td></tr>
{% for row in results %}
<tr>
{% for item in row %}
<td> {{item}} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endblock %}
The templates uses static files for the background image and for the CSS stylesheet.
body {
font-family: Verdana, Geneva, Arial, sans-serif;
font-size: medium;
margin-top: 5%;
margin-bottom: 5%;
margin-left: 10%;
margin-right: 10%;
border: 1px dotted gray;
padding: 10px 10px 10px 10px;
}
h2 {
font-size: 150%;
color: maroon;
}
table {
margin-left: 20px;
margin-right: 20px;
caption-side: bottom;
background-color: #fafcff;
border: 1px solid #909090;
color: #2a2a2a;
padding: 5px 5px 2px;
border-collapse: collapse;
}
td, th {
padding: 5px;
text-align: left;
border: thin dotted gray;
}
The main Flask application file is quite simple. It imports all the required Flask modules and then defines a function for each of the web pages used in the app. Each function uses a Python decorator that defines the url path to a page. These paths are appended to the base url when the app runs.
The entry_page() function takes as its arguments the name of the template, and passes in the title for the page and the weather station name and month data structures. These are used by the drop-down boxes to show a list of values.
The do_search() function retrieves the search parameters and passes them to the get_data() function that retrieves the station data and filters the results. These are then displayed in a dynamic table.
Finally the call to app.run sets the host argument to 0.0.0.0 – this will allow any device on the network to access the web app. The debug argument is set to true which allows the code to be changed without restarting Flask.
from flask import Flask, render_template, request
import stations
app = Flask(__name__)
@app.route('/')
def entry_page():
return render_template('entry.html',
title='Welcome to Met Office historical climate data search',
stations=stations.stations,
months=stations.months,)
@app.route('/search', methods=['POST'])
def do_search():
the_year = request.form['from_year']
the_month = request.form['month']
the_station = request.form['station']
the_results = stations.get_data(int(the_year),
stations.months[the_month],
stations.stations[the_station])
return render_template('results.html',
year=the_year,
month=the_month,
station=the_station,
results=the_results)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
cd /home/pi/flask
python3 climate_search.py
You should get a message informing you that the Flask server is running at URL http://0.0.0.0:5000
You should be presented with the entry page to the app where you can set your search parameters and click the search button. The data should be retrieved from the weather station and filtered by the same month for each year in the selection. Some of the stations have historical records dating back to the 1800’s!
If you have any errors the web page will display a listing with the error message and the line number in the file where the error occurs. Look at the last line in the listing and trace back to fix the error, then refresh the browser.
The principles used on this project can be adapted to build your own dynamic web apps that can display whatever information you want. Once the basic site us up and running it can be extended by adding further Python functions, templates and Flask route definitions to build out the site.
There are web hosting services like Pythonanywhere.com that can host your Flask apps securely, so once everything is running and tested to your satisfaction, upload the code to a production server to launch your own internet site.
From a quick tap to smashing that love button and show how much you enjoyed this project.