Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for environment variables #14

Open
wants to merge 3 commits into
base: release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
25 changes: 15 additions & 10 deletions mandrel/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
97 changes: 97 additions & 0 deletions mandrel/test/bootstrap/bootstrap_file_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to see tests, but this changes too much core stuff for a single test case to cover.

Given that you're introducing two changes (the mandrel root variable; the bootstrap basename variable), the cases to consider are considerably more complicated:

  • Base case: neither variable is defined (the original behaviors)
  • Root is specified, bootstrapper is not, Mandrel.py is present in specified root (should parse the file it finds)
  • Root is specified, bootstrapper is not, Mandrel.py is not present in specified root (should get basic defaults)
  • Root is specified, bootstrapper is specified, the specified file exists in the specified root
  • Root is specified, bootstrapper is specified, the specified file does not exist in the specified root
  • Root is not specified, bootstrapper is specified, the specified file exists; it should be used and the root should be where that guy was found. This would need testing at various file system levels.
  • Root is not specified, bootstrapper is specified, the specified file does not exist within the file system hierarchy; this should blow up, just like it does now if Mandrel.py can't be found.

So there's quite a few test cases that would need to be covered.

It's considerably simpler if only one of the issues is done: either specifying a root, or specifying a basename, but not both.

But as it stands, the test only covers the case where both are set, it doesn't treat them separately.

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)
13 changes: 12 additions & 1 deletion mandrel/test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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