Skip to main content

Making web app with bottle and python Tutorial - part 5 - Weaving the weather app

In the previous part we have seen how bottle works and some other magic that bottle can do for us. In this part we will create our weather web app Weave. This part will be more challenging when compared to other parts because you will learn a lot after completing this part and will be satisfied using bottle

If you have come here without seeing the previous parts then the pre-requisite for understanding this part is a little knowledge of python requests module, bottle framework and obviously python

Okay folks fasten your seat belts and get ready for take off ;).

As you all know as we will be creating our weather app using API's from IPInfoDB and OpenWeatherMap first go create an account on both the sites: OpenWeather Members Signup and IPInfo Create Free Account.

At this point please make a note that I will represent my API key as '#####' and you should replace it with the corresponding API key that you have got from the website after signing up.

We will start our programming by using the IPInfoDB API.
Okay visit the following url:
http://api.ipinfodb.com/v3/ip-city/?key=#####&ip=4.4.4.4

You will find something like this:
IPInfoDB API result
Of course ip address will change but to use this API we need to create it in a usable data format. We will create a reusable script. Open a new script ctrl+n and then type the following:

import requests

def ipinfo(ipaddress=None):
    """
This function will return the data related to the ip address
"""
    payload = {'key':'#####','ip':ipaddress}
    r = requests.get('http://api.ipinfodb.com/v3/ip-city/',params=payload)
    ipinfo = r.text
    ip_list = ipinfo.strip().split(';')
    ip_dict = {}
    ip_dict['status'] = ip_list[0]
    ip_dict['ip'] = ip_list[2]
    ip_dict['country_code'] = ip_list[3]
    ip_dict['country'] = ip_list[4]
    ip_dict['region'] = ip_list[5]
    ip_dict['city'] = ip_list[6]
    ip_dict['zip_code'] = ip_list[7]
    ip_dict['latitude'] = ip_list[8]
    ip_dict['longitude'] = ip_list[9]
    ip_dict['time_zone'] = ip_list[10]
    return ip_dict
I have saved this file as ip.py in my working directory. We are doing this in order to convert the data that we get from the server to dictionary format or the python usable format.

Lets see what is happening in the code.
First I have imported the requests module
Then we have created a function with an optional parameter of ipaddress so that if the user has not given the value of the parameter then None is assumed. 
Next we are creating a dictionary so that we can use it in our data request.
In the next line we are sending a request to get the value and storing the value in variable r.
In the next line we are converting the request object to text and storing it in a variable ipinfo.
In the following lines we are just manipulating this string and converting it into a dictionary.
To get a feel of what is happening in each and every line open your interpreter, declare a variable ipaddress with your own value and then type each and every line in the body of the function ipinfo.

We can also get the data in json format, you can check out the official documentation of IPInfoDB if you it directly. But here we have not used because some of you are here to learn something new. Also you can use their python module to get the data.

Next we will get the weather data from openweathermap API. This data is returned in json format and so the conversion of the data into dictionary format will be easy. For now we will use interpreter directly just for understanding and use the same concept in our main app.

>>> from pprint import pprint
>>> payload = {'lat':'39.8828','lon':'-105.106', 'units':'metric','appid':'#####'}
>>> weather_data = requests.get('http://api.openweathermap.org/data/2.5/weather',params=payload)
>>> weather = weather_data.json()
>>> pprint(weather)
{u'base': u'stations',
 u'clouds': {u'all': 20},
 u'cod': 200,
 u'coord': {u'lat': 39.89, u'lon': -105.12},
 u'dt': 1456590964,
 u'id': 7150586,
 u'main': {u'humidity': 27,
           u'pressure': 1015,
           u'temp': 11.41,
           u'temp_max': 18.3,
           u'temp_min': 6},
 u'name': u'Countryside',
 u'sys': {u'country': u'US',
          u'id': 538,
          u'message': 0.0109,
          u'sunrise': 1456580176,
          u'sunset': 1456620632,
          u'type': 1},
 u'visibility': 48279,
 u'weather': [{u'description': u'few clouds',
               u'icon': u'02d',
               u'id': 801,
               u'main': u'Clouds'}],
 u'wind': {u'deg': 180, u'speed': 1.5}}
>>> description = weather['weather'][0]['description']
>>> print(description)
few clouds
>>> 
Okay we are ready to weave ;)
Save the following file as app.py:(We will modify this file to create our web app)

from bottle import run, route, request

@route('/myip')
@route('/myip/')
def test():
    ip = request.remote_addr
    return ip

run(reloader=True, debug=True)
Run the above code in command prompt and visit the following site:
http://localhost:8080/myip You should see something like this:
app.py with bottle and python
app.py with bottle
It will be returning the client ip address. But as we are in the development stage we will see 127.0.0.1. When the data related to this ip address(127.0.01) is seen, we will see the following data:
>>> import ip
>>> ip.ipinfo('127.0.0.1')
{'status': u'OK', 'city': u'-', 'ip': u'127.0.0.1', 'region': u'-', 'time_zone': u'-', 'longitude': u'0', 'country_code': u'-', 'country': u'-', 'latitude': u'0', 'zip_code': u'-'}
As you can see that the data is empty and useless. But if we will host the same web app on server then we will get the real ip address of the user or client.

To address this issue we will assume some value for our variable; so, ip = '4.4.4.4' and at the end we will revert it back to ip = request.remote_addr, so that there will not be any error or bugs.

Thus the changed code will be something like this:

from bottle import run, route #,request

@route('/myip')
@route('/myip/')
def test():
    ip = '4.4.4.4'
    return 'Your IP Address is {0}'.format(ip)

run(reloader=True, debug=True)
Next we will start using templates. Now in your working directory create folders like this:

`--app.py
`--ip.py
`--static
|----css
|----js
|----images
Now the static files will be as follows:
ip.tpl

<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8' />
        <title>Radius of circle</title>
        <link rel="stylesheet" href="style.css" />
    </head>
    <body>
    
     <h1>Hello Visitor!</h1>
     <h3>Your Details are as follows:</h3>
     <p>IP Address: {{ip}}</p>
     <p>City: {{city}}</p>
     <p>Region: {{region}}</p>
     <p>Country: {{country}}</p>
     <p>Country Code: {{country_code}}</p>
     <p>Zip Code: {{zip_code}}</p>
     <p>Latitude: {{latitude}}</p>
     <p>Longitude: {{longitude}}</p>
     <p>Time Zone: {{time_zone}}</p>

    </body>
</html>
static/css/style.css

@import url(https://fonts.googleapis.com/css?family=Fauna+One);
body{
 padding: 10px;
 font-family: 'Fauna One', serif;
}
h1{
 text-align: center;
}
Correspondingly we will modify our python web app app.py as follows:

from bottle import run, route, template,get,static_file#,request
import ip

#To serve the static files
@get('/<filename:re:.*\.css>')
def stylesheets(filename):
    return static_file(filename, root='static/css')

@route('/myip')
@route('/myip/')
def test():
    ip_add = '4.4.4.4'
    data = ip.ipinfo(ip_add)
    if data['status'] == 'OK':
        city = data['city']
        region = data['region']
        time_zone = data['time_zone']
        longitude = data['longitude']
        country_code = data['country_code']
        country = data['country']
        latitude = data['latitude']
        zip_code = data['zip_code']
        return template('ip', ip=ip_add, city = city,
                        region = region, country = country,
                        country_code = country_code,
                        zip_code = zip_code, latitude = latitude,
                        longitude = longitude, time_zone = time_zone)

    else:
        return 'We are sorry some error occured'
    

run(reloader=True, debug=True)
Start the server and when you visit http://localhost:8080/myip then you should see something like this:
bottle app returning the data that is acquired using the user ip address
app.py returning the user ip address and related data
Now we will use combine both the weather data and the ip data in our website. Now go ahead save the following as weather_ip.tpl

<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8' />
        <title>Radius of circle</title>
        <link rel='stylesheet' href="style.css" />
    </head>
    <body>
     <table border="1">
      <tr>
       <td> <!--IP Data-->
       <h1>IP Details</h1>
       <h3> {{ip}}</h3>
       <p>City: {{city}}</p>
       <p>Region: {{region}}</p>
       <p>Country: {{country}}</p>
       <p>Country Code: {{country_code}}</p>
       <p>Zip Code: {{zip_code}}</p>
       <p>Latitude: {{latitude}}</p>
       <p>Longitude: {{longitude}}</p>
       <p>Time Zone: {{time_zone}}</p>
       </td>

       <td><!--Weather-->
        <h1>Weather</h1>
        <h3>{{city}}</h3>
        <p>Minimum Temperature: {{min_temp}} K</p>
        <p>Maximum Temperature: {{max_temp}} K</p>
        <p>Temperature: {{avg_temp}} K</p>
        <p>Pressure: {{pressure}} mm of Hg</p>
        <p>Description: {{description}}</p>
        <p>Wind Direction: {{wind_dir}}</p>
        <p>Wind Speed: {{wind_speed}}</p>
        <p>Humidity: {{humidity}}</p>
       </td>
      </tr>
     </table> 
    </body>
</html>
static/css/style.css

@import url(https://fonts.googleapis.com/css?family=Fauna+One);
body{
 padding: 10px;
 font-family: 'Fauna One', serif;
}
h1{
 text-align: center;
 border-bottom: 1px solid black;
}
h3{
 text-align: center;
}
td{
 padding: 20px;
}
app.py:

from bottle import run, route, template,get,static_file#,request
import ip, requests

@get('/<filename:re:.*\.css>')
def stylesheets(filename):
    return static_file(filename, root='static/css')

@route('/')
def test():
    ip_add = '4.4.4.4'
    data = ip.ipinfo(ip_add)
    if data['status'] == 'OK':
        city = data['city']
        region = data['region']
        time_zone = data['time_zone']
        longitude = data['longitude']
        country_code = data['country_code']
        country = data['country']
        latitude = data['latitude']
        zip_code = data['zip_code']
        payload = {'q':city,'appid':'44db6a862fba0b067b1930da0d769e98'}
        url = 'http://api.openweathermap.org/data/2.5/weather'
        r = requests.get(url,params=payload)
        weather = r.json()
        humidity = weather['main']['humidity']
        pressure = int(float(weather['main']['pressure'])*0.750062)
        avg_temp = int(round(weather['main']['temp']))
        max_temp = int(round(weather['main']['temp_max']))
        min_temp = int(weather['main']['temp_min'])
        description = weather['weather'][0]['description']
        wind_direction = float(weather['wind']['deg'])
        wind_speed = weather['wind']['speed']
        return template('weather_ip', ip=ip_add, city = city,
                        region = region, country = country,
                        country_code = country_code,
                        zip_code = zip_code, latitude = latitude,
                        longitude = longitude, time_zone = time_zone,
                        humidity = humidity, pressure=pressure,
                        avg_temp=avg_temp, max_temp = max_temp,
                        min_temp=min_temp, description=description,
                        wind_dir = wind_direction, wind_speed=wind_speed)

    else:
        return 'We are sorry some error occured'
    

run(reloader=True, debug=True)
Please sit aside, take some time and try to understand each and everything in the above code, I have written it in a very simple way. This is the major step to complete this part.

The only thing I would like to explain in the above code is the routing or the url. In the previous parts we have used something like this:

@route('/something')
@route('/something/')
This is to tell the server that if the user visits either 'http://localhost:8080/something' or 'http://localhost:8080/something/' then show the same thing. But in the previous app.py code we have only used @route('/')  because even if the user visits 'http://localhost:8080' the same function  test will be called and the data is returned properly.

Now start the server and visit the following url:
http://localhost:8080/, you should see something like this:
Bottle web app made with python returning ip details and weather details pertaining to ip
Bottle webapp returning weather and ip data
I think now you are looking for the next section on how to make the webapp look like that of weave. But according to me it would be better if I give this to you are a homework/practice. But I will give you some hints on how I made the web app.
  1. First change ip = request.remote_addr
  2. I have used the HTML template from w3layouts for the design and can be found here: Clime and Timer Weather Responsive Widget Template
  3. I have modified the original template a little bit, I am assuming that with a basic HTML knowledge you can do that on your own.
  4. If you want to display the images based on the weather then you can get the icon name by typing this: icon = weather['weather'][0]['icon']
  5. You can get the icons from here: Icons
  6. If you want to see the source code(this is not the original one hosted on web) you can see it from here: Github Weather
If you want to host your bottle application then I would seriously recommend pythonanywhere. It is very easy to deploy your app and also they have a very great customer support team who will help you if you have any trouble. 

If you want to deploy on any other service then you can see other options from here:Web Hosting providers (All most all of them offer hosing)

Even though our web app is not suitable for very large traffic you can use it directly with some changes made to make it compatible with server, if you want to make it available for large traffic then you will have to make a few changes of the present web app which out of the scope of this tutorial.

Finally congrats, you are now a python developer. You can create dynamic websites on your own. It should be remembered that we have only seen one side of the n-sides that bottle can do for us. If you want to know what bottle can do for you, read the official documentation.

As always, I have tried to use simple terms in this post/part so that even the beginners will also be able to understand it easily. But if you didn't understand anything or have any doubt then please comment in the comment box below or contact me. You can contact me from here: Contact me

Please do comment on how I can improve this post so that it will be more comfortable even for small children. Also comment if I have missed any topic or want me to add any new topic or for anything else. I would be glad to give you a reply. "Let's share our knowledge and make this world beautiful!"

Thank you, Have a nice day :)

Popular posts from this blog

Making a quiz web app with python and flask

Edit : When you are creating a web app with h tml templates, then y ou will have to sa ve the html file in templates folder in the Current Wor ki ng Directory( CWD). If you save the file in the C W D directl y you will get a TemplateNotFound error. Thank you Udhay for pointing it out.   In this post we will create a quiz website using python . I will be using the flask framework . After reading this tutorial you will learn form submission , flask templates , python code in flask templates , shuffling the questions and options with the random module and few others.  Please note that this tutorial is not big as it seems to be. Some of the code has been rewritten to maintain consistency and also font size is somewhat big so that your eyes won't get stressed reading this tutorial. Also the content has not occupied the full width of the page. In this tutorial I am assuming that you are having a very basic understanding of the flask framework . Please refer the documentation

Problem 11 Project Euler Solution with python

Largest product in a grid In the 20×20 grid below, four numbers along a diagonal line have been marked in red. 08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08 49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00 81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65 52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91 22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80 24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50 32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70 67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21 24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72 21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95 78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92 16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57 86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58 19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40 04 52 08 83 97 35 99 16 07

Problem 60 Project Euler Solution with python

Prime pair sets The primes 3, 7, 109, and 673, are quite remarkable. By taking any two primes and concatenating them in any order the result will always be prime. For example, taking 7 and 109, both 7109 and 1097 are prime. The sum of these four primes, 792, represents the lowest sum for a set of four primes with this property. Find the lowest sum for a set of five primes for which any two primes concatenate to produce another prime. This problem is j u st a brute force problem. If you have come here because you don't know the limit upto which you will h ave to gener ate the prime numbers t hen go ahe ad and t r y with 10,000 . When I first start ed solving the problem I chose 1 million(beca use most of the problem s on project E uler have this limit ), but it took very long for the computer to fin d the solution. After searching on the internet then I found many people choosing 10, 000 so I have changed my in put f rom 1 million to 10000 and the output was f ast. He