[Spambayes-checkins] spambayes/spambayes OptionConfig.py,NONE,1.1
Richie Hindle
richiehindle at users.sourceforge.net
Fri Jan 24 12:51:34 EST 2003
Update of /cvsroot/spambayes/spambayes/spambayes
In directory sc8-pr-cvs1:/tmp/cvs-serv5439
Added Files:
OptionConfig.py
Log Message:
Moved OptionConfig into the spambayes package - there's no longer
any need to run it standalone now that it's a part of the main web UI.
--- NEW FILE: OptionConfig.py ---
"""Options Configurator
Classes:
OptionsConfigurator - changes select values in Options.py
Abstract:
This module implements a browser based Spambayes option file configuration
utility. Users may use the pages in this application to customize the
options in the bayescustomize.ini file.
This does not support the BAYESCUSTOMIZE environment variable. Is this even
used anywhere?
By default, this module forms a part of the web user interface provided by
pop3proxy.py. You can also run it standalone, but only for historical
reasons. To do this, just invoke OptionConfig.py <optional port number>
The port number is the port the http server will listen on, and defaults to
8000. Your web browser should launch automatically; if it doesn't, then
point it to http://locahost:8000 (or whatever port you chose).
To Do:
o Suggestions?
"""
# This module is part of the spambayes project, which is Copyright 2002
# The Python Software Foundation and is covered by the Python Software
# Foundation license.
__author__ = "Tim Stone <tim at fourstonesExpressions.com>"
# Blame for bugs caused by using Dibbler: Richie Hindle <richie at entrian.com>
try:
True, False
except NameError:
# Maintain compatibility with Python 2.2
True, False = 1, 0
from spambayes import Dibbler, PyMeldLite
from spambayes.Options import options
import re
import os, sys
import ConfigParser
import copy
IMAGES = ('helmet', 'config', 'status')
# This control dictionary maps http request parameters and template fields
# to ConfigParser sections and options. The key matches both the input
# field that corresponds to a section/option, and also the HTML template
# variable that is used to display the value of that section/option.
parm_ini_map = \
{'hamcutoff': ('Categorization', 'ham_cutoff'),
'spamcutoff': ('Categorization', 'spam_cutoff'),
'dbname': ('pop3proxy', 'pop3proxy_persistent_storage_file'),
'headername': ('Hammie', 'hammie_header_name'),
'spamstring': ('Hammie', 'header_spam_string'),
'hamstring': ('Hammie', 'header_ham_string'),
'unsurestring': ('Hammie', 'header_unsure_string'),
'p3servers': ('pop3proxy', 'pop3proxy_servers'),
'p3ports': ('pop3proxy', 'pop3proxy_ports'),
}
# "Restore defaults" ignores these, because it would be pointlessly
# destructive - they default to being empty, so you gain nothing by
# restoring them.
noRestore = ('pop3proxy_servers', 'pop3proxy_ports')
# This governs the order in which the options appear on the configurator
# page, and the headings and help text that are used.
page_layout = \
(
("POP3 Options",
( ("p3servers", "Servers",
"""The Spambayes POP3 proxy intercepts incoming email and classifies
it before sending it on to your email client. You need to specify
which POP3 server(s) you wish it to intercept - a POP3 server
address typically looks like "pop3.myisp.net". If you use more than
one server, simply separate their names with commas. You can get
these server names from your existing email configuration, or from
your ISP or system administrator. If you are using Web-based email,
you can't use the Spambayes POP3 proxy (sorry!). In your email
client's configuration, where you would normally put your POP3 server
address, you should now put the address of the machine running
Spambayes."""),
("p3ports", "Ports",
"""Each POP3 server that is being monitored must be assigned to a
'port' in the Spambayes POP3 proxy. This port must be different for
each monitored server, and there MUST be a port for each monitored
server. Again, you need to configure your email client to use this
port. If there are multiple servers, you must specify the same
number of ports as servers, separated by commas."""),
)),
("Statistics Options",
( ("hamcutoff", "Ham Cutoff",
"""Spambayes gives each email message a spam probability between
0 and 1. Emails below the Ham Cutoff probability are classified
as Ham. Larger values will result in more messages being
classified as ham, but with less certainty that all of them
actually <i>are</i> ham. This value should be between 0 and 1,
and should be smaller than the Spam Cutoff."""),
("spamcutoff", "Spam Cutoff",
"""Emails with a spam probability above the Spam Cutoff are
classified as Spam - just like the Ham Cutoff but at the other
end of the scale. Messages that fall between the two values
are classified as Unsure."""),
("dbname", "Database filename",
"""Spambayes builds a database of information that it gathers
from incoming emails and from you, the user, to get better and
better at classifying your email. This option specifies the
name of the database file. If you don't give a full pathname,
the name will be taken to be relative to the current working
directory."""),
)),
)
# Tim Stone's original OptionConfig.py had these options as well, but I
# (Richie) suggested that they were overkill, and Tim agreed. We can always
# put them back if people want them.
_insertedHeaderOptions = '''
("Inserted Header Options",
( ("headername", "Header Name",
"""Spambayes classifies each message by inserting a new header into
the message. This header can then be used by your email client
(provided your client supports filtering) to move spam into a
separate folder (recommended), delete it (not recommended), etc.
This option specifies the name of the header that Spambayes inserts.
The default value should work just fine, but you may change it to
anything that you wish."""),
("spamstring", "Spam Designation",
"""The header that Spambayes inserts into each email has a name,
(Header Name, above), and a value. If the classifier determines
that this email is probably spam, it places a header named as
above with a value as specified by this string. The default
value should work just fine, but you may change it to anything
that you wish."""),
("hamstring", "Ham Designation",
"""As for Spam Designation above, but for emails classified as
Ham."""),
("unsurestring", "Unsure Designation",
"""As for Spam/Ham Designation above, but for emails which the
classifer wasn't sure about (ie. the spam probability fell between
the Ham and Spam Cutoffs). Emails that have this classification
should always be the subject of training."""),
)),
'''
OK_MESSAGE = "%s. Return <a href='home'>Home</a>."
PIMapSect = 0
PIMapOpt = 1
class OptionsConfigurator(Dibbler.HTTPPlugin):
def __init__(self, proxyUI=None):
Dibbler.HTTPPlugin.__init__(self)
# Store the proxy UI; this won't be given when we're standalone.
self.proxyUI = proxyUI
# Load up the necessary resources: ui.html and the GIFs.
from pop3proxy import readUIResources
htmlSource, self._images = readUIResources()
self.html = PyMeldLite.Meld(htmlSource)
# Adjust the HTML according to whether we're running standalone or as
# a part of the proxy.
if not self.proxyUI:
self.html.productName = "Spambayes Options Configurator"
self.html.footerHome = "Spambayes Options Configurator"
self.html.shutdownButton.value = "Shutdown Configurator"
else:
# "Save and Shutdown" is confusing here - it means "Save database"
# but that's not clear.
self.html.shutdownTableCell = " "
def onConfig(self):
# start with the options config file, add bayescustomize.ini to it
bcini = ConfigParser.ConfigParser()
# this is a pain...
for sect in options._config.sections():
for opt in options._config.options(sect):
try:
bcini.set(sect, opt, options._config.get(sect, opt))
except ConfigParser.NoSectionError:
bcini.add_section(sect)
bcini.set(sect, opt, options._config.get(sect, opt))
bcini.read('bayescustomize.ini')
# Start with an empty config form then add the sections.
html = self.html.clone()
html.mainContent = self.html.configForm.clone()
html.mainContent.configFormContent = ""
# Loop though the sections in the `page_layout` structure above.
for sectionHeading, values in page_layout:
# Start the yellow-headed box for this section.
section = self.html.headedBox.clone()
section.heading = sectionHeading
del section.iconCell
# Get a clone of the config table and a clone of each example row,
# then blank out the example rows to make way for the real ones.
configTable = self.html.configTable.clone()
configRow1 = configTable.configRow1.clone()
configRow2 = configTable.configRow2.clone()
blankRow = configTable.blankRow.clone()
del configTable.configRow1
del configTable.configRow2
del configTable.blankRow
# Now within this section, loop though the values, adding a
# labelled input control for each one, populated with the current
# value.
isFirstRow = True
for name, label, unusedHelp in values:
newConfigRow1 = configRow1.clone()
newConfigRow2 = configRow2.clone()
currentValue = bcini.get(parm_ini_map[name][PIMapSect], \
parm_ini_map[name][PIMapOpt])
# If this is the first row, insert the help text in a cell
# with a `rowspan` that covers all the rows.
if isFirstRow:
entries = []
for unusedName, topic, help in values:
entries.append("<p><b>%s: </b>%s</p>" % (topic, help))
newConfigRow1.helpSpacer = ' ' * 10
newConfigRow1.helpCell = '\n'.join(entries)
else:
del newConfigRow1.helpSpacer
del newConfigRow1.helpCell
# Populate the rows with the details and add them to the table.
newConfigRow1.label = label
newConfigRow1.input.name = name
newConfigRow1.input.value = currentValue
newConfigRow2.currentValue = currentValue
configTable += newConfigRow1 + newConfigRow2 + blankRow
isFirstRow = False
# Finish off the box for this section and add it to the form.
section.boxContent = configTable
html.configFormContent += section
# Customise the page according to whether we're standalone or a proxy.
if self.proxyUI:
html.title = 'Home > Configure'
html.pagename = '> Configure'
else:
html.title = 'Home'
del html.homelink
html.pagename = 'Home'
self.writeOKHeaders('text/html')
self.write(html)
# Implement `onHome` for the standalone version. In the POP3 proxy, the
# proxy UI's `onHome` will take precedence over this one.
onHome = onConfig
def onChangeopts(self, **parms):
html = self.html.clone()
html.mainContent = self.html.headedBox.clone()
errmsg = editInput(parms)
if errmsg != '':
html.mainContent.heading = "Errors Detected"
html.mainContent.boxContent = errmsg
html.title = 'Home > Error'
html.pagename = '> Error'
self.writeOKHeaders('text/html')
self.write(html)
return
updateIniFile(parms)
html.mainContent.heading = "Options Changed"
if self.proxyUI:
html.mainContent.boxContent = OK_MESSAGE % "Options changed"
self.proxyUI.reReadOptions()
else:
html.mainContent.boxContent = """The options changes you've made
have been recorded. You will need to restart any Spambayes
processes you have running, such as the pop3proxy, in order
for your changes to take effect. When you return to the
Options Configuration homepage, you may need to refresh the
page to see the changes you have made."""
html.title = 'Home > Options Changed'
html.pagename = '> Options Changed'
self.writeOKHeaders('text/html')
self.write(html)
def onRestoredefaults(self, how):
restoreIniDefaults()
html = self.html.clone()
html.mainContent = self.html.headedBox.clone()
html.mainContent.heading = "Option Defaults Restored"
if self.proxyUI:
html.mainContent.boxContent = OK_MESSAGE % "Defaults restored"
self.proxyUI.reReadOptions()
else:
html.mainContent.boxContent = """All options have been reverted to
their default values. You will need to restart any Spambayes
processes you have running, such as the pop3proxy, in order for
your changes to take effect. When you return to the Options
Configuration homepage, you may need to refresh the page to see
the changes you have made."""
html.title = 'Home > Defaults Restored'
html.pagename = '> Defaults Restored'
self.writeOKHeaders('text/html')
self.write(html)
def onSave(self, how):
# Really 'shutdown'; this is the button in the footer, not on the
# form. Again, the proxy UI's `onSave` will override this one when
# we're running as part of the proxy.
html = self.html.clone()
del html.helmet
del html.homelink
html.shutdownTableCell = " "
html.mainContent = self.html.shutdownMessage
html.title = 'Home > Shutdown'
html.pagename = 'Shutdown'
self.writeOKHeaders('text/html')
self.write(html)
self.close()
sys.exit()
def _writeImage(self, image):
self.writeOKHeaders('image/gif')
self.write(self._images[image])
# If you are easily offended, look away now...
for imageName in IMAGES:
exec "def %s(self): self._writeImage('%s')" % \
("on%sGif" % imageName.capitalize(), imageName)
def editInput(parms):
errmsg = ''
# edit numericity of hamcutoff and spamcutoff
try:
hco = parms['hamcutoff']
except KeyError:
hco = options.ham_cutoff
try:
sco = parms['spamcutoff']
except KeyError:
sco = options.spam_cutoff
errmsg = ''
try:
hco = float(hco)
except ValueError:
errmsg += '<li>Ham cutoff must be a number, between 0 and 1</li>\n'
try:
sco = float(sco)
except ValueError:
errmsg += '<li>Spam cutoff must be a number, \
between 0 and 1</li>\n'
# edit 0 <= hamcutoff < spamcutoff <= 1
if hco < 0 or hco > 1:
errmsg += '<li>Ham cutoff must be between 0 and 1</li>\n'
if sco < 0 or sco > 1:
errmsg += '<li>Spam cutoff must be between 0 and 1</li>\n'
if not hco < sco:
errmsg += '<li>Ham cutoff must be less than Spam cutoff</li>\n'
# edit for equal number of pop3servers and ports
try:
slist = parms['p3servers'].split(',')
except KeyError:
slist = options.pop3proxy_servers.split(',')
try:
plist = parms['p3ports'].split(',')
except KeyError:
plist = options.pop3proxy_ports.split(',')
# edit for duplicate ports
if len(slist) != len(plist):
errmsg += '<li>The number of ports specified must match the \
number of servers specified</li>\n'
plist.sort()
for p in range(len(plist)-1):
try:
if plist[p] == plist[p+1]:
errmsg += '<li>All port numbers must be unique</li>'
break
except IndexError:
pass
return errmsg
def updateIniFile(parms):
# assumes bayescustomize.ini is in this process' working directory
inipath = os.path.abspath('bayescustomize.ini')
bcini = ConfigParser.ConfigParser()
bcini.read(inipath)
for httpParm in parm_ini_map:
map = parm_ini_map[httpParm]
sect = map[PIMapSect]
opt = map[PIMapOpt]
try:
val = parms[httpParm]
except KeyError:
continue
try:
bcini.add_section(sect)
except ConfigParser.DuplicateSectionError:
pass
bcini.set(sect, opt, val)
o = open(inipath, 'wt')
bcini.write(o)
o.close()
def restoreIniDefaults():
# assumes bayescustomize.ini is in this process' working directory
inipath = os.path.abspath('bayescustomize.ini')
bcini = ConfigParser.ConfigParser()
bcini.read(inipath)
# Only restore the settings that appear on the form.
for section, option in parm_ini_map.values():
if option not in noRestore:
try:
bcini.remove_option(section, option)
except ConfigParser.NoSectionError:
pass # Already missing.
o = open(inipath, 'wt')
bcini.write(o)
o.close()
#
# Running this standalone is no longer required, and doesn't work out of
# the box. The code's here for reference only.
#
def run(port):
httpServer = Dibbler.HTTPServer(port)
httpServer.register(OptionsConfigurator())
Dibbler.run(launchBrowser=True)
if __name__ == '__main__':
if len(sys.argv) > 1:
port = int(sys.argv[1])
else:
port = 8000
run(port)
More information about the Spambayes-checkins
mailing list