Bharat Kalluri     ·  About

Create Albert extensions in Python

Albert describes itself as

Albert is a desktop agnostic launcher. Its goals are usability and beauty, performance and extensibility. It is written in C++ and based on the Qt framework.GPL-licensed, 100% free and open source.

It has a lot of positives

And the best part is

I consider myself as a mediocre developer. but If you ask me to write a extension in C++, I would not even try. But python! Sign me up!

So, I thought I would start developing albert extensions to make my life simple . Let us develop a extension which just shows the weather of a city with a trigger word like weather.

weather bangalore

should return weather of bangalore(high and low) and humidity. We will get data from openweathermap api. So, signup for an account and get a key from openweathermap api. If I say <API_KEY>, replace it with your own API key.

Let’s Dive in!

Albert gives you a library called albertv0. To use it, the script needs to be inside the path of default python extension.

Some basic concepts to understand are that, the python library gives you a function called handleQuery which takes in a argument, which will be the query you type after the trigger. The metadata of the program will contain the trigger word, name and author. Finally, everything which pops under albert is an Item object. An Item object takes in a couple of arguments. It’ll take an Id, icons which can take system icons using a helper function called iconLookup, text which is the heading for each item and a subtext which will be the subtitle.

So, If you bundle all the data into an array of items and return the array of Item, We are done! The simplicity of the API is extremly appeling!

Let’s start off by importing albertv0 library and since we are using python 3, urllib3 will be great for making API requests. We should setup some metadata before starting the program.

from albertv0 import *

import urllib3
import json

__iid__ = "PythonInterface/v0.1"
__prettyname__ = "Weather"
__version__ = "0.1"
__trigger__ = "weather "
__author__ = "Bharat Kalluri"
__dependencies__ = []

The initial function is handleQuery(query). Check if query is triggered, else display the default item.

def handleQuery(query):
    if query.isTriggered:
        if query.string.strip():
            return showWeather(query)
        else:
            return Item(
                id=__prettyname__,
                icon=iconLookup("weather-overcast"),
                text=__prettyname__,
                subtext="Type in the city name",
                completion=query.rawString,
            )

Now, showWeather contains the actual logic of calling the API and returning Items.

http = urllib3.PoolManager()
def showWeather(query):
    qurl = (
        "http://api.openweathermap.org/data/2.5/weather?appid=<API_KEY>&type=accurate&units=metric&q="
        + query.string.strip()
    )

    try:
        res = http.request("GET", qurl)
        data = json.loads(res.data)
    except:
        critical("No Internet!")
        return [
            Item(
                id=__prettyname__,
                icon=iconLookup("dialog-warning"),
                text="Is internet working?",
                subtext="We could not query, check your internet connection",
                completion=query.rawString,
            )
        ]

    if data["cod"] == "404":
        return [
            Item(
                id=__prettyname__,
                icon=iconLookup("weather-overcast"),
                text=__prettyname__,
                subtext="The city name does not exist in the database",
                completion=query.rawString,
            )
        ]
    else:
        critical(weatherDict.get(data["weather"][0]["description"], "weather-overcast"))
        return [
            Item(
                id=__prettyname__,
                icon=iconLookup(
                    weatherDict.get(
                        data["weather"][0]["description"], "weather-overcast"
                    )
                ),
                text="Weather in {}: {}".format(
                    data["name"], data["weather"][0]["main"]
                ),
                subtext="High: {}°C ({}°F) Low: {}°C ({}°F) Humidity: {}%".format(
                    data["main"]["temp_min"],
                    fahrenheitConverter(data["main"]["temp_min"]),
                    data["main"]["temp_max"],
                    fahrenheitConverter(data["main"]["temp_max"]),
                    data["main"]["humidity"],
                ),
                completion=query.rawString,
            )
        ]

Let me quickly summarize what’s happening in there. Since the function already has a query of a city name, append the city name to the url. Before making the request, a instance of pool manager must be created which will make the requests. Then, It tries to make a request, if it fails, it returns a item saying ‘Check your internet connection’.

Now, since we have the API response. Check if the code is 200, if not the city does not exist in the database. So, show an item saying the city does not exist in the database. If it does, then the code will be 200. Now parse the json for relevent data and format the data into an item and return it.

One thing to note is that, extensions can be debbuged by starting albert in terminal and putting critical() or debug() to test the response. Another thing to note is, all default linux icon sets have weather icons already. So, we take the description from the API and find the corresponding icon. Normally, switch works well in this case, since we do not have switch statments in python. Create a dictionary of conditions as such.

weatherDict = {
    "clear sky": "weather-clear",
    "few clouds": "weather-few-clouds",
    "scattared clouds": "weather-overcast",
    "broken clouds": "weather-overcast",
    "shower rain": "weather-showers",
    "rain": "weather-showers",
    "thunderstorm": "weather-storm",
    "snow": "weather-snow",
    "mist": "weather-fog",
}

And use the dictionary to get the icon name for the value in the API. By the way, since farenhiet is also nesscarry for some people. Let us make a simple converter from celsius.

def fahrenheitConverter(celsius):
    return 9 / 5 * celsius + 32

In less than 100 lines of code, a simple albert extension was made! In the future, I will add 5 day forecast and let the user input their API key and preffered default city.

All my extensions are curated at Albert-extras.

So, that’s it for now.

Cheers!

Written June 23, 2018.

← Linux setup  Theming firefox →