Weather Report - Part 2

Example Code - Fetching Weather Data


Prerequisites: This post assumes basic knowledge of Python and HTTP APIs.

Welcome to the second part of this eInk prototype experiment mini-series. Today we’ll look at the draft code (Gist) used to fetch the weather itself.

Note that we’ll be speeding through some very rough code. We’ll polish it up as the series progresses. You can see the complete file at the end of the post.

The Program

Let’s break it down section by section, focusing on the most relevant code.

Weather Report

class WeatherReport:
    def __init__(self, report, weather):
        self.current_temperature = report["temp"]
        self.feels_like = report["feels_like"]
        self.high = report["temp_max"]
        self.low = report["temp_min"]
        self.humidity = report["humidity"]
        self.weather_description = weather[0]["description"]

A simple Python class serving as a data store. It provides a clean interface for the code that will import this module.

Weather Error

class WeatherError:
    def __init__(self, response):
        self.response = response
        self.error_code = response["cod"]
        self.message = response["message"]

The counterpart to WeatherError. This is returned when something goes wrong while fetching the data.

fetch_weather

The core functionality exported by this module. This method performs the actual weather service query. We’ll cover it in multiple parts.

def fetch_weather():
    base_url = "http://api.openweathermap.org/data/2.5/weather"
    api_key = os.environ.get('OPENWEATHER_API_KEY')
    city_name = "<Your City Goes Here>"
    complete_url = "{}?appid={}&q={}&units=imperial".format(base_url, api_key, city_name)

    response = requests.get(complete_url).json()

Initial setup. The required API key is passed in as an environment variable in order to keep it private and to prevent accidental uploads to, say, GitHub. Looking at the code again, I would do the same for the city name. This URL likely also needs a state parameter or the like. We then make the actual HTTP request to the weather API.

    if response["cod"] == 200:
        weather = response["weather"]
        report = WeatherReport(response["main"], weather)

The HTTP code for the response is then checked. If it’s 200 (OK) then we’re good to go and can pass the necessary parts of the response onto the initializer for WeatherReport.

        print("Temp: {}F\nFeels Like: {}F\nHigh: {}F\nLow: {}F\nHumidity: {}\nDescription: {}".format(
            report.current_temperature,
            report.feels_like,
            report.high,
            report.low,
            report.humidity,
            report.weather_description
        ))

        return report

The results are shown with print for debugging purposes. It’s unusual for a module to print logs like these but this is proof of concept code.

    else:
        print("Error: {}", response)

        return WeatherError(response)

If the response code was anything other than 200, we treat it as an error. Common errors for this API include: 401 - Invalid API key and 404 - City not found. As before, we print out the results for debugging purposes.

Summary

And that’s a quick run through the code! It’s not what I would call “excellent” code but it does the job and that’s often enough. After all, I care more about having a readable weather report on my wall than about having perfect code hidden in a hard drive. I hope that this peek into what real, pragmatic programming looks like inspires you to start your own such project!

Look forward to an analysis of the eInk display code in the next part of this series.

The Complete File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests, json, os

class WeatherReport:
    def __init__(self, report, weather):
        self.current_temperature = report["temp"]
        self.feels_like = report["feels_like"]
        self.high = report["temp_max"]
        self.low = report["temp_min"]
        self.humidity = report["humidity"]
        self.weather_description = weather[0]["description"]

class WeatherError:
    def __init__(self, response):
        self.response = response
        self.error_code = response["cod"]
        self.message = response["message"]

def fetch_weather():
    base_url = "http://api.openweathermap.org/data/2.5/weather"
    api_key = os.environ.get('OPENWEATHER_API_KEY')
    city_name = "Astoria"
    complete_url = "{}?appid={}&q={}&units=imperial".format(base_url, api_key, city_name)

    response = requests.get(complete_url).json()

    if response["cod"] == 200:
        weather = response["weather"]
        report = WeatherReport(response["main"], weather)

        print("Temp: {}F\nFeels Like: {}F\nHigh: {}F\nLow: {}F\nHumidity: {}\nDescription: {}".format(
            report.current_temperature,
            report.feels_like,
            report.high,
            report.low,
            report.humidity,
            report.weather_description
        ))

        return report

    else:
        print("Error: {}", response)

        return WeatherError(response)