Added valid_record option to door_warning()
authorwking <wking@loki.(none)>
Thu, 28 Aug 2008 23:17:32 +0000 (19:17 -0400)
committerwking <wking@loki.(none)>
Thu, 28 Aug 2008 23:20:07 +0000 (19:20 -0400)
Also deleted chem_web_secure.py, since it is trivially similar to
chem_web.py.  Only the log/pid filenames and the value of the SSL
variable were different.

chem_db.py
chem_web_secure.py [deleted file]

index 181d0663184a43ba8f8722b37a3765086494eca0..aabe3484611102254e6b230ebeaad6d1d13cb818 100644 (file)
@@ -172,6 +172,7 @@ class docgen (object) :
         os.system('cp ./docs/main.pdf %s' % path)
         return path
     def inventory(self, namewidth='a') :
+        "Create a pdf list of all currently owned chemicals."
         pp = db_pretty_printer(self.db)
         active_ids = []
         for record in self.db.records() :
@@ -215,7 +216,16 @@ class docgen (object) :
         ## run latex
         path = self._make_pdf('inventory.pdf')
         return path
-    def door_warning(self) :
+    def door_warning(self,
+                     valid_record=lambda r: r['Disposed'] == '') :
+        """create a warning NFPA diamond and list of the most dangerous
+        chemicals for which valid_record(record) is true.  For
+        example, to generate a door warning for the front door use
+          door_warning(lambda r: r['Disposed'] == '')
+        or to generate the warning for the fridge
+          door_warning(lambda r: r['Location'] == 'Refrigerator')
+        Note that valid_record defaults to the first example.
+        """
         pp = db_pretty_printer(self.db)
         all_ids = range(self.db.len_records())
 
@@ -229,7 +239,7 @@ class docgen (object) :
         Reactivities = []
         Others = []
         for record in self.db.records() :
-            if record['Disposed'] == '' : # chemicals we still have
+            if valid_record(record) :
                 for field in ['H', 'F', 'R', 'O'] :
                     r = record[field]
                     if r != '' and r != '?' :
@@ -246,7 +256,7 @@ class docgen (object) :
         # now that we've found the max NFPAs,
         # find all the chemicals at those levels
         for record in self.db.records() :
-            if record['Disposed'] == '' : # chemicals we still have
+            if valid_record(record) :
                 for field,array in zip(['H', 'F', 'R', 'O'],
                                        [Healths, Fires,
                                         Reactivities, Others]) :
diff --git a/chem_web_secure.py b/chem_web_secure.py
deleted file mode 100755 (executable)
index d17395d..0000000
+++ /dev/null
@@ -1,560 +0,0 @@
-#!/usr/bin/python
-#
-# Web application for posting additions/modifications to the chem-inventory
-# derived from simplewiki and wiki_py, by Adam Bachman
-#  ( http://bachman.infogami.com/ )
-# Authentication following Snakelets
-# http://snakelets.sourceforge.net/manual/authorization.html
-
-# Remember to delete the templates directory after making alterations,
-# because this program only generates templates if they are missing.
-
-
-## Bring in some useful goodies:
-# web, because we're running a web server, and generating html;
-# time, for timestamping backups;
-# os, for creating the template directories; and
-# re, for replacing internal node links with proper markup
-import web, os, re
-# md5, for hashing passwords
-#import md5
-
-# database must support the following methods :
-#   field_list() : return an ordered list of available fields (fields unique)
-#   long_field() : return an dict of long field names (keyed by field names)
-#   record(id)   : return a record dict (keyed by field names)
-#   records()    : return an ordered list of available records.
-#   backup()     : save a copy of the current database somehow.
-#   set_record(i, newvals) : set a record by overwriting any preexisting data
-#                          : with data from the field-name-keyed dict NEWVALS
-import text_db
-database = text_db.text_db
-from chem_db import valid_CASno, MSDS_manager, docgen
-DB_FILE = 'inventory.db'
-import sys, daemon, logging
-PID_FILE = './chem_web_secure.pid'
-LOG_FILE = './chem_web_secure.log'
-
-## Also import a markup language, and associated regexp functions
-from markdown import markdown
-## for converting the first character in record names, and stripping |
-from string import upper, rstrip
-
-## for SSL
-SSL = True
-if SSL :
-      from mk_simple_certs import get_cert_filenames
-      server_name = 'chem_web'
-
-__version__ = "0.2"
-
-## For web.py, when a URL matches '/view', handle with class 'view'
-# when it matches '/edit', handle with class 'edit',
-# and when it matches '/login', handle with class 'login',
-# Note, there are no .html extension.
-#
-# link to edit pages using db_id
-# link to MSDSs with ID
-urls = ('/(view)?', 'view',
-        '/edit/([0-9]+)', 'edit',
-        '/MSDS/([0-9]+)', 'MSDS',
-        '/docs/([a-z._]*)', 'docs',
-        '/login', 'login')
-
-# limit the display width of selected fields
-WIDTH_MAX = {'CAS#':12}
-
-# set the display width of entry fields
-ENTRY_WIDTH = 50
-
-class view (object) :
-      "Print the database"
-      # when the server recieves the command "GET '/view', call view.get('view')
-      def GET(self, name):
-            """
-            Render the view.html template using name and the previous text.
-            If the file doesn't exist, use default text '%s doesn't exist'.
-            """
-            if name == None :
-                  name = 'view'
-            db._refresh()
-            recs = []
-            for record in db.records() :
-                  id = int(record['ID'])
-                  record['ID'] = '<a href="/edit/%d">%s</a>' \
-                                 % (id, record['ID'])
-                  if MSDSman.has_MSDS(id) : # link the id to the MSDS
-                        record['Name'] = '<a href="/MSDS/%d">%s</a>' \
-                                 % (record['db_id'], record['Name'])
-                  recs.append(record)
-            print render.view('Chemical inventory',
-                              db.len_records(), db.field_list(), recs)
-
-class edit (object) :
-      "Provide methods for handling requests related to edit pages"
-      def GET(self, name):
-            """
-            Render the edit.html template using the specified record number.
-            """
-            db._refresh()
-            db_id = int(name)
-            if db_id >= db.len_records() :
-                  logging.info("new record")
-                  assert db_id == db.len_records(), \
-                      'invalid id: %d (max %d)' % (db_id, db.len_records())
-                  record = db.new_record()
-                  record['ID'] = str(record['db_id'])
-                  db.set_record(db_id, record)
-            if MSDSman.has_MSDS(db_id) :
-                  MSDSs = None
-            else :
-                  # only ask for an MSDS if we still need one
-                  MSDSs = MSDSman.get_all(simlinks=False)
-            print render.edit(int(name), db.field_list(),
-                              db.long_fields(), db.record(int(name)), MSDSs)
-      def POST(self, name):
-            """
-            Read the form input and update the database accordingly.
-            Then redirect to /view.
-            """
-            db._refresh()
-            record = db.record(int(name))
-            ## Generate a new record from the form input
-            # 'MSDS={}' to storify MSDS as a FieldStorage (dict-like) instance,
-            # otherwise it's saved as a string, and we loose info about it.
-            # e.g. newvals['MSDS'].type = 'text/html'
-            # The contents of the file are saved to newvals['MSDS'].value
-            newvals = web.input(MSDS={})
-            update = False
-            for field in db.field_list() :
-                  if newvals[field] != record[field] :
-                        update=True
-                        record[field] = newvals[field]
-            if 'MSDS source' in newvals :
-                  # Handle any MSDS file actions
-                  if newvals['MSDS source'] == 'upload' :
-                        if len(newvals['MSDS'].filename) > 0 and len(newvals['MSDS'].value) > 0:
-                              # only save if there is a file there to save ;)
-                              #print >> stderr, web.ctx.env['CONTENT_TYPE']
-                              #print >> stderr, web.ctx.keys()
-                              #print >> stderr, dir(newvals['MSDS']) 
-                              #print >> stderr, type(newvals['MSDS'])
-                              #print >> stderr, newvals['MSDS'].filename 
-                              #print >> stderr, newvals['MSDS'].type
-                              MSDSman.save(int(name), newvals['MSDS'].value,
-                                           newvals['MSDS'].type)
-                  else :
-                        logging.info('linking MSDS %d to %d' \
-                                         % (int(record['ID']),
-                                            int(newvals['MSDS share'])) )
-                        MSDSman.link(int(record['ID']),
-                                     int(newvals['MSDS share']))
-            if update :
-                  db.set_record(int(name), record, backup=True)
-            # redirect to view all
-            web.seeother('/view')
-
-class MSDS (object) :
-      "Serve MSDS files by ID"
-      def GET(self, name):
-            "Serve MSDS files by ID"
-            id = int(name)
-            if MSDSman.has_MSDS(id) :
-                  mime = MSDSman.get_MSDS_MIME(id)
-                  web.header("Content-Type", mime)
-                  print file(MSDSman.get_MSDS_path(id), 'rb').read() ,
-            else :
-                  print render.error(id)
-
-class docs (object) :
-      "Generate and serve assorted official documents"
-      def GET(self, name):
-            """
-            List the available documents.
-            """
-            db._refresh()
-            if name == '' :
-                  print render.docs()
-            if name == 'inventory.pdf' :
-                  path = dgen.inventory(namewidth=40)
-                  print file(path, 'rb').read() ,
-            if name == 'door_warning.pdf' :
-                  path = dgen.door_warning()
-                  print file(path, 'rb').read() ,
-                  
-
-class login (object) :
-      "Print an alphabetized index of all existing pages"
-      def GET(self):
-            print "Not yet implemented"
-            #print render.stat('login')
-
-class markup (object) :
-      "Convert text to html, using Markdown with a bit of preformatting"
-      def __init__(self) :
-            # [[optional display text|name with or_without_spaces]]
-            linkregexp = ('\[\['
-                          +'([^\|\[\]]*\|)?'
-                          +'([a-zA-Z0-9-_ ]*)'
-                          +'\]\]')
-            #print linkregexp
-            self.linkexp = re.compile(linkregexp)
-            self.linebreak = '  \n'
-            self.uscore = re.compile('_')
-            self.space = re.compile(' ')
-      def htmlize(self, text) :
-            return text
-            pre_markup = self._preformat(text)
-            #print "Preformatted '"+pre_markup+"'"
-            return self._markup( pre_markup )
-      def _markup(self, text) :
-            # markdown() implements the text->html Markdown semantics
-            # the str() builtin ensures a nice, printable string
-            return str(markdown(text))
-      def _preformat(self, text) :
-            #print "Preformatting '"+text+"'"
-            return self._autolink(text)
-      def _autolink(self, text) :
-            "Replace linkexps in text with Markdown syntax versions"
-            # sub passes a match object to link2markup for each match
-            # see http://docs.python.org/lib/match-objects.html
-            return self.linkexp.sub(self.matchlink2markup, text)
-      def matchlink2markup(self, match) :
-            # The name is the first (and only) matched region
-            #print match.groups()
-            name = match.groups()[1]
-            text = match.groups()[0]
-            return self.link2markup(text, name)
-      def linklist(self, text) :
-            linklist = [] # empty list
-            for m in self.linkexp.finditer(text) :
-                  linklist.append( self.str2name(m.groups()[1]) )
-            #print linklist
-            return linklist
-      def link2markup(self, text, name) :
-            """
-            usage: string = link2markup(text, target)
-            takes the string name of a wiki page,
-            and returns a string that will be markuped into a link to that page
-            displaying the text text.
-            """
-            # convert to Markdown pretty link syntax
-            if text : # remove trailing |
-                  text = text.rstrip('|')
-            else :
-                  text = self.name2str(name)
-            name = self.str2name(name)
-            return str('['+text+'](/'+name+')')
-      def name2str(self, name) :
-            """
-            usage: string = name2str(name)
-            takes the string name of a wiki page,
-            and converts it to display format
-            See str2name()
-            """
-            w_spaces = self.uscore.sub(' ', name)
-            return w_spaces
-      def str2name(self, string) :
-            """
-            usage: string = name2str(name)
-            Converts strings to the relevent wiki page name.
-            See str2name()
-            """
-            wo_spaces = self.space.sub('_', string)
-            cap_first = upper(wo_spaces[0]) + wo_spaces[1:]
-            return cap_first
-      def make_add_button(self, next_db_id) :
-            string =  '<form action="/edit/%d" method="get">\n' % next_db_id
-            string += ' <input type="submit" value="Add entry">\n'
-            string += '</form><br>\n'
-            return string
-      def make_table(self, fields, records, width_max=WIDTH_MAX, 
-                     raw_fields=()):
-            """
-            >>> print make_table(['A','B','C'],[{'A':'a','B':'b'},{'A':'d','B':'e','C':'f'}]),
-            <table>
-            <tr><td><b>A</b></td><td><b>B</b></td><td><b>C</b></td></tr>
-            <tr><td>a</td><td>b</td><td></td></tr>
-            <tr><td>d</td><td>e</td><td>f</td></tr>
-            </table>
-            """
-            # open the table
-            string = '<table>\n'
-            # add the header
-            string += '<tr>'
-            for field in fields :
-                  string += '<td><b>%s<b></td>' % self.htmlize(field)
-            string += '</tr>\n'
-            # add the records
-            for record in records :
-                  string += '<tr>'
-                  for field in fields :
-                        rstring = record[field]
-                        # truncate if the width is regulated...
-                        if field in width_max :
-                              w = width_max[field]
-                              # ... and the full string is too long
-                              if len(rstring) > w :
-                                    rstring = "%s..." % rstring[:w]
-                        # if the field isn't raw, protect special chars
-                        if not field in raw_fields :
-                              rstring = self.htmlize(rstring)
-                        string += '<td>%s</td>' % rstring
-                  string += '</tr>\n'
-            # close the table
-            string += '</table>\n'
-            return string
-      def record_form_entries(self, index, fields, lfields, record, MSDSs=None, entry_width=ENTRY_WIDTH):
-            """
-            >>> print record_form_entries(4,['A','B','MSDS'],{'A':'AA','B':'BB','MSDS'},{'A':'a','B':'b'}),
-            <table>
-             <tr><td><label for="A">AA</label></td><td>:</td>
-              <td><input type="text" size="50" id="A" name="A" value="a"></td></tr>
-             <tr><td><label for="B">BB</label></td><td>:</td>
-              <td><input type="text" size="50" id="B" name="B" value="b"></td></tr>
-             <tr><td><label for="MSDS">MSDS</label></td><td>:</td>
-              <td><input type="file" size="50" id="MSDS" name="MSDS" value=""></td></tr>
-            </table>
-            """
-            # open the table
-            string = '<table>\n'
-            # add the record fields
-            for field in fields :
-                  # get the record string
-                  rstring = self.htmlize(record[field])
-                  fstring = self.htmlize(field)
-                  lfstring = self.htmlize(lfields[field])                  
-                  string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' % (fstring, lfstring)
-                  string += '  <td><input type="text" size="%d" id="%s" name="%s" value="%s"></td></tr>\n' \
-                            % (entry_width, fstring, fstring, rstring)
-            if MSDSs != None :
-                  ## add an MSDS radio, share menu, and file upload fields
-                  # radio
-                  lfstring = fstring = 'MSDS source'
-                  string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' \
-                            % (fstring, lfstring)
-                  string += '  <td><input type="radio" id="%s" name="%s" value="upload" checked>Upload\n' \
-                            % (fstring, fstring)
-                  string += '      <input type="radio" id="%s" name="%s" value="share">Share</td></tr>\n' \
-                            % (fstring, fstring)
-
-                  # file upload
-                  fstring = 'MSDS'
-                  lfstring = 'Upload MSDS'
-                  string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' \
-                            % (fstring, lfstring)
-                  string += '  <td><input type="file" size="%d" id="%s" name="%s" accept="text/plain,text/html,application/pdf" value=""></td></tr>\n' \
-                            % (entry_width, fstring, fstring)
-
-                  # file share menu
-                  fstring = 'MSDS share'
-                  lfstring = 'Use an already uploaded MSDS'
-                  string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' \
-                            % (fstring, lfstring)
-                  string += '  <td><select size="1" id="%s" name="%s">\n' \
-                            % (fstring, fstring)
-                  for MSDS in MSDSs :
-                        lfstring = self.htmlize(MSDS['Name'])
-                        id = int(MSDS['ID'])
-                        string += '   <option value="%d">%d: %s</option>\n' \
-                                  % (id, id, lfstring)
-                  string += '   </select></td></tr>\n'
-            # close the table
-            string += '</table><br>\n'
-            return string
-
-class templt (object) :
-     "Handle some template wrapping"
-     def __init__(self) :
-           self.dir = 'templates/'
-           if not os.path.exists(self.dir): os.mkdir(self.dir)
-           ## Write simple template html pages if they don't already exist.
-           # see ( http://webpy.org/templetor ) for the syntax.
-           html=('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
-                 ' "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
-                 '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n')
-           if not os.path.exists(self.dir+'view.html') :
-                 view=('$def with (name, next_db_id, fields, records)\n'
-                       +html+
-                       ' <head>\n'
-                       '  <title>\n'
-                       '   $:name2str(name) \n'
-                       '  </title>\n'
-                       ' </head>\n'
-                       ' <body>\n'
-                       '  <h1>\n'
-                       '   $:name2str(name)\n'
-                       '  </h1>\n'
-                       '  See \n'
-                       '   <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm">the rules</a>\n'
-                       '   for more information.\n'
-                       '  See the <a href="/docs/">docs</a> page to generate required documents.<br>\n'
-                       '  <br>\n'
-                       '  $:make_add_button(next_db_id)\n'
-                       '  $:make_table(fields, records)\n'
-                       '  $:make_add_button(next_db_id)\n'
-                       ' </body>\n'
-                       '</html>\n')
-                 file(self.dir+'view.html', 'w').write(view)
-           if not os.path.exists(self.dir+'edit.html') :
-                 # note:
-                 # the form encoding type 'enctype="multipart/form-data">\n'
-                 # is only required because of the MSDS file-upload field.
-                 edit=('$def with (index, fields, lfields, record, MSDSs)\n'
-                       +html+
-                       ' <head>\n'
-                       '  <title>\n'
-                       '   $:htmlize(record["Name"]) \n'
-                       '  </title>\n'
-                       ' </head>\n'
-                       ' <body>\n'
-                       '  <a style="cursor:pointer;" onclick="history.back()"><h1>\n'
-                       '   Editing: $:htmlize(record["Name"]) \n'
-                       '  </h1></a>\n'
-                       '  <form action="" method="post" enctype="multipart/form-data">\n'
-                       '   $:record_form(index, fields, lfields, record, MSDSs)<br />\n'
-                       '   <input type="submit" value="Submit" />\n'
-                       '  </form>\n'
-                       ' </body>\n'
-                       '</html>\n')
-                 file(self.dir+'edit.html', 'w').write(edit)
-           if not os.path.exists(self.dir+'error.html') :
-                 # like view, but no edit ability
-                 #  since the content is generated,
-                 # or backlink list, since there would be so many.
-                 stat=('$def with (id)\n'
-                       +html+
-                       ' <head>\n'
-                       ' </head>\n'
-                       ' <body>\n'
-                       '  <h1>Error</h1>\n'
-                       '  There is currently no MSDS file for \n'
-                       '  $:htmlize(id)\n'
-                       ' </body>\n'
-                       '</html>\n')
-                 file(self.dir+'error.html', 'w').write(stat)
-           if not os.path.exists(self.dir+'docs.html') :
-                 docs=(html +
-                       '<a href="/docs/inventory.pdf">Inventory</a>\n'
-                       ' in accordance with the \n'
-                       ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm#chpe7">\n'
-                       '  Chemical Hygiene Plan Section E-7</a>.<br>\n'
-                       '<a href="/docs/door_warning.pdf">Door warning</a>\n'
-                       ' in accordance with the Chemical Hygiene Plan Sections\n'
-                       ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm#chpe7">E-7</a>\n'
-                       ' and \n'
-                       ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm#chpe10">E-10</a>\n'
-                       '.<br>\n'
-                       ' </body>\n'
-                       '</html>\n')
-                 file(self.dir+'docs.html', 'w').write(docs)
-
-
-class chem_web_daemon (daemon.Daemon) :
-      gid = None
-      uid = None
-      pidfile = PID_FILE
-      logfile = LOG_FILE
-      loglevel = 'INFO'
-      def run(self) :
-            if self.pkey_file == None or self.cert_file == None :
-                  logging.info("http://%s:%d/" % web.validip(self.options.ip))
-            else :
-                  logging.info("https://%s:%d/" % web.validip(self.options.ip))
-
-            ## web.py should give detailed error messages, not just `internal error'
-            web.internalerror = web.debugerror
-
-            ## How we'd start webpy server if we didn't need command line args or SSL
-            # Pass web my URL regexps, and the globals from this script
-            # You can also pass it web.profiler to help optimize for speed
-            #web.run(urls, globals())
-            ## How we have to start it now
-            webpy_func = web.webpyfunc(urls, globals(), False)
-            wsgi_func = web.wsgifunc(webpy_func)
-            web.httpserver.runsecure(wsgi_func,
-                                     web.validip(self.options.ip),
-                                     ssl_private_key_filename=self.pkey_file,
-                                     ssl_certificate_filename=self.cert_file)
-
-      def read_basic_config(self) :
-            pass
-      def parse_options(self):
-            from optparse import OptionParser
-            
-            usage_string = ('%prog [options]\n'
-                            '\n'
-                            '2008, W. Trevor King.\n')
-            version_string = '%%prog %s' % __version__
-            parser = OptionParser(usage=usage_string, version=version_string)
-            
-            # Daemon options
-            parser.add_option('--start', dest='action',
-                              action='store_const', const='start', default='start',
-                              help='Start the daemon (the default action)')
-            parser.add_option('--stop', dest='action',
-                              action='store_const', const='stop', default='start',
-                              help='Stop the daemon')
-            parser.add_option('-n', '--nodaemon', dest='daemonize',
-                              action='store_false', default=True,
-                              help='Run in the foreground')
-            
-            # Server options
-            parser.add_option('-a', '--ip-address', dest="ip",
-                              help="IP address (default '%default')",
-                              type='string', metavar="IP", default='0.0.0.0:8080')
-            parser.add_option('-s', '--secure', dest="secure",
-                              help="Run in secure (HTTPS) mode.",
-                              type='string', metavar="PKEY_FILE:CERT_FILE")
-            parser.add_option('-v', '--verbose', dest="verbose", action="store_true",
-                              help="Print lots of debugging information",
-                              default=False)
-            
-            self.options, self.args = parser.parse_args()
-            parser.destroy()
-            
-            if self.options.verbose :
-                  self.loglevel = 'DEBUG'
-            # get options for httpserver
-            if self.options.secure != None :
-                  split = self.options.secure.split(':')
-                  assert len(split) == 2, "Invalid secure argument '%s'"
-                  self.pkey_file = split[0]
-                  self.cert_file = split[1]
-            else :
-                  self.pkey_file = None
-                  self.cert_file = None
-
-### the following instances and definitions must have
-## global scope. because they are called from inside web
-# create the database
-db = database(filename=DB_FILE)
-MSDSman = MSDS_manager(db)
-dgen = docgen(db)
-
-## set up the templates
-tmplt = templt()
-# Tell templator where to look for templates
-# to provide a framework for the generated webpages
-render = web.template.render(tmplt.dir)
-
-## Define markup functions
-mkup = markup()
-htmlize = lambda t : mkup.htmlize(t)
-name2str = lambda n : mkup.name2str(n)
-mktable = lambda h,r : mkup.make_table(h,r,WIDTH_MAX,raw_fields=('ID','Name'))
-mkadd = lambda ni : mkup.make_add_button(ni)
-record_form = lambda i,f,l,r,M : mkup.record_form_entries(i,f,l,r,M)
-## Give templates access to the htmlize, make_table, and name2str functions
-# ( see http://webpy.org/templetor and our view.html )
-web.template.Template.globals['htmlize'] = htmlize
-web.template.Template.globals['make_table'] = mktable
-web.template.Template.globals['make_add_button'] = mkadd
-web.template.Template.globals['name2str'] = name2str
-web.template.Template.globals['record_form'] = record_form
-
-
-## If this script is run from the command line,
-# use the tools we've just defined to host the chemical inventory
-if __name__=="__main__":
-      chem_web_daemon().main()