Search justacoding.blog. [Enter] to search. Click anywhere to close.

August 15th, 2022

Build A Python Weather App

In this simple guide, we’ll use the OpenWeather API to fetch the 5 day weather forecast based on a user-supplied location using Python.

This is a suitable Python project for beginners — the weather forecast script itself is fairly small, and we’ll go over it in considerable detail within this article.

So with that said, let’s begin!

The weather forecast script

This is the Python weather forecast script in it’s entirety.

It should be all you need to get up and running, just remember to substitute {YOUR_API_KEY} with your actual API key (more on that later):

import requests
import datetime
from time import strftime

# Use Geolocation API to fetch long/lat of target location
city = input("Enter the city: ")
state = input("Enter the state: ")
country = input("Enter the country: ")

query_parts = []
if len(city) > 0:
    query_parts.append(city)

if len(state) > 0:
    query_parts.append(state)

if len(country) > 0:
    query_parts.append(country)

query_string = ",".join(query_parts)

url = "http://api.openweathermap.org/geo/1.0/direct?q=" + query_string + "&limit=3&appid={YOUR_API_KEY}"

response = requests.request("GET", url)

status_code = response.status_code

if status_code != 200:
	print("Uh oh, there was a problem when accessing the Geolocation API. Please try again later")
	quit()

results = response.json()

target_location = None
if len(results) > 1:

    message = "There is more than 1 result for your query, please select an option from the list below."
    for index, result in enumerate(results):
        message += "\n Enter (" + str(index + 1) + ") for: " + result["name"] + ", " + result["state"] + ", " + result["country"]
    message += "\n"

    target_location_index = input(message)

    try:
        target_location_index = int(target_location_index)

        if target_location_index < 1 or target_location_index > len(results):
            raise Exception
    except Exception:
        print("Valid option not selected, defaulting to option 1")
        target_location_index = 1

    target_location = results[target_location_index - 1]
else: 
    target_location = results[0]

longitude = target_location["lon"]
latitude = target_location["lat"]

# Now use 5 day weather forecast API to fetch weather data based on the returned long/lat coordinates
print("Looking up weather data for " + target_location["name"] + ", " + target_location["state"] + ", " + target_location["country"] + " (with long/lat: " + str(longitude) + "/" + str(latitude) + ")")

url = "https://api.openweathermap.org/data/2.5/forecast?lat=" + str(latitude) + "&lon=" + str(longitude) + "&appid={YOUR_API_KEY}&units=metric"

response = requests.request("GET", url)

status_code = response.status_code

if status_code != 200:
	print("Uh oh, there was a problem when accessing the 5 day weather forecast API. Please try again later")
	quit()

results = response.json()

output = ""

for result in results["list"]:

    time = datetime.datetime.fromtimestamp(result["dt"]).strftime("%a %d %b %H:%M %p")
    temperature = result["main"]["temp"]
    weather = result["weather"][0]["description"]
    
    output += time + " " + str(temperature) + " " + str(weather) + "\n"

print(output)

As you can see, it’s a fairly short project. The main functionalities contained within the script are as follows:

  • Information is requested from the user. In this case, the city, state and country combination that they’d like to perform the weather forecast lookup on
  • The first API request makes used of OpenWeather’s Geocoding API. This is responsible for translating the input term (ie. the city, state and country combination) to a series of longitude & latitude values (aka coordinates)
  • These coordinates are then fed in to OpenWeather’s weather forecast API, where the actual weather information is retrieved

Here’s what the output looks like in the terminal:

The user is prompted for their location, and then a weather forecast lookup takes place

Running the script

To use this Python script, open a terminal and type:

python3 weather-app.py

That’s assuming you’re using Python 3, and that you’ve saved the script to a file called weather-app.py.

You’ll be prompted for user inputs upon running the script. Following the process should execute the OpenWeather API request(s), provided you’ve supplied your own individual API key in place of the {YOUR_API_KEY} indicators in the script!

With that brief overview out of the way, let’s look in more detail at what’s going on.

Requesting user inputs

First and foremost, we need to gather inputs from the user. This will inform us of the location where we should fetch the weather-related information for.

You can do this via Python’s input function.

The user inputs are requested and stored in variables for use later on.

Since we’re building a query string here, and some of these variables aren’t strictly required — I’ve opted to store the variables in an array initially, and then use Python’s join function to formulate a string, as follows:

",".join(["London", "England", "UK])
=>
"London,England,UK"

This resultant string now matches the format (&q=city,state,country) that the specific API endpoint we will be using next is expecting (more on this shortly).

The OpenWeather documentation tells us how we should build our requests

Working with the OpenWeather API

Before we make any requests, let’s firstly briefly cover the role of the OpenWeather API here.

The OpenWeather API returns weather forecast information based on a supplied input. We’ll be needing to make use of this API to form the crux of our Python weather app’s functionality.

There are many functionalities associated with this API, but we’ll only be needing 2 of these free services for now:

  • The Geocoding API
  • The 5 day weather forecast API

However, before we can start to interact with the API, we’ll need an API key.

To retrieve this, it’s simply a matter of signing up via the website and then navigating to the API keys page. You can then copy/paste this API key in to the appropriate parts of the Python script above (denoted by the {YOUR_API_KEY} indicators).

At this point, we can start to pass this API key along with the web requests we’ll be making from our simple Python weather app.

This effectively allows us the access OpenWeather’s API resources. As such, if you make some requests without the API key present (or with an invalid API key), you’ll see a 401 (or similar) response instead of the 200 you’d naturally expect.

Now let’s look in detail at the individual requests and how they are formulated.

The Geocoding API request

We already have a textual representation of the user’s target location, based on their prior inputs.

The next task required by our weather app is to retrieve a longitude/latitude combination which we’ll use to actually fetch weather forecast information with.

Just for the sake of clarity, the longitude/latitude references an exact and specific location, so there’s no ambiguity when working with these coordinates (as opposed to a textual representation/name of a place).

We’ll use the Geocoding API to fetch these all important longitude/latitude coordinates.

We can do this as follows, via a simple GET request:

url = "http://api.openweathermap.org/geo/1.0/direct?q=" + query_string + "&limit=3&appid={YOUR_API_KEY}"

response = requests.request("GET", url)

We just need to import Python’s requests library beforehand:

import requests

It’s possible to retrieve multiple longitude/latitudes combinations…

You may have noticed that it’s possible to retrieve more than 1 result here. That’s because it’s possible for the supplied search term to be ambiguous in some way.

If we enter London in as our city, for instance, and leave the other fields empty — this may refer to London in England, or London in Ontario, Canada. Both of these locations will be returned via the Geocoding API.

Before we proceed with fetching the actual weather forecast information, this ambiguity will need to be handled. Ultimately, we only want one longitude/latitude pairing before moving onwards to the next step.

So we’ll need a way to narrow down the results to a single location (or rather, a single longitude/latitude pairing).

Narrowing down the Geocoding results

To achieve this, it’s firstly a matter of simply counting the amount of results returned.

If there’s only one result – then that’s fine, we don’t need to do anything, just set target_location to be the first (and only) result automatically:

target_location = results[0]

If there’s more than one result, however, I’ve opted for this approach:

target_location = None
if len(results) > 1:

    message = "There is more than 1 result for your query, please select an option from the list below."
    for index, result in enumerate(results):
        message += "\n Enter (" + str(index + 1) + ") for: " + result["name"] + ", " + result["state"] + ", " + result["country"]
    message += "\n"

    target_location_index = input(message)

    try:
        target_location_index = int(target_location_index)

        if target_location_index < 1 or target_location_index > len(results):
            raise Exception
    except Exception:
        print("Valid option not selected, defaulting to option 1")
        target_location_index = 1

    target_location = results[target_location_index - 1]

So simply put, list the returned results and allow the user to “select” one of these options via the appropriate index (1 for the first result, 2 for the second result and so on).

For the sake of ease, if an invalid input is entered here, I’m just defaulting to the first option (with some feedback in the terminal informing the user of such). For a nicer experience, you could simply prompt for the user input again, informing them that their previous entry was not a valid selection, for instance.

With that being said, at this point we have all of the information we need to continue with the process of fetching the forecast via our small Python weather app.

We have a target location and hence a longitude/latitude combination to proceed with.

The weather forecast API request

After fetching the longitude/latitude coordinates via the Geocoding API in the previous step, we’re now in a position to communicate with the weather forecast API instead. This (second) request will provide us with the actual weather forecast information we are after!

It’s simply a matter of feeding these longitude/latitude coordinates to the OpenWeather’s 5 day weather forecast API, as follows:

url = "https://api.openweathermap.org/data/2.5/forecast?lat=" + str(latitude) + "&lon=" + str(longitude) + "&appid={YOUR_API_KEY}&units=metric"

response = requests.request("GET", url)

We know the coordinates that we are providing here are valid, and hence they will return the valid weather forecast results that we are expecting; so we just need to parse this retrieved data and present it back to the user in the required format.

We’re using Python’s str function when concatenating the longitude/latitude here. That’s because those values are integers — before concatenating we must cast them to be strings, instead.

Returning the weather forecast back to the user

For this sake of demonstrating this simple Python weather app script, I’m keeping it basic and just returning the date/time (dt), temperature (temp) and weather description (description) fields:

for result in results["list"]:

    time = datetime.datetime.fromtimestamp(result["dt"]).strftime("%a %d %b %H:%M %p")
    temperature = result["main"]["temp"]
    weather = result["weather"][0]["description"]
    
    output += time + " " + str(temperature) + " " + str(weather) + "\n"

print(output)

So it’s a case of looping through the results and fetching these pieces of information accordingly, based on the shape of the returned object.

Then I’m pushing each row to it’s own line within the terminal, so the user is presented with a list of weather forecasts separated by hour of the day.

Note: the dt field returned by the OpenWeather API is an integer representing the timestamp in question – I’m converting it to a more human-readable/formatted string using Python’s strftime function.

Inspecting the shape of the return object(s)

Of course, you may want to display your weather information differently to how I am handling it here.

For this, you’ll just need to inspect the response data and pick out the relevant fields accordingly.

To do this, you can simply just print the output to the terminal directly (during development).

Alternatively, you can inspect the documentation itself. This will tell you exactly what fields are returned.

From here, it should be easy enough to loop through the results and build up your output in to the structure that you require. Your own Python weather app may way to output the API fetch results in a much different manner; so this is where you’d do it.

Further enhancements

Now you know how to make requests to the OpenWeather API, this has opened a lot of options in terms of enhancing this simple Python app/project build.

There are many APIs to work with.

Why not try expanding the functionalities of this simple weather forecast tool?

An example of this would be allowing the user to supply their own unit parameter. So the temperatures can be returned in fahrenheit, for instance, instead of celsius.

Similarly, you could allow the user to dictate what language the weather information should be returned in. You can use the lang parameter for this.

For more ideas, it’d be a good idea to browse through the various API endpoints available, as these will ultimately drive the type of functionality your weather app can implement.

More small Python projects like this one

This concludes our simply Python weather app project!

I hope you’ve enjoyed this brief overview. We’ve touched on some important topics: the user of public APIs, receiving user input, making requests & parsing response data and so on.

If you are looking for similar Python projects that are short and easy to digest, I’d recommend checking out the currency converter project/guide. It’s a short Python script that can be used to request currency information via the terminal.

For more beginner Python project ideas in general – check out our 10 Python Project Ideas For Beginners article we’ve recently added.

Thanks for reading!

Have any questions? Ping me over at @justacodingblog
← Back to blog