From ca8f767de1d08158b9e8f047a4f913fe35eccd48 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 6 Mar 2012 13:05:55 -0500 Subject: [PATCH 1/9] Removing py.test-specifics. --- pecan/ext/pytest.py | 226 -------------------------------------------- 1 file changed, 226 deletions(-) delete mode 100644 pecan/ext/pytest.py diff --git a/pecan/ext/pytest.py b/pecan/ext/pytest.py deleted file mode 100644 index 5141985..0000000 --- a/pecan/ext/pytest.py +++ /dev/null @@ -1,226 +0,0 @@ -""" -Plugin for py.test that sets up the app. - -App configuration inspired by the Pylons nose equivalent: -https://github.com/Pylons/pylons/blob/master/pylons/test.py - -Handling of multiprocessing inspired by pytest-cov. -""" -from configuration import set_config, _runtime_conf as conf -from tempfile import mkdtemp - -import py -import py.test -import os -import shutil -import socket -import sys - - -def pytest_addoption(parser): - """ - Adds the custom "with-config" option to take in the config file. - """ - group = parser.getgroup('pecan') - group._addoption('--with-config', - dest='config_file', - metavar='path', - default='./test.py', - action='store', - type='string', - help='configuration file for pecan tests') - - -def pytest_configure(config): - """ - Loads the Pecan plugin if using a configuration file. - """ - if config.getvalue('config_file'): - config.pluginmanager.register(PecanPlugin(config), '_pecan') - - -class PecanPlugin(object): - """ - Plugin for a Pecan application. Sets up and tears down the - WSGI application based on the configuration and session type. - """ - - def __init__(self, config): - self.config = config - self.impl = None - - def pytest_namespace(self): - """ - Add the session variables to the namespace. - """ - return { - 'temp_dir': None, - 'wsgi_app': None - } - - def pytest_sessionstart(self, session): - """ - Set up the testing session. - """ - self.impl = PecanPluginImpl.create_from_session(session) - self.impl.sessionstart(session) - - def pytest_configure_node(self, node): - """ - Configures a new slave node. - """ - if self.impl: - self.impl.configure_node(node) - - def pytest_runtest_setup(self, item): - if self.impl: - self.impl.ensure_config_loaded() - - def pytest_testnodedown(self, node, error): - """ - Tears down an exiting node. - """ - if self.impl: - self.impl.testnodedown(node, error) - - def pytest_sessionfinish(self, session, exitstatus): - """ - Cleans up the testing session. - """ - if self.impl: - self.impl.sessionfinish(session, exitstatus) - - -class PecanPluginImpl(object): - """ - Actual implementation of the Pecan plugin. This ensures the proper - environment is configured for each session type. - """ - - def __init__(self, config): - self.config = config - self.log = py.log.Producer('pecan-%s' % self.name) - if not config.option.debug: - py.log.setconsumer(self.log._keywords, None) - self.log('Created %s instance' % self.__class__.__name__) - - @property - def name(self): - return 'main' - - def _setup_app(self): - self.log('Invoking setup_app') - path = os.getcwd() - if path not in sys.path: - sys.path.insert(0, path) - set_config(self.config.getvalue('config_file')) - py.test.wsgi_app = self._load_app(conf) - - def _get_package_names(self, config): - if not hasattr(config.app, 'modules'): - return [] - return [module.__name__ for module in config.app.modules if hasattr(module, '__name__')] - - def _can_import(self, name): - try: - __import__(name) - return True - except ImportError: - return False - - def _load_app(self, config): - for package_name in self._get_package_names(config): - module_name = '%s.app' % package_name - if self._can_import(module_name): - module = sys.modules[module_name] - if hasattr(module, 'setup_app'): - return module.setup_app(config) - raise RuntimeError('No app.setup_app found in any of the configured app.modules') - - def _create_temp_directory(self): - temp_dir = mkdtemp() - self.log('Created temporary directory %s' % temp_dir) - py.test.temp_dir = temp_dir - - def _delete_temp_directory(self): - if py.test.temp_dir and os.path.exists(py.test.temp_dir): - self.log('Removing temporary directory %s' % py.test.temp_dir) - shutil.rmtree(py.test.temp_dir) - - def sessionstart(self, session): - self.log('Starting session') - self._create_temp_directory() - - def ensure_config_loaded(self): - if not hasattr(py.test, 'wsgi_app') or py.test.wsgi_app is None: - self._setup_app() - - def configure_node(self, node): - pass - - def testnodedown(self, node, error): - pass - - def sessionfinish(self, session, exitstatus): - self.log('Stopping session') - self._delete_temp_directory() - - @staticmethod - def create_from_session(session): - if session.config.option.dist != 'no': - impl_cls = MasterPecanPluginImpl - elif getattr(session.config, 'slaveinput', {}).get('slaveid'): - impl_cls = SlavePecanPluginImpl - else: - impl_cls = PecanPluginImpl - return impl_cls(session.config) - - -class MasterPecanPluginImpl(PecanPluginImpl): - """ - Plugin implementation for distributed master. - """ - - def sessionstart(self, session): - self.log('Starting master session') - self._create_temp_directory() - self._setup_app() - - def configure_node(self, node): - self.log('Configuring slave node %s' % node.gateway.id) - node.slaveinput['pecan_master_host'] = socket.gethostname() - node.slaveinput['pecan_temp_dir'] = py.test.temp_dir - - def sessionfinish(self, session, exitstatus): - self.log('Stopping master session') - self._delete_temp_directory() - - -class SlavePecanPluginImpl(PecanPluginImpl): - """ - Plugin implementation for distributed slaves. - """ - - @property - def name(self): - return self.config.slaveinput['slaveid'] - - def _is_collocated(self, session): - return (socket.gethostname() == session.config.slaveinput['pecan_master_host']) - - def _set_temp_directory(self, session): - self.log('Setting temporary directory to %s' % session.config.slaveinput['pecan_temp_dir']) - py.test.temp_dir = session.config.slaveinput['pecan_temp_dir'] - - def sessionstart(self, session): - self.log('Starting slave session') - if self._is_collocated(session): - self._set_temp_directory(session) - else: - self._create_temp_directory() - self._setup_app() - - def sessionfinish(self, session, exitstatus): - self.log('Stopping slave session') - if not self._is_collocated(session): - self._delete_temp_directory() From 054e1c4828f3d719da92a679fc576e7469e4a539 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 6 Mar 2012 23:21:28 -0500 Subject: [PATCH 2/9] A bunch of cleanup, and the removal of a few features: * Consolidating a considerable amount of duplicate (for loading a Pecan app and its environment) into `pecan.core.load_app`. * Removing support for module-based configurations. * Wrote a simple utility for loading a test environment, `pecan.testing.load_test_app`. --- pecan/__init__.py | 4 +-- pecan/commands/base.py | 36 +++++-------------- pecan/commands/serve.py | 18 ++-------- pecan/commands/shell.py | 6 ++-- pecan/configuration.py | 77 +++++++++++++---------------------------- pecan/core.py | 21 +++++++++++ pecan/default_config.py | 25 ------------- pecan/deploy.py | 16 ++------- pecan/testing.py | 5 +++ 9 files changed, 69 insertions(+), 139 deletions(-) delete mode 100644 pecan/default_config.py create mode 100644 pecan/testing.py 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)) From 4931a0cce6a6f3860438c9de291cab8991b25055 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 6 Mar 2012 23:44:29 -0500 Subject: [PATCH 3/9] Improving `pecan.configuration.set_config` to take a dict *or* file. --- pecan/configuration.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pecan/configuration.py b/pecan/configuration.py index 5756519..b2d08f6 100644 --- a/pecan/configuration.py +++ b/pecan/configuration.py @@ -179,15 +179,23 @@ def initconf(): return conf_from_dict(DEFAULT) -def set_config(name, overwrite=False): +def set_config(config, overwrite=False): ''' Updates the global configuration a filename. - :param name: filename, as a string. + :param config: Can be a dictionary containing configuration, or a string which + represents a (relative) configuration filename. ''' - conf = conf_from_file(name) - _runtime_conf.update(conf) + if overwrite is True: + _runtime_conf.__values__ == {} + + if isinstance(config, basestring): + _runtime_conf.update(conf_from_file(config)) + elif isinstance(config, dict): + _runtime_conf.update(conf_from_dict(config)) + else: + raise TypeError('%s is neither a dictionary of a string.' % config) _runtime_conf = initconf() From 1d0a4284bb77149144dfa1507d6ec800cfd51b4d Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 7 Mar 2012 09:59:56 -0500 Subject: [PATCH 4/9] Removing some meaningless tests. --- pecan/tests/test_deploy.py | 58 -------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 pecan/tests/test_deploy.py diff --git a/pecan/tests/test_deploy.py b/pecan/tests/test_deploy.py deleted file mode 100644 index 2717727..0000000 --- a/pecan/tests/test_deploy.py +++ /dev/null @@ -1,58 +0,0 @@ -from pecan.deploy import deploy -from unittest import TestCase - -import os -import sys - - -class TestDeploy(TestCase): - - def setUp(self): - test_config_d = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps') - - if test_config_d not in sys.path: - sys.path.append(test_config_d) - - def test_module_lookup(self): - """ - 1. A config file has: - app { 'modules': ['valid_module'] } - 2. The module, `valid_module` has an app.py that defines a `def setup.py` - """ - test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config.py') - assert deploy(test_config_file) == 'DEPLOYED!' - - def test_module_lookup_find_best_match(self): - """ - 1. A config file has: - app { 'modules': ['invalid_module', 'valid_module'] } - 2. The module, `valid_module` has an app.py that defines a `def setup_app` - """ - test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config.py') - assert deploy(test_config_file) == 'DEPLOYED!' - - def test_missing_app_file_lookup(self): - """ - 1. A config file has: - app { 'modules': ['valid_module'] } - 2. The module has no `app.py` file. - """ - test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config_missing.py') - self.assertRaises( - Exception, - deploy, - test_config_file - ) - - def test_missing_setup_app(self): - """ - 1. A config file has: - app { 'modules': ['valid_module'] } - 2. The module, `valid_module` has an `app.py` that contains no `def setup_app` - """ - test_config_file = os.path.join(os.path.dirname(__file__), 'test_config', 'sample_apps', 'sample_app_config_missing_app.py') - self.assertRaises( - Exception, - deploy, - test_config_file - ) From a0186eafd98b83632d8495d23d1fa1e8204662bb Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 7 Mar 2012 11:24:30 -0500 Subject: [PATCH 5/9] Updating `pecan.configuration` tests to use files (not modules). --- pecan/tests/test_conf.py | 82 +++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/pecan/tests/test_conf.py b/pecan/tests/test_conf.py index d65f85f..585c2b8 100644 --- a/pecan/tests/test_conf.py +++ b/pecan/tests/test_conf.py @@ -4,14 +4,10 @@ from unittest import TestCase from pecan import configuration from pecan import conf as _runtime_conf +__here__ = os.path.dirname(__file__) + class TestConf(TestCase): - def setUp(self): - test_config_d = os.path.join(os.path.dirname(__file__), 'test_config') - - if test_config_d not in sys.path: - sys.path.append(test_config_d) - def test_update_config_fail_identifier(self): """Fail when naming does not pass correctness""" bad_dict = {'bad name':'value'} @@ -21,7 +17,10 @@ class TestConf(TestCase): """Update an empty configuration with the default values""" conf = configuration.initconf() - conf.update_with_module('config') + conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config/config.py' + ))) self.assertTrue(conf.app.debug) self.assertEqual(conf.app.root, None) @@ -35,7 +34,10 @@ class TestConf(TestCase): """Update an empty configuration with the default values""" conf = configuration.initconf() - conf.update_with_module('empty') + conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config/empty.py' + ))) self.assertFalse(conf.app.debug) self.assertEqual(conf.app.root, None) @@ -48,7 +50,10 @@ class TestConf(TestCase): def test_update_force_dict(self): """Update an empty configuration with the default values""" conf = configuration.initconf() - conf.update_with_module('forcedict') + conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config/forcedict.py' + ))) self.assertFalse(conf.app.debug) self.assertEqual(conf.app.root, None) @@ -70,12 +75,6 @@ class TestConf(TestCase): conf['attr'] = d self.assertTrue(conf.attr.attr) - def test_config_dirname(self): - from pecan import default_config - conf = configuration.initconf() - conf['path'] = '%(confdir)s' - self.assertEqual(conf.path, os.path.dirname(default_config.__file__)) - def test_config_repr(self): conf = configuration.Config({'a':1}) self.assertEqual(repr(conf),"Config({'a': 1})") @@ -92,16 +91,55 @@ class TestConf(TestCase): def test_config_illegal_ids(self): conf = configuration.Config({}) - conf.update_with_module('bad.module_and_underscore') + conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config/bad/module_and_underscore.py' + ))) self.assertEqual([], list(conf)) - def test_config_bad_module(self): + def test_config_missing_file(self): + path = ('doesnotexist.py',) conf = configuration.Config({}) - self.assertRaises(ImportError, conf.update_with_module, 'doesnotexist') - self.assertRaises(ImportError, conf.update_with_module, 'bad.doesnotexists') - self.assertRaises(ImportError, conf.update_with_module, 'bad.bad.doesnotexist') - self.assertRaises(SyntaxError, conf.update_with_module, 'bad.syntaxerror') - self.assertRaises(ImportError, conf.update_with_module, 'bad.importerror') + + call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config', + *path + ))) + self.assertRaises(IOError, call_, path) + + def test_config_missing_file_on_path(self): + path = ('bad', 'bad', 'doesnotexist.py',) + conf = configuration.Config({}) + + call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config', + *path + ))) + self.assertRaises(IOError, call_, path) + + def test_config_with_syntax_error(self): + path = ('bad', 'syntaxerror.py') + conf = configuration.Config({}) + + call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config', + *path + ))) + self.assertRaises(SyntaxError, call_, path) + + def test_config_with_bad_import(self): + path = ('bad', 'importerror.py') + conf = configuration.Config({}) + + call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + __here__, + 'test_config', + *path + ))) + self.assertRaises(ImportError, call_, path) def test_config_set_from_file(self): path = os.path.join(os.path.dirname(__file__), 'test_config', 'empty.py') From 6d098bb5aba4cd71b7ac9b2548fa6c3406173c8c Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 7 Mar 2012 12:04:40 -0500 Subject: [PATCH 6/9] Improving test discovery and coverage options. --- .coveragerc | 2 -- pecan/tests/test_base.py | 2 +- pecan/tests/test_generic.py | 3 ++- pecan/tests/test_hooks.py | 7 ++++--- pecan/tests/test_jsonify.py | 2 +- pecan/tests/test_rest.py | 3 ++- pecan/tests/test_secure.py | 2 +- pecan/tests/test_static.py | 3 ++- pecan/tests/test_validation.py | 3 ++- setup.cfg | 7 +++++++ 10 files changed, 22 insertions(+), 12 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 1e3fc17..0000000 --- a/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -omit = pecan/commands/*.py, pecan/templates/__init__.py, pecan/testing.py diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py index ae68603..cfaed68 100644 --- a/pecan/tests/test_base.py +++ b/pecan/tests/test_base.py @@ -943,7 +943,7 @@ class TestLogging(TestCase): assert len(writes) == 1 -class TestEngines(object): +class TestEngines(TestCase): template_path = os.path.join(os.path.dirname(__file__), 'templates') diff --git a/pecan/tests/test_generic.py b/pecan/tests/test_generic.py index bbe9d0e..72c5af8 100644 --- a/pecan/tests/test_generic.py +++ b/pecan/tests/test_generic.py @@ -1,4 +1,5 @@ from pecan import Pecan, expose, request, response, redirect +from unittest import TestCase from webtest import TestApp try: from simplejson import dumps @@ -6,7 +7,7 @@ except: from json import dumps -class TestGeneric(object): +class TestGeneric(TestCase): def test_simple_generic(self): class RootController(object): diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py index 3ba4022..bf479ff 100644 --- a/pecan/tests/test_hooks.py +++ b/pecan/tests/test_hooks.py @@ -5,11 +5,12 @@ from pecan.hooks import PecanHook, TransactionHook, HookController, Requ from pecan.configuration import Config from pecan.decorators import transactional, after_commit, after_rollback from copy import copy +from unittest import TestCase from formencode import Schema, validators from webtest import TestApp -class TestHooks(object): +class TestHooks(TestCase): def test_basic_single_hook(self): run_hook = [] @@ -446,7 +447,7 @@ class TestHooks(object): assert run_hook[5] == 'inside' assert run_hook[6] == 'after' -class TestTransactionHook(object): +class TestTransactionHook(TestCase): def test_transaction_hook(self): run_hook = [] @@ -1109,7 +1110,7 @@ class TestTransactionHook(object): assert run_hook[3] == 'clear' -class TestRequestViewerHook(object): +class TestRequestViewerHook(TestCase): def test_hook_from_config(self): from pecan.configuration import _runtime_conf as conf diff --git a/pecan/tests/test_jsonify.py b/pecan/tests/test_jsonify.py index 00f8e4b..bd77515 100644 --- a/pecan/tests/test_jsonify.py +++ b/pecan/tests/test_jsonify.py @@ -48,7 +48,7 @@ def test_simple_rule(): assert len(result) == 1 -class TestJsonify(object): +class TestJsonify(TestCase): def test_simple_jsonify(self): Person = make_person() diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py index 9bcb4b5..a5c88cd 100644 --- a/pecan/tests/test_rest.py +++ b/pecan/tests/test_rest.py @@ -1,5 +1,6 @@ from pecan import abort, expose, make_app, request, response from pecan.rest import RestController +from unittest import TestCase from webtest import TestApp try: from simplejson import dumps, loads @@ -9,7 +10,7 @@ except: import formencode -class TestRestController(object): +class TestRestController(TestCase): def test_basic_rest(self): diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py index e430ab6..b16bbf0 100644 --- a/pecan/tests/test_secure.py +++ b/pecan/tests/test_secure.py @@ -9,7 +9,7 @@ try: except: from sets import Set as set -class TestSecure(object): +class TestSecure(TestCase): def test_simple_secure(self): authorized = False diff --git a/pecan/tests/test_static.py b/pecan/tests/test_static.py index ea2b049..fc2bc50 100644 --- a/pecan/tests/test_static.py +++ b/pecan/tests/test_static.py @@ -1,8 +1,9 @@ import os from pecan import expose, make_app +from unittest import TestCase from webtest import TestApp -class TestStatic(object): +class TestStatic(TestCase): def test_simple_static(self): class RootController(object): diff --git a/pecan/tests/test_validation.py b/pecan/tests/test_validation.py index 815d2a5..a978dd1 100644 --- a/pecan/tests/test_validation.py +++ b/pecan/tests/test_validation.py @@ -1,5 +1,6 @@ from formencode import ForEach, Schema, validators from webtest import TestApp +from unittest import TestCase import os.path @@ -12,7 +13,7 @@ except ImportError: from json import dumps -class TestValidation(object): +class TestValidation(TestCase): template_path = os.path.join(os.path.dirname(__file__), 'templates') diff --git a/setup.cfg b/setup.cfg index de09182..1a78052 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,10 @@ +[nosetests] +match=^test +where=pecan +nocapture=1 +cover-package=pecan +cover-erase=1 + [egg_info] tag_build = dev tag_svn_revision = true From 3857c3e2cddde13267802fe0fe67c967be3c2b61 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 7 Mar 2012 12:12:20 -0500 Subject: [PATCH 7/9] Replacing some print statements with warnings. --- pecan/core.py | 7 +++++-- pecan/routing.py | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pecan/core.py b/pecan/core.py index 73d224c..38d3cb8 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -459,11 +459,14 @@ class Pecan(object): elif cfg.get('content_type') is not None and \ request.pecan['content_type'] not in cfg.get('content_types', {}): - print "Controller '%s' defined does not support content_type '%s'. Supported type(s): %s" % ( + import warnings + warnings.warn("Controller '%s' defined does not support content_type '%s'. Supported type(s): %s" % ( controller.__name__, request.pecan['content_type'], cfg.get('content_types', {}).keys() - ) + ), + RuntimeWarning + ) raise exc.HTTPNotFound # get a sorted list of hooks, by priority diff --git a/pecan/routing.py b/pecan/routing.py index 8f22bc9..9739520 100644 --- a/pecan/routing.py +++ b/pecan/routing.py @@ -39,7 +39,11 @@ def lookup_controller(obj, url_path): cross_boundary(prev_obj, obj) break except TypeError, te: - print 'Got exception calling lookup(): %s (%s)' % (te, te.args) + import warnings + warnings.warn( + 'Got exception calling lookup(): %s (%s)' % (te, te.args), + RuntimeWarning + ) else: raise exc.HTTPNotFound From 63b9ad69488d2c1e2b1f6f187581d868f4bc2ceb Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 7 Mar 2012 15:31:04 -0500 Subject: [PATCH 8/9] Fixing the `model` shortcut in pecan shell. --- pecan/commands/shell.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py index 43824cf..78b55e7 100644 --- a/pecan/commands/shell.py +++ b/pecan/commands/shell.py @@ -31,8 +31,7 @@ class ShellCommand(Command): locs['wsgiapp'] = app locs['app'] = TestApp(app) - # find the model for the app - model = getattr(app, 'model', None) + model = self.load_model(app.config) if model: locs['model'] = model @@ -65,3 +64,10 @@ class ShellCommand(Command): except ImportError: pass shell.interact(shell_banner + banner) + + def load_model(self, config): + for package_name in getattr(config.app, 'modules', []): + module = __import__(package_name, fromlist=['model']) + if hasattr(module, 'model'): + return module.model + return None From 06fe8a12e1c51e7ef80db41a283d0d5b80b735ed Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 7 Mar 2012 17:14:26 -0500 Subject: [PATCH 9/9] Cleaning up some hard-to-read tests. --- pecan/tests/test_conf.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pecan/tests/test_conf.py b/pecan/tests/test_conf.py index 585c2b8..49d076c 100644 --- a/pecan/tests/test_conf.py +++ b/pecan/tests/test_conf.py @@ -100,46 +100,41 @@ class TestConf(TestCase): def test_config_missing_file(self): path = ('doesnotexist.py',) conf = configuration.Config({}) - - call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + self.assertRaises(IOError, configuration.conf_from_file, os.path.join( __here__, 'test_config', *path - ))) - self.assertRaises(IOError, call_, path) + )) def test_config_missing_file_on_path(self): path = ('bad', 'bad', 'doesnotexist.py',) conf = configuration.Config({}) - call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + self.assertRaises(IOError, configuration.conf_from_file, os.path.join( __here__, 'test_config', *path - ))) - self.assertRaises(IOError, call_, path) + )) def test_config_with_syntax_error(self): path = ('bad', 'syntaxerror.py') conf = configuration.Config({}) - call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + self.assertRaises(SyntaxError, configuration.conf_from_file, os.path.join( __here__, 'test_config', *path - ))) - self.assertRaises(SyntaxError, call_, path) + )) def test_config_with_bad_import(self): path = ('bad', 'importerror.py') conf = configuration.Config({}) - call_ = lambda path: conf.update(configuration.conf_from_file(os.path.join( + self.assertRaises(ImportError, configuration.conf_from_file, os.path.join( __here__, 'test_config', *path - ))) - self.assertRaises(ImportError, call_, path) + )) def test_config_set_from_file(self): path = os.path.join(os.path.dirname(__file__), 'test_config', 'empty.py')