diff --git a/pecan/__init__.py b/pecan/__init__.py index cadbcef..154f0ad 100644 --- a/pecan/__init__.py +++ b/pecan/__init__.py @@ -9,7 +9,7 @@ from paste.urlparser import StaticURLParser from weberror.errormiddleware import ErrorMiddleware from weberror.evalexception import EvalException -from core import abort, error_for, override_template, Pecan, redirect, render, request, response, ValidationException +from core import abort, error_for, override_template, Pecan, load_app, redirect, render, request, response, ValidationException from decorators import expose from hooks import RequestViewerHook from templating import error_formatters @@ -18,7 +18,7 @@ from configuration import set_config from configuration import _runtime_conf as conf __all__ = [ - 'make_app', 'Pecan', 'request', 'response', 'override_template', 'expose', 'conf', 'set_config' + 'make_app', 'load_app', 'Pecan', 'request', 'response', 'override_template', 'expose', 'conf', 'set_config' ] def make_app(root, static_root=None, debug=False, errorcfg={}, wrap_app=None, logging=False, **kw): diff --git a/pecan/commands/base.py b/pecan/commands/base.py index 63e2efd..f2df071 100644 --- a/pecan/commands/base.py +++ b/pecan/commands/base.py @@ -1,6 +1,7 @@ """ PasteScript base command for Pecan. """ +from pecan import load_app from pecan.configuration import _runtime_conf, set_config from paste.script import command as paste_command @@ -31,37 +32,18 @@ class Command(paste_command.Command): except paste_command.BadCommand, ex: ex.args[0] = self.parser.error(ex.args[0]) raise - - def get_package_names(self, config): - if not hasattr(config.app, 'modules'): - return [] - return config.app.modules - - def import_module(self, package, name): - parent = __import__(package, fromlist=[name]) - return getattr(parent, name, None) - - def load_configuration(self, name): - set_config(name) - return _runtime_conf - - def load_app(self, config): - for package_name in self.get_package_names(config): - module = self.import_module(package_name, 'app') - if hasattr(module, 'setup_app'): - return module.setup_app(config) - raise paste_command.BadCommand('No app.setup_app found in any app modules') - - def load_model(self, config): - for package_name in self.get_package_names(config): - module = self.import_module(package_name, 'model') - if module: - return module - return None + + def load_app(self): + return load_app(self.validate_file(self.args)) def logging_file_config(self, config_file): if os.path.splitext(config_file)[1].lower() == '.ini': paste_command.Command.logging_file_config(self, config_file) + + def validate_file(self, argv): + if not argv or not os.path.isfile(argv[0]): + raise paste_command.BadCommand('This command needs a valid config file.') + return argv[0] def command(self): pass diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py index 80f2681..874296c 100644 --- a/pecan/commands/serve.py +++ b/pecan/commands/serve.py @@ -45,25 +45,11 @@ class ServeCommand(_ServeCommand, Command): setattr(self.options, 'server', None) setattr(self.options, 'server_name', None) - config_file = self.validate_file(self.args) - - # for file-watching to work, we need a filename, not a module - if self.requires_config_file and self.args: - self.config = self.load_configuration(config_file) - self.args[0] = self.config.__file__ - if self.options.reload is None: - self.options.reload = getattr(self.config.app, 'reload', False) - # run the base command _ServeCommand.command(self) def loadserver(self, server_spec, name, relative_to, **kw): - return (lambda app: httpserver.serve(app, self.config.server.host, self.config.server.port)) + return (lambda app: httpserver.serve(app, app.config.server.host, app.config.server.port)) def loadapp(self, app_spec, name, relative_to, **kw): - return self.load_app(self.config) - - def validate_file(self, argv): - if not argv or not os.path.isfile(argv[0]): - raise paste_command.BadCommand('This command needs a valid config file.') - return argv[0] + return self.load_app() diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py index 8be6688..43824cf 100644 --- a/pecan/commands/shell.py +++ b/pecan/commands/shell.py @@ -24,9 +24,7 @@ class ShellCommand(Command): def command(self): # load the application - config = self.load_configuration(self.args[0]) - setattr(config.app, 'reload', False) - app = self.load_app(config) + app = self.load_app() # prepare the locals locs = dict(__name__='pecan-admin') @@ -34,7 +32,7 @@ class ShellCommand(Command): locs['app'] = TestApp(app) # find the model for the app - model = self.load_model(config) + model = getattr(app, 'model', None) if model: locs['model'] = model diff --git a/pecan/configuration.py b/pecan/configuration.py index b0c861b..5756519 100644 --- a/pecan/configuration.py +++ b/pecan/configuration.py @@ -5,6 +5,27 @@ import os IDENTIFIER = re.compile(r'[a-z_](\w)*$', re.IGNORECASE) +DEFAULT = { + # Server Specific Configurations + 'server' : { + 'port' : '8080', + 'host' : '0.0.0.0' + }, + + # Pecan Application Configurations + 'app' : { + 'root' : None, + 'modules' : [], + 'static_root' : 'public', + 'template_path' : '', + 'debug' : False, + 'force_canonical' : True, + 'errors' : { + '__force_dict__' : True + } + } +} + class ConfigDict(dict): pass @@ -80,15 +101,6 @@ class Config(object): conf_obj = dict(self) return self.__dictify__(conf_obj, prefix) - def update_with_module(self, module): - ''' - Updates this configuration with a module. - - :param module: The module to update this configuration with. Either a string or the module itself. - ''' - - self.update(conf_from_module(module)) - def __getattr__(self, name): try: return self.__values__[name] @@ -123,20 +135,6 @@ class Config(object): def __repr__(self): return 'Config(%s)' % str(self.__values__) -def conf_from_module(module): - ''' - Creates a configuration dictionary from a module. - - :param module: The module, either as a string or the module itself. - ''' - - if isinstance(module, str): - module = import_module(module) - - module_dict = dict(inspect.getmembers(module)) - - return conf_from_dict(module_dict) - def conf_from_file(filepath): ''' @@ -173,48 +171,23 @@ def conf_from_dict(conf_dict): return conf -def import_module(conf): - ''' - Imports the a configuration as a module. - - :param conf: The string to the configuration. Automatically strips off ".py" file extensions. - ''' - - if '.' in conf: - parts = conf.split('.') - name = '.'.join(parts[:-1]) - fromlist = parts[-1:] - - try: - module = __import__(name, fromlist=fromlist) - conf_mod = getattr(module, parts[-1]) - except AttributeError, e: - raise ImportError('No module named %s' % conf) - else: - conf_mod = __import__(conf) - - return conf_mod - - def initconf(): ''' Initializes the default configuration and exposes it at ``pecan.configuration.conf``, which is also exposed at ``pecan.conf``. ''' - - import default_config - conf = conf_from_module(default_config) - return conf + return conf_from_dict(DEFAULT) -def set_config(name): +def set_config(name, overwrite=False): ''' Updates the global configuration a filename. :param name: filename, as a string. ''' - _runtime_conf.update(conf_from_file(name)) + conf = conf_from_file(name) + _runtime_conf.update(conf) _runtime_conf = initconf() diff --git a/pecan/core.py b/pecan/core.py index a01d40f..73d224c 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -1,3 +1,4 @@ +from configuration import _runtime_conf, set_config from templating import RendererFactory from routing import lookup_controller, NonCanonicalPath from util import _cfg, splitext, encode_if_needed @@ -171,6 +172,26 @@ class ValidationException(ForwardRequestException): ForwardRequestException.__init__(self, location) +def load_app(config): + ''' + 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. + :returns a pecan.Pecan object + ''' + set_config(config, overwrite=True) + + for package_name in getattr(_runtime_conf.app, 'modules', []): + module = __import__(package_name, fromlist=['app']) + if hasattr(module, 'app') and hasattr(module.app, 'setup_app'): + app = module.app.setup_app(_runtime_conf) + app.config = _runtime_conf + return app + raise RuntimeError('No app.setup_app found in any of the configured app.modules') + + class Pecan(object): ''' Base Pecan application object. Generally created using ``pecan.make_app``, diff --git a/pecan/default_config.py b/pecan/default_config.py deleted file mode 100644 index bff8a2c..0000000 --- a/pecan/default_config.py +++ /dev/null @@ -1,25 +0,0 @@ -# Server Specific Configurations -server = { - 'port' : '8080', - 'host' : '0.0.0.0' -} - -# Pecan Application Configurations -app = { - 'root' : None, - 'modules' : [], - 'static_root' : 'public', - 'template_path' : '', - 'debug' : False, - 'force_canonical' : True, - 'errors' : { - '__force_dict__' : True - } -} - -# Custom Configurations must be in Python dictionary format in user config -# -# foo = {'bar':'baz'} -# -# All configurations are accessible at:: -# pecan.conf diff --git a/pecan/deploy.py b/pecan/deploy.py index bd7f9d8..4a3bfc8 100644 --- a/pecan/deploy.py +++ b/pecan/deploy.py @@ -1,14 +1,4 @@ -from configuration import set_config, import_module, _runtime_conf as conf - -def deploy(config_module_or_path): - set_config(config_module_or_path) - for module in getattr(conf.app, 'modules'): - try: - module_app = import_module('%s.app' % module) - if hasattr(module_app, 'setup_app'): - return module_app.setup_app(conf) - except ImportError: - continue - - raise Exception, 'No app.setup_app found in any of the configured app.modules' +from core import load_app +def deploy(config): + return load_app(config) diff --git a/pecan/testing.py b/pecan/testing.py new file mode 100644 index 0000000..6f0ed98 --- /dev/null +++ b/pecan/testing.py @@ -0,0 +1,5 @@ +from pecan import load_app +from webtest import TestApp + +def load_test_app(config): + return TestApp(load_app(config))