From cdf893aca0b45f8a3c9b6e79f9c537b21edf3e04 Mon Sep 17 00:00:00 2001 From: Martijn Vermaat <martijn@vermaat.name> Date: Mon, 22 Aug 2011 14:20:11 +0000 Subject: [PATCH] Support merging user configuration and system configuration. mutalyzer/config.py: - Try to load system configuration and user configuration, in that order, and merge them if we find both. tests/test_website.py: - Use running webserver to retrieve batch results. extras/config.user.example: - Example user configuration, overwriting some values from system configuration. git-svn-id: https://humgenprojects.lumc.nl/svn/mutalyzer/branches/refactor-mutalyzer-branch@336 eb6bd6ab-9ccd-42b9-aceb-e2899b4a52f1 --- README | 4 -- extras/config.example | 3 +- extras/config.user.example | 11 ++++ mutalyzer/config.py | 114 +++++++++++++++--------------------- mutalyzer/variantchecker.py | 1 - tests/test_website.py | 28 ++++++--- 6 files changed, 79 insertions(+), 82 deletions(-) create mode 100644 extras/config.user.example diff --git a/README b/README index dc7d6ad1..5120e879 100644 --- a/README +++ b/README @@ -42,10 +42,6 @@ Development notes Todo list: - Improve the web interface design :) -- Running Mutalyzer as a local user (either bin/mutalyzer-website.wsgi on port - 8080, or bin/mutalyzer for example) should be able with a partly different - configuration. Expecially cache and log file locations, because they are - owned by www-data. - Test all uses of mkstemp(). - Use naming conventions for modules Crossmap, Db, File, GenRecord, Mapper, Retriever, Scheduler. diff --git a/extras/config.example b/extras/config.example index 05e01241..bce2c1e4 100644 --- a/extras/config.example +++ b/extras/config.example @@ -1,8 +1,7 @@ # # Mutalyzer config file. # -# Copy this file to /etc/mutalyzer/config or ~/.config/mutalyzer/config and -# modify to suit your preferences. +# Copy this file to /etc/mutalyzer/config and modify to suit your preferences. # # These settings are used by the Retriever module. diff --git a/extras/config.user.example b/extras/config.user.example new file mode 100644 index 00000000..8abc160f --- /dev/null +++ b/extras/config.user.example @@ -0,0 +1,11 @@ +# +# Mutalyzer config file. +# +# Copy this file to ~/.config/mutalyzer/config to overwrite definitions from +# /etc/mutalyzer.config. + +# The cache directory. +cache = "/home/user/.cache/mutalyzer" + +# Name and location of the log file. +log = "/tmp/mutalyzer.log" diff --git a/mutalyzer/config.py b/mutalyzer/config.py index 4f222c97..1f5b36cd 100644 --- a/mutalyzer/config.py +++ b/mutalyzer/config.py @@ -6,10 +6,14 @@ module. import os -import tempfile from configobj import ConfigObj -import mutalyzer + +SYSTEM_CONFIGURATION = '/etc/mutalyzer/config' +USER_CONFIGURATION = os.path.join( + os.environ.get('XDG_CONFIG_HOME', None) or \ + os.path.join(os.path.expanduser('~'), '.config'), + 'mutalyzer', 'config') class ConfigurationError(Exception): @@ -36,10 +40,16 @@ class Config(): hard coded constant is used (the name and path to the configuration file). - The configuration file location is automatically detected, in the - following order: - 1) $XDG_CONFIG_HOME/mutalyzer/config - 2) /etc/mutalyzer/config + Configuration values are read from two locations, in this order: + 1) /etc/mutalyzer/config + 2) $XDG_CONFIG_HOME/mutalyzer/config + + If both files exist, values defined in the second overwrite values + defined in the first. + + An exception to this system is when the optional {filename} argument + is set. In that case, the locations listed above are ignored and the + configuration is read from {filename}. By the DRY-principle, we don't enumerate the configuration variables for each class in documentation. Instead, what variables are used by @@ -55,32 +65,23 @@ class Config(): - Supplied argument {filename} could not be opened. - Configuration file could not be parsed. - Not all variables are present in configuration file. - - @todo: Be able to have /etc/mutalyzer/config as 'base' configuration - and some additional configuration in ~/.mutalyzer/config to - overwrite the base configuration. - For example, a normal user could use a different cache directory - (writable by the user) than the system wide Mutalyzer config. - We could use the 'merge' method from configobj. """ - if filename is None: - base = os.environ.get('XDG_CONFIG_HOME', None) - if base is None: - base = os.path.join(os.path.expanduser('~'), '.config') - filename = os.path.join(base, 'mutalyzer', 'config') - - if not os.path.isfile(filename): - filename = '/etc/mutalyzer/config' - if not os.path.isfile(filename): - raise ConfigurationError('Could not locate configuration.') - - try: - config = ConfigObj(filename) - except IOError: - raise ConfigurationError('Could not open configuration file: %s' \ - % filename) - except SyntaxError: - raise ConfigurationError('Could not parse configuration file.') + config = None + + if filename: + config = self._load_config(filename) + else: + if os.path.isfile(SYSTEM_CONFIGURATION): + config = self._load_config(SYSTEM_CONFIGURATION) + if os.path.isfile(USER_CONFIGURATION): + user_config = self._load_config(USER_CONFIGURATION) + if config: + config.merge(user_config) + else: + config = user_config + + if not config: + raise ConfigurationError('Could not locate configuration.') try: @@ -135,44 +136,21 @@ class Config(): self.GenRecord.spliceAlarm = int(config["spliceAlarm"]) self.GenRecord.spliceWarn = int(config["spliceWarn"]) - # If we are in a testing environment, use a temporary file for - # logging and a temporary directory for the cache. - # We don't remove these after the tests, since they might be - # useful for debugging. - if mutalyzer.is_test(): - # Todo: - # - # This needs some refactoring. The problem with the temporary - # file and dir names is that they will not be used by the - # (running) batch daemon, which will thus save its results to - # to 'normal' directory. - # Furthermore, subsequent web requests from a unit test will - # use different configuration instantiations, so might not - # see results from previous requests. - # - # We need a more robust solution for different configurations, - # depending of the running user/setting (e.g. unit tests). - # - # Idea: Don't create a local instance of the website in the - # unit tests, but only use running instances of all servers - # (website, webservice, batch daemon). They will use their - # own 'normal' configuration. - # All other parts of the unit tests will use temporary test - # configuration values. We might even decorate the tests - # needing server access as such and provide the option of - # skipping these. - - #handle, filename = tempfile.mkstemp(suffix='.log', - # prefix='mutalyzer-tests-') - #os.close(handle) - #self.Output.log = filename - #dirname = tempfile.mkdtemp(suffix='.cache', - # prefix='mutalyzer-tests-') - #self.Retriever.cache = dirname - #self.Scheduler.resultsDir = dirname - pass - except KeyError as e: raise ConfigurationError('Missing configuration value: %s' % e) #__init__ + + def _load_config(self, filename): + """ + Create a ConfigObj from the configuration in {filename}. + """ + try: + return ConfigObj(filename) + except IOError: + raise ConfigurationError('Could not open configuration file: %s' \ + % filename) + except SyntaxError: + raise ConfigurationError('Could not parse configuration file: %s' \ + % filename) + #_load_config #Config diff --git a/mutalyzer/variantchecker.py b/mutalyzer/variantchecker.py index 12c4be66..5f4cb61e 100644 --- a/mutalyzer/variantchecker.py +++ b/mutalyzer/variantchecker.py @@ -20,7 +20,6 @@ from Bio.Seq import Seq from Bio.Alphabet import IUPAC from mutalyzer import util -from mutalyzer.config import Config from mutalyzer.grammar import Grammar from mutalyzer.mutator import Mutator from mutalyzer import Retriever diff --git a/tests/test_website.py b/tests/test_website.py index 7d01e93d..7b3b0f01 100644 --- a/tests/test_website.py +++ b/tests/test_website.py @@ -14,6 +14,7 @@ I just installed webtest by 'easy_install webtest'. #import logging; logging.basicConfig() import os import re +import urllib2 import time import web from nose.tools import * @@ -24,6 +25,9 @@ from mutalyzer import website from mutalyzer.util import slow +BATCH_RESULT_URL = 'http://localhost/mutalyzer/Results_{id}.txt' + + class TestWSGI(): """ Test the Mutalyzer WSGI interface. @@ -255,6 +259,14 @@ class TestWSGI(): @return: The batch result document. @rtype: string + + @note: Since the batch files are processed by a running batch daemon + process, the result gets written to the directory defined by the + system-wide configuration (e.g. /var/mutalyzer/cache), thus + inaccessible for the TestApp instance under our current user. + The 'solution' for this is to download the results via a running + webserver that should be using the same configuration as the batch + daemon. Yes, this is a hack. """ r = self.app.get('/batch') form = r.forms[0] @@ -275,17 +287,19 @@ class TestWSGI(): assert re.match('[0-9]+', r.body) time.sleep(2) assert_equal(r.body, 'OK') - r = self.app.get('/Results_' + id + '.txt') - assert_equal(r.content_type, 'text/plain') - r.mustcontain(header) + # This is a hack to get to the batch results (see @note above). + response = urllib2.urlopen(BATCH_RESULT_URL.format(id=id)) + assert_equal(response.info().getheader('Content-Type'), 'text/plain') + result = response.read() + assert header in result if not lines: lines = size - if len(r.body.strip().split('\n')) -1 != lines: + if len(result.strip().split('\n')) -1 != lines: # Heisenbug, whenever it occurs we want to see some info. print 'File: /Results_' + id + '.txt' - print r.body - assert_equal(len(r.body.strip().split('\n')) - 1, lines) - return r.body + print result + assert_equal(len(result.strip().split('\n')) - 1, lines) + return result def test_batch_namechecker(self): """ -- GitLab