So, you’ve been banging your head for some time now trying to understand -
what is this Flask framework and how do I use it
I know, I’ve been there too. But don’t worry, you don’t have to go through all the pain of searching articles for days to understand it’s functionalities.
Here, we’ll make a simple weather application using Flask to understand it’s functionalities and also cover some core concepts like -
- Creating and maintaining virtual environments using pipenv
- How does routes work in Flask
- How to render HTML Templates in Flask
- Interacting with public APIs
- Passing data from Python to HTML
- How to get and process user input
- How to serve static files(CSS, JS, images etc) in Flask
- How to work with environment variables in python
- Deploying your ****Flask app on Heroku
Right, the list seems quite a bit for a simple app but trust me, you’ll zoom past it in no time!
Here’s what we’re gonna build - Live app
And let’s get right into it!
Creating and maintaining virtual environments using pipenv
First, we’ll create a new folder for our project, I’ll name it “SimpleFlaskWeatherApp”, and then initialise a virtual environment.
For creating a virtual environment i’ll be using pipenv rather than virtualenv or pyenv. pipenv was first released in 2018 and it aims to simply the workflow by bringing the packaging concepts from bundlers, npm, yarn etc to python!
It automatically creates and manages the virtualenv for your projects by creating a Pipfile
and Pipfile.lock
without you having to do anything except installing the packages you need!
First install pipenv by entering the command in your terminal-
pip install pipenv
if you have multiple versions of python then type the command,
python3.x -m pip install pipenv
where x would be your choice of python version installed on your system
Now, to create a virtual env, go into your project folder and type -
pipenv shell
this should create a virtualenv for your project with the name of your project folder followed by some random characters.
In my case, you can see it on the right of the image, it’s “SimpleFlaskWeatherApp-0rR_1i25”
If you list the contents of your directory, you would see that pipenv
has created a Pipfile
which it would use to manage your entire project dependency. It looks something like this -
If you now want to install any python package, simple type the following command
pipenv install flask
This would install and make the package available to your project and also add it to your Pipfile
automatically!
If you later want to pass this environment to someone else then you don’t have to create a requirements.txt, just give them your Pipfile.lock
file. Then all they have to do is run the command pipenv install
and pipenv will automatically create a virtualenv and install all the packages that are required and re-create the exact same environment.
How cool is that!
After installing flask, our Pipfile looks something like this -
If you want to exit your virtual environment, simply type exit
and press enter. And to activate it again, type pipenv shell
Since we’ve covered pipenv, let’s move on to creating our project!
For context, this is what our project directory looks like -
How does routes work in Flask
Go ahead and create a file called app.py, we’ll write a starter code for a basic flask application like below -
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "<h1>This is working!</h1>"
if __name__ == '__main__':
app.run()
and run the python file
python app.py
you should get the following output -
In a flask application, the function following the app.route
is the logic that should be run if a user open the route specified in the @app.route
decorator
Since we wrote @app.route('/')
, where “/” means the index route, if the user opens localhost:5000, the index
function logic would be executed and whatever the function returns would be returned to the user.
How to render HTML Templates in Flask
We’ve returned the html as a string for now just to test whether our application works or not, but this is not practical if you want to render more complex pages with data that will be returned by us later in this article.
So, instead of writing the html as a string we will create HTML files and return them instead.
Go ahead and create a templates
folder in your project directory and inside this templates directory create an html file called index.html
Our directory structure looks something like this -
Inside the index.html, write a simple html code as follows -
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask Weather App</title>
</head>
<body>
<h1>This template is working</h1>
</body>
</html>
Now, in our app.py, along with Flask
module also import render_template
module which will allow us to serve html files to the users.
And in the index
function, simply type return render_template('index.html')
So our code looks like this now -
from flask import Flask, render_template # new
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html') # new
if __name__ == '__main__':
app.run()
Now, if you run your application, you should see the output as below -
The render_template
function looks for the html template specified inside the templates folder that we created earlier.
Interacting with public APIs
Now that we have our flask application set up to render html templates, let’s start working with public APIs.
We’ll be using the Current Weather Data of openweathermap.org to get weather data for free
Sign up on the website and subscribe to the Current Weather Data API.
They should provide you with an API key to access that API. You can find it here. We’ll be using this API Key to make API calls to get the current weather data!
First, Let’s do a simple call to the API and make sure that the API is returning data as expected.
We’ll call the API using the requests
library in python as follows -
API = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
city = 'Mumbai'
api_key = "<your_openweather_api_key>"
try:
weather = requests.get(API.format(
city=city, api_key=api_key)).json()
except Exception as e:
print(f"Error in fetching data")
It is always a good idea to use try except
blocks wherever you are not sure about the output
Now this piece of code will make a call to the API using the city
and api_key
variables and store the result in weather
variable
but before we can display this result to the user, we must first check that infact, we have got back a correct result!
In HTTP
status codes, 200 means that the API call was success and you have received some data and 404 means there was some error when you sent the API request.
We’ll be checking the status code of our API call by checking the key value cod
that would be present in the weather
variable after the API call was made. if the value of cod
is 200, that means we have correct data, if it is 404 or if we did not receive an output from the API, then it means we made a mistake while calling the API.
The code for that would be like this -
if weather["cod"] == 200:
# We got correct result
return render_template('index.html')
else:
# We got incorrect/no result
return render_template('error.html')
Create an error.html
file alongside your index.html
file in the templates folder, and just write a simple code to let you know that some error has occured
<h1>Some error ocurred while making the API call</h1>
Ideally, you should have proper error handling for all the status codes like 4xx
and 5xx
, but for the context of this article is should suffice.
Our code for app.py
looks something like this now -
import requests
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
URL = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
city = 'Mumbai'
api_key = "<your_openweather_api_key>"
try:
weather = requests.get(URL.format(
city=city, api_key=api_key)).json()
except Exception as e:
print(f"Error in fetching data")
if weather["cod"] == 200:
return render_template('index.html')
else:
return render_template('error.html')
if __name__ == '__main__':
app.run()
If you restart the app and open localhost:5000, you would still get the “This template is working” message which we wrote in the html.
Passing data from Python to HTML
You must be wondering
Why is the data that we received from the API call not showing in our template?
That’s because we have to pass that data, or infact any data that we want to show to the user, to the template in the render_template
function
Here’s how we would do it -
return render_template('index.html', weather=weather)
And in your index.html
file, simply write the variable name enclosed within two curly braces like below -
...
<body>
<h1>This template is working</h1>
<div>{{ weather }}</div>
</body>
...
Now if you restart your app and refresh the page, you should be able to see the data that is returned by the API we called!
Sweet! now we can pass data from our python code and show it to the user!
Python frameworks like Flask or Django use a templating language for html called Jinja2 which enables us to pass data from our python code and display it to the user and also get data from the html given by user and use it in our python logic, which is how we were able to pass the data from python to html.
But we do not want to show the user all of the data. how do we select only certain parts it?
Since it’s a dictionary, we can access it the way we acess a dictionary, but there is a twist to it.
we cannot access the dictionary like my_dict["key"]
. To do it in html, or rather jinja, we would do my_dict.key
, we have to use the dot notation!
Let’s filter out the raw weather
data and keep only the useful bits! Our index.html
after filtering would look like this -
...
<body>
<h1>This template is working</h1>
<div>
<div>{{ weather.timeOfDay }}</div>
<div>{{ weather.weather.0.main }}</div>
</div>
<div>
<p>{{ weather.name }} <span>{{ weather.sys.country }}</span></p>
</div>
<div>
<p>{{ weather.main.temp }}</p>
<div>
<div>
<p>{{ weather.main.humidity }}</p>
</div>
<div>
<p>{{ weather.wind.speed }}</p>
</div>
</div>
</div>
</body>
...
We can also use control flow like if else
or for loop
in our html file! we’ll wrap all the divs containing reference to weather variable after our h1
tag under an if statement so that we only show the result from the API call if we have some data in the weather variable, else we do not show anything
<body>
<h1>This template is working</h1>
{% if weather %} <!-- new -->
<div>
<div>{{ weather.timeOfDay }}</div>
. . .
</div>
</div>
{% endif %} <!-- new -->
</body>
Now if you restart your app and refresh, you should see something like this -
This is all the data that we have to show the user!
How to get and process user input
Let’s now add a search box that will allow the user to search for places they like and get details of those places. Add a form
and inside that form add a input
search box to get input of city and a button
to submit the query.
...
<h1>This template is working</h1>
<div>
<form method="post" action="{{ url_for('index') }}">
<input type="search" name="city">
<button type="submit">Submit</button>
</form>
</div>
{% if weather %}
...
The form in html needs the action attribute which would typically be a url the html will redirect to once the form is submitted. Here, we provide the url for our index
page itself using the url_for
function inside double curly braces. Note inside the url_for
we give the function name and not the route, that’s because in flask the route and function name should be identical!
This is also one of Jinja2’s features!
Since now are are submitting a post request from our page, we need to handle that in our function as well.
Flask let’s us identify whether a request is GET
request or a POST
request simply by doing request.method
inside the function.
We will now update the index function in our app.py by checking the type of request the function is receiving.
If the request is a GET
request then just render the index.html form without any processing or API calls since it’s the first time user is loading the page.
If the request is a POST
request, then we would read the city name that was submitted by the user and make the API call to get the weather data for that city and return the data to our index.html so that we can show the data to the user
Let’s update the index function to handle different types of requests,
from crypt import methods
import requests
from flask import Flask, render_template, request # new
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST']) # new
def index():
if request.method == 'POST': # new
URL = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
city = request.form.get('city') # new
api_key = '<your_openweather_api_key>'
try:
weather = requests.get(URL.format(
city=city, api_key=api_key)).json()
except Exception as e:
print(f"Error in fetching data")
if weather["cod"] == 200:
return render_template('index.html', weather=weather)
else:
return render_template('error.html')
else:
return render_template('index.html')
if __name__ == '__main__':
app.run()
Now, if you restart the app and refresh the page, you’ll get an empty page because when the page is first loaded the form is not submitted hence it is a GET
request.
If you now enter a city name and press submit, you should get weather data for that city!
But this page looks a bit dull to be honest, let’s add some CSS styling and make it come alive!
How to serve static files(CSS, JS, images etc) in Flask
First, create a static folder in your project root directory alongside the templates folder, and add a style.css
to it
Add some basic code in your style.css
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,600;1,400&display=swap");
*,
*::before,
*::after {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
height: 100vh;
width: 100vw;
font-family: "Source Sans Pro", sans-serif;
margin: 0;
background: linear-gradient(blueviolet, skyblue);
}
And to load this CSS in your html file, add a link
tag to your html
...
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <!-- new -->
<title>Flask Weather App</title>
</head>
...
To load the CSS input html, we again use url_for
but this time we give it two arguments.
The first is the name of our static folder which is static and second we give it a filename which in our case is style.css
You should see something like this,
And now you can customise your page however you like!
Since styling a page is very subjective, i’ll leave it upto you now. However, if you want to style it like how it is in the video at the start, i’ll link my github repo at the end of this article!
How to work with environment variables in python
Remember how we are using an API key to make the API calls. Well that API key is specific to you and should always be hidden from others. You should also never upload this key anywhere, even on github.
But then you might ask,
How can we use it in out project, without having it in our project ?!
Well, we can make use of something called environment variables. We can add some variables to the environment that is running our code, and access those variables in our code whenever we want!
To accomplish this, first create a .env
file in our project root directory
And inside this .env
file, assign your API key to a variable like so
Make sure you don’t add any quotes, just paste your key right after the equals to sign
Now to access this API key in our project, we need to install a library called python-dotenv
pipenv install python-dotenv
And we now import and initialize the packages we just installed in our project
import os #new
from crypt import methods
import requests
from flask import Flask, render_template, request
from dotenv import load_dotenv, find_dotenv # new
load_dotenv(find_dotenv()) # new
app = Flask(__name__)
...
The find_dotenv
function will find the .env file located in our project and the load_dotenv
function will load it into our project so we can use it
Now, to reference the api key variable in our .env file in your project we will make use of the os library that we imported,
...
if request.method == 'POST':
URL = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
city = request.form.get('city')
api_key = os.getenv("OPENW_API_KEY") # new
try:
weather = requests.get(URL.format(
city=city, api_key=api_key)).json()
except Exception as e:
print(f"Error in fetching data")
...
Restart your app and you would see your application is working just like before!
You can add any number of environment variables in the .env file and use them anywhere in your project!
By using environment variables, we can secure information that is private to us so that no other person can misuse it
Deploying your Flask app on Heroku
This project works fine on my laptop but -
what if i want to show it to the people online? How can I deploy my project?
That’s where heroku comes in
Heroku is a cloud platform that supports deployment of projects created in many different languages, even python!
So go ahead and create an account. After you login, you would see the screen something like this
To deploy our flask weather application, we’ll have to create a new app on heroku. So click on new
and select Create new app
after that, type in your App name and click Create app
After that scroll down to Deploying using Heroku Git section
and simply follow these steps,
Let’s deploy our app!
First log in to heroku in your terminal, When you type heroku login
it will open a browser to log you in.
After you’ve logged in, make sure you are in your project root directory, type git init
to initialise a git repository
Now add all the changes and commit to the main branch,
git add .
git commit -m 'pushing to heroku'
Now, set the remote as shown on the heroku website, and push your changes
heroku git:remote -a <name_of_you_app>
git push heroku main
Note: since we are using environment variables, any deployment platform does not support .env files
You’ll have to manually add your environment variables on the website in your app settings
In heroku, go over to the settings tab, you should be able to see Reveal Config Vars button, click on it.
It will now allow you to add your environment variables just as you did in your .env
file!
Now if we open our app typing in the terminal,
heroku open
We see an application error!
This is because we have only pushed our application to the platform, we haven’t run it yet!
To run you python or any application on platform like these, you need to have a Procfile. **Inside a **Procfile you add all the process that you want the platform to execute.
Let’s add a Procfile to our root project folder
Note that the Procfile does not have any extension, this is super important!
And inside the procfile add this line,
Let’s also install gunicorn in our virtual environment by typing the following command in the terminal,
pipenv install gunicorn
Now follow the process to push the changes to heroku again,
git add .
git commit -m 'adding procfile'
git push heroku main
And now if you open your application,
TA-DA! our application is deployed and running online! 🥳
Now you can share what you’ve built over the course of this article with other people!
I know it was quite a long read but hope you got to learn something new, if not new then maybe a better way of doing things.
Likes and Shares would be deeply appreciated!
Please do not hesitate to message me, you can text/follow me on LinkedIn or Instagram or Twitter
Make sure you follow to not miss any future articles! 😉
And as promised, my github repository
Top comments (2)
Great Stuff! Really cool stuff
Thanks Graham!