Storing the weather data locally doesn't make sense because I can query much more historical data on OpenWeatherMap. So I am going to use a database to log information about for which city and when the data was requested. That information, in aggregated form, could then be reported as fun fact to each user of the app. I chose Cloudant because it is simple to use, adequate for the intended purpose, has free usage plans on Bluemix, and I can use it and test locally as couchDB.
Add Cloudant as new service |
Cloudant is offered as one of several services in the "Data Management" category on Bluemix. While on the Dashboard you simply click on the "Add a service" button as show on the right. Navigate to the Data Management section and choose Cloudant.
It will bring up a screen showing information about the service itself, on usage terms, and on the right side of it a dialog "Add Service" for adding the service to your account. Here you can already bind the new database service to your application by selecting an existing application from a dropdown list. I did that and gave my new Cloudant service the name "cloudantWeather" as shown:
Bind Cloudant to your application |
#get service information if on Bluemix
if 'VCAP_SERVICES' in os.environ:
couchInfo = json.loads(os.environ['VCAP_SERVICES'])['cloudantNoSQLDB'][0]
couchServer = couchInfo["credentials"]["url"]
couch = couchdb.Server(couchServer)
#we are local
else:
couchServer = "http://127.0.0.1:5984"
couch = couchdb.Server(couchServer)
Storing new documents is simple and is shown in the full code listing. For the queries I am using the MapReduce feature of couchDB. In a "map" function I return the city name (and just the integer value 1), in the reduce function I am aggregating (summing up) the values by city. Both functions could be defined in the Python script and then passed into Cloudant as part of the query or predefined for more performance. I chose the latter one. So I created a so-called "secondary index" in my Cloudant database, it is called "view" in my couchDB. They are stored as part of a "design document" (shown is Cloudant):
Secondary index / permanent view |
With that I finish my Python application, add some calls to the couchDB Python API (which I needed to add to the file "requirements.txt" as dependency) and test it locally. The final step is to deploy the application to Bluemix using the Cloud Foundry tool "cf push". Done, seems to work:
Bluemix weather app with Cloudant stats |
Last but not least, here is the code I used for my little app:
import os
from flask import Flask,redirect
import urllib
import datetime
import json
import couchdb
BASE_URL = "http://api.openweathermap.org/data/2.5/weather?q="
BASE_URL_fc ="http://api.openweathermap.org/data/2.5/forecast/daily?cnt=1&q="
app = Flask(__name__)
# couchDB/Cloudant-related global variables
couchInfo=''
couchServer=''
couch=''
#get service information if on Bluemix
if 'VCAP_SERVICES' in os.environ:
couchInfo = json.loads(os.environ['VCAP_SERVICES'])['cloudantNoSQLDB'][0]
couchServer = couchInfo["credentials"]["url"]
couch = couchdb.Server(couchServer)
#we are local
else:
couchServer = "http://127.0.0.1:5984"
couch = couchdb.Server(couchServer)
# access the database which was created separately
db = couch['weather']
@app.route('/')
def index():
return redirect('/weather/Friedrichshafen')
@app.route('/weather/<city>')
def weather(city):
# log city into couchDB/Cloudant
# basic doc structure
doc= { "type" : "city",
"c_by" : "bm",
}
# we store the city and the current timestamp
doc["city"]=city
doc["timestamp"]=str(datetime.datetime.utcnow())
# and store the document
db.save (doc)
# Time to grab the weather data and to create the resulting Web page
# build URIs and query current weather data and forecast
# JSON data needs to be converted
url = "%s/%s" % (BASE_URL, city)
wdata = json.load(urllib.urlopen(url))
url_fc = "%s/%s" % (BASE_URL_fc, city)
wdata_fc = json.load(urllib.urlopen(url_fc))
# build up result page
page='<title>current weather for '+wdata["name"]+'</title>'
page +='<h1>Current weather for '+wdata["name"]+' ('+wdata["sys"]["country"]+')</h1>'
page += '<br/>Min Temp. '+str(wdata["main"]["temp_min"]-273.15)
page += '<br/>Max Temp. '+str(wdata["main"]["temp_max"]-273.15)
page += '<br/>Current Temp. '+str(wdata["main"]["temp"]-273.15)+'<br/>'
page += '<br/>Weather: '+wdata["weather"][0]["description"]+'<br/>'
page += '<br/><br/>'
page += '<h2>Forecast</h2>'
page += 'Temperatures'
page += '<br/>Min: '+str(wdata_fc["list"][0]["temp"]["min"]-273.15)
page += '<br/>Max: '+str(wdata_fc["list"][0]["temp"]["max"]-273.15)
page += '<br/>Morning: '+str(wdata_fc["list"][0]["temp"]["morn"]-273.15)
page += '<br/>Evening: '+str(wdata_fc["list"][0]["temp"]["eve"]-273.15)
page += '<br/><br/>Weather: '+wdata_fc["list"][0]["weather"][0]["description"]
page += '<br/><br/>'
# Gather information from database about which city was requested how many times
page += '<h3>Requests so far</h3>'
# We use an already created view
for row in db.view('weatherQueries/cityCount',group=True):
page += row.key+': '+str(row.value)+'<br/>'
# finish the page structure and return it
page += '<br/><br/>Data by <a href="http://openweathermap.org/">OpenWeatherMap</a>'
return page
port = os.getenv('VCAP_APP_PORT', '5000')
if __name__ == "__main__":
app.run(host='0.0.0.0', port=int(port))