Creating a Python Weather App for Terminal

I’m spending waaaay too much time in terminal these days and I’m slowly replacing functions I use on my Google Home Mini with commands on my MacBook, because, as always, why not?

The title says it all. I can now run a weather command in terminal to grab the weather of my home town, including wind speed / direction, temperature and general description (clouds, rain, snow, sun). It’s a pretty crude program, it’ll probably crash if you specify an unknown location, but thats okay. Don’t specify an unknown location and it’ll be fine!

So where to begin on this one. First of all, I needed to find a free weather API provider. Easy stuff, thanks Google. OpenWeather provide just that. Of course there are limited API requests, but I don’t think i will be exceeding those any time soon. From there on, it’s just as easy as sending a GET request to fetch some JSON and then parsing it to be displayed how I see fit.

The only technicality involved was converting a wind direction from degrees to an actual direction. But a spot of math and a big List later, simple.

So let’s get started. First of all, I had to make an account on https://openweathermap.org which is so self-explanatory I won’t even entertain explaining it. It takes a few hours for your API key to become active though, I kinda forgot until the evening so I don’t know exactly how long it takes.

Once thats done, take a look at their API documentation to see what kind of requests you can make. I just went with the first one, get current data by city name.

What the documentation doesn’t say for whatever strange reason is that you need to include your API key as a parameter with the name appid.

So let’s get to writing some python code. I’ll be using the json and requests python libraries. If you don’t have them installed, just run:

pip install json
pip install requests

I honestly can’t remember if json is something you have to install or if its available by default. But here’s the script, nonetheless.

#! /usr/bin/python import json import requests import sys # api-endpoint URL = "http://api.openweathermap.org/data/2.5/weather" location = "city_here"; api_key = "api_key_here" units = "metric" # metric for celsius, imperial for fahrenheit, no param for kelvin temp_unit = "celsius" # descriptor for string PARAMS = {'q': location, 'appid': api_key, 'units': units} # using the requests library, we make a get request with the given URL and PARAMS r = requests.get(url=URL, params=PARAMS) # the json response is stored here data = r.json() direction_list = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"] # is json key present def ijkp(json, key): try: buf = json[key] except KeyError: return False return True # the JSON structure can be found at https://openweathermap.org/current # set up some vars to contain the data I want to show if ijkp(data, "main") == False: print("Critical data missing. Terminating gracefully") sys.exit() if ijkp(data, "weather") == False: print("Critical data missing. Terminating gracefully") sys.exit() if ijkp(data, "wind") == False: print("Critical data missing. Terminating gracefully") sys.exit() temp = int(data['main']['temp']) if ijkp(data['main'], "temp") else int(-9999) feels_like = int(data['main']['feels_like']) if ijkp(data['main'], "feels_like") else None temp_low = int(data['main']['temp_min']) if ijkp(data['main'], "temp_min") else None temp_high = int(data['main']['temp_max']) if ijkp(data['main'], "temp_max") else None humidity = int(data['main']['humidity']) if ijkp(data['main'], "humidity") else None weather_main = data['weather'][0]['main'] if ijkp(data['weather'][0], "main") else None weather_desc = data['weather'][0]['description'] if ijkp(data['weather'][0], "description") else None wind_speed = data['wind']['speed'] if ijkp(data['wind'], "speed") else None wind_speed_mph = int(wind_speed * 2.237) # calculate wind direction from degrees dir_index = int(int(data['wind']['deg']) / 22.5) if ijkp(data['wind'], "deg") else None wind_dir = direction_list[dir_index] print("The current temperature in %s is %i degrees %s but it feels like %s degrees %s" % ( location, temp, temp_unit, feels_like, temp_unit)) print("The weather is mostly %s: %s" % (weather_main, weather_desc)) print("There is a %imph wind in a %s direction" % (wind_speed_mph, wind_dir)) print("The current humidity is %i%%" % (humidity))

So the code is a bit rough. It’s a hackjob. I might tidy it up in the future to traverse the JSON and only extract values if they are there and only display them if they are there, but for now, this does what I want. It crashed when I ran it a minute ago because data[‘wind’][‘deg’] was missing, so I just threw together a quick, hacky solution to avoid that for now.

def ijkp(json, key): try: buf = json[key] except KeyError: return False return True

This function’s sole purpose is to return false if a key doesn’t exist, and true otherwise. I use it in every variable declaration that relies on fetching information from the JSON, to ensure the program doesn’t crash. There are also several if-statements to ensure the core data is present in the JSON data and the program will terminate if it is missing.

 

In order to calculate the wind direction from meteorological degrees, I take a List containing all the possible directions:

direction_list = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"]

North is at both the start and end because both 0 and 360 degrees can represent north, so there are 16 unique elements in this List.

360 / 16 = 22.5

360 degrees divided by 16 unique possibilities = 22.5

So I fetch the wind direction in degrees and then divide that by 22.5. The wind direction is explicitly converted to an int just to ensure it remains a whole number. As is the result of the next calculation, to force round the number. Let’s assume the degrees value is 180, a South direction.

180 / 22.5 = 8.

and thus we fetch the value at the 8th index of the list, which is “S”.

Finally, as always, I move this script to my /scripts/ directory, remove its extension and make sure it is permitted to execute, so I can run it from anywhere on my system, within terminal.

Although I simply threw this code together quickly, the functionality I sought is there, and the real reason behind it is because I’ve been neglecting Python for too long. All the years I’ve been writing code, I’ve barely touched this language. Because I started with PHP and then C++, I am very comfortable with a C-style syntax, so when indentation and colons become the norm, its very foreign to me. So, the more I write, the more comfortable I get. And that, ladies and gentlemen, is why this script now exists on my laptop!