In a previous article I sketched the road map for implementing a small web application to present data from a small weather station with the help of PyWWS and the Flot plug-in. In this article we show how to provide data from the pywww/DataStore
module as JSON encoded information that we can use in AJAX calls.
Producing JSON encoded weather data with CherryPy
Our web application is a single web page with some added Javascript that relies on a number of
services that provide JSON encoded data to AJAX calls.
These services are implemented as Python classes within a CherryPy application. The first class we define is called Basic
and its initializer takes a single tz
argument. This will be the timezone used to display data.
class Basic: def __init(self,tz=pytz.timezone('Europe/Amsterdam')): self.tz=tz @cherrypy.expose def default(self,name,_=None,start=None,end=None): start,end = self.verifystartend(start,end) if name in { 'temp_out','hum_out','wind_ave', 'wind_gust','wind_dir','rain'}: return self.getdata(name,start,end)
The crucial method is default()
. It is exposed to the CherryPy engine with the cherrypy.expose
decorator. A exposed method with the name default
will receive any request that cannot be mapped to a more specific name. The name
attribute will hold the final part of the URL. The _
argument will hold a random string that we simply ignore: it is added to any AJAX call by jQuery to prevent the browser from caching results. The start
and stop
arguments are optional and may contain a date/time argument in YYYYMMDDHH
format. If the end
argument is absent, it defaults to now, if the start
argument is absent it defaults to 24 hours before end
. These defaults are calculated by the verifystartend()
method (not shown).
The next check is to see whether name
is one of the known types of data in the DataStore
. If all is well we simply pass the name
, start
and end
arguments to the getdata()
method that we encounter in the next section. The getdata()
method is not part of the Basic
class but will be provided by a mixin class called Service
. The idea is that Basic
and its subclasses provide web application logic to be embedded in CherryPy, while Service
provides an interface to produce JSON encoded data based on the data provided by PyWWS.
We define two subclasses of Basic
. The first is called Hourly
and should return weather data that are the hourly averages. Its initializer takes a weatherdata
argument which should point to the directory where PyWWS stores its data and an optional tz
argument to hold the timezone:
class Hourly(Service,Basic): def __init__(self, weatherdata, tz=pytz.timezone('Europe/Amsterdam')): super().__init__(tz) self.datastore = DataStore.hourly_store self.weatherdata=weatherdataThe most important bit is that
__init__()
creates a datastore
instance variable and initializes it to the hourly_store
class from the DataStore
module. The datastore
and weatherdata
variables will be used by the getdata()
method from the Service
mixin.
The Raw
class is very similar to the Hourly
class only its datastore
variable is initialized to the data_store
class from the DataStore
module which produces non-averaged weather data.
class Raw(Service,Basic): def __init__(self, weatherdata, tz=pytz.timezone('Europe/Amsterdam')): super().__init__(tz) self.datastore = DataStore.data_store self.weatherdata=weatherdataThe
Service
mixin class is where all the hard work is happening. The bulk of the work is done by its getdata()
method. It first creates a suitable instance of a pywws DataStore
and then converts the data it retrieves from this datastore with the dumps
function from the json
module.
def getdata(self,what,start,end): ds=self.datastore(self.weatherdata) return dumps( [( self.millisecondsfromnaiveutc( data['idx']), data[what]) for data in ds[ start.astimezone( Service.utc).replace( tzinfo=None): end.astimezone( Service.utc).replace( tzinfo=None)] ] )The single argument to the
dumps()
function is a list of tuples, each consisting of a timestamp in milliseconds and a floating point value as this is the format that the Flot library expects. This data is retrieved from the datastore with the slice notation: ds[a:b]
will retrieve all the data between a
and b
(if a
and b
are datetime
instances).
All this work was needed to be able to request URLs like http://localhost:8080/temp_out
and receive a response like [[123456,14.1],[123654,14.2],[123789,14.4]]
for example.
In the next installment of this series we will look into the HTML and Javascript code needed to make this web application really work.