Wednesday 28 October 2020

PyBloom coding project part 5: Making a weather observation

 

Over the course of the first parts, we've set up the coding environments and pre-requisites. Now it's time to actually start coding PyBloom.

Weather observation object

In this module, I use the Open Weather Map service to fetch the current weather at my location.

The technologies 

The code

class weather_observation:


Each weather reading is called an observation. The weather_observation class is responsible for fetching the current weather observation, for and for logging. Let’s get into it.


Initialisation


def __init__(self, timestamp=None, temperature=None, detailed_status=None):

    self.timestamp = timestamp,

    self.temperature = temperature,

    self.detailed_status = detailed_status


The observation has three components:

  • Timestamp: time that the observation was taken

  • Temperature: temperature at the location, at the time of the observation

  • Detailed_status: verbose description of weather at the location, at the time of the observation


This observation object is initialised with None values, to show if there’s never been an observation taken.


Representation string


def __str__(self):

    return f'''Current weather: {self.detailed_status}, {self.temperature} celsius (made at {self.timestamp})'''


It’s always good practice to include a representation string for debugging purposes. This is accessed by simply print ing the observation object, and the last observation content is displayed.


The mechanism for constructing this string uses Python 3’s f-strings - if you still use stock Python 2.7 then this won’t work for you.


Fetch new observation


def new(self, location):  # expect e.g. 'London, GB'


The .new method fetches an observation. The API needs a location, so this is the only parameter that needs to be passed into the method. Note that the API needs a specific string format.


owm = pyowm.OWM(OWM_KEY)

mgr = owm.weather_manager()

weather = mgr.weather_at_place(location).weather


These three commands together are how pyOWM accesses the API and then presents the observation. The weather object has a number of fields, but I’m only interested in the temperature and detailed_status. The next three commands update the observation object.


self.timestamp = datetime.now().strftime(DATETIME_STRING)


The current timestamp is obtained not from the OWM API, but from the built-in datetime library. There are two chained methods used. The first .now() method is self-evident, but the second merits some examination.


Python’s datetime object is complicated because it’s built to simplify mathematical operations across time zones and local time. It’s also different to the datetime object used in other languages like SQLite. So to make sure both my Python and SQLite scripts have the same understanding of datetime, I use the built-in operators to transform to a string.


Datetime (Python) <-> string (common syntax) <-> Datetime (SQLite)


The .strftime() method handles the conversion into a string. But it can’t be any old string, it must be the same syntax that is accepted by both languages. The DATETIME_STRING global constant from earlier defines this syntax:

  • %Y: year in four digits, e.g. 2020

  • %m: month in two digits, e.g. October = 10

  • %d: day in two digits, e..g first = 01

  • %h: hour in two digits

  • %m: minute in two digits

  • %s: second in two digits


self.temperature = weather.temperature('celsius')['temp']


The pyOWM .temperature object is quite extensive, so requires some parameters to access just the current temperature. 


self.detailed_status = weather.detailed_status


Verbose status is easier to access (although it isn’t really that verbose).


return 'Fetched new observation'


Whilst this function doesn’t need to return anything, as all it does is to fetch a new observation, it’s good practice to aid debugging to return a string. This way, the command print(observation.new()) returns an acknowledgement if successful.


Log an observation


def log(self):

    # also write to external database

    con = db_connect()

    cur = con.cursor()


I'll describe the db_connect() utility in a later section, but the takeaway for now is that I'm using it to aid clarity. These two statements create a cursor object by connecting to the database. This object comes with a .execute method which passes a SQL statement, with a bit of SQLite cleverness, as a Python string. Here’s the SQL statement:


sql = '''INSERT INTO observations (timestamp, temperature, detailed_status)

         VALUES (?, ?, ?)'''


It’s pretty basic SQL, but let’s break it down:

  • INSERT INTO observations: SQL commands are in caps, variables in lower. Here, the variable is the table name within the database that we’re interested in.

  • (timestamp, temperature, detailed_status): these are the columns into which we’re going to put the values.

  • VALUES (?, ?, ?): this is where the SQLite cleverness comes in; it allows us to pass in variables from the Python script into the SQL script.


cur.execute(sql, (self.timestamp, self.temperature, self.detailed_status))


This .execute method combines the SQL and the variables to create the full SQL query that is applied to the database.


con.commit()

con.close()


Once we’ve written the information to the table, we need to remember to commit the changes, then close the connection.


return 'Observation logged'


As before, an acknowledgement string is returned if successful.


Manually setting an observation (for debugging)


def set(self, timestamp, temperature, detailed_status):  # for debug

    self.timestamp = timestamp

    self.temperature = temperature

    self.detailed_status = detailed_status

    return 'Observation set'


This final method is for debugging purposes, and allows me to set an observation manually, just in case.

Putting it together

class weather_observation:


    def __init__(self, timestamp=None, temperature=None, detailed_status=None):

        self.timestamp = timestamp,

        self.temperature = temperature,

        self.detailed_status = detailed_status


    def __str__(self):

        return f'''Current weather: {self.detailed_status},

             {self.temperature} celsius

            (made at {self.timestamp})'''


    def new(self, location):  # expect e.g. 'London, GB'

        owm = pyowm.OWM('a15c8eee1a77e17d0aa2860990d7c9f8')

        mgr = owm.weather_manager()

        weather = mgr.weather_at_place(location).weather

        self.timestamp = datetime.now().strftime(DATETIME_STRING)

        self.temperature = weather.temperature('celsius')['temp']

        self.detailed_status = weather.detailed_status

        return 'Fetched new observation'


    def log(self):

        # write to external database

        con = db_connect()

        cur = con.cursor()


        sql = '''INSERT INTO observations (timestamp,

                                           temperature,

                                           detailed_status)

                 VALUES (?, ?, ?)'''

        cur.execute(sql, (self.timestamp,

                          self.temperature,

                          self.detailed_status))

        con.commit()

        con.close()

        return 'Observation logged'


    def set(self, timestamp, temperature, detailed_status):  # for debug

        self.timestamp = timestamp

        self.temperature = temperature

        self.detailed_status = detailed_status

        return 'Observation set'



This part has gone through our first code object, making the weather observation. In part 6, I'll take you through how I set the colours of my Philips Hue Bloom lamps.

No comments:

Post a Comment

It's always great to hear what you think. Please leave a comment, and start a conversation!