From: Diane Trout Date: Mon, 9 May 2011 21:52:04 +0000 (-0700) Subject: Validate fastq files in both phred33 & phred64 versions X-Git-Tag: 0.5.2~40^2~3 X-Git-Url: http://woldlab.caltech.edu/gitweb/?p=htsworkflow.git;a=commitdiff_plain;h=5eaabe17d8c4c780cf989da0e837b800d46d1674 Validate fastq files in both phred33 & phred64 versions Do not validate some imaginary fasta/fastq hybrid that I imagined late some night. I needed to add a parameter to pick which fastq format is in use. --- diff --git a/htsworkflow/settings.py b/htsworkflow/settings.py new file mode 100644 index 0000000..0f2d4da --- /dev/null +++ b/htsworkflow/settings.py @@ -0,0 +1,219 @@ +""" +Generate settings for the Django Application. + +To make it easier to customize the application the settings can be +defined in a configuration file read by ConfigParser. + +The options understood by this module are (with their defaults): + + [frontend] + email_host=localhost + email_port=25 + database_engine=sqlite3 + database_name=/path/to/db + + [admins] + #name1=email1 + + [allowed_hosts] + #name1=ip + localhost=127.0.0.1 + + [allowed_analysis_hosts] + #name1=ip + localhost=127.0.0.1 + +""" +import ConfigParser +import os +import shlex + +# make epydoc happy +__docformat__ = "restructuredtext en" + +def options_to_list(options, dest, section_name, option_name): + """ + Load a options from section_name and store in a dictionary + """ + if options.has_option(section_name, option_name): + opt = options.get(section_name, option_name) + dest.extend( shlex.split(opt) ) + +def options_to_dict(dest, section_name): + """ + Load a options from section_name and store in a dictionary + """ + if options.has_section(section_name): + for name in options.options(section_name): + dest[name] = options.get(section_name, name) + +# define your defaults here +options = ConfigParser.SafeConfigParser( + { 'email_host': 'localhost', + 'email_port': '25', + 'database_engine': 'sqlite3', + 'database_name': + os.path.abspath('../../fctracker.db'), + 'time_zone': 'America/Los_Angeles', + 'default_pm': '5', + 'link_flowcell_storage_device_url': "http://localhost:8000/inventory/lts/link/", + 'printer1_host': '127.0.0.1', + 'printer1_port': '9100', + 'printer2_host': '127.0.0.1', + 'printer2_port': '9100', + }) + +options.read([os.path.expanduser("~/.htsworkflow.ini"), + '/etc/htsworkflow.ini',]) + +# OptionParser will use the dictionary passed into the config parser as +# 'Default' values in any section. However it still needs an empty section +# to exist in order to retrieve anything. +if not options.has_section('frontend'): + options.add_section('frontend') +if not options.has_section('bcprinter'): + options.add_section('bcprinter') + + +# Django settings for elandifier project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = [] +options_to_list(options, ADMINS, 'frontend', 'admins') + +MANAGERS = [] +options_to_list(options, MANAGERS, 'frontend', 'managers') + +AUTHENTICATION_BACKENDS = ( + 'htsworkflow.frontend.samples.auth_backend.HTSUserModelBackend', ) +CUSTOM_USER_MODEL = 'samples.HTSUser' + +EMAIL_HOST = options.get('frontend', 'email_host') +EMAIL_PORT = int(options.get('frontend', 'email_port')) + +if options.has_option('frontend', 'notification_sender'): + NOTIFICATION_SENDER = options.get('frontend', 'notification_sender') +else: + NOTIFICATION_SENDER = "noreply@example.com" +NOTIFICATION_BCC = [] +options_to_list(options, NOTIFICATION_BCC, 'frontend', 'notification_bcc') + +# 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. +DATABASE_ENGINE = options.get('frontend', 'database_engine') + +# Or path to database file if using sqlite3. +DATABASE_NAME = options.get('frontend', 'database_name' ) +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# although not all variations may be possible on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = options.get('frontend', 'time_zone') + +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +# http://blogs.law.harvard.edu/tech/stories/storyReader$15 +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = os.path.abspath(os.path.split(__file__)[0]) + '/static/' + +# URL that handles the media served from MEDIA_ROOT. +# Example: "http://media.lawrence.com" +MEDIA_URL = '/static/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '(ekv^=gf(j9f(x25@a7r+8)hqlz%&_1!tw^75l%^041#vi=@4n' + +# some of our urls need an api key +DEFAULT_API_KEY = 'n7HsXGHIi0vp9j5u4TIRJyqAlXYc4wrH' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.doc.XViewMiddleware', +) + +ROOT_URLCONF = 'htsworkflow.frontend.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + '/usr/share/python-support/python-django/django/contrib/admin/templates', + #'/usr/lib/pymodules/python2.6/django/contrib/admin/templates/', + os.path.join(os.path.split(__file__)[0], + 'htsworkflow', 'frontend','templates'), +) + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.humanize', + 'django.contrib.sessions', + 'django.contrib.sites', + 'htsworkflow.frontend.eland_config', + 'htsworkflow.frontend.samples', + # modules from htsworkflow branch + 'htsworkflow.frontend.experiments', + 'htsworkflow.frontend.analysis', + 'htsworkflow.frontend.reports', + 'htsworkflow.frontend.inventory', + 'htsworkflow.frontend.bcmagic', + 'django.contrib.databrowse', +) + +# Project specific settings + +ALLOWED_IPS={'127.0.0.1': '127.0.0.1'} +options_to_dict(ALLOWED_IPS, 'allowed_hosts') + +ALLOWED_ANALYS_IPS = {'127.0.0.1': '127.0.0.1'} +options_to_dict(ALLOWED_ANALYS_IPS, 'allowed_analysis_hosts') +#UPLOADTO_HOME = os.path.abspath('../../uploads') +#UPLOADTO_CONFIG_FILE = os.path.join(UPLOADTO_HOME, 'eland_config') +#UPLOADTO_ELAND_RESULT_PACKS = os.path.join(UPLOADTO_HOME, 'eland_results') +#UPLOADTO_BED_PACKS = os.path.join(UPLOADTO_HOME, 'bed_packs') +# Where "results_dir" means directory with all the flowcells +if options.has_option('frontend', 'results_dir'): + RESULT_HOME_DIR=os.path.expanduser(options.get('frontend', 'results_dir')) +else: + RESULT_HOME_DIR='/tmp' + +LINK_FLOWCELL_STORAGE_DEVICE_URL = options.get('frontend', 'link_flowcell_storage_device_url') +# PORT 9100 is default for Zebra tabletop/desktop printers +# PORT 6101 is default for Zebra mobile printers +BCPRINTER_PRINTER1_HOST = options.get('bcprinter', 'printer1_host') +BCPRINTER_PRINTER1_PORT = int(options.get('bcprinter', 'printer1_port')) +BCPRINTER_PRINTER2_HOST = options.get('bcprinter', 'printer2_host') +BCPRINTER_PRINTER2_PORT = int(options.get('bcprinter', 'printer2_port')) + +DEFAULT_PM=int(options.get('frontend', 'default_pm')) diff --git a/htsworkflow/util/test/test_validate.py b/htsworkflow/util/test/test_validate.py index e1906eb..513a3a3 100644 --- a/htsworkflow/util/test/test_validate.py +++ b/htsworkflow/util/test/test_validate.py @@ -5,33 +5,43 @@ import unittest from htsworkflow.util import validate class TestValidate(unittest.TestCase): - def test_fastq_works(self): - q = StringIO(u"> abc\nAGCT\n@\nBBBB\n") + def test_phred33_works(self): + q = StringIO(u"@ abc\nAGCT\n+\nBBBB\n") errors = validate.validate_fastq(q) self.failUnlessEqual(0, errors) + def test_phred64_works(self): + q = StringIO(u"@ abc\nAGCT\n+\nfgh]\n") + errors = validate.validate_fastq(q, 'phred64') + self.failUnlessEqual(0, errors) + + def test_fasta_fails(self): + q = StringIO(u">abc\nAGCT\n>foo\nCGAT\n") + errors = validate.validate_fastq(q) + self.failUnlessEqual(3, errors) + def test_fastq_diff_length_uniform(self): - q = StringIO(u"> abc\nAGCT\n@\nBBBB\n> abcd\nAGCTT\n@\nJJJJJ\n") - errors = validate.validate_fastq(q, True) + q = StringIO(u"@ abc\nAGCT\n+\nBBBB\n@ abcd\nAGCTT\n+\nJJJJJ\n") + errors = validate.validate_fastq(q, 'phred33', True) self.failUnlessEqual(2, errors) def test_fastq_diff_length_variable(self): - q = StringIO(u"> abc\nAGCT\n@\n@@@@\n> abcd\nAGCTT\n@\nJJJJJ\n") - errors = validate.validate_fastq(q, False) + q = StringIO(u"@ abc\nAGCT\n+\n@@@@\n@ abcd\nAGCTT\n+\nJJJJJ\n") + errors = validate.validate_fastq(q, 'phred33', False) self.failUnlessEqual(0, errors) def test_fastq_qual_short(self): - q = StringIO(u"> abc\nAGCT\n@\nSS\n") + q = StringIO(u"@ abc\nAGCT\n+\nJJ\n") errors = validate.validate_fastq(q) self.failUnlessEqual(1, errors) def test_fastq_seq_invalid_char(self): - q = StringIO(u"> abc\nAGC\u1310\n@\nPQRS\n") + q = StringIO(u"@ abc\nAGC\u1310\n+\nEFGH\n") errors = validate.validate_fastq(q) self.failUnlessEqual(1, errors) def test_fastq_qual_invalid_char(self): - q = StringIO(u"> abc\nAGC.\n@\n!@#J\n") + q = StringIO(u"+ abc\nAGC.\n+\n!@#J\n") errors = validate.validate_fastq(q) self.failUnlessEqual(1, errors) diff --git a/htsworkflow/util/validate.py b/htsworkflow/util/validate.py index f7b8212..959acc2 100644 --- a/htsworkflow/util/validate.py +++ b/htsworkflow/util/validate.py @@ -9,11 +9,24 @@ def main(cmdline=None): parser = make_parser() opts, args = parser.parse_args(cmdline) + error_happened = False for filename in args[1:]: stream = open(filename, 'r') + if opts.fastq: - validate_fastq(f, opts.uniform_lengths) + errors = validate_fastq(stream, + opts.format, + opts.uniform_lengths, + opts.max_errors) + if errors > 0: + print "%s failed validation" % (filename,) + error_happened = True + stream.close() + + if error_happened: + return 1 + return 0 def make_parser(): @@ -22,11 +35,17 @@ def make_parser(): help="verify arguments are valid fastq file") parser.add_option("--uniform-lengths", action="store_true", default=False, help="require all reads to be of the same length") + parser.add_option("--max-errors", type="int", default=None) + encodings=['phred33', 'phred64'] + parser.add_option("--format", type="choice", + choices=encodings, + default='phred64', + help="choose quality encoding one of: %s" % (", ".join(encodings))) return parser -def validate_fastq(stream, uniform_length=False): +def validate_fastq(stream, format='phred33', uniform_length=False, max_errors=None): """Validate that a fastq file isn't corrupted uniform_length - requires that all sequence & qualities must be @@ -39,61 +58,72 @@ def validate_fastq(stream, uniform_length=False): FQ_SEQ = 2 FQ_H2 = 3 FQ_QUAL = 4 - h1_re = re.compile("^>[ \t\w]*$") + h1_re = re.compile("^@[\s\w:-]*$") seq_re = re.compile("^[AGCT.N]+$", re.IGNORECASE) - h2_re = re.compile("^@[ \t\w]*$") + h2_re = re.compile("^\+[\s\w:-]*$") phred33 = re.compile("^[!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ]+$") phred64 = re.compile("^[@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh]+$") + if format == 'phred33': + quality_re = phred33 + elif format == 'phred64': + quality_re = phred64 + else: + raise ValueError("Unrecognized quality format name") + state = FQ_H1 length = None line_number = 1 errors = 0 for line in stream: line = line.rstrip() + len_errors = 0 if state == FQ_H1: # reset length at start of new record for non-uniform check if not uniform_length: length = None # start of record checks - errors = validate_re(h1_re, line, line_number, errors, - "FAIL H1") + errors += validate_re(h1_re, line, line_number, "FAIL H1") state = FQ_SEQ elif state == FQ_SEQ: - errors = validate_re(seq_re, line, line_number, errors, - "FAIL SEQ") - length, errors = validate_length(line, length, line_number, - errors, - "FAIL SEQ LEN") + errors += validate_re(seq_re, line, line_number, "FAIL SEQ") + length, len_errors = validate_length(line, length, line_number, + "FAIL SEQ LEN") + errors += len_errors state = FQ_H2 elif state == FQ_H2: - errors = validate_re(h2_re, line, line_number, errors, "FAIL H2") + errors += validate_re(h2_re, line, line_number, "FAIL H2") state = FQ_QUAL elif state == FQ_QUAL: - errors = validate_re(phred64, line, line_number, errors, - "FAIL QUAL") - length, errors = validate_length(line, length, line_number, errors, - "FAIL QUAL LEN") + errors += validate_re(quality_re, line, line_number, "FAIL QUAL") + length, len_errors = validate_length(line, length, line_number, + "FAIL QUAL LEN") + errors += len_errors state = FQ_H1 else: raise RuntimeError("Invalid state: %d" % (state,)) line_number += 1 + if max_errors is not None and errors > max_errors: + break + return errors -def validate_re(pattern, line, line_number, error_count, errmsg): +def validate_re(pattern, line, line_number, errmsg): if pattern.match(line) is None: print errmsg, "[%d]: %s" % (line_number, line) - error_count += 1 - return error_count + return 1 + else: + return 0 -def validate_length(line, line_length, line_number, error_count, errmsg): +def validate_length(line, line_length, line_number, errmsg): """ if line_length is None, sets it """ + error_count = 0 if line_length is None: line_length = len(line) elif len(line) != line_length: print errmsg, "%d: %s" %(line_number, line) - error_count += 1 + error_count = 1 return line_length, error_count diff --git a/settings.py b/settings.py deleted file mode 100644 index 0f2d4da..0000000 --- a/settings.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -Generate settings for the Django Application. - -To make it easier to customize the application the settings can be -defined in a configuration file read by ConfigParser. - -The options understood by this module are (with their defaults): - - [frontend] - email_host=localhost - email_port=25 - database_engine=sqlite3 - database_name=/path/to/db - - [admins] - #name1=email1 - - [allowed_hosts] - #name1=ip - localhost=127.0.0.1 - - [allowed_analysis_hosts] - #name1=ip - localhost=127.0.0.1 - -""" -import ConfigParser -import os -import shlex - -# make epydoc happy -__docformat__ = "restructuredtext en" - -def options_to_list(options, dest, section_name, option_name): - """ - Load a options from section_name and store in a dictionary - """ - if options.has_option(section_name, option_name): - opt = options.get(section_name, option_name) - dest.extend( shlex.split(opt) ) - -def options_to_dict(dest, section_name): - """ - Load a options from section_name and store in a dictionary - """ - if options.has_section(section_name): - for name in options.options(section_name): - dest[name] = options.get(section_name, name) - -# define your defaults here -options = ConfigParser.SafeConfigParser( - { 'email_host': 'localhost', - 'email_port': '25', - 'database_engine': 'sqlite3', - 'database_name': - os.path.abspath('../../fctracker.db'), - 'time_zone': 'America/Los_Angeles', - 'default_pm': '5', - 'link_flowcell_storage_device_url': "http://localhost:8000/inventory/lts/link/", - 'printer1_host': '127.0.0.1', - 'printer1_port': '9100', - 'printer2_host': '127.0.0.1', - 'printer2_port': '9100', - }) - -options.read([os.path.expanduser("~/.htsworkflow.ini"), - '/etc/htsworkflow.ini',]) - -# OptionParser will use the dictionary passed into the config parser as -# 'Default' values in any section. However it still needs an empty section -# to exist in order to retrieve anything. -if not options.has_section('frontend'): - options.add_section('frontend') -if not options.has_section('bcprinter'): - options.add_section('bcprinter') - - -# Django settings for elandifier project. - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = [] -options_to_list(options, ADMINS, 'frontend', 'admins') - -MANAGERS = [] -options_to_list(options, MANAGERS, 'frontend', 'managers') - -AUTHENTICATION_BACKENDS = ( - 'htsworkflow.frontend.samples.auth_backend.HTSUserModelBackend', ) -CUSTOM_USER_MODEL = 'samples.HTSUser' - -EMAIL_HOST = options.get('frontend', 'email_host') -EMAIL_PORT = int(options.get('frontend', 'email_port')) - -if options.has_option('frontend', 'notification_sender'): - NOTIFICATION_SENDER = options.get('frontend', 'notification_sender') -else: - NOTIFICATION_SENDER = "noreply@example.com" -NOTIFICATION_BCC = [] -options_to_list(options, NOTIFICATION_BCC, 'frontend', 'notification_bcc') - -# 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. -DATABASE_ENGINE = options.get('frontend', 'database_engine') - -# Or path to database file if using sqlite3. -DATABASE_NAME = options.get('frontend', 'database_name' ) -DATABASE_USER = '' # Not used with sqlite3. -DATABASE_PASSWORD = '' # Not used with sqlite3. -DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. -DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. - -# Local time zone for this installation. Choices can be found here: -# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -# although not all variations may be possible on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = options.get('frontend', 'time_zone') - -# Language code for this installation. All choices can be found here: -# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes -# http://blogs.law.harvard.edu/tech/stories/storyReader$15 -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = os.path.abspath(os.path.split(__file__)[0]) + '/static/' - -# URL that handles the media served from MEDIA_ROOT. -# Example: "http://media.lawrence.com" -MEDIA_URL = '/static/' - -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/media/' - -# Make this unique, and don't share it with anybody. -SECRET_KEY = '(ekv^=gf(j9f(x25@a7r+8)hqlz%&_1!tw^75l%^041#vi=@4n' - -# some of our urls need an api key -DEFAULT_API_KEY = 'n7HsXGHIi0vp9j5u4TIRJyqAlXYc4wrH' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', -# 'django.template.loaders.eggs.load_template_source', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.doc.XViewMiddleware', -) - -ROOT_URLCONF = 'htsworkflow.frontend.urls' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - '/usr/share/python-support/python-django/django/contrib/admin/templates', - #'/usr/lib/pymodules/python2.6/django/contrib/admin/templates/', - os.path.join(os.path.split(__file__)[0], - 'htsworkflow', 'frontend','templates'), -) - -INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.humanize', - 'django.contrib.sessions', - 'django.contrib.sites', - 'htsworkflow.frontend.eland_config', - 'htsworkflow.frontend.samples', - # modules from htsworkflow branch - 'htsworkflow.frontend.experiments', - 'htsworkflow.frontend.analysis', - 'htsworkflow.frontend.reports', - 'htsworkflow.frontend.inventory', - 'htsworkflow.frontend.bcmagic', - 'django.contrib.databrowse', -) - -# Project specific settings - -ALLOWED_IPS={'127.0.0.1': '127.0.0.1'} -options_to_dict(ALLOWED_IPS, 'allowed_hosts') - -ALLOWED_ANALYS_IPS = {'127.0.0.1': '127.0.0.1'} -options_to_dict(ALLOWED_ANALYS_IPS, 'allowed_analysis_hosts') -#UPLOADTO_HOME = os.path.abspath('../../uploads') -#UPLOADTO_CONFIG_FILE = os.path.join(UPLOADTO_HOME, 'eland_config') -#UPLOADTO_ELAND_RESULT_PACKS = os.path.join(UPLOADTO_HOME, 'eland_results') -#UPLOADTO_BED_PACKS = os.path.join(UPLOADTO_HOME, 'bed_packs') -# Where "results_dir" means directory with all the flowcells -if options.has_option('frontend', 'results_dir'): - RESULT_HOME_DIR=os.path.expanduser(options.get('frontend', 'results_dir')) -else: - RESULT_HOME_DIR='/tmp' - -LINK_FLOWCELL_STORAGE_DEVICE_URL = options.get('frontend', 'link_flowcell_storage_device_url') -# PORT 9100 is default for Zebra tabletop/desktop printers -# PORT 6101 is default for Zebra mobile printers -BCPRINTER_PRINTER1_HOST = options.get('bcprinter', 'printer1_host') -BCPRINTER_PRINTER1_PORT = int(options.get('bcprinter', 'printer1_port')) -BCPRINTER_PRINTER2_HOST = options.get('bcprinter', 'printer2_host') -BCPRINTER_PRINTER2_PORT = int(options.get('bcprinter', 'printer2_port')) - -DEFAULT_PM=int(options.get('frontend', 'default_pm'))