Make a Mac menu bar resident weather app with rumps!

I want to make an app that is resident in the menu bar!

I suddenly wanted to make an app in the menu bar. First of all, what is the menu bar here ↓ menubar.png After studying what I can do, I decided to make a weather app.

What is rumps

I think many people will use Objective-C or Swift to develop the menu bar for the Mac. (Perhaps) There is rumps as a library to easily create a Mac menu bar in Python. https://github.com/jaredks/rumps The apps that use rumps are introduced, so I used them as a reference.

Preparation

Install what you need to use rumps. Let's start with python3. (If you have already installed it, please skip it) First, install pyenv, which makes it easy to switch between python versions.

$ brew install pyenv

After installing pyenv, add the following to your .bash_profile in your home directory:

.bash_profile


export PYENV_ROOT=${HOME}/.pyenv
if [ -d "${PYENV_ROOT}" ]; then
   export PATH=${PYENV_ROOT}/bin:$PATH
   eval "$(pyenv init -)"
fi
$ source ~/.bash_profile
$ pyenv install 3.7.7
$ pyenv global 3.7.7
$ python -V
Python 3.7.7
$ pip -V
pip 19.2.3 from /Users/username/.pyenv/versions/3.7.7/lib/python3.7/site-packages/pip (python 3.7)

Next, we will build the environment with venv. venv is useful because you can manage the installation status of packages by pip for each project.

$ mkdir weather
$ cd weather
$ python -m venv venv
$ . venv/bin/activate
(venv) $ pip install rumps

How to use

Let's start with the rumps sample program below.

sample.py


import rumps

class RumpsTest(rumps.App):
    @rumps.clicked("Hello World")
    def hello_world(self, _): 
        rumps.alert("Hello World!")

    @rumps.clicked("Check")
    def check(self, sender):
        sender.state = not sender.state

    @rumps.clicked("Notify")
    def sayhello(self, _): 
        rumps.notification("Hello", "hello", "hello world")

if __name__ == "__main__":
    RumpsTest("Rumps Test").run()
(venv) $ python sample.py

When I run the program, the title of Rumps Test appears in the menu bar of my Mac. Click the title of Rumps Test and you will see the following. sample_1.png

@rumps.clicked("Hello World") #By writing before the function, a clickable Hello World is added to the list.
def hello_world(self, _):
    rumps.alert("Hello World!") # Hello World!Alert is displayed.
sender.state = not sender.state #Every time Check is clicked, sender.state switches to 1 or 0.
rumps.notification("Hello", "hello","hello world") # title, subtitle,message is notified.

You can easily create a menu bar app like this. Let's actually make a weather app.

Try to make a weather app

This time, we will use the livedoor Weather Hacks API to get the weather information. http://weather.livedoor.com/weather_hacks/webservice Use Python requests to fetch the data.

(venv) $ pip install requests

I will create it with the file name weather.py.

weather.py


import rumps
import requests

# Livedoor weather api
URL = 'http://weather.livedoor.com/forecast/webservice/json/v1'


class Weather(rumps.App):
    @rumps.clicked("Get weather")
    def get_weather(self, _):
        payload = {'city': '130010'} #130010 is the area code for Tokyo in Tokyo.
        data = requests.get(URL, params=payload).json()
        tenki = data['forecasts'][0]['telop']
        rumps.alert(tenki)


if __name__ == "__main__":
    Weather("Weather").run()

Click Get weather to specify the Tokyo area of Tokyo, I was able to get the weather forecast and display it in the alert.

Since the area code of Tokyo is entered directly, it is inconvenient if you do not know the area code. Get the area code list and use Tokyo as the key so that you can enter the area code. Get the xml published by Livedoor and store it in the dictionary.

weather.py


import rumps
import requests

# Livedoor weather api
URL = 'http://weather.livedoor.com/forecast/webservice/json/v1'

# City list
XML = "http://weather.livedoor.com/forecast/rss/primary_area.xml"


class Weather(rumps.App):
    @rumps.clicked("Get weather")
    def get_weather(self, _): 
        self.area = self.get_city()
        payload = {'city': self.area[('Tokyo', 'Tokyo')]} #You specified the area, not the area code.
        data = requests.get(URL, params=payload).json()
        tenki = data['forecasts'][0]['telop']
        rumps.alert(tenki)

    def get_city(self):
        area = {}
        src = et.fromstring(requests.get(XML).text)[0][12]
        for c in src.findall("pref"):
            for cc in c.findall("city"):
                key = (c.attrib["title"], cc.attrib["title"])
                area[key] = cc.attrib["id"]
        return area # {('Prefectures', 'area'): 'Area code'}It is a dictionary in the form of.


if __name__ == "__main__":
    Weather("Weather").run()

Tokyo I was able to know the weather in Tokyo, but would you like to see the weather in other areas as well? Next, let's make it possible to see the weather information of the area selected from the area list. I would like to click on an area to alert me to the weather information for that area.

weather.py


    @rumps.clicked("Tokyo Tokyo")
    def get_weather(self, _): 
        self.area = self.get_city()
        payload = {'city': self.area[('Tokyo', 'Tokyo')]}

It's hard to write as above for all regions, so let's play with it a bit.

weather.py


class Weather(rumps.App):
    def __init__(self, name):
        super(Weather, self).__init__(
            "Weather",
            menu=[
                rumps.MenuItem("Get weather", callback=self.get_weather)
            ]   
        )   

    def get_weather(self, _): 
        self.area = self.get_city()
        payload = {'city': self.area[('Tokyo', 'Tokyo')]}
        data = requests.get(URL, params=payload).json()
        tenki = data['forecasts'][0]['telop']
        rumps.alert(tenki)

I put in a constructor and removed the get_weather decoration. Instead of decoration, I used rumps.MenuItem and modified it to work in much the same way. I would like to be able to select a region from here.

weather.py


class Weather(rumps.App):
    def __init__(self, name):
        super(Weather, self).__init__(
            "Weather",
            menu=[
                self.build_area()
            ]
        )

    def build_area(self):
        self.area = self.get_city()
        menu = rumps.MenuItem("Area")
        for (pref, area), code in self.area.items():
            title = "{} {}".format(pref, area)
            menu[title] = rumps.MenuItem(title)
        return menu
sample_2.png

I think it looks like the image for the time being. Next, click on the area to display it in the alert.

weather.py


    def build_area(self):
        self.area = self.get_city()
        menu = rumps.MenuItem("Area")
        for (pref, area), code in self.area.items():
            def get_weather(sender):
                payload = {'city': code}
                data = requests.get(URL, params=payload).json()
                tenki = data['forecasts'][0]['telop']
                rumps.alert(tenki)
            title = "{} {}".format(pref, area)
            menu[title] = rumps.MenuItem(title, callback=get_weather)
        return menu

I tried to make it like this. For the time being, we will make this program into an app (in the form of weather.app).

(venv) $ pip install py2app
(venv) $ py2applet --make-setup weather.py

I think running the py2applet command generated setup.py. Add a little to setup.py.

setup.py


"""                                                                           
This is a setup.py script generated by py2applet

Usage:
    python setup.py py2app
"""

from setuptools import setup

APP = ['weather.py']
DATA_FILES = []
OPTIONS = { 
        'plist': {
            'LSUIElement': True, #If specified, it will not be displayed in the Dock.
        }   
}

setup(
    app=APP,
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)
(venv) $ python setup.py py2app
...
...
ValueError: '/Users/User name/.pyenv/versions/3.7.7/lib/libpython3.7.dylib' does not exist

I got an error in my environment. I don't have libpython3.7.dylib, so reinstall Python 3.7.7. At the time of installation, there is information that you can add --enable-shared, so I will try it.

(venv) $ deactive
$ cd
$ pyenv uninstall 3.7.7
$ PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.7.7
$ ls /Users/User name/.pyenv/versions/3.7.7/lib/
libpython3.7m.dylib   libpkgconfig   python3.7

In python3.7.7 it was libpython3.7m.dylib. (In python3.8.0 it was libpython3.8.dylib properly.) I couldn't help it, so I renamed libpython3.7m.dylib to libpython3.7.dylib.

$ mv /Users/User name/.pyenv/versions/3.7.7/lib/libpython3.7m.dylib /Users/User name/.pyenv/versions/3.7.7/lib/libpython3.7.dylib

Run setup.py again.

$ cd weather
$ . venv/bin/activate
(venv) $ python setup.py py2app
...
...
Done!

I managed to get it right. I think that the menu bar application works by executing the application contained in dist.

How was that. You can easily create a weather app!

I have very much referred to the rumps app made by other people. https://shinaji.bitbucket.io/2014/09/08/manu_bar_app.html https://github.com/rbrich/computer-time/

Finally, I improved it to make it easier to use. sample_2.png https://github.com/hiro1112/WeatherApp

Task

--I want to make it inoperable when the network is not connected I want to know if the network is connected without communicating from python. (I want to check every second)

――Since I'm using Python, I want to add machine learning functions! !! I want to use weather information to provide useful information to users of the app. I wanted to do something with text mining using information such as twitter.

--I can't write the test code at all ... The current coverage rate is terrible. I'll do it someday lol

Recommended Posts

Make a Mac menu bar resident weather app with rumps!
Let's make a Mac app with Tkinter and py2app
Make a desktop app with Python with Electron
[Practice] Make a Watson app with Python! # 2 [Translation function]
[Practice] Make a Watson app with Python! # 1 [Language discrimination]
[Practice] Make a Watson app with Python! # 3 [Natural language classification]
[Django] Make a pull-down menu
Make a fortune with Python
Make a fire with kdeplot
Make a scraping app with Python + Django + AWS and change jobs
Make a sound with Jupyter notebook
Let's make a breakout with wxPython
Creating a simple app with flask
Make a recommender system with python
Make a filter with a django template
Let's make a graph with python! !!
Let's make a supercomputer with xCAT
Make a model iterator with PySide
Make a nice graph with plotly
Tweet the weather forecast with a bot
Let's make a shiritori game with Python
Make a video player with PySimpleGUI + OpenCV
Play like a web app with ipywidgets
Make a Notebook Pipeline with Kedro + Papermill
Create a GUI app with Python's Tkinter
Make a partially zoomed figure with matplotlib
Daemonize a Python web app with Supervisor
Make a drawing quiz with kivy + PyTorch
Let's make a voice slowly with Python
Make a cascade classifier with google colaboratory
Let's make a simple language with PLY 1
Make Qt for Python app a desktop app
Create a simple web app with flask
Make a logic circuit with a perceptron (multilayer perceptron)
Make a Yes No Popup with Kivy
Make a wash-drying timer with a Raspberry Pi
Make a GIF animation with folder monitoring
Let's make a web framework with Python! (1)
Let's make a tic-tac-toe AI with Pylearn 2
Let's make a Twitter Bot with Python!
Let's make a web framework with Python! (2)
Let's make a nervous breakdown app with Vue.js and Django-Rest-Framework [Part 2] ~ Vue setup ~
Let's make a nervous breakdown app with Vue.js and Django-Rest-Framework [Part 1] ~ Django setup ~
A new form of app that works with GitHub: How to make GitHub Apps