Merge pull request #174 from alfredodeza/env_var
Environment Variable implementation
This commit is contained in:
@@ -8,7 +8,7 @@ Command Line Pecan
|
||||
Any Pecan application can be controlled and inspected from the command line
|
||||
using the built-in ``pecan`` command. The usage examples of the ``pecan``
|
||||
command in this document are intended to be invoked from your project's root
|
||||
directory.
|
||||
directory.
|
||||
|
||||
Serving a Pecan App For Development
|
||||
-----------------------------------
|
||||
@@ -40,7 +40,7 @@ command::
|
||||
Pecan Interactive Shell
|
||||
Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53)
|
||||
[GCC 4.2.1 (Based on Apple Inc. build 5658)
|
||||
|
||||
|
||||
The following objects are available:
|
||||
wsgiapp - This project's WSGI App instance
|
||||
conf - The current configuration
|
||||
@@ -51,7 +51,7 @@ command::
|
||||
'app': Config({
|
||||
'root': 'myapp.controllers.root.RootController',
|
||||
'modules': ['myapp'],
|
||||
'static_root': '/Users/somebody/myapp/public',
|
||||
'static_root': '/Users/somebody/myapp/public',
|
||||
'template_path': '/Users/somebody/myapp/project/templates',
|
||||
'errors': {'404': '/error/404'},
|
||||
'debug': True
|
||||
@@ -79,6 +79,34 @@ which can be specified with the ``--shell`` flag (or its abbreviated alias,
|
||||
$ pecan shell --shell=ipython config.py
|
||||
$ pecan shell -s bpython config.py
|
||||
|
||||
|
||||
.. _env_config:
|
||||
|
||||
Configuration from an environment variable
|
||||
------------------------------------------
|
||||
In all the examples shown, you will see that the `pecan` commands were
|
||||
accepting a file path to the configuration file. An alternative to this is to
|
||||
specify the configuration file in an environment variable (``PECAN_CONFIG``).
|
||||
|
||||
This is completely optional; if a file path is passed in explicitly, Pecan will
|
||||
honor that before looking for an environment variable.
|
||||
|
||||
For example, to ``serve`` a Pecan application, a variable could be exported and
|
||||
subsequently be re-used when no path is passed in::
|
||||
|
||||
$ export PECAN_CONFIG=/path/to/app/config.py
|
||||
$ pecan serve
|
||||
Starting server in PID 000.
|
||||
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080
|
||||
|
||||
Note that the path needs to reference a valid pecan configuration file,
|
||||
otherwise the command will error out with a meaningful message indicating that
|
||||
the path is invalid (for example, if a directory is passed in).
|
||||
|
||||
If ``PECAN_CONFIG`` is not set and no configuration is passed in, the command
|
||||
will error out because it will not be able to locate a configuration file.
|
||||
|
||||
|
||||
Extending ``pecan`` with Custom Commands
|
||||
----------------------------------------
|
||||
While the commands packaged with Pecan are useful, the real utility of its
|
||||
@@ -125,7 +153,7 @@ Overriding the ``run`` Method
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
|
||||
First, we're subclassing ``pecan.commands.BaseCommand`` and extending
|
||||
the ``run`` method to:
|
||||
the ``run`` method to:
|
||||
|
||||
* Load a Pecan application - ``self.load_app()``
|
||||
* Wrap it in a fake WGSI environment - ``webtest.TestApp()``
|
||||
@@ -184,11 +212,11 @@ e.g., ::
|
||||
...
|
||||
)
|
||||
|
||||
Assuming it doesn't exist already, we'll add the ``entry_points`` argument
|
||||
Assuming it doesn't exist already, we'll add the ``entry_points`` argument
|
||||
to the ``setup()`` call, and define a ``[pecan.command]`` definition for your custom
|
||||
command::
|
||||
|
||||
|
||||
|
||||
# myapp/setup.py
|
||||
...
|
||||
setup(
|
||||
|
||||
@@ -136,7 +136,9 @@ class BaseCommand(object):
|
||||
|
||||
arguments = ({
|
||||
'name': 'config_file',
|
||||
'help': 'a Pecan configuration file'
|
||||
'help': 'a Pecan configuration file',
|
||||
'nargs': '?',
|
||||
'default': None,
|
||||
},)
|
||||
|
||||
def run(self, args):
|
||||
@@ -144,6 +146,4 @@ class BaseCommand(object):
|
||||
|
||||
def load_app(self):
|
||||
from pecan import load_app
|
||||
if not os.path.isfile(self.args.config_file):
|
||||
raise RuntimeError('`%s` is not a file.' % self.args.config_file)
|
||||
return load_app(self.args.config_file)
|
||||
|
||||
@@ -17,15 +17,12 @@ class ServeCommand(BaseCommand):
|
||||
configuration file for the server and application.
|
||||
"""
|
||||
|
||||
arguments = ({
|
||||
'name': 'config_file',
|
||||
'help': 'a Pecan configuration file'
|
||||
}, {
|
||||
arguments = BaseCommand.arguments + ({
|
||||
'name': '--reload',
|
||||
'help': 'Watch for changes and automatically reload.',
|
||||
'default': False,
|
||||
'action': 'store_true'
|
||||
})
|
||||
},)
|
||||
|
||||
def run(self, args):
|
||||
super(ServeCommand, self).run(args)
|
||||
|
||||
@@ -147,6 +147,8 @@ def conf_from_file(filepath):
|
||||
|
||||
abspath = os.path.abspath(os.path.expanduser(filepath))
|
||||
conf_dict = {}
|
||||
if not os.path.isfile(abspath):
|
||||
raise RuntimeError('`%s` is not a file.' % abspath)
|
||||
|
||||
execfile(abspath, globals(), conf_dict)
|
||||
conf_dict['__file__'] = abspath
|
||||
@@ -154,6 +156,25 @@ def conf_from_file(filepath):
|
||||
return conf_from_dict(conf_dict)
|
||||
|
||||
|
||||
def conf_from_env():
|
||||
'''
|
||||
If the ``PECAN_CONFIG`` environment variable exists and it points to
|
||||
a valid path it will return that, otherwise it will raise
|
||||
a ``RuntimeError``.
|
||||
'''
|
||||
config_path = os.environ.get('PECAN_CONFIG')
|
||||
error = None
|
||||
if not config_path:
|
||||
error = "PECAN_CONFIG is not set and " \
|
||||
"no config file was passed as an argument."
|
||||
elif not os.path.isfile(config_path):
|
||||
error = "PECAN_CONFIG was set to an invalid path: %s" % config_path
|
||||
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
return config_path
|
||||
|
||||
|
||||
def conf_from_dict(conf_dict):
|
||||
'''
|
||||
Creates a configuration dictionary from a dictionary.
|
||||
@@ -191,6 +212,9 @@ def set_config(config, overwrite=False):
|
||||
if overwrite is True:
|
||||
_runtime_conf.empty()
|
||||
|
||||
if config is None:
|
||||
config = conf_from_env()
|
||||
|
||||
if isinstance(config, basestring):
|
||||
config = conf_from_file(config)
|
||||
_runtime_conf.update(config)
|
||||
|
||||
@@ -138,13 +138,15 @@ def render(template, namespace):
|
||||
return state.app.render(template, namespace)
|
||||
|
||||
|
||||
def load_app(config):
|
||||
def load_app(config=None):
|
||||
'''
|
||||
Used to load a ``Pecan`` application and its environment based on passed
|
||||
configuration.
|
||||
|
||||
:param config: Can be a dictionary containing configuration, or a string
|
||||
which represents a (relative) configuration filename.
|
||||
:param config: Can be a dictionary containing configuration, a string which
|
||||
represents a (relative) configuration filename or ``None``
|
||||
which will fallback to get the ``PECAN_CONFIG`` env
|
||||
variable.
|
||||
|
||||
returns a pecan.Pecan object
|
||||
'''
|
||||
|
||||
@@ -2,13 +2,15 @@ from pecan import load_app
|
||||
from webtest import TestApp
|
||||
|
||||
|
||||
def load_test_app(config):
|
||||
def load_test_app(config=None):
|
||||
"""
|
||||
Used for functional tests where you need to test your
|
||||
literal application and its integration with the framework.
|
||||
|
||||
:param config: Can be a dictionary containing configuration, or a string
|
||||
which represents a (relative) configuration filename.
|
||||
:param config: Can be a dictionary containing configuration, a string which
|
||||
represents a (relative) configuration filename or ``None``
|
||||
which will fallback to get the ``PECAN_CONFIG`` env
|
||||
variable.
|
||||
|
||||
returns a pecan.Pecan WSGI application wrapped in a webtest.TestApp
|
||||
instance.
|
||||
@@ -21,5 +23,13 @@ def load_test_app(config):
|
||||
|
||||
resp = app.post('/path/to/some/resource', params={'param': 'value'})
|
||||
assert resp.status_int == 302
|
||||
|
||||
Alternatively you could call ``load_test_app`` with no parameters if the
|
||||
environment variable is set ::
|
||||
|
||||
app = load_test_app()
|
||||
|
||||
resp = app.get('/path/to/some/resource').status_int
|
||||
assert resp.status_int == 200
|
||||
"""
|
||||
return TestApp(load_app(config))
|
||||
|
||||
@@ -94,7 +94,7 @@ class BaseTest(unittest.TestCase):
|
||||
match = pat.search(actual)
|
||||
if not match:
|
||||
self.fail("Log line does not match expected pattern:\n" +
|
||||
actual)
|
||||
actual)
|
||||
self.assertEquals(tuple(match.groups()), expected)
|
||||
s = stream.read()
|
||||
if s:
|
||||
@@ -303,7 +303,7 @@ class ConfigDictTest(BaseTest):
|
||||
},
|
||||
'root': {
|
||||
'level': 'NOTSET',
|
||||
'handlers': ['hand1'],
|
||||
'handlers': ['hand1'],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ class ConfigDictTest(BaseTest):
|
||||
},
|
||||
'root': {
|
||||
'level': 'NOTSET',
|
||||
'handlers': ['hand1'],
|
||||
'handlers': ['hand1'],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -602,8 +602,8 @@ class ConfigDictTest(BaseTest):
|
||||
except RuntimeError:
|
||||
logging.exception("just testing")
|
||||
sys.stdout.seek(0)
|
||||
self.assertEquals(output.getvalue(),
|
||||
"ERROR:root:just testing\nGot a [RuntimeError]\n")
|
||||
expected = "ERROR:root:just testing\nGot a [RuntimeError]\n"
|
||||
self.assertEquals(output.getvalue(), expected)
|
||||
# Original logger output is empty
|
||||
self.assert_log_lines([])
|
||||
|
||||
@@ -617,8 +617,8 @@ class ConfigDictTest(BaseTest):
|
||||
except RuntimeError:
|
||||
logging.exception("just testing")
|
||||
sys.stdout.seek(0)
|
||||
self.assertEquals(output.getvalue(),
|
||||
"ERROR:root:just testing\nGot a [RuntimeError]\n")
|
||||
expected = "ERROR:root:just testing\nGot a [RuntimeError]\n"
|
||||
self.assertEquals(output.getvalue(), expected)
|
||||
# Original logger output is empty
|
||||
self.assert_log_lines([])
|
||||
|
||||
|
||||
@@ -110,22 +110,22 @@ class TestConf(TestCase):
|
||||
from pecan import configuration
|
||||
path = ('doesnotexist.py',)
|
||||
configuration.Config({})
|
||||
self.assertRaises(IOError, configuration.conf_from_file, os.path.join(
|
||||
__here__,
|
||||
'config_fixtures',
|
||||
*path
|
||||
))
|
||||
self.assertRaises(
|
||||
RuntimeError,
|
||||
configuration.conf_from_file,
|
||||
os.path.join(__here__, 'config_fixtures', *path)
|
||||
)
|
||||
|
||||
def test_config_missing_file_on_path(self):
|
||||
from pecan import configuration
|
||||
path = ('bad', 'bad', 'doesnotexist.py',)
|
||||
configuration.Config({})
|
||||
|
||||
self.assertRaises(IOError, configuration.conf_from_file, os.path.join(
|
||||
__here__,
|
||||
'config_fixtures',
|
||||
*path
|
||||
))
|
||||
self.assertRaises(
|
||||
RuntimeError,
|
||||
configuration.conf_from_file,
|
||||
os.path.join(__here__, 'config_fixtures', *path)
|
||||
)
|
||||
|
||||
def test_config_with_syntax_error(self):
|
||||
from pecan import configuration
|
||||
@@ -274,6 +274,47 @@ class TestGlobalConfig(TestCase):
|
||||
)
|
||||
assert dict(configuration._runtime_conf) == {'foo': 'bar'}
|
||||
|
||||
def test_set_config_invalid_type(self):
|
||||
def test_set_config_none_type(self):
|
||||
from pecan import configuration
|
||||
self.assertRaises(TypeError, configuration.set_config, None)
|
||||
self.assertRaises(RuntimeError, configuration.set_config, None)
|
||||
|
||||
def test_set_config_to_dir(self):
|
||||
from pecan import configuration
|
||||
self.assertRaises(RuntimeError, configuration.set_config, '/')
|
||||
|
||||
|
||||
class TestConfFromEnv(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.conf_from_env = self.get_conf_from_env()
|
||||
os.environ['PECAN_CONFIG'] = ''
|
||||
|
||||
def tearDown(self):
|
||||
os.environ['PECAN_CONFIG'] = ''
|
||||
|
||||
def get_conf_from_env(self):
|
||||
from pecan import configuration
|
||||
return configuration.conf_from_env
|
||||
|
||||
def assertRaisesMessage(self, msg, exc, func, *args, **kwargs):
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
self.assertFail()
|
||||
except Exception as error:
|
||||
assert issubclass(exc, error.__class__)
|
||||
assert error.message == msg
|
||||
|
||||
def test_invalid_path(self):
|
||||
os.environ['PECAN_CONFIG'] = '/'
|
||||
msg = "PECAN_CONFIG was set to an invalid path: /"
|
||||
self.assertRaisesMessage(msg, RuntimeError, self.conf_from_env)
|
||||
|
||||
def test_is_not_set(self):
|
||||
msg = "PECAN_CONFIG is not set and " \
|
||||
"no config file was passed as an argument."
|
||||
self.assertRaisesMessage(msg, RuntimeError, self.conf_from_env)
|
||||
|
||||
def test_return_valid_path(self):
|
||||
here = os.path.abspath(__file__)
|
||||
os.environ['PECAN_CONFIG'] = here
|
||||
assert self.conf_from_env() == here
|
||||
|
||||
Reference in New Issue
Block a user