[Tutor] Feedback on coding style

Dennis Lee Bieber wlfraed at ix.netcom.com
Mon May 9 14:52:06 EDT 2022


	o/~	Talking to myself in public	o/~

On Mon, 09 May 2022 11:24:24 -0400, Dennis Lee Bieber
<wlfraed at ix.netcom.com> declaimed the following:

	For some reason I felt challenged, and spent three hours to produce
this variation... WARNING: news client may be adding undesirable line
wrapping -- and should be viewed with fixed width font!

NOTE: I should have created a helper function to do the formatting rather
than repeating all those print blocks.

	I'm actually surprised the DML_MAX_* statements worked first time.
Whereas I must have spent over half an hour tweaking the report format.

-=-=-
#!/usr/bin/env python3
"""
    name:       bp_tracker.py
    version:    0.0.1-a
    date:       20220509
    author:     Leam Hall
    desc:       Track and report on blood pressure numbers

    revisions:
        20220509    Dennis Lee Bieber
        rewritten as an exercise to use SQLite3 for the
        data store

    TODO:
        ADD TRY/EXCEPT BLOCKS TO HANDLE ERRORS IN DATABASE OPERATIONS
        
    TODO:
        add SQLite3 aggregate function for std. deviations

    TODO:
        Add report options for time-of-day
        (early, mid-morning, afternoon, evening)

    TODO:
        Add current distance from goal (requires input of target)

"""

import argparse
import datetime
import os.path as op
import sqlite3 as db

#TIMESTAMP is the name used by the Python API for
#conversion as SQLite3 does not have a native
#date/datetime data type
#I always create an integer primary key, even if not
#needed by the application
DDL = """CREATE TABLE IF NOT EXISTS reading
    (
        ID              INTEGER PRIMARY KEY,
        reading_date    TIMESTAMP NOT NULL,
        systolic        INTEGER NOT NULL,
        diastolic       INTEGER NOT NULL,
        pulse           INTEGER NOT NULL
    )
"""

DML_ADD = """INSERT INTO reading
        (reading_date, systolic, diastolic, pulse)
    VALUES (?, ?, ?, ?)
"""

DML_MAX_SYSTOLIC = """ SELECT
        r.ID, r.reading_date, r.systolic, r.diastolic, r.pulse
    FROM reading AS r
    INNER JOIN (SELECT max(m.systolic) AS mx
                FROM reading AS m)
    ON r.systolic = mx
    ORDER BY r.reading_date
"""

DML_MAX_DIASTOLIC = """ SELECT
        r.ID, r.reading_date, r.systolic, r.diastolic, r.pulse
    FROM reading AS r
    INNER JOIN (SELECT max(m.diastolic) AS mx
                FROM reading AS m)
    ON r.diastolic = mx
    ORDER BY r.reading_date
"""

DML_MAX_PULSE = """ SELECT
        r.ID, r.reading_date, r.systolic, r.diastolic, r.pulse
    FROM reading AS r
    INNER JOIN (SELECT max(m.pulse) AS mx
                FROM reading AS m)
    ON r.pulse = mx
    ORDER BY r.reading_date
"""

DML_MAX_DATE = """ SELECT
        r.ID, r.reading_date, r.systolic, r.diastolic, r.pulse
    FROM reading AS r
    INNER JOIN (SELECT max(m.reading_date) AS mx
                FROM reading AS m)
    ON r.reading_date = mx
"""

DML_SUMMARY = """ SELECT
    count(*) AS Count,
    max(systolic) AS Max_Systolic,
    min(systolic) AS Min_Systolic,
    avg(systolic) AS Avg_Systolic,
    max(diastolic) AS Max_Diastolic,
    min(diastolic) AS min_Diastolic,
    avg(diastolic) AS Avg_Diastolic,
    max(pulse) AS Max_Pulse,
    min(pulse) AS Min_Pulse,
    avg(pulse) AS Avg_Pulse
    FROM reading
"""

DEFAULT_DATABASE = "bp_numbers.sqlite"

def open_database(db_name):
    """
        Opens the specified database file.write
        Creates table schema (use of IF NOT EXISTS
        should mean this is safe for pre-existing
        database, yet will initialize a new database.
        CAVEAT: it also means if some random database
        file is provided a new table will be created
        within that database!)
    """
    #specify that datatypes should be parsed -- used to
    #associate datetime converters
    con = db.connect(db_name, detect_types=db.PARSE_DECLTYPES)
    cur = con.cursor()
    cur.execute(DDL)
    con.commit()
    cur.close()
    return con

def add_reading(connection, datestamp, systolic, diastolic, pulse):
    """
        Using the provided database connection, adds a new
        record with provided datestamp, systolic, diastolic, pulse
    """
    cur = connection.cursor()
    cur.execute(DML_ADD, (datestamp, systolic, diastolic, pulse))
    connection.commit()
    cur.close()

def generate_report(connection):
    """
        Produce a summary report showing the record(s) with maximum
        systolic, diastolic, pulse, datestamp.
        Also list overall maximum and minimum, means
        (SQLite3 does not have a built-in standard deviation function;
        one can be defined in Python and registered to the SQLite3
        API so it can be called in SQL statements -- this is left
        for the future)
    """
    cur = connection.cursor()
    #obtain summary max/min values
    cur.execute(DML_SUMMARY)
    summary = cur.fetchone()    #there is only one record
    #should use a dictionary cursor for this, so later formatting
    #can reference by column name, rather than having to know
    #index
    
    #collect the full records for each maximum category
    cur.execute(DML_MAX_SYSTOLIC)
    systolic = cur.fetchall()
    cur.execute(DML_MAX_DIASTOLIC)
    diastolic = cur.fetchall()
    cur.execute(DML_MAX_PULSE)
    pulse = cur.fetchall()
    cur.execute(DML_MAX_DATE)
    date = cur.fetchall()[0]

    print("\n\n\n%s\n" % "*************************************")
    print("\tSummary for %s readings\n" % summary[0])
    print("          %-9s   %-9s   %-9s" %
          ("Systolic", "Diastolic", "  Pulse"))
    print("          %-9s   %-9s   %-9s" %
          ("=========", "=========", "========="))
    print("%-9s %9s   %9s   %9s" %
          ("MAX", summary[1], summary[4], summary[7]))
    print("%-9s %9s   %9s   %9s" %
          ("MIN", summary[2], summary[5], summary[8]))
    print("%-9s %9.9s   %9.9s   %9.9s" %
          ("AVERAGE", summary[3], summary[6], summary[9]))

    print("\n\t******\n")

    print("\tMaximum %s event(s)" % "SYSTOLIC")
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("   ID", "        Datestamp", "Systolic", "Diastolic", "
Pulse"))
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("=========", "=============================",
           "=========", "=========", "========="))
    for record in systolic:
        print("%9s   %-30s   %9s   %9s   %9s" %
              (record[0], record[1], record[2], record[3], record[4]))
    print()

    print("\tMaximum %s event(s)" % "DIASTOLIC")
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("   ID", "        Datestamp", "Systolic", "Diastolic", "
Pulse"))
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("=========", "=============================",
           "=========", "=========", "========="))
    for record in diastolic:
        print("%9s   %-30s   %9s   %9s   %9s" %
              (record[0], record[1], record[2], record[3], record[4]))
    print()

    print("\tMaximum %s event(s)" % "PULSE")
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("   ID", "        Datestamp", "Systolic", "Diastolic", "
Pulse"))
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("=========", "=============================",
           "=========", "=========", "========="))
    for record in pulse:
        print("%9s   %-30s   %9s   %9s   %9s" %
              (record[0], record[1], record[2], record[3], record[4]))
    print()

    print("\tMaximum %s event" % "DATESTAMP")
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("   ID", "        Datestamp", "Systolic", "Diastolic", "
Pulse"))
    print("%-9s   %-30s   %-9s   %-9s   %-9s" %
          ("=========", "=============================",
           "=========", "=========", "========="))
    if date:
        print("%9s   %-30s   %9s   %9s   %9s" %
              (date[0], date[1], date[2], date[3], date[4]))
    
    print("\n\n\n%s\n" % "*************************************")

if __name__ == "__main__":
    #create argument/command line parser
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--add", nargs=3,
                        help = "Add in the order of systolic, diastolic,
pulse. "
                                "Date/Time is assumed")
    parser.add_argument("-f", "--file", help = "Database file",
                        default = DEFAULT_DATABASE)
    args = parser.parse_args()

    if args.file:
        database = args.file

    #open/initialize database
    con = open_database(database)
    
    if args.add:
        #generate timestamp
        datestamp = datetime.datetime.now()
        add_reading(connection=con,
                    datestamp=datestamp,
                    systolic=args.add[0],
                    diastolic=args.add[1],
                    pulse=args.add[2])

    #produce summary report regardless of any other actions
    generate_report(connection=con)

    #close database
    con.close()

-=-=-

	Designed as an IMPORTable module so other programs could import it,
call the database open, add entry, and report generation functions (though
the latter should be configured to take an output destination so the report
could be sent to a file, not just blatted out on the console).

-=-=-
C:\Users\Wulfraed\Documents\_Hg-Repositories\Python
Progs\LH_BP_tracker>bp_tracker.py -a 123 98 69



*************************************

        Summary for 5 readings

          Systolic    Diastolic     Pulse
          =========   =========   =========
MAX             136          98          70
MIN             115          65          65
AVERAGE       126.8        86.4        68.4

        ******

        Maximum SYSTOLIC event(s)
   ID               Datestamp                Systolic    Diastolic Pulse
=========   =============================    =========   =========
=========
        1   2022-05-09 14:14:37.494260             136          92 70
        2   2022-05-09 14:20:40.620195             136          88 68

        Maximum DIASTOLIC event(s)
   ID               Datestamp                Systolic    Diastolic Pulse
=========   =============================    =========   =========
=========
        5   2022-05-09 14:34:50.873347             123          98 69

        Maximum PULSE event(s)
   ID               Datestamp                Systolic    Diastolic Pulse
=========   =============================    =========   =========
=========
        1   2022-05-09 14:14:37.494260             136          92 70
        3   2022-05-09 14:21:19.428237             124          89 70

        Maximum DATESTAMP event
   ID               Datestamp                Systolic    Diastolic Pulse
=========   =============================    =========   =========
=========
        5   2022-05-09 14:34:50.873347             123          98 69



*************************************

-=-=-
C:\Users\Wulfraed\Documents\_Hg-Repositories\Python
Progs\LH_BP_tracker>sqlite3 bp_numbers.sqlite
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .mode box
sqlite> select * from reading;
+----------------------------------------------------------------+
¦ ID ¦        reading_date        ¦ systolic ¦ diastolic ¦ pulse ¦
+----+----------------------------+----------+-----------+-------¦
¦ 1  ¦ 2022-05-09 14:14:37.494260 ¦ 136      ¦ 92        ¦ 70    ¦
¦ 2  ¦ 2022-05-09 14:20:40.620195 ¦ 136      ¦ 88        ¦ 68    ¦
¦ 3  ¦ 2022-05-09 14:21:19.428237 ¦ 124      ¦ 89        ¦ 70    ¦
¦ 4  ¦ 2022-05-09 14:32:31.471217 ¦ 115      ¦ 65        ¦ 65    ¦
¦ 5  ¦ 2022-05-09 14:34:50.873347 ¦ 123      ¦ 98        ¦ 69    ¦
+----------------------------------------------------------------+
sqlite>
-=-=-


-- 
	Wulfraed                 Dennis Lee Bieber         AF6VN
	wlfraed at ix.netcom.com    http://wlfraed.microdiversity.freeddns.org/



More information about the Tutor mailing list