diff --git a/README.md b/README.md index 79c1e57..5902b04 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,19 @@ You can fetch a logger named "storage" from it, too: logger = StorageConfig.get_logger() +### Bootstrap Configuration ### + +By default, mandrel looks for a bootstrap file named 'Mandrel.py' in the search path, as described above. +In most cases this works well and the default behaviour is exactly what you need. +There are rare exceptions though where you don't want Mandrel to throw an exception if the +bootstrap file isn't found or don't want to the default search path to be the current working directory. + +Mandrel supports two environment variables for these specific cases: +`MANDREL_ROOT = The root directory from which to start looking for the bootstrap file` +`MANDREL_BOOTSTRAP_NAME = The name of the bootstrap file to look for in the search path` + +These environment variables can be set independently of each other and do not have to both be set. + # License # Mandrel is free software and is released under the terms diff --git a/changelog.md b/changelog.md index 7a665bc..5648181 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,9 @@ -2.0.0 +TBD + +* Issue #8: Allow mandrel root to be specified via an environment variable +* Issue #13: Allow bootstrap basename to be specified via an environment variable + +0.2.0 * mandrel-runner uses return value of callable as the exit code. diff --git a/mandrel/bootstrap.py b/mandrel/bootstrap.py index f405605..1fed1d5 100644 --- a/mandrel/bootstrap.py +++ b/mandrel/bootstrap.py @@ -5,7 +5,7 @@ from mandrel import exception from mandrel import util -__BOOTSTRAP_BASENAME = 'Mandrel.py' +__BOOTSTRAP_BASENAME = os.getenv('MANDREL_BOOTSTRAP_NAME', 'Mandrel.py') LOGGING_CONFIG_BASENAME = 'logging.cfg' DEFAULT_LOGGING_LEVEL = logging.INFO @@ -105,12 +105,16 @@ def get_logger(name=None): def _find_bootstrap_base(): - current = os.path.realpath('.') - while not os.path.isfile(os.path.join(current, __BOOTSTRAP_BASENAME)): - parent = os.path.dirname(current) - if parent == current: - raise exception.MissingBootstrapException, 'Cannot find %s file in directory hierarchy' % __BOOTSTRAP_BASENAME - current = parent + if os.getenv('MANDREL_ROOT'): + current = os.getenv('MANDREL_ROOT') + else: + current = os.path.realpath('.') + + while not os.path.isfile(os.path.join(current, __BOOTSTRAP_BASENAME)): + parent = os.path.dirname(current) + if parent == current: + raise exception.MissingBootstrapException, 'Cannot find %s file in directory hierarchy' % __BOOTSTRAP_BASENAME + current = parent return current, os.path.join(current, __BOOTSTRAP_BASENAME) @@ -129,9 +133,10 @@ def parse_bootstrap_file(): This makes it easy for the BOOTSTRAP_FILE to configure mandrel settings without performing further imports. """ - with open(BOOTSTRAP_FILE, 'rU') as source: - code = compile(source.read(), BOOTSTRAP_FILE, 'exec') - eval(code, {'bootstrap': sys.modules[__name__], 'config': config}) + if os.path.exists(BOOTSTRAP_FILE): + with open(BOOTSTRAP_FILE, 'rU') as source: + code = compile(source.read(), BOOTSTRAP_FILE, 'exec') + eval(code, {'bootstrap': sys.modules[__name__], 'config': config}) (ROOT_PATH, BOOTSTRAP_FILE) = _find_bootstrap_base() SEARCH_PATHS = util.TransformingList(normalize_path) diff --git a/mandrel/test/bootstrap/bootstrap_file_test.py b/mandrel/test/bootstrap/bootstrap_file_test.py index 1da1865..994be35 100644 --- a/mandrel/test/bootstrap/bootstrap_file_test.py +++ b/mandrel/test/bootstrap/bootstrap_file_test.py @@ -3,6 +3,7 @@ import unittest import mandrel.exception from mandrel.test import utils +from test.test_support import EnvironmentVarGuard class TestBootstrapFile(utils.TestCase): def testNoFile(self): @@ -43,3 +44,99 @@ def testDefaultLoggingConfig(self): utils.refresh_bootstrapper() self.assertEqual('logging.cfg', mandrel.bootstrap.LOGGING_CONFIG_BASENAME) + def testNoOSEnv(self): + """ + Base case: neither variable is defined (the original behaviors) + """ + with EnvironmentVarGuard() as env: + with utils.bootstrap_scenario(dir='~') as spec: + utils.refresh_bootstrapper() + self.assertEqual(spec[0], mandrel.bootstrap.ROOT_PATH) + self.assertEqual(spec[1], mandrel.bootstrap.BOOTSTRAP_FILE) + + def testBothOSEnv(self): + """ + Root is specified, bootstrapper is specified, the specified file does not exist in the specified root + """ + with EnvironmentVarGuard() as env: + env.set('MANDREL_ROOT', '/blah') + env.set('MANDREL_BOOTSTRAP_NAME', 'bootstrapper.py') + + utils.refresh_bootstrapper() + self.assertEqual(os.getenv('MANDREL_ROOT'), mandrel.bootstrap.ROOT_PATH) + expected = os.path.join(os.getenv('MANDREL_ROOT'), os.getenv('MANDREL_BOOTSTRAP_NAME')) + self.assertEqual(expected, mandrel.bootstrap.BOOTSTRAP_FILE) + self.assertEqual(['/blah'], mandrel.bootstrap.SEARCH_PATHS._list) + + def testBothOSEnvFileExists(self): + """ + Root is specified, bootstrapper is specified, the specified file exists in the specified root + """ + with EnvironmentVarGuard() as env: + env.set('MANDREL_BOOTSTRAP_NAME', 'bootstrapper.py') + with utils.bootstrap_scenario(text='bootstrap.SEARCH_PATHS.append("/blah/myconf")', dir='~') as spec: + env.set('MANDREL_ROOT', spec[0]) + + utils.refresh_bootstrapper() + self.assertEqual(spec[0], mandrel.bootstrap.ROOT_PATH) + expected = os.path.join(spec[0], os.getenv('MANDREL_BOOTSTRAP_NAME')) + self.assertEqual(expected, mandrel.bootstrap.BOOTSTRAP_FILE) + self.assertEqual([spec[0], '/blah/myconf'], mandrel.bootstrap.SEARCH_PATHS._list) + + def testOSEnvNoBootstrapName(self): + """ + Root is specified, bootstrapper is not, Mandrel.py is not present in specified root (should get basic defaults) + """ + with EnvironmentVarGuard() as env: + env.set('MANDREL_ROOT', '/blah') + + utils.refresh_bootstrapper() + self.assertEqual(os.getenv('MANDREL_ROOT'), mandrel.bootstrap.ROOT_PATH) + expected = os.path.join(os.getenv('MANDREL_ROOT'), 'Mandrel.py') + self.assertEqual(expected, mandrel.bootstrap.BOOTSTRAP_FILE) + self.assertEqual(['/blah'], mandrel.bootstrap.SEARCH_PATHS._list) + + def testOSEnvNoBootstrapNameFileExists(self): + """ + Root is specified, bootstrapper is not, Mandrel.py is present in specified root (should parse the file it finds) + """ + with EnvironmentVarGuard() as env: + with utils.bootstrap_scenario(text='bootstrap.SEARCH_PATHS.append("/blah/myconf")', dir='~') as spec: + env.set('MANDREL_ROOT', spec[0]) + utils.refresh_bootstrapper() + self.assertEqual(os.getenv('MANDREL_ROOT'), mandrel.bootstrap.ROOT_PATH) + self.assertEqual(spec[1], mandrel.bootstrap.BOOTSTRAP_FILE) + self.assertEqual([spec[0], '/blah/myconf'], mandrel.bootstrap.SEARCH_PATHS._list) + + def testOSEnvNoMandrelRoot(self): + """ + Root is not specified, bootstrapper is specified, + the specified file does not exist within the file system hierarchy + """ + with EnvironmentVarGuard() as env: + env.set('MANDREL_BOOTSTRAP_NAME', 'bootstrapper.py') + with utils.workdir(dir='~') as path: + self.assertRaises(mandrel.exception.MissingBootstrapException, utils.refresh_bootstrapper) + + def testOSEnvNoMandrelRootFileExists(self): + """ + Root is not specified, bootstrapper is specified, the specified file exists + """ + with EnvironmentVarGuard() as env: + env.set('MANDREL_BOOTSTRAP_NAME', 'bootstrapper.py') + with utils.bootstrap_scenario(text='bootstrap.SEARCH_PATHS.append("/blah/myconf")', dir='~') as spec: + utils.refresh_bootstrapper() + self.assertEqual(spec[0], mandrel.bootstrap.ROOT_PATH) + expected = os.path.join(spec[0], os.getenv('MANDREL_BOOTSTRAP_NAME')) + self.assertEqual(expected, mandrel.bootstrap.BOOTSTRAP_FILE) + self.assertEqual([spec[0], '/blah/myconf'], mandrel.bootstrap.SEARCH_PATHS._list) + + # Create a folder structure such that starting search path is several levels + # below where the bootstrap file exists. + # Make sure that the bootstrap file is found and parsed + with utils.nested_bootstrap_scenario(text='bootstrap.SEARCH_PATHS.append("/blah/myconf")', dir='~') as spec: + utils.refresh_bootstrapper() + self.assertEqual(spec[0], mandrel.bootstrap.ROOT_PATH) + expected = os.path.join(spec[0], os.getenv('MANDREL_BOOTSTRAP_NAME')) + self.assertEqual(expected, mandrel.bootstrap.BOOTSTRAP_FILE) + self.assertEqual([spec[0], '/blah/myconf'], mandrel.bootstrap.SEARCH_PATHS._list) diff --git a/mandrel/test/utils.py b/mandrel/test/utils.py index df7dbc8..05a88a7 100644 --- a/mandrel/test/utils.py +++ b/mandrel/test/utils.py @@ -48,12 +48,23 @@ def refresh_bootstrapper(): else: __import__('mandrel.bootstrap') -BOOTSTRAP_FILE = 'Mandrel.py' + @contextlib.contextmanager def bootstrap_scenario(text="", dir=None): + BOOTSTRAP_FILE = os.getenv('MANDREL_BOOTSTRAP_NAME', 'Mandrel.py') with workdir(dir=dir) as path: bootstrapper = os.path.join(path, BOOTSTRAP_FILE) with open(bootstrapper, 'w') as f: f.write(text) yield path, bootstrapper +@contextlib.contextmanager +def nested_bootstrap_scenario(text="", dir=None): + BOOTSTRAP_FILE = os.getenv('MANDREL_BOOTSTRAP_NAME', 'Mandrel.py') + with workdir(dir=dir) as path0: + with workdir(dir=path0) as path1: + with workdir(dir=path1) as path2: + bootstrapper = os.path.join(path0, BOOTSTRAP_FILE) + with open(bootstrapper, 'w') as f: + f.write(text) + yield path0, bootstrapper