diff --git a/pecan/__init__.py b/pecan/__init__.py index 154f0ad..a9e77a7 100644 --- a/pecan/__init__.py +++ b/pecan/__init__.py @@ -9,7 +9,10 @@ from paste.urlparser import StaticURLParser from weberror.errormiddleware import ErrorMiddleware from weberror.evalexception import EvalException -from core import abort, error_for, override_template, Pecan, load_app, redirect, render, request, response, ValidationException +from core import ( + abort, override_template, Pecan, load_app, redirect, render, + request, response, ValidationException +) from decorators import expose from hooks import RequestViewerHook from templating import error_formatters @@ -18,12 +21,16 @@ from configuration import set_config from configuration import _runtime_conf as conf __all__ = [ - 'make_app', 'load_app', 'Pecan', 'request', 'response', 'override_template', 'expose', 'conf', 'set_config' + 'make_app', 'load_app', 'Pecan', 'request', 'response', + 'override_template', 'expose', 'conf', 'set_config', 'render', + 'abort', 'ValidationException', 'redirect' ] -def make_app(root, static_root=None, debug=False, errorcfg={}, wrap_app=None, logging=False, **kw): + +def make_app(root, static_root=None, debug=False, errorcfg={}, + wrap_app=None, logging=False, **kw): ''' - + ''' if hasattr(conf, 'requestviewer'): existing_hooks = kw.get('hooks', []) @@ -35,7 +42,11 @@ def make_app(root, static_root=None, debug=False, errorcfg={}, wrap_app=None, lo app = wrap_app(app) app = RecursiveMiddleware(app) if debug: - app = EvalException(app, templating_formatters=error_formatters, **errorcfg) + app = EvalException( + app, + templating_formatters=error_formatters, + **errorcfg + ) else: app = ErrorMiddleware(app, **errorcfg) app = make_errordocument(app, conf, **conf.app.errors) diff --git a/pecan/commands/__init__.py b/pecan/commands/__init__.py index dd0a7c8..3148810 100644 --- a/pecan/commands/__init__.py +++ b/pecan/commands/__init__.py @@ -1,7 +1,8 @@ """ PasteScript commands for Pecan. """ -from runner import CommandRunner -from create import CreateCommand -from shell import ShellCommand -from serve import ServeCommand + +from runner import CommandRunner # noqa +from create import CreateCommand # noqa +from shell import ShellCommand # noqa +from serve import ServeCommand # noqa diff --git a/pecan/commands/base.py b/pecan/commands/base.py index f2df071..f30704c 100644 --- a/pecan/commands/base.py +++ b/pecan/commands/base.py @@ -2,30 +2,28 @@ 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 import os.path -import sys class Command(paste_command.Command): """ Base class for Pecan commands. - - This provides some standard functionality for interacting with Pecan + + This provides some standard functionality for interacting with Pecan applications and handles some of the basic PasteScript command cruft. - + See ``paste.script.command.Command`` for more information. """ - + # command information group_name = 'Pecan' summary = '' - + # command parser parser = paste_command.Command.standard_parser() - + def run(self, args): try: return paste_command.Command.run(self, args) @@ -35,15 +33,17 @@ class Command(paste_command.Command): 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.') + raise paste_command.BadCommand( + 'This command needs a valid config file.' + ) return argv[0] - + def command(self): pass diff --git a/pecan/commands/create.py b/pecan/commands/create.py index 8244b39..5ade3c2 100644 --- a/pecan/commands/create.py +++ b/pecan/commands/create.py @@ -13,16 +13,16 @@ import sys class CreateCommand(CreateDistroCommand, Command): """ Creates the file layout for a new Pecan distribution. - - For a template to show up when using this command, its name must begin - with "pecan-". Although not required, it should also include the "Pecan" + + For a template to show up when using this command, its name must begin + with "pecan-". Although not required, it should also include the "Pecan" egg plugin for user convenience. """ - + # command information summary = __doc__.strip().splitlines()[0].rstrip('.') description = None - + def command(self): if not self.options.list_templates: if not self.options.templates: @@ -33,7 +33,7 @@ class CreateCommand(CreateDistroCommand, Command): sys.stderr.write('%s\n\n' % ex) CreateDistroCommand.list_templates(self) return 2 - + def all_entry_points(self): entry_points = [] for entry in CreateDistroCommand.all_entry_points(self): diff --git a/pecan/commands/runner.py b/pecan/commands/runner.py index 6043d9e..77b18f2 100644 --- a/pecan/commands/runner.py +++ b/pecan/commands/runner.py @@ -13,50 +13,52 @@ import warnings class CommandRunner(object): """ Dispatches command execution requests. - - This is a custom PasteScript command runner that is specific to Pecan - commands. For a command to show up, its name must begin with "pecan-". - It is also recommended that its group name be set to "Pecan" so that it + + This is a custom PasteScript command runner that is specific to Pecan + commands. For a command to show up, its name must begin with "pecan-". + It is also recommended that its group name be set to "Pecan" so that it shows up under that group when using ``paster`` directly. """ - + def __init__(self): - + # set up the parser - self.parser = optparse.OptionParser(add_help_option=False, - version='Pecan %s' % self.get_version(), - usage='%prog [options] COMMAND [command_options]') + self.parser = optparse.OptionParser( + add_help_option=False, + version='Pecan %s' % self.get_version(), + usage='%prog [options] COMMAND [command_options]' + ) self.parser.disable_interspersed_args() self.parser.add_option('-h', '--help', action='store_true', dest='show_help', help='show detailed help message') - + # suppress BaseException.message warnings for BadCommand if sys.version_info < (2, 7): warnings.filterwarnings( - 'ignore', - 'BaseException\.message has been deprecated as of Python 2\.6', - DeprecationWarning, + 'ignore', + 'BaseException\.message has been deprecated as of Python 2\.6', + DeprecationWarning, paste_command.__name__.replace('.', '\\.')) - + # register Pecan as a system plugin when using the custom runner paste_command.system_plugins.append('Pecan') - + def get_command_template(self, command_names): if not command_names: max_length = 10 else: max_length = max([len(name) for name in command_names]) return ' %%-%ds %%s\n' % max_length - + def get_commands(self): commands = {} for name, command in paste_command.get_commands().iteritems(): if name.startswith('pecan-'): commands[name[6:]] = command.load() return commands - + def get_version(self): try: dist = pkg_resources.get_distribution('Pecan') @@ -66,7 +68,7 @@ class CommandRunner(object): return '(development)' except: return '(development)' - + def print_usage(self, file=sys.stdout): self.parser.print_help(file=file) file.write('\n') @@ -88,7 +90,7 @@ class CommandRunner(object): file.write(command_template % (name, command.summary)) if i + 1 < len(command_groups): file.write('\n') - + def print_known_commands(self, file=sys.stderr): commands = self.get_commands() command_names = sorted(commands.keys()) @@ -99,7 +101,7 @@ class CommandRunner(object): command_template = self.get_command_template(command_names) for name in command_names: file.write(command_template % (name, commands[name].summary)) - + def run(self, args): options, args = self.parser.parse_args(args) if not args: @@ -117,7 +119,7 @@ class CommandRunner(object): return command.run(['-h']) else: return command.run(args) - + @classmethod def handle_command_line(cls): try: diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py index 874296c..0d699e9 100644 --- a/pecan/commands/serve.py +++ b/pecan/commands/serve.py @@ -1,45 +1,45 @@ """ PasteScript serve command for Pecan. """ -from paste import httpserver -from paste.script import command as paste_command +from paste import httpserver from paste.script.serve import ServeCommand as _ServeCommand from base import Command -import os import re class ServeCommand(_ServeCommand, Command): """ Serves a Pecan web application. - - This command serves a Pecan web application using the provided + + This command serves a Pecan web application using the provided configuration file for the server and application. - - If start/stop/restart is given, then --daemon is implied, and it will + + If start/stop/restart is given, then --daemon is implied, and it will start (normal operation), stop (--stop-daemon), or do both. """ - + # command information usage = 'CONFIG_FILE [start|stop|restart|status]' summary = __doc__.strip().splitlines()[0].rstrip('.') - description = '\n'.join(map(lambda s: s.rstrip(), __doc__.strip().splitlines()[2:])) - + description = '\n'.join( + map(lambda s: s.rstrip(), __doc__.strip().splitlines()[2:]) + ) + # command options/arguments max_args = 2 - + # command parser parser = _ServeCommand.parser parser.remove_option('-n') parser.remove_option('-s') parser.remove_option('--server-name') - + # configure scheme regex _scheme_re = re.compile(r'.*') - + def command(self): - + # set defaults for removed options setattr(self.options, 'app_name', None) setattr(self.options, 'server', None) @@ -47,9 +47,11 @@ class ServeCommand(_ServeCommand, Command): # run the base command _ServeCommand.command(self) - + def loadserver(self, server_spec, name, relative_to, **kw): - return (lambda app: httpserver.serve(app, app.config.server.host, app.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() diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py index 78b55e7..574a490 100644 --- a/pecan/commands/shell.py +++ b/pecan/commands/shell.py @@ -12,41 +12,47 @@ class ShellCommand(Command): """ Open an interactive shell with the Pecan app loaded. """ - + # command information usage = 'CONFIG_NAME' summary = __doc__.strip().splitlines()[0].rstrip('.') - + # command options/arguments min_args = 1 max_args = 1 - + def command(self): - + # load the application app = self.load_app() - + # prepare the locals locs = dict(__name__='pecan-admin') locs['wsgiapp'] = app locs['app'] = TestApp(app) - + model = self.load_model(app.config) if model: locs['model'] = model - + # insert the pecan locals - exec('from pecan import abort, conf, redirect, request, response') in locs - + exec( + 'from pecan import abort, conf, redirect, request, response' + ) in locs + # prepare the banner banner = ' The following objects are available:\n' banner += ' %-10s - This project\'s WSGI App instance\n' % 'wsgiapp' banner += ' %-10s - The current configuration\n' % 'conf' banner += ' %-10s - webtest.TestApp wrapped around wsgiapp\n' % 'app' if model: - model_name = getattr(model, '__module__', getattr(model, '__name__', 'model')) + model_name = getattr( + model, + '__module__', + getattr(model, '__name__', 'model') + ) banner += ' %-10s - Models from %s\n' % ('model', model_name) - + # launch the shell, using IPython if available try: from IPython.Shell import IPShellEmbed @@ -60,7 +66,7 @@ class ShellCommand(Command): (py_prefix, sys.version) shell = code.InteractiveConsole(locals=locs) try: - import readline + import readline # noqa except ImportError: pass shell.interact(shell_banner + banner) diff --git a/pecan/configuration.py b/pecan/configuration.py index 4489bb7..bcb84fb 100644 --- a/pecan/configuration.py +++ b/pecan/configuration.py @@ -7,43 +7,45 @@ IDENTIFIER = re.compile(r'[a-z_](\w)*$', re.IGNORECASE) DEFAULT = { # Server Specific Configurations - 'server' : { - 'port' : '8080', - 'host' : '0.0.0.0' + 'server': { + 'port': '8080', + 'host': '0.0.0.0' }, # Pecan Application Configurations - 'app' : { - 'root' : None, - 'modules' : [], - 'static_root' : 'public', - 'template_path' : '', - 'debug' : False, - 'logging' : False, - 'force_canonical' : True, - 'errors' : { - '__force_dict__' : True + 'app': { + 'root': None, + 'modules': [], + 'static_root': 'public', + 'template_path': '', + 'debug': False, + 'logging': False, + 'force_canonical': True, + 'errors': { + '__force_dict__': True } } } + class ConfigDict(dict): pass + class Config(object): ''' Base class for Pecan configurations. ''' - + def __init__(self, conf_dict={}, filename=''): ''' - Create a Pecan configuration object from a dictionary or a + Create a Pecan configuration object from a dictionary or a filename. - + :param conf_dict: A python dictionary to use for the configuration. :param filename: A filename to use for the configuration. ''' - + self.__values__ = {} self.__file__ = filename self.update(conf_dict) @@ -51,16 +53,17 @@ class Config(object): def update(self, conf_dict): ''' Updates this configuration with a dictionary. - - :param conf_dict: A python dictionary to update this configuration with. + + :param conf_dict: A python dictionary to update this configuration + with. ''' - + if isinstance(conf_dict, dict): iterator = conf_dict.iteritems() else: iterator = iter(conf_dict) - - for k,v in iterator: + + for k, v in iterator: if not IDENTIFIER.match(k): raise ValueError('\'%s\' is not a valid indentifier' % k) @@ -94,11 +97,11 @@ class Config(object): def as_dict(self, prefix=None): ''' Converts recursively the Config object into a valid dictionary. - - :param prefix: A string to optionally prefix all key elements in the + + :param prefix: A string to optionally prefix all key elements in the returned dictonary. ''' - + conf_obj = dict(self) return self.__dictify__(conf_obj, prefix) @@ -106,7 +109,8 @@ class Config(object): try: return self.__values__[name] except KeyError: - raise AttributeError, "'pecan.conf' object has no attribute '%s'" % name + msg = "'pecan.conf' object has no attribute '%s'" % name + raise AttributeError(msg) def __getitem__(self, key): return self.__values__[key] @@ -129,7 +133,8 @@ class Config(object): def __dir__(self): """ - When using dir() returns a list of the values in the config. Note: This function only works in Python2.6 or later. + When using dir() returns a list of the values in the config. Note: + This function only works in Python2.6 or later. """ return self.__values__.keys() @@ -140,10 +145,10 @@ class Config(object): def conf_from_file(filepath): ''' Creates a configuration dictionary from a file. - + :param filepath: The path to the file. ''' - + abspath = os.path.abspath(os.path.expanduser(filepath)) conf_dict = {} @@ -156,26 +161,26 @@ def conf_from_file(filepath): def conf_from_dict(conf_dict): ''' Creates a configuration dictionary from a dictionary. - + :param conf_dict: The configuration dictionary. ''' - + conf = Config(filename=conf_dict.get('__file__', '')) - for k,v in conf_dict.iteritems(): + for k, v in conf_dict.iteritems(): if k.startswith('__'): continue elif inspect.ismodule(v): continue - + conf[k] = v return conf def initconf(): ''' - Initializes the default configuration and exposes it at ``pecan.configuration.conf``, - which is also exposed at ``pecan.conf``. + Initializes the default configuration and exposes it at + ``pecan.configuration.conf``, which is also exposed at ``pecan.conf``. ''' return conf_from_dict(DEFAULT) @@ -183,8 +188,9 @@ def initconf(): def set_config(config, overwrite=False): ''' Updates the global configuration a filename. - - :param config: Can be a dictionary containing configuration, or a string which + + :param config: Can be a dictionary containing configuration, or a string + which represents a (relative) configuration filename. ''' @@ -200,4 +206,3 @@ def set_config(config, overwrite=False): _runtime_conf = initconf() - diff --git a/pecan/core.py b/pecan/core.py index 38d3cb8..35cae46 100644 --- a/pecan/core.py +++ b/pecan/core.py @@ -14,8 +14,8 @@ from urlparse import urlsplit, urlunsplit try: from simplejson import loads -except ImportError: # pragma: no cover - from json import loads +except ImportError: # pragma: no cover + from json import loads # noqa import urllib @@ -30,63 +30,76 @@ def proxy(key): def __getattr__(self, attr): obj = getattr(state, key) return getattr(obj, attr) + def __setattr__(self, attr, value): obj = getattr(state, key) return setattr(obj, attr, value) + def __delattr__(self, attr): obj = getattr(state, key) return delattr(obj, attr) return ObjectProxy() -request = proxy('request') -response = proxy('response') +request = proxy('request') +response = proxy('response') def override_template(template, content_type=None): ''' Call within a controller to override the template that is used in your response. - - :param template: a valid path to a template file, just as you would specify in an ``@expose``. - :param content_type: a valid MIME type to use for the response. + + :param template: a valid path to a template file, just as you would specify + in an ``@expose``. + :param content_type: a valid MIME type to use for the response.func_closure ''' - + request.pecan['override_template'] = template if content_type: - request.pecan['override_content_type'] = content_type + request.pecan['override_content_type'] = content_type def abort(status_code=None, detail='', headers=None, comment=None, **kw): ''' Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. - + :param status_code: The HTTP status code as an integer. :param detail: The message to send along, as a string. :param headers: A dictionary of headers to send along with the response. :param comment: A comment to include in the response. ''' - - raise exc.status_map[status_code](detail=detail, headers=headers, comment=comment, **kw) + + raise exc.status_map[status_code]( + detail=detail, + headers=headers, + comment=comment, + **kw + ) -def redirect(location=None, internal=False, code=None, headers={}, add_slash=False): +def redirect(location=None, internal=False, code=None, headers={}, + add_slash=False): ''' Perform a redirect, either internal or external. An internal redirect performs the redirect server-side, while the external redirect utilizes an HTTP 302 status code. - + :param location: The HTTP location to redirect to. - :param internal: A boolean indicating whether the redirect should be internal. + :param internal: A boolean indicating whether the redirect should be + internal. :param code: The HTTP status code to use for the redirect. Defaults to 302. - :param headers: Any HTTP headers to send with the response, as a dictionary. + :param headers: Any HTTP headers to send with the response, as a + dictionary. ''' - + if add_slash: if location is None: split_url = list(urlsplit(state.request.url)) - new_proto = state.request.environ.get('HTTP_X_FORWARDED_PROTO', split_url[0]) + new_proto = state.request.environ.get( + 'HTTP_X_FORWARDED_PROTO', split_url[0] + ) split_url[0] = new_proto else: split_url = urlsplit(location) @@ -110,10 +123,10 @@ def error_for(field): A convenience function for fetching the validation error for a particular field in a form. Useful within templates when not using ``htmlfill`` for forms. - + :param field: The name of the field to get the error for. ''' - + return request.pecan['validation_errors'].get(field, '') @@ -122,13 +135,13 @@ def static(name, value): When using ``htmlfill`` validation support, this function indicates that ``htmlfill`` should not fill in a value for this field, and should instead use the value specified. - + :param name: The name of the field. :param value: The value to specify. ''' - + if 'pecan.params' not in request.environ: - request.environ['pecan.params'] = dict(request.params) + request.environ['pecan.params'] = dict(request.params) request.environ['pecan.params'][name] = value return value @@ -138,11 +151,13 @@ def render(template, namespace): Render the specified template using the Pecan rendering framework with the specified template namespace as a dictionary. Useful in a controller where you have no template specified in the ``@expose``. - - :param template: The path to your template, as you would specify in ``@expose``. - :param namespace: The namespace to use for rendering the template, as a dictionary. + + :param template: The path to your template, as you would specify in + ``@expose``. + :param namespace: The namespace to use for rendering the template, as a + dictionary. ''' - + return state.app.render(template, namespace) @@ -151,7 +166,7 @@ class ValidationException(ForwardRequestException): This exception is raised when a validation error occurs using Pecan's built-in validation framework. ''' - + def __init__(self, location=None, errors={}): if state.controller is not None: cfg = _cfg(state.controller) @@ -164,7 +179,9 @@ class ValidationException(ForwardRequestException): merge_dicts(request.pecan['validation_errors'], errors) if 'pecan.params' not in request.environ: request.environ['pecan.params'] = dict(request.params) - request.environ['pecan.validation_errors'] = request.pecan['validation_errors'] + request.environ[ + 'pecan.validation_errors' + ] = request.pecan['validation_errors'] if cfg.get('htmlfill') is not None: request.environ['pecan.htmlfill'] = cfg['htmlfill'] request.environ['REQUEST_METHOD'] = 'GET' @@ -177,8 +194,8 @@ 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. + :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) @@ -189,7 +206,9 @@ def load_app(config): 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') + raise RuntimeError( + 'No app.setup_app found in any of the configured app.modules' + ) class Pecan(object): @@ -197,37 +216,42 @@ class Pecan(object): Base Pecan application object. Generally created using ``pecan.make_app``, rather than being created manually. ''' - - def __init__(self, root, - default_renderer = 'mako', - template_path = 'templates', - hooks = [], - custom_renderers = {}, - extra_template_vars = {}, - force_canonical = True - ): + + def __init__(self, root, + default_renderer='mako', + template_path='templates', + hooks=[], + custom_renderers={}, + extra_template_vars={}, + force_canonical=True + ): ''' Creates a Pecan application instance, which is a WSGI application. - + :param root: A string representing a root controller object (e.g., "myapp.controller.root.RootController") - :param default_renderer: The default rendering engine to use. Defaults to mako. - :param template_path: The default relative path to use for templates. Defaults to 'templates'. + :param default_renderer: The default rendering engine to use. Defaults + to mako. + :param template_path: The default relative path to use for templates. + Defaults to 'templates'. :param hooks: A list of Pecan hook objects to use for this application. - :param custom_renderers: Custom renderer objects, as a dictionary keyed by engine name. - :param extra_template_vars: Any variables to inject into the template namespace automatically. - :param force_canonical: A boolean indicating if this project should require canonical URLs. + :param custom_renderers: Custom renderer objects, as a dictionary keyed + by engine name. + :param extra_template_vars: Any variables to inject into the template + namespace automatically. + :param force_canonical: A boolean indicating if this project should + require canonical URLs. ''' if isinstance(root, basestring): root = self.__translate_root__(root) - self.root = root - self.renderers = RendererFactory(custom_renderers, extra_template_vars) + self.root = root + self.renderers = RendererFactory(custom_renderers, extra_template_vars) self.default_renderer = default_renderer - self.hooks = hooks - self.template_path = template_path - self.force_canonical = force_canonical + self.hooks = hooks + self.template_path = template_path + self.force_canonical = force_canonical def __translate_root__(self, item): ''' @@ -235,8 +259,8 @@ class Pecan(object): > __translate_root__("myproject.controllers.RootController") myproject.controllers.RootController() - - :param item: The string to the item + + :param item: The string to the item ''' if '.' in item: @@ -247,68 +271,75 @@ class Pecan(object): try: module = __import__(name, fromlist=fromlist) kallable = getattr(module, parts[-1]) - assert hasattr(kallable, '__call__'), "%s does not represent a callable class or function." % item + msg = "%s does not represent a callable class or function." + assert hasattr(kallable, '__call__'), msg % item return kallable() - except AttributeError, e: + except AttributeError: raise ImportError('No item named %s' % item) raise ImportError('No item named %s' % item) - + def route(self, node, path): ''' Looks up a controller from a node based upon the specified path. - + :param node: The node, such as a root controller object. :param path: The path to look up on this node. ''' - + path = path.split('/')[1:] try: node, remainder = lookup_controller(node, path) return node, remainder except NonCanonicalPath, e: - if self.force_canonical and not _cfg(e.controller).get('accept_noncanonical', False): + if self.force_canonical and \ + not _cfg(e.controller).get('accept_noncanonical', False): if request.method == 'POST': - raise RuntimeError, "You have POSTed to a URL '%s' which '\ + raise RuntimeError( + "You have POSTed to a URL '%s' which '\ 'requires a slash. Most browsers will not maintain '\ 'POST data when redirected. Please update your code '\ 'to POST to '%s/' or set force_canonical to False" % \ - (request.pecan['routing_path'], request.pecan['routing_path']) + (request.pecan['routing_path'], + request.pecan['routing_path']) + ) redirect(code=302, add_slash=True) return e.controller, e.remainder - + def determine_hooks(self, controller=None): ''' Determines the hooks to be run, in which order. - - :param controller: If specified, includes hooks for a specific controller. + + :param controller: If specified, includes hooks for a specific + controller. ''' - + controller_hooks = [] if controller: controller_hooks = _cfg(controller).get('hooks', []) return list( sorted( - chain(controller_hooks, self.hooks), - lambda x,y: cmp(x.priority, y.priority) + chain(controller_hooks, self.hooks), + lambda x, y: cmp(x.priority, y.priority) ) ) - + def handle_hooks(self, hook_type, *args): ''' Processes hooks of the specified type. - - :param hook_type: The type of hook, including ``before``, ``after``, ``on_error``, and ``on_route``. + + :param hook_type: The type of hook, including ``before``, ``after``, + ``on_error``, and ``on_route``. :param *args: Arguments to pass to the hooks. ''' - + if hook_type in ['before', 'on_route']: hooks = state.hooks else: hooks = reversed(state.hooks) for hook in hooks: - getattr(hook, hook_type)(*args) + getattr(hook, hook_type)(*args) def get_args(self, all_params, remainder, argspec, im_self): ''' @@ -321,35 +352,35 @@ class Pecan(object): def _decode(x): return urllib.unquote_plus(x) if isinstance(x, basestring) else x - + remainder = [_decode(x) for x in remainder] - + if im_self is not None: args.append(im_self) - + # grab the routing args from nested REST controllers if 'routing_args' in request.pecan: remainder = request.pecan['routing_args'] + list(remainder) del request.pecan['routing_args'] - + # handle positional arguments if valid_args and remainder: args.extend(remainder[:len(valid_args)]) remainder = remainder[len(valid_args):] valid_args = valid_args[len(args):] - + # handle wildcard arguments if remainder: if not argspec[1]: abort(404) args.extend(remainder) - + # get the default positional arguments if argspec[3]: defaults = dict(zip(argspec[0][-len(argspec[3]):], argspec[3])) else: defaults = dict() - + # handle positional GET/POST params for name in valid_args: if name in all_params: @@ -358,47 +389,59 @@ class Pecan(object): args.append(defaults[name]) else: break - + # handle wildcard GET/POST params if argspec[2]: for name, value in all_params.iteritems(): if name not in argspec[0]: kwargs[encode_if_needed(name)] = value - + return args, kwargs - + def render(self, template, namespace): - renderer = self.renderers.get(self.default_renderer, self.template_path) + renderer = self.renderers.get( + self.default_renderer, + self.template_path + ) if template == 'json': renderer = self.renderers.get('json', self.template_path) else: namespace['error_for'] = error_for namespace['static'] = static if ':' in template: - renderer = self.renderers.get(template.split(':')[0], self.template_path) + renderer = self.renderers.get( + template.split(':')[0], + self.template_path + ) template = template.split(':')[1] return renderer.render(template, namespace) - - def validate(self, schema, params, json=False, error_handler=None, + + def validate(self, schema, params, json=False, error_handler=None, htmlfill=None, variable_decode=None): ''' - Performs validation against a schema for any passed params, + Performs validation against a schema for any passed params, including support for ``JSON``. - + :param schema: A ``formencode`` ``Schema`` object to validate against. :param params: The dictionary of parameters to validate. - :param json: A boolean, indicating whether or not the validation should validate against JSON content. - :param error_handler: The path to a controller which will handle errors. If not specified, validation errors will raise a ``ValidationException``. + :param json: A boolean, indicating whether or not the validation should + validate against JSON content. + :param error_handler: The path to a controller which will handle + errors. If not specified, validation errors will raise a + ``ValidationException``. :param htmlfill: Specifies whether or not to use htmlfill. - :param variable_decode: Indicates whether or not to decode variables when using htmlfill. + :param variable_decode: Indicates whether or not to decode variables + when using htmlfill. ''' - + try: to_validate = params if json: to_validate = loads(request.body) if variable_decode is not None: - to_validate = variabledecode.variable_decode(to_validate, **variable_decode) + to_validate = variabledecode.variable_decode( + to_validate, **variable_decode + ) params = schema.to_python(to_validate) except Invalid, e: kwargs = {} @@ -411,21 +454,21 @@ class Pecan(object): if json: params = dict(data=params) return params or {} - + def handle_request(self): ''' The main request handler for Pecan applications. ''' - + # get a sorted list of hooks, by priority (no controller hooks yet) state.hooks = self.determine_hooks() - + # store the routing path to allow hooks to modify it request.pecan['routing_path'] = request.path # handle "on_route" hooks self.handle_hooks('on_route', state) - + # lookup the controller, respecting content-type as requested # by the file extension on the URI path = request.pecan['routing_path'] @@ -441,7 +484,7 @@ class Pecan(object): if cfg.get('generic_handler'): raise exc.HTTPNotFound - + # handle generic controllers im_self = None if cfg.get('generic'): @@ -449,58 +492,65 @@ class Pecan(object): handlers = cfg['generic_handlers'] controller = handlers.get(request.method, handlers['DEFAULT']) cfg = _cfg(controller) - + # add the controller to the state so that hooks can use it state.controller = controller - - # if unsure ask the controller for the default content type + + # if unsure ask the controller for the default content type if not request.pecan['content_type']: - request.pecan['content_type'] = cfg.get('content_type', 'text/html') + request.pecan['content_type'] = cfg.get( + 'content_type', + 'text/html' + ) elif cfg.get('content_type') is not None and \ request.pecan['content_type'] not in cfg.get('content_types', {}): 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() + msg = "Controller '%s' defined does not support content_type " + \ + "'%s'. Supported type(s): %s" + warnings.warn( + msg % ( + controller.__name__, + request.pecan['content_type'], + cfg.get('content_types', {}).keys() ), RuntimeWarning ) raise exc.HTTPNotFound - + # get a sorted list of hooks, by priority state.hooks = self.determine_hooks(controller) - + # handle "before" hooks self.handle_hooks('before', state) - + # fetch and validate any parameters params = dict(request.params) if 'schema' in cfg: params = self.validate( - cfg['schema'], - params, - json=cfg['validate_json'], - error_handler=cfg.get('error_handler'), + cfg['schema'], + params, + json=cfg['validate_json'], + error_handler=cfg.get('error_handler'), htmlfill=cfg.get('htmlfill'), variable_decode=cfg.get('variable_decode') ) elif 'pecan.validation_errors' in request.environ: - request.pecan['validation_errors'] = request.environ.pop('pecan.validation_errors') - + errors = request.environ.pop('pecan.validation_errors') + request.pecan['validation_errors'] = errors + # fetch the arguments for the controller args, kwargs = self.get_args( - params, + params, remainder, cfg['argspec'], im_self ) - + # get the result from the controller result = controller(*args, **kwargs) - # a controller can return the response object which means they've taken + # a controller can return the response object which means they've taken # care of filling it out if result == response: return @@ -508,29 +558,41 @@ class Pecan(object): raw_namespace = result # pull the template out based upon content type and handle overrides - template = cfg.get('content_types', {}).get(request.pecan['content_type']) + template = cfg.get('content_types', {}).get( + request.pecan['content_type'] + ) # check if for controller override of template template = request.pecan.get('override_template', template) - request.pecan['content_type'] = request.pecan.get('override_content_type', request.pecan['content_type']) + request.pecan['content_type'] = request.pecan.get( + 'override_content_type', + request.pecan['content_type'] + ) # if there is a template, render it if template: if template == 'json': request.pecan['content_type'] = 'application/json' result = self.render(template, result) - - # pass the response through htmlfill (items are popped out of the + + # pass the response through htmlfill (items are popped out of the # environment even if htmlfill won't run for proper cleanup) _htmlfill = cfg.get('htmlfill') if _htmlfill is None and 'pecan.htmlfill' in request.environ: _htmlfill = request.environ.pop('pecan.htmlfill') if 'pecan.params' in request.environ: params = request.environ.pop('pecan.params') - if request.pecan['validation_errors'] and _htmlfill is not None and request.pecan['content_type'] == 'text/html': + if request.pecan['validation_errors'] and _htmlfill is not None and \ + request.pecan['content_type'] == 'text/html': errors = request.pecan['validation_errors'] - result = htmlfill.render(result, defaults=params, errors=errors, text_as_default=True, **_htmlfill) - + result = htmlfill.render( + result, + defaults=params, + errors=errors, + text_as_default=True, + **_htmlfill + ) + # If we are in a test request put the namespace where it can be # accessed directly if request.environ.get('paste.testing'): @@ -538,32 +600,33 @@ class Pecan(object): testing_variables['namespace'] = raw_namespace testing_variables['template_name'] = template testing_variables['controller_output'] = result - + # set the body content if isinstance(result, unicode): response.unicode_body = result else: response.body = result - + # set the content type if request.pecan['content_type']: response.content_type = request.pecan['content_type'] - + def __call__(self, environ, start_response): ''' - Implements the WSGI specification for Pecan applications, utilizing ``WebOb``. + Implements the WSGI specification for Pecan applications, utilizing + ``WebOb``. ''' - + # create the request and response object - state.request = Request(environ) - state.response = Response() - state.hooks = [] - state.app = self - state.controller = None - + state.request = Request(environ) + state.response = Response() + state.hooks = [] + state.app = self + state.controller = None + # handle the request try: - # add context and environment to the request + # add context and environment to the request state.request.context = {} state.request.pecan = dict(content_type=None, validation_errors={}) @@ -572,21 +635,21 @@ class Pecan(object): # if this is an HTTP Exception, set it as the response if isinstance(e, exc.HTTPException): state.response = e - + # if this is not an internal redirect, run error hooks if not isinstance(e, ForwardRequestException): self.handle_hooks('on_error', state, e) - + if not isinstance(e, exc.HTTPException): raise finally: # handle "after" hooks self.handle_hooks('after', state) - + # get the response try: return state.response(environ, start_response) - finally: + finally: # clean up state del state.hooks del state.request diff --git a/pecan/decorators.py b/pecan/decorators.py index 7670489..2ae86eb 100644 --- a/pecan/decorators.py +++ b/pecan/decorators.py @@ -2,7 +2,8 @@ from inspect import getargspec, getmembers, isclass, ismethod from util import _cfg __all__ = [ - 'expose', 'transactional', 'accept_noncanonical', 'after_commit', 'after_rollback' + 'expose', 'transactional', 'accept_noncanonical', 'after_commit', + 'after_rollback' ] @@ -16,88 +17,107 @@ def when_for(controller): return decorate return when -def expose(template = None, - content_type = 'text/html', - schema = None, - json_schema = None, - variable_decode = False, - error_handler = None, - htmlfill = None, - generic = False): - + +def expose(template=None, + content_type='text/html', + schema=None, + json_schema=None, + variable_decode=False, + error_handler=None, + htmlfill=None, + generic=False): + ''' Decorator used to flag controller methods as being "exposed" for access via HTTP, and to configure that access. - - :param template: The path to a template, relative to the base template directory. + + :param template: The path to a template, relative to the base template + directory. :param content_type: The content-type to use for this template. :param schema: A ``formencode`` ``Schema`` object to use for validation. - :param json_schema: A ``formencode`` ``Schema`` object to use for validation of JSON POST/PUT content. - :param variable_decode: A boolean indicating if you want to use ``htmlfill``'s variable decode capability of transforming flat HTML form structures into nested ones. - :param htmlfill: Indicates whether or not you want to use ``htmlfill`` for this controller. - :param generic: A boolean which flags this as a "generic" controller, which uses generic functions based upon ``simplegeneric`` generic functions. Allows you to split a single controller into multiple paths based upon HTTP method. + :param json_schema: A ``formencode`` ``Schema`` object to use for + validation of JSON POST/PUT content. + :param variable_decode: A boolean indicating if you want to use + ``htmlfill``'s variable decode capability of transforming flat HTML form + structures into nested ones. + :param htmlfill: Indicates whether or not you want to use ``htmlfill`` for + this controller. + :param generic: A boolean which flags this as a "generic" controller, which + uses generic functions based upon ``simplegeneric`` generic functions. + Allows you to split a single controller into multiple paths based upon HTTP + method. ''' - - if template == 'json': content_type = 'application/json' + + if template == 'json': + content_type = 'application/json' + def decorate(f): # flag the method as exposed f.exposed = True - + # set a "pecan" attribute, where we will store details cfg = _cfg(f) cfg['content_type'] = content_type cfg.setdefault('template', []).append(template) cfg.setdefault('content_types', {})[content_type] = template - + # handle generic controllers if generic: cfg['generic'] = True cfg['generic_handlers'] = dict(DEFAULT=f) f.when = when_for(f) - + # store the arguments for this controller method cfg['argspec'] = getargspec(f) - + # store the schema cfg['error_handler'] = error_handler - if schema is not None: + if schema is not None: cfg['schema'] = schema cfg['validate_json'] = False - elif json_schema is not None: + elif json_schema is not None: cfg['schema'] = json_schema cfg['validate_json'] = True - + # store the variable decode configuration if isinstance(variable_decode, dict) or variable_decode == True: _variable_decode = dict(dict_char='.', list_char='-') if isinstance(variable_decode, dict): _variable_decode.update(variable_decode) cfg['variable_decode'] = _variable_decode - + # store the htmlfill configuration - if isinstance(htmlfill, dict) or htmlfill == True or schema is not None: + if isinstance(htmlfill, dict) or htmlfill == True or \ + schema is not None: _htmlfill = dict(auto_insert_errors=False) if isinstance(htmlfill, dict): _htmlfill.update(htmlfill) cfg['htmlfill'] = _htmlfill return f return decorate - + + def transactional(ignore_redirects=True): ''' If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you to flag a controller method or class as being wrapped in a transaction, regardless of HTTP method. - - :param ignore_redirects: Indicates if the hook should ignore redirects for this controller or not. + + :param ignore_redirects: Indicates if the hook should ignore redirects + for this controller or not. ''' def deco(f): if isclass(f): - for method in [m[1] for m in getmembers(f) if ismethod(m[1])]: - if getattr(method, 'exposed', False): - _cfg(method)['transactional'] = True - _cfg(method)['transactional_ignore_redirects'] = _cfg(method).get('transactional_ignore_redirects', ignore_redirects) + for meth in [m[1] for m in getmembers(f) if ismethod(m[1])]: + if getattr(meth, 'exposed', False): + _cfg(meth)['transactional'] = True + _cfg(meth)['transactional_ignore_redirects'] = _cfg( + meth + ).get( + 'transactional_ignore_redirects', + ignore_redirects + ) else: _cfg(f)['transactional'] = True _cfg(f)['transactional_ignore_redirects'] = ignore_redirects @@ -111,25 +131,26 @@ def after_action(action_type, action): to flag a controller method to perform a callable action after the action_type is successfully issued. - :param action: The callable to call after the commit is successfully issued. - ''' + :param action: The callable to call after the commit is successfully + issued. ''' if action_type not in ('commit', 'rollback'): - raise Exception, 'action_type (%s) is not valid' % action_type - + raise Exception('action_type (%s) is not valid' % action_type) def deco(func): _cfg(func).setdefault('after_%s' % action_type, []).append(action) return func return deco + def after_commit(action): ''' If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you to flag a controller method to perform a callable action after the commit is successfully issued. - :param action: The callable to call after the commit is successfully issued. + :param action: The callable to call after the commit is successfully + issued. ''' return after_action('commit', action) @@ -140,7 +161,8 @@ def after_rollback(action): to flag a controller method to perform a callable action after the rollback is successfully issued. - :param action: The callable to call after the rollback is successfully issued. + :param action: The callable to call after the rollback is successfully + issued. ''' return after_action('rollback', action) @@ -149,6 +171,6 @@ def accept_noncanonical(func): ''' Flags a controller method as accepting non-canoncial URLs. ''' - + _cfg(func)['accept_noncanonical'] = True return func diff --git a/pecan/deploy.py b/pecan/deploy.py index 4a3bfc8..3559e99 100644 --- a/pecan/deploy.py +++ b/pecan/deploy.py @@ -1,4 +1,5 @@ from core import load_app + def deploy(config): return load_app(config) diff --git a/pecan/hooks.py b/pecan/hooks.py index 90c5b63..f01eb1d 100644 --- a/pecan/hooks.py +++ b/pecan/hooks.py @@ -5,32 +5,38 @@ from webob.exc import HTTPFound from util import iscontroller, _cfg from routing import lookup_controller -__all__ = ['PecanHook', 'TransactionHook', 'HookController', 'RequestViewerHook'] +__all__ = [ + 'PecanHook', 'TransactionHook', 'HookController', + 'RequestViewerHook' +] def walk_controller(root_class, controller, hooks): if not isinstance(controller, (int, dict)): for name, value in getmembers(controller): - if name == 'controller': continue - if name.startswith('__') and name.endswith('__'): continue - + if name == 'controller': + continue + if name.startswith('__') and name.endswith('__'): + continue + if iscontroller(value): for hook in hooks: value._pecan.setdefault('hooks', []).append(hook) elif hasattr(value, '__class__'): - if name.startswith('__') and name.endswith('__'): continue + if name.startswith('__') and name.endswith('__'): + continue walk_controller(root_class, value, hooks) class HookController(object): ''' A base class for controllers that would like to specify hooks on - their controller methods. Simply create a list of hook objects + their controller methods. Simply create a list of hook objects called ``__hooks__`` as a member of the controller's namespace. ''' - + __hooks__ = [] - + class __metaclass__(type): def __init__(cls, name, bases, dict_): walk_controller(cls, cls, dict_['__hooks__']) @@ -42,41 +48,41 @@ class PecanHook(object): own hooks. Set a priority on a hook by setting the ``priority`` attribute for the hook, which defaults to 100. ''' - + priority = 100 - + def on_route(self, state): ''' Override this method to create a hook that gets called upon the start of routing. - + :param state: The Pecan ``state`` object for the current request. ''' return - + def before(self, state): ''' Override this method to create a hook that gets called after routing, but before the request gets passed to your controller. - + :param state: The Pecan ``state`` object for the current request. ''' return - + def after(self, state): ''' Override this method to create a hook that gets called after the request has been handled by the controller. - + :param state: The Pecan ``state`` object for the current request. ''' return - + def on_error(self, state, e): ''' Override this method to create a hook that gets called upon an exception being raised in your controller. - + :param state: The Pecan ``state`` object for the current request. :param e: The ``Exception`` object that was raised. ''' @@ -90,21 +96,23 @@ class TransactionHook(PecanHook): requests in a transaction. Override the ``is_transactional`` method to define your own rules for what requests should be transactional. ''' - + def __init__(self, start, start_ro, commit, rollback, clear): ''' - :param start: A callable that will bind to a writable database and start a transaction. + :param start: A callable that will bind to a writable database and + start a transaction. :param start_ro: A callable that will bind to a readable database. :param commit: A callable that will commit the active transaction. - :param rollback: A callable that will roll back the active transaction. + :param rollback: A callable that will roll back the active + transaction. :param clear: A callable that will clear your current context. ''' - - self.start = start + + self.start = start self.start_ro = start_ro - self.commit = commit + self.commit = commit self.rollback = rollback - self.clear = clear + self.clear = clear def is_transactional(self, state): ''' @@ -112,10 +120,10 @@ class TransactionHook(PecanHook): upon the state of the request. By default, wraps all but ``GET`` and ``HEAD`` requests in a transaction, along with respecting the ``transactional`` decorator from :mod:pecan.decorators. - + :param state: The Pecan state object for the current request. ''' - + controller = getattr(state, 'controller', None) if controller: force_transactional = _cfg(controller).get('transactional', False) @@ -147,13 +155,20 @@ class TransactionHook(PecanHook): # (e.g., shouldn't consider them rollback-worthy) # don't set `state.request.error = True`. # - transactional_ignore_redirects = state.request.method not in ('GET', 'HEAD') + trans_ignore_redirects = ( + state.request.method not in ('GET', 'HEAD') + ) if state.controller is not None: - transactional_ignore_redirects = _cfg(state.controller).get('transactional_ignore_redirects', transactional_ignore_redirects) - if type(e) is HTTPFound and transactional_ignore_redirects is True: + trans_ignore_redirects = ( + _cfg(state.controller).get( + 'transactional_ignore_redirects', + trans_ignore_redirects + ) + ) + if type(e) is HTTPFound and trans_ignore_redirects is True: return state.request.error = True - + def after(self, state): if state.request.transactional: action_name = None @@ -178,21 +193,22 @@ class TransactionHook(PecanHook): self.clear() + class RequestViewerHook(PecanHook): ''' Returns some information about what is going on in a single request. It accepts specific items to report on but uses a default list of items when none are passed in. Based on the requested ``url``, items can also be blacklisted. - Configuration is flexible, can be passed in (or not) and can contain some or - all the keys supported. + Configuration is flexible, can be passed in (or not) and can contain + some or all the keys supported. ``items`` --------- - This key holds the items that this hook will display. When this key is passed - only the items in the list will be used. - Valid items are *any* item that the ``request`` object holds, by default it uses - the following: + This key holds the items that this hook will display. When this key is + passed only the items in the list will be used. Valid items are *any* + item that the ``request`` object holds, by default it uses the + following: * path * status @@ -206,11 +222,12 @@ class RequestViewerHook(PecanHook): ``blacklist`` ------------- - This key holds items that will be blacklisted based on ``url``. If there is a need - to ommit urls that start with `/javascript`, then this key would look like:: + This key holds items that will be blacklisted based on ``url``. If + there is a need to ommit urls that start with `/javascript`, then this + key would look like:: 'blacklist': ['/javascript'] - + As many blacklisting items as needed can be contained in the list. The hook will verify that the url is not starting with items in this list to display results, otherwise it will get ommited. @@ -218,44 +235,50 @@ class RequestViewerHook(PecanHook): .. :note:: This key should always use a ``list`` of items to use. - For more detailed documentation about this hook, please see :ref:`requestviewerhook` + For more detailed documentation about this hook, please see + :ref:`requestviewerhook` ''' available = ['path', 'status', 'method', 'controller', 'params', 'hooks'] - def __init__(self, config=None, writer=sys.stdout, terminal=True, headers=True): + def __init__(self, config=None, writer=sys.stdout, terminal=True, + headers=True): ''' :param config: A (optional) dictionary that can hold ``items`` and/or - ``blacklist`` keys. - :param writer: The stream writer to use. Can redirect output to other - streams as long as the passed in stream has a ``write`` - callable method. - :param terminal: Outputs to the chosen stream writer (usually the terminal) + ``blacklist`` keys. + :param writer: The stream writer to use. Can redirect output to other + streams as long as the passed in stream has a + ``write`` callable method. + :param terminal: Outputs to the chosen stream writer (usually + the terminal) :param headers: Sets values to the X-HTTP headers ''' if not config: - self.config = {'items' : self.available} + self.config = {'items': self.available} else: if config.__class__.__name__ == 'Config': self.config = config.as_dict() else: self.config = config - self.writer = writer - self.items = self.config.get('items', self.available) - self.blacklist = self.config.get('blacklist', []) - self.terminal = terminal - self.headers = headers + self.writer = writer + self.items = self.config.get('items', self.available) + self.blacklist = self.config.get('blacklist', []) + self.terminal = terminal + self.headers = headers def after(self, state): # Default and/or custom response information responses = { - 'controller' : lambda self, state: self.get_controller(state), - 'method' : lambda self, state: state.request.method, - 'path' : lambda self, state: state.request.path, - 'params' : lambda self, state: [(p[0].encode('utf-8'), p[1].encode('utf-8')) for p in state.request.params.items()], - 'status' : lambda self, state: state.response.status, - 'hooks' : lambda self, state: self.format_hooks(state.app.hooks), + 'controller': lambda self, state: self.get_controller(state), + 'method': lambda self, state: state.request.method, + 'path': lambda self, state: state.request.path, + 'params': lambda self, state: [ + (p[0].encode('utf-8'), p[1].encode('utf-8')) + for p in state.request.params.items() + ], + 'status': lambda self, state: state.response.status, + 'hooks': lambda self, state: self.format_hooks(state.app.hooks), } is_available = [ @@ -263,16 +286,19 @@ class RequestViewerHook(PecanHook): if i in self.available or hasattr(state.request, i) ] - terminal = [] - headers = [] - will_skip = [i for i in self.blacklist if state.request.path.startswith(i)] + terminal = [] + headers = [] + will_skip = [ + i for i in self.blacklist + if state.request.path.startswith(i) + ] if will_skip: return - + for request_info in is_available: try: - value = responses.get(request_info) + value = responses.get(request_info) if not value: value = getattr(state.request, request_info) else: @@ -289,9 +315,9 @@ class RequestViewerHook(PecanHook): if self.headers: for h in headers: - key = str(h[0]) + key = str(h[0]) value = str(h[1]) - name = 'X-Pecan-%s' % key + name = 'X-Pecan-%s' % key state.response.headers[name] = value def get_controller(self, state): @@ -310,5 +336,3 @@ class RequestViewerHook(PecanHook): ''' str_hooks = [str(i).split()[0].strip('<') for i in hooks] return [i.split('.')[-1] for i in str_hooks if '.' in i] - - diff --git a/pecan/jsonify.py b/pecan/jsonify.py index 6417ae1..5ff4642 100644 --- a/pecan/jsonify.py +++ b/pecan/jsonify.py @@ -1,7 +1,7 @@ try: from simplejson import JSONEncoder -except ImportError: # pragma: no cover - from json import JSONEncoder +except ImportError: # pragma: no cover + from json import JSONEncoder # noqa from datetime import datetime, date from decimal import Decimal @@ -20,15 +20,16 @@ from simplegeneric import generic try: from sqlalchemy.engine.base import ResultProxy, RowProxy -except ImportError: #pragma no cover +except ImportError: # pragma no cover # dummy classes since we don't have SQLAlchemy installed - class ResultProxy: pass - class RowProxy: pass + class ResultProxy: pass # noqa + class RowProxy: pass # noqa # # exceptions # + class JsonEncodeError(Exception): pass @@ -73,16 +74,18 @@ class GenericJSON(JSONEncoder): _default = GenericJSON() + @generic def jsonify(obj): return _default.default(obj) + class GenericFunctionJSON(GenericJSON): def default(self, obj): return jsonify(obj) _instance = GenericFunctionJSON() - + def encode(obj): return _instance.encode(obj) diff --git a/pecan/rest.py b/pecan/rest.py index aac32bc..b25d5b9 100644 --- a/pecan/rest.py +++ b/pecan/rest.py @@ -12,12 +12,12 @@ class RestController(object): to implement a REST controller. A set of custom actions can also be specified. For more details, see :ref:`pecan_rest`. ''' - + _custom_actions = {} - + @expose() def _route(self, args): - + # convention uses "_method" to handle browser-unsupported methods if request.environ.get('pecan.validation_redirected', False) == True: # @@ -28,7 +28,7 @@ class RestController(object): method = request.method.lower() else: method = request.params.get('_method', request.method).lower() - + # make sure DELETE/PUT requests don't use GET if request.method == 'GET' and method in ('delete', 'put'): abort(405) @@ -37,23 +37,23 @@ class RestController(object): result = self._find_sub_controllers(args) if result: return result - + # handle the request handler = getattr(self, '_handle_%s' % method, self._handle_custom) result = handler(method, args) - + # return the result return result - + def _find_controller(self, *args): for name in args: obj = getattr(self, name, None) if obj and iscontroller(obj): return obj return None - + def _find_sub_controllers(self, remainder): - + # need either a get_one or get to parse args method = None for name in ('get_one', 'get'): @@ -62,12 +62,14 @@ class RestController(object): break if not method: return - + # get the args to figure out how much to chop off args = getargspec(getattr(self, method)) - fixed_args = len(args[0][1:]) - len(request.pecan.get('routing_args', [])) + fixed_args = len(args[0][1:]) - len( + request.pecan.get('routing_args', []) + ) var_args = args[1] - + # attempt to locate a sub-controller if var_args: for i, item in enumerate(remainder): @@ -75,20 +77,25 @@ class RestController(object): if controller and not ismethod(controller): self._set_routing_args(remainder[:i]) return lookup_controller(controller, remainder[i + 1:]) - elif fixed_args < len(remainder) and hasattr(self, remainder[fixed_args]): + elif fixed_args < len(remainder) and hasattr( + self, remainder[fixed_args] + ): controller = getattr(self, remainder[fixed_args]) if not ismethod(controller): self._set_routing_args(remainder[:fixed_args]) - return lookup_controller(controller, remainder[fixed_args + 1:]) - + return lookup_controller( + controller, + remainder[fixed_args + 1:] + ) + def _handle_custom(self, method, remainder): - + # try finding a post_{custom} or {custom} method first controller = self._find_controller('post_%s' % method, method) if controller: return controller, remainder - - # if no controller exists, try routing to a sub-controller; note that + + # if no controller exists, try routing to a sub-controller; note that # since this isn't a safe GET verb, any local exposes are 405'd if remainder: if self._find_controller(remainder[0]): @@ -96,18 +103,18 @@ class RestController(object): sub_controller = getattr(self, remainder[0], None) if sub_controller: return lookup_controller(sub_controller, remainder[1:]) - + abort(404) - + def _handle_get(self, method, remainder): - + # route to a get_all or get if no additional parts are available if not remainder: controller = self._find_controller('get_all', 'get') if controller: return controller, [] abort(404) - + # check for new/edit/delete GET requests method_name = remainder[-1] if method_name in ('new', 'edit', 'delete'): @@ -116,31 +123,34 @@ class RestController(object): controller = self._find_controller(method_name) if controller: return controller, remainder[:-1] - + # check for custom GET requests if method.upper() in self._custom_actions.get(method_name, []): - controller = self._find_controller('get_%s' % method_name, method_name) + controller = self._find_controller( + 'get_%s' % method_name, + method_name + ) if controller: return controller, remainder[:-1] controller = getattr(self, remainder[0], None) if controller and not ismethod(controller): return lookup_controller(controller, remainder[1:]) - + # finally, check for the regular get_one/get requests controller = self._find_controller('get_one', 'get') if controller: return controller, remainder - + abort(404) - + def _handle_delete(self, method, remainder): - + # check for post_delete/delete requests first controller = self._find_controller('post_delete', 'delete') if controller: return controller, remainder - - # if no controller exists, try routing to a sub-controller; note that + + # if no controller exists, try routing to a sub-controller; note that # since this is a DELETE verb, any local exposes are 405'd if remainder: if self._find_controller(remainder[0]): @@ -148,30 +158,33 @@ class RestController(object): sub_controller = getattr(self, remainder[0], None) if sub_controller: return lookup_controller(sub_controller, remainder[1:]) - + abort(404) - + def _handle_post(self, method, remainder): # check for custom POST/PUT requests if remainder: method_name = remainder[-1] if method.upper() in self._custom_actions.get(method_name, []): - controller = self._find_controller('%s_%s' % (method, method_name), method_name) + controller = self._find_controller( + '%s_%s' % (method, method_name), + method_name + ) if controller: return controller, remainder[:-1] controller = getattr(self, remainder[0], None) if controller and not ismethod(controller): return lookup_controller(controller, remainder[1:]) - + # check for regular POST/PUT requests controller = self._find_controller(method) if controller: return controller, remainder - + abort(404) - + _handle_put = _handle_post - + def _set_routing_args(self, args): request.pecan.setdefault('routing_args', []).extend(args) diff --git a/pecan/routing.py b/pecan/routing.py index 9739520..246352f 100644 --- a/pecan/routing.py +++ b/pecan/routing.py @@ -1,16 +1,17 @@ from webob import exc -from inspect import ismethod from secure import handle_security, cross_boundary from util import iscontroller __all__ = ['lookup_controller', 'find_object'] + class NonCanonicalPath(Exception): def __init__(self, controller, remainder): self.controller = controller self.remainder = remainder + def lookup_controller(obj, url_path): remainder = url_path notfound_handlers = [] @@ -40,8 +41,9 @@ def lookup_controller(obj, url_path): break except TypeError, te: import warnings + msg = 'Got exception calling lookup(): %s (%s)' warnings.warn( - 'Got exception calling lookup(): %s (%s)' % (te, te.args), + msg % (te, te.args), RuntimeWarning ) else: @@ -51,19 +53,22 @@ def lookup_controller(obj, url_path): def find_object(obj, remainder, notfound_handlers): prev_obj = None while True: - if obj is None: raise exc.HTTPNotFound - if iscontroller(obj): return obj, remainder + if obj is None: + raise exc.HTTPNotFound + if iscontroller(obj): + return obj, remainder # are we traversing to another controller cross_boundary(prev_obj, obj) - + if remainder and remainder[0] == '': index = getattr(obj, 'index', None) - if iscontroller(index): return index, remainder[1:] + if iscontroller(index): + return index, remainder[1:] elif not remainder: # the URL has hit an index method without a trailing slash index = getattr(obj, 'index', None) - if iscontroller(index): + if iscontroller(index): raise NonCanonicalPath(index, remainder[1:]) default = getattr(obj, '_default', None) if iscontroller(default): @@ -72,14 +77,15 @@ def find_object(obj, remainder, notfound_handlers): lookup = getattr(obj, '_lookup', None) if iscontroller(lookup): notfound_handlers.append(('_lookup', lookup, remainder)) - + route = getattr(obj, '_route', None) if iscontroller(route): next, next_remainder = route(remainder) cross_boundary(route, next) return next, next_remainder - - if not remainder: raise exc.HTTPNotFound + + if not remainder: + raise exc.HTTPNotFound next, remainder = remainder[0], remainder[1:] prev_obj = obj obj = getattr(obj, next, None) diff --git a/pecan/secure.py b/pecan/secure.py index 8c67b48..cf14f6f 100644 --- a/pecan/secure.py +++ b/pecan/secure.py @@ -6,23 +6,28 @@ from util import _cfg, iscontroller __all__ = ['unlocked', 'secure', 'SecureController'] + class _SecureState(object): def __init__(self, desc, boolean_value): self.description = desc self.boolean_value = boolean_value + def __repr__(self): return '' % self.description + def __nonzero__(self): return self.boolean_value Any = _SecureState('Any', False) Protected = _SecureState('Protected', True) -# security method decorators + +# security method decorators def _unlocked_method(func): _cfg(func)['secured'] = Any return func + def _secure_method(check_permissions_func): def wrap(func): cfg = _cfg(func) @@ -31,6 +36,7 @@ def _secure_method(check_permissions_func): return func return wrap + # classes to assist with wrapping attributes class _UnlockedAttribute(object): def __init__(self, obj): @@ -41,6 +47,7 @@ class _UnlockedAttribute(object): def _lookup(self, *remainder): return self.obj, remainder + class _SecuredAttribute(object): def __init__(self, obj, check_permissions): self.obj = obj @@ -55,6 +62,7 @@ class _SecuredAttribute(object): def __get_parent(self): return self._parent + def __set_parent(self, parent): if ismethod(parent): self._parent = parent.im_self @@ -67,13 +75,15 @@ class _SecuredAttribute(object): def _lookup(self, *remainder): return self.obj, remainder + # helper for secure decorator def _allowed_check_permissions_types(x): - return (ismethod(x) or - isfunction(x) or + return (ismethod(x) or + isfunction(x) or isinstance(x, basestring) ) + # methods that can either decorate functions or wrap classes # these should be the main methods used for securing or unlocking def unlocked(func_or_obj): @@ -102,13 +112,15 @@ def secure(func_or_obj, check_permissions_for_obj=None): return _secure_method(func_or_obj) else: if not _allowed_check_permissions_types(check_permissions_for_obj): - raise TypeError, "When securing an object, secure() requires the second argument to be method" + msg = "When securing an object, secure() requires the " + \ + "second argument to be method" + raise TypeError(msg) return _SecuredAttribute(func_or_obj, check_permissions_for_obj) class SecureController(object): """ - Used to apply security to a controller. + Used to apply security to a controller. Implementations of SecureController should extend the `check_permissions` method to return a True or False value (depending on whether or not the user has permissions @@ -116,15 +128,23 @@ class SecureController(object): """ class __metaclass__(type): def __init__(cls, name, bases, dict_): - cls._pecan = dict(secured=Protected, check_permissions=cls.check_permissions, unlocked=[]) + cls._pecan = dict( + secured=Protected, + check_permissions=cls.check_permissions, + unlocked=[] + ) for name, value in getmembers(cls): if ismethod(value): - if iscontroller(value) and value._pecan.get('secured') is None: + if iscontroller(value) and value._pecan.get( + 'secured' + ) is None: value._pecan['secured'] = Protected - value._pecan['check_permissions'] = cls.check_permissions + value._pecan['check_permissions'] = \ + cls.check_permissions elif hasattr(value, '__class__'): - if name.startswith('__') and name.endswith('__'): continue + if name.startswith('__') and name.endswith('__'): + continue if isinstance(value, _UnlockedAttribute): # mark it as unlocked and remove wrapper cls._pecan['unlocked'].append(value.obj) @@ -132,7 +152,7 @@ class SecureController(object): elif isinstance(value, _SecuredAttribute): # The user has specified a different check_permissions # than the class level version. As far as the class - # is concerned, this method is unlocked because + # is concerned, this method is unlocked because # it is using a check_permissions function embedded in # the _SecuredAttribute wrapper cls._pecan['unlocked'].append(value) @@ -141,6 +161,7 @@ class SecureController(object): def check_permissions(cls): return False + # methods to evaluate security during routing def handle_security(controller): """ Checks the security of a controller. """ @@ -153,6 +174,7 @@ def handle_security(controller): if not check_permissions(): raise exc.HTTPUnauthorized + def cross_boundary(prev_obj, obj): """ Check permissions as we move between object instances. """ if prev_obj is None: diff --git a/pecan/templates/__init__.py b/pecan/templates/__init__.py index 8056ca4..e9034fa 100644 --- a/pecan/templates/__init__.py +++ b/pecan/templates/__init__.py @@ -2,6 +2,7 @@ from paste.script.templates import Template DEFAULT_TEMPLATE = 'base' + class BaseTemplate(Template): summary = 'Template for creating a basic Pecan project' _template_dir = 'project' diff --git a/pecan/templating.py b/pecan/templating.py index eedfe7a..b9fa702 100644 --- a/pecan/templating.py +++ b/pecan/templating.py @@ -7,10 +7,11 @@ error_formatters = [] # JSON rendering engine # + class JsonRenderer(object): def __init__(self, path, extra_vars): pass - + def render(self, template_path, namespace): from jsonify import encode return encode(namespace) @@ -20,7 +21,7 @@ _builtin_renderers['json'] = JsonRenderer # # Genshi rendering engine -# +# try: from genshi.template import (TemplateLoader, @@ -30,21 +31,21 @@ try: def __init__(self, path, extra_vars): self.loader = TemplateLoader([path], auto_reload=True) self.extra_vars = extra_vars - + def render(self, template_path, namespace): tmpl = self.loader.load(template_path) stream = tmpl.generate(**self.extra_vars.make_ns(namespace)) return stream.render('html') _builtin_renderers['genshi'] = GenshiRenderer - + def format_genshi_error(exc_value): if isinstance(exc_value, (gTemplateError)): retval = '

Genshi error %s

' % cgi.escape(exc_value.message) retval += format_line_context(exc_value.filename, exc_value.lineno) return retval error_formatters.append(format_genshi_error) -except ImportError: #pragma no cover +except ImportError: # pragma no cover pass @@ -59,9 +60,12 @@ try: class MakoRenderer(object): def __init__(self, path, extra_vars): - self.loader = TemplateLookup(directories=[path], output_encoding='utf-8') + self.loader = TemplateLookup( + directories=[path], + output_encoding='utf-8' + ) self.extra_vars = extra_vars - + def render(self, template_path, namespace): tmpl = self.loader.get_template(template_path) return tmpl.render(**self.extra_vars.make_ns(namespace)) @@ -116,36 +120,43 @@ try: _builtin_renderers['jinja'] = JinjaRenderer def format_jinja_error(exc_value): + retval = '

Jinja2 error in \'%s\' on line %d

%s
' if isinstance(exc_value, (jTemplateSyntaxError)): - retval = '

Jinja2 template syntax error in \'%s\' on line %d

%s
' % (exc_value.name, exc_value.lineno, exc_value.message) + retval = retval % ( + exc_value.name, + exc_value.lineno, + exc_value.message + ) retval += format_line_context(exc_value.filename, exc_value.lineno) return retval error_formatters.append(format_jinja_error) except ImportError: # pragma no cover pass + # # format helper function # def format_line_context(filename, lineno, context=10): lines = open(filename).readlines() - lineno = lineno - 1 # files are indexed by 1 not 0 + lineno = lineno - 1 # files are indexed by 1 not 0 if lineno > 0: - start_lineno = max(lineno-context, 0) - end_lineno = lineno+context + start_lineno = max(lineno - context, 0) + end_lineno = lineno + context lines = [cgi.escape(l) for l in lines[start_lineno:end_lineno]] - i = lineno-start_lineno + i = lineno - start_lineno lines[i] = '%s' % lines[i] else: lines = [cgi.escape(l) for l in lines[:context]] + msg = '
%s
' + return msg % ''.join(lines) - return '
%s
' % ''.join(lines) # -# Extra Vars Rendering +# Extra Vars Rendering # class ExtraNamespace(object): def __init__(self, extras={}): @@ -163,6 +174,7 @@ class ExtraNamespace(object): else: return ns + # # Rendering Factory # diff --git a/pecan/testing.py b/pecan/testing.py index 6f0ed98..cb7d09c 100644 --- a/pecan/testing.py +++ b/pecan/testing.py @@ -1,5 +1,6 @@ from pecan import load_app from webtest import TestApp + def load_test_app(config): return TestApp(load_app(config)) diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py index cfaed68..fbb2207 100644 --- a/pecan/tests/test_base.py +++ b/pecan/tests/test_base.py @@ -1,36 +1,40 @@ -from formencode import Schema, validators -from paste.recursive import ForwardRequestException from paste.translogger import TransLogger from unittest import TestCase from webtest import TestApp -from pecan import Pecan, expose, request, response, redirect, abort, make_app, override_template, render -from pecan.templating import _builtin_renderers as builtin_renderers, error_formatters +from pecan import ( + Pecan, expose, request, response, redirect, abort, make_app, + override_template, render +) +from pecan.templating import ( + _builtin_renderers as builtin_renderers, error_formatters +) from pecan.decorators import accept_noncanonical import os -class SampleRootController(object): pass +class SampleRootController(object): + pass class TestBase(TestCase): - - def test_simple_app(self): + + def test_simple_app(self): class RootController(object): @expose() def index(self): return 'Hello, World!' - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 assert r.body == 'Hello, World!' - + r = app.get('/index') assert r.status_int == 200 assert r.body == 'Hello, World!' - + r = app.get('/index.html') assert r.status_int == 200 assert r.body == 'Hello, World!' @@ -38,76 +42,77 @@ class TestBase(TestCase): def test_controller_lookup_by_string_path(self): app = Pecan('pecan.tests.test_base.SampleRootController') assert app.root and isinstance(app.root, SampleRootController) - + def test_object_dispatch(self): class SubSubController(object): @expose() def index(self): return '/sub/sub/' - + @expose() def deeper(self): return '/sub/sub/deeper' - + class SubController(object): @expose() def index(self): return '/sub/' - + @expose() def deeper(self): return '/sub/deeper' - + sub = SubSubController() - + class RootController(object): @expose() def index(self): return '/' - + @expose() def deeper(self): return '/deeper' - + sub = SubController() - + app = TestApp(Pecan(RootController())) - for path in ('/', '/deeper', '/sub/', '/sub/deeper', '/sub/sub/', '/sub/sub/deeper'): + for path in ('/', '/deeper', '/sub/', '/sub/deeper', '/sub/sub/', + '/sub/sub/deeper'): r = app.get(path) assert r.status_int == 200 assert r.body == path - + def test_lookup(self): class LookupController(object): def __init__(self, someID): self.someID = someID - + @expose() def index(self): return '/%s' % self.someID - + @expose() def name(self): return '/%s/name' % self.someID - + class RootController(object): @expose() def index(self): return '/' - + @expose() def _lookup(self, someID, *remainder): return LookupController(someID), remainder - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 assert r.body == '/' - + r = app.get('/100/') assert r.status_int == 200 assert r.body == '/100' - + r = app.get('/100/name') assert r.status_int == 200 assert r.body == '/100/name' @@ -127,56 +132,69 @@ class TestBase(TestCase): @expose() def index(self, id): return 'index: %s' % id - + @expose() def multiple(self, one, two): return 'multiple: %s, %s' % (one, two) - + @expose() def optional(self, id=None): return 'optional: %s' % str(id) - + @expose() def multiple_optional(self, one=None, two=None, three=None): return 'multiple_optional: %s, %s, %s' % (one, two, three) - + @expose() def variable_args(self, *args): return 'variable_args: %s' % ', '.join(args) - + @expose() def variable_kwargs(self, **kwargs): - data = ['%s=%s' % (key, kwargs[key]) for key in sorted(kwargs.keys())] + data = [ + '%s=%s' % (key, kwargs[key]) + for key in sorted(kwargs.keys()) + ] return 'variable_kwargs: %s' % ', '.join(data) - + @expose() def variable_all(self, *args, **kwargs): - data = ['%s=%s' % (key, kwargs[key]) for key in sorted(kwargs.keys())] + data = [ + '%s=%s' % (key, kwargs[key]) + for key in sorted(kwargs.keys()) + ] return 'variable_all: %s' % ', '.join(list(args) + data) - + @expose() def eater(self, id, dummy=None, *args, **kwargs): - data = ['%s=%s' % (key, kwargs[key]) for key in sorted(kwargs.keys())] - return 'eater: %s, %s, %s' % (id, dummy, ', '.join(list(args) + data)) - + data = [ + '%s=%s' % (key, kwargs[key]) + for key in sorted(kwargs.keys()) + ] + return 'eater: %s, %s, %s' % ( + id, + dummy, + ', '.join(list(args) + data) + ) + @expose() def _route(self, args): if hasattr(self, args[0]): return getattr(self, args[0]), args[1:] else: return self.index, args - + app = TestApp(Pecan(RootController())) - + # required arg - + try: r = app.get('/') assert r.status_int != 200 except Exception, ex: assert type(ex) == TypeError assert ex.args[0] == 'index() takes exactly 2 arguments (1 given)' - + r = app.get('/1') assert r.status_int == 200 assert r.body == 'index: 1' @@ -184,10 +202,10 @@ class TestBase(TestCase): r = app.get('/This%20is%20a%20test%21') assert r.status_int == 200 assert r.body == 'index: This is a test!' - + r = app.get('/1/dummy', status=404) assert r.status_int == 404 - + r = app.get('/?id=2') assert r.status_int == 200 assert r.body == 'index: 2' @@ -195,7 +213,7 @@ class TestBase(TestCase): r = app.get('/?id=This%20is%20a%20test%21') assert r.status_int == 200 assert r.body == 'index: This is a test!' - + r = app.get('/3?id=three') assert r.status_int == 200 assert r.body == 'index: 3' @@ -203,25 +221,25 @@ class TestBase(TestCase): r = app.get('/This%20is%20a%20test%21?id=three') assert r.status_int == 200 assert r.body == 'index: This is a test!' - + r = app.post('/', {'id': '4'}) assert r.status_int == 200 assert r.body == 'index: 4' - + r = app.post('/4', {'id': 'four'}) assert r.status_int == 200 assert r.body == 'index: 4' - + r = app.get('/?id=5&dummy=dummy') assert r.status_int == 200 assert r.body == 'index: 5' - + r = app.post('/', {'id': '6', 'dummy': 'dummy'}) assert r.status_int == 200 assert r.body == 'index: 6' - + # multiple args - + r = app.get('/multiple/one/two') assert r.status_int == 200 assert r.body == 'multiple: one, two' @@ -229,7 +247,7 @@ class TestBase(TestCase): r = app.get('/multiple/One%20/Two%21') assert r.status_int == 200 assert r.body == 'multiple: One , Two!' - + r = app.get('/multiple?one=three&two=four') assert r.status_int == 200 assert r.body == 'multiple: three, four' @@ -237,7 +255,7 @@ class TestBase(TestCase): r = app.get('/multiple?one=Three%20&two=Four%20%21') assert r.status_int == 200 assert r.body == 'multiple: Three , Four !' - + r = app.post('/multiple', {'one': 'five', 'two': 'six'}) assert r.status_int == 200 assert r.body == 'multiple: five, six' @@ -245,13 +263,13 @@ class TestBase(TestCase): r = app.post('/multiple', {'one': 'Five%20', 'two': 'Six%20%21'}) assert r.status_int == 200 assert r.body == 'multiple: Five%20, Six%20%21' - + # optional arg - + r = app.get('/optional') assert r.status_int == 200 assert r.body == 'optional: None' - + r = app.get('/optional/1') assert r.status_int == 200 assert r.body == 'optional: 1' @@ -259,10 +277,10 @@ class TestBase(TestCase): r = app.get('/optional/Some%20Number') assert r.status_int == 200 assert r.body == 'optional: Some Number' - + r = app.get('/optional/2/dummy', status=404) assert r.status_int == 404 - + r = app.get('/optional?id=2') assert r.status_int == 200 assert r.body == 'optional: 2' @@ -270,7 +288,7 @@ class TestBase(TestCase): r = app.get('/optional?id=Some%20Number') assert r.status_int == 200 assert r.body == 'optional: Some Number' - + r = app.get('/optional/3?id=three') assert r.status_int == 200 assert r.body == 'optional: 3' @@ -278,7 +296,7 @@ class TestBase(TestCase): r = app.get('/optional/Some%20Number?id=three') assert r.status_int == 200 assert r.body == 'optional: Some Number' - + r = app.post('/optional', {'id': '4'}) assert r.status_int == 200 assert r.body == 'optional: 4' @@ -286,7 +304,7 @@ class TestBase(TestCase): r = app.post('/optional', {'id': 'Some%20Number'}) assert r.status_int == 200 assert r.body == 'optional: Some%20Number' - + r = app.post('/optional/5', {'id': 'five'}) assert r.status_int == 200 assert r.body == 'optional: 5' @@ -294,7 +312,7 @@ class TestBase(TestCase): r = app.post('/optional/Some%20Number', {'id': 'five'}) assert r.status_int == 200 assert r.body == 'optional: Some Number' - + r = app.get('/optional?id=6&dummy=dummy') assert r.status_int == 200 assert r.body == 'optional: 6' @@ -302,7 +320,7 @@ class TestBase(TestCase): r = app.get('/optional?id=Some%20Number&dummy=dummy') assert r.status_int == 200 assert r.body == 'optional: Some Number' - + r = app.post('/optional', {'id': '7', 'dummy': 'dummy'}) assert r.status_int == 200 assert r.body == 'optional: 7' @@ -310,13 +328,13 @@ class TestBase(TestCase): r = app.post('/optional', {'id': 'Some%20Number', 'dummy': 'dummy'}) assert r.status_int == 200 assert r.body == 'optional: Some%20Number' - + # multiple optional args - + r = app.get('/multiple_optional') assert r.status_int == 200 assert r.body == 'multiple_optional: None, None, None' - + r = app.get('/multiple_optional/1') assert r.status_int == 200 assert r.body == 'multiple_optional: 1, None, None' @@ -324,7 +342,7 @@ class TestBase(TestCase): r = app.get('/multiple_optional/One%21') assert r.status_int == 200 assert r.body == 'multiple_optional: One!, None, None' - + r = app.get('/multiple_optional/1/2/3') assert r.status_int == 200 assert r.body == 'multiple_optional: 1, 2, 3' @@ -332,10 +350,10 @@ class TestBase(TestCase): r = app.get('/multiple_optional/One%21/Two%21/Three%21') assert r.status_int == 200 assert r.body == 'multiple_optional: One!, Two!, Three!' - + r = app.get('/multiple_optional/1/2/3/dummy', status=404) assert r.status_int == 404 - + r = app.get('/multiple_optional?one=1') assert r.status_int == 200 assert r.body == 'multiple_optional: 1, None, None' @@ -343,7 +361,7 @@ class TestBase(TestCase): r = app.get('/multiple_optional?one=One%21') assert r.status_int == 200 assert r.body == 'multiple_optional: One!, None, None' - + r = app.get('/multiple_optional/1?one=one') assert r.status_int == 200 assert r.body == 'multiple_optional: 1, None, None' @@ -351,7 +369,7 @@ class TestBase(TestCase): r = app.get('/multiple_optional/One%21?one=one') assert r.status_int == 200 assert r.body == 'multiple_optional: One!, None, None' - + r = app.post('/multiple_optional', {'one': '1'}) assert r.status_int == 200 assert r.body == 'multiple_optional: 1, None, None' @@ -359,7 +377,7 @@ class TestBase(TestCase): r = app.post('/multiple_optional', {'one': 'One%21'}) assert r.status_int == 200 assert r.body == 'multiple_optional: One%21, None, None' - + r = app.post('/multiple_optional/1', {'one': 'one'}) assert r.status_int == 200 assert r.body == 'multiple_optional: 1, None, None' @@ -367,23 +385,36 @@ class TestBase(TestCase): r = app.post('/multiple_optional/One%21', {'one': 'one'}) assert r.status_int == 200 assert r.body == 'multiple_optional: One!, None, None' - + r = app.get('/multiple_optional?one=1&two=2&three=3&four=4') assert r.status_int == 200 assert r.body == 'multiple_optional: 1, 2, 3' - r = app.get('/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4') + r = app.get( + '/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4' + ) assert r.status_int == 200 assert r.body == 'multiple_optional: One!, Two!, Three!' - - r = app.post('/multiple_optional', {'one': '1', 'two': '2', 'three': '3', 'four': '4'}) + + r = app.post( + '/multiple_optional', + {'one': '1', 'two': '2', 'three': '3', 'four': '4'} + ) assert r.status_int == 200 assert r.body == 'multiple_optional: 1, 2, 3' - r = app.post('/multiple_optional', {'one': 'One%21', 'two': 'Two%21', 'three': 'Three%21', 'four': '4'}) + r = app.post( + '/multiple_optional', + { + 'one': 'One%21', + 'two': 'Two%21', + 'three': 'Three%21', + 'four': '4' + } + ) assert r.status_int == 200 assert r.body == 'multiple_optional: One%21, Two%21, Three%21' - + r = app.get('/multiple_optional?three=3') assert r.status_int == 200 assert r.body == 'multiple_optional: None, None, 3' @@ -391,17 +422,17 @@ class TestBase(TestCase): r = app.get('/multiple_optional?three=Three%21') assert r.status_int == 200 assert r.body == 'multiple_optional: None, None, Three!' - + r = app.get('/multiple_optional', {'two': '2'}) assert r.status_int == 200 assert r.body == 'multiple_optional: None, 2, None' - + # variable args - + r = app.get('/variable_args') assert r.status_int == 200 assert r.body == 'variable_args: ' - + r = app.get('/variable_args/1/dummy') assert r.status_int == 200 assert r.body == 'variable_args: 1, dummy' @@ -409,24 +440,24 @@ class TestBase(TestCase): r = app.get('/variable_args/Testing%20One%20Two/Three%21') assert r.status_int == 200 assert r.body == 'variable_args: Testing One Two, Three!' - + r = app.get('/variable_args?id=2&dummy=dummy') assert r.status_int == 200 assert r.body == 'variable_args: ' - + r = app.post('/variable_args', {'id': '3', 'dummy': 'dummy'}) assert r.status_int == 200 assert r.body == 'variable_args: ' - + # variable keyword args - + r = app.get('/variable_kwargs') assert r.status_int == 200 assert r.body == 'variable_kwargs: ' - + r = app.get('/variable_kwargs/1/dummy', status=404) assert r.status_int == 404 - + r = app.get('/variable_kwargs?id=2&dummy=dummy') assert r.status_int == 200 assert r.body == 'variable_kwargs: dummy=dummy, id=2' @@ -434,114 +465,124 @@ class TestBase(TestCase): r = app.get('/variable_kwargs?id=Two%21&dummy=This%20is%20a%20test') assert r.status_int == 200 assert r.body == 'variable_kwargs: dummy=This is a test, id=Two!' - + r = app.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'}) assert r.status_int == 200 assert r.body == 'variable_kwargs: dummy=dummy, id=3' - r = app.post('/variable_kwargs', {'id': 'Three%21', 'dummy': 'This%20is%20a%20test'}) + r = app.post( + '/variable_kwargs', + {'id': 'Three%21', 'dummy': 'This%20is%20a%20test'} + ) assert r.status_int == 200 - assert r.body == 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21' - + result = 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21' + assert r.body == result + # variable args & keyword args - + r = app.get('/variable_all') assert r.status_int == 200 assert r.body == 'variable_all: ' - + r = app.get('/variable_all/1') assert r.status_int == 200 assert r.body == 'variable_all: 1' - + r = app.get('/variable_all/2/dummy') assert r.status_int == 200 assert r.body == 'variable_all: 2, dummy' - + r = app.get('/variable_all/3?month=1&day=12') assert r.status_int == 200 assert r.body == 'variable_all: 3, day=12, month=1' - + r = app.get('/variable_all/4?id=four&month=1&day=12') assert r.status_int == 200 assert r.body == 'variable_all: 4, day=12, id=four, month=1' - + r = app.post('/variable_all/5/dummy') assert r.status_int == 200 assert r.body == 'variable_all: 5, dummy' - + r = app.post('/variable_all/6', {'month': '1', 'day': '12'}) assert r.status_int == 200 assert r.body == 'variable_all: 6, day=12, month=1' - - r = app.post('/variable_all/7', {'id': 'seven', 'month': '1', 'day': '12'}) + + r = app.post( + '/variable_all/7', + {'id': 'seven', 'month': '1', 'day': '12'} + ) assert r.status_int == 200 assert r.body == 'variable_all: 7, day=12, id=seven, month=1' - + # the "everything" controller - + try: r = app.get('/eater') assert r.status_int != 200 except Exception, ex: assert type(ex) == TypeError assert ex.args[0] == 'eater() takes at least 2 arguments (1 given)' - + r = app.get('/eater/1') assert r.status_int == 200 assert r.body == 'eater: 1, None, ' - + r = app.get('/eater/2/dummy') assert r.status_int == 200 assert r.body == 'eater: 2, dummy, ' - + r = app.get('/eater/3/dummy/foo/bar') assert r.status_int == 200 assert r.body == 'eater: 3, dummy, foo, bar' - + r = app.get('/eater/4?month=1&day=12') assert r.status_int == 200 assert r.body == 'eater: 4, None, day=12, month=1' - + r = app.get('/eater/5?id=five&month=1&day=12&dummy=dummy') assert r.status_int == 200 assert r.body == 'eater: 5, dummy, day=12, month=1' - + r = app.post('/eater/6') assert r.status_int == 200 assert r.body == 'eater: 6, None, ' - + r = app.post('/eater/7/dummy') assert r.status_int == 200 assert r.body == 'eater: 7, dummy, ' - + r = app.post('/eater/8/dummy/foo/bar') assert r.status_int == 200 assert r.body == 'eater: 8, dummy, foo, bar' - + r = app.post('/eater/9', {'month': '1', 'day': '12'}) assert r.status_int == 200 assert r.body == 'eater: 9, None, day=12, month=1' - - r = app.post('/eater/10', {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}) + + r = app.post( + '/eater/10', + {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'} + ) assert r.status_int == 200 assert r.body == 'eater: 10, dummy, day=12, month=1' - + def test_abort(self): class RootController(object): @expose() def index(self): abort(404) - + app = TestApp(Pecan(RootController())) r = app.get('/', status=404) assert r.status_int == 404 - + def test_abort_with_detail(self): class RootController(object): @expose() def index(self): abort(status_code=401, detail='Not Authorized') - + app = TestApp(Pecan(RootController())) r = app.get('/', status=401) assert r.status_int == 401 @@ -551,15 +592,15 @@ class TestBase(TestCase): @expose() def index(self): redirect('/testing') - + @expose() def internal(self): redirect('/testing', internal=True) - + @expose() def bad_internal(self): redirect('/testing', internal=True, code=301) - + @expose() def permanent(self): redirect('/testing', code=301) @@ -568,58 +609,61 @@ class TestBase(TestCase): def testing(self): return 'it worked!' - app = TestApp(make_app(RootController(), debug=True)) r = app.get('/') assert r.status_int == 302 r = r.follow() assert r.status_int == 200 assert r.body == 'it worked!' - + r = app.get('/internal') assert r.status_int == 200 assert r.body == 'it worked!' - + self.assertRaises(ValueError, app.get, '/bad_internal') - + r = app.get('/permanent') assert r.status_int == 301 r = r.follow() assert r.status_int == 200 assert r.body == 'it worked!' - def test_x_forward_proto(self): class ChildController(object): @expose() def index(self): redirect('/testing') + class RootController(object): @expose() def index(self): redirect('/testing') + @expose() def testing(self): return 'it worked!' child = ChildController() - + app = TestApp(make_app(RootController(), debug=True)) - response = app.get('/child', extra_environ=dict(HTTP_X_FORWARDED_PROTO='https')) + res = app.get( + '/child', extra_environ=dict(HTTP_X_FORWARDED_PROTO='https') + ) ##non-canonical url will redirect, so we won't get a 301 - assert response.status_int == 302 + assert res.status_int == 302 ##should add trailing / and changes location to https - assert response.location == 'https://localhost/child/' - assert response.request.environ['HTTP_X_FORWARDED_PROTO'] == 'https' - + assert res.location == 'https://localhost/child/' + assert res.request.environ['HTTP_X_FORWARDED_PROTO'] == 'https' + def test_streaming_response(self): import StringIO + class RootController(object): @expose(content_type='text/plain') def test(self, foo): if foo == 'stream': # mimic large file contents = StringIO.StringIO('stream') - response.content_type='application/octet-stream' + response.content_type = 'application/octet-stream' contents.seek(0, os.SEEK_END) response.content_length = contents.tell() contents.seek(0, os.SEEK_SET) @@ -636,24 +680,24 @@ class TestBase(TestCase): r = app.get('/test/plain') assert r.content_type == 'text/plain' assert r.body == 'plain text' - + def test_request_state_cleanup(self): """ After a request, the state local() should be totally clean except for state.app (so that objects don't leak between requests) """ from pecan.core import state - + class RootController(object): @expose() def index(self): return '/' - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 assert r.body == '/' - + assert state.__dict__.keys() == ['app'] def test_extension(self): @@ -663,7 +707,6 @@ class TestBase(TestCase): class RootController(object): @expose(content_type=None) def _default(self, *args): - from pecan.core import request return request.pecan['extension'] app = TestApp(Pecan(RootController())) @@ -688,24 +731,25 @@ class TestBase(TestCase): pass wrapped_apps = [] + def wrap(app): wrapped_apps.append(app) return app - app = make_app(RootController(), wrap_app=wrap, debug=True) + make_app(RootController(), wrap_app=wrap, debug=True) assert len(wrapped_apps) == 1 - + def test_bad_content_type(self): class RootController(object): @expose() def index(self): return '/' - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 assert r.body == '/' - + r = app.get('/index.html', expect_errors=True) assert r.status_int == 200 assert r.body == '/' @@ -718,15 +762,18 @@ class TestBase(TestCase): @expose() def index(self, arg): return arg + class AcceptController(object): @accept_noncanonical @expose() def index(self): return 'accept' + class SubController(object): @expose() def index(self, **kw): return 'subindex' + class RootController(object): @expose() def index(self): @@ -745,7 +792,7 @@ class TestBase(TestCase): r = app.get('/index') assert r.status_int == 200 assert 'index' in r.body - + # for broken clients r = app.get('', status=302) assert r.status_int == 302 @@ -766,7 +813,7 @@ class TestBase(TestCase): try: r = app.post('/sub', dict(foo=1)) - raise Exception, "Post should fail" + raise Exception("Post should fail") except Exception, e: assert isinstance(e, RuntimeError) @@ -798,7 +845,7 @@ class TestBase(TestCase): r = app.get('/sub/') assert r.status_int == 200 assert 'subindex' in r.body - + def test_proxy(self): class RootController(object): @expose() @@ -808,7 +855,7 @@ class TestBase(TestCase): del request.testing assert hasattr(request, 'testing') == False return '/' - + app = TestApp(make_app(RootController(), debug=True)) r = app.get('/') assert r.status_int == 200 @@ -816,127 +863,136 @@ class TestBase(TestCase): class TestLogging(TestCase): """ - Mocks logging calls so we can make sure they get called. We could use - Fudge for this, but it would add an additional dependency to Pecan for + Mocks logging calls so we can make sure they get called. We could use + Fudge for this, but it would add an additional dependency to Pecan for a single set of tests. """ - + def setUp(self): self._write_log = TransLogger.write_log - + def tearDown(self): TransLogger.write_log = self._write_log - + def test_default(self): - + class RootController(object): @expose() def index(self): return '/' - + # monkeypatch the logger writes = [] + def _write_log(self, *args, **kwargs): writes.append(1) TransLogger.write_log = _write_log - + # check the request app = TestApp(make_app(RootController(), debug=True)) r = app.get('/') assert r.status_int == 200 assert writes == [] - - def test_default(self): - + + def test_default_route(self): + class RootController(object): @expose() def index(self): return '/' - + # monkeypatch the logger writes = [] + def _write_log(self, *args, **kwargs): writes.append(1) TransLogger.write_log = _write_log - + # check the request app = TestApp(make_app(RootController(), debug=True)) r = app.get('/') assert r.status_int == 200 assert len(writes) == 0 - + def test_no_logging(self): - + class RootController(object): @expose() def index(self): return '/' - + # monkeypatch the logger writes = [] + def _write_log(self, *args, **kwargs): writes.append(1) + TransLogger.write_log = _write_log - + # check the request app = TestApp(make_app(RootController(), debug=True, logging=False)) r = app.get('/') assert r.status_int == 200 assert len(writes) == 0 - + def test_basic_logging(self): - + class RootController(object): @expose() def index(self): return '/' - + # monkeypatch the logger writes = [] + def _write_log(self, *args, **kwargs): writes.append(1) + TransLogger.write_log = _write_log - + # check the request app = TestApp(make_app(RootController(), debug=True, logging=True)) r = app.get('/') assert r.status_int == 200 assert len(writes) == 1 - + def test_empty_config(self): - + class RootController(object): @expose() def index(self): return '/' - + # monkeypatch the logger writes = [] + def _write_log(self, *args, **kwargs): writes.append(1) + TransLogger.write_log = _write_log - + # check the request app = TestApp(make_app(RootController(), debug=True, logging={})) r = app.get('/') assert r.status_int == 200 assert len(writes) == 1 - + def test_custom_config(self): - + class RootController(object): @expose() def index(self): return '/' - + # create a custom logger writes = [] + class FakeLogger(object): def log(self, *args, **kwargs): writes.append(1) - + # check the request - app = TestApp(make_app(RootController(), debug=True, + app = TestApp(make_app(RootController(), debug=True, logging={'logger': FakeLogger()})) r = app.get('/') assert r.status_int == 200 @@ -944,7 +1000,7 @@ class TestLogging(TestCase): class TestEngines(TestCase): - + template_path = os.path.join(os.path.dirname(__file__), 'templates') def test_genshi(self): @@ -959,16 +1015,18 @@ class TestEngines(TestCase): @expose('genshi:genshi_bad.html') def badtemplate(self): return dict() - - app = TestApp(Pecan(RootController(), template_path=self.template_path)) + + app = TestApp( + Pecan(RootController(), template_path=self.template_path) + ) r = app.get('/') assert r.status_int == 200 assert "

Hello, Jonathan!

" in r.body - + r = app.get('/index.html?name=World') assert r.status_int == 200 assert "

Hello, World!

" in r.body - + error_msg = None try: r = app.get('/badtemplate.html') @@ -978,7 +1036,7 @@ class TestEngines(TestCase): if error_msg: break assert error_msg is not None - + def test_kajiki(self): if 'kajiki' not in builtin_renderers: return @@ -987,12 +1045,14 @@ class TestEngines(TestCase): @expose('kajiki:kajiki.html') def index(self, name='Jonathan'): return dict(name=name) - - app = TestApp(Pecan(RootController(), template_path=self.template_path)) + + app = TestApp( + Pecan(RootController(), template_path=self.template_path) + ) r = app.get('/') assert r.status_int == 200 assert "

Hello, Jonathan!

" in r.body - + r = app.get('/index.html?name=World') assert r.status_int == 200 assert "

Hello, World!

" in r.body @@ -1000,6 +1060,7 @@ class TestEngines(TestCase): def test_jinja(self): if 'jinja' not in builtin_renderers: return + class RootController(object): @expose('jinja:jinja.html') def index(self, name='Jonathan'): @@ -1009,7 +1070,9 @@ class TestEngines(TestCase): def badtemplate(self): return dict() - app = TestApp(Pecan(RootController(), template_path=self.template_path)) + app = TestApp( + Pecan(RootController(), template_path=self.template_path) + ) r = app.get('/') assert r.status_int == 200 assert "

Hello, Jonathan!

" in r.body @@ -1023,10 +1086,11 @@ class TestEngines(TestCase): if error_msg: break assert error_msg is not None - + def test_mako(self): if 'mako' not in builtin_renderers: return + class RootController(object): @expose('mako:mako.html') def index(self, name='Jonathan'): @@ -1035,16 +1099,18 @@ class TestEngines(TestCase): @expose('mako:mako_bad.html') def badtemplate(self): return dict() - - app = TestApp(Pecan(RootController(), template_path=self.template_path)) + + app = TestApp( + Pecan(RootController(), template_path=self.template_path) + ) r = app.get('/') assert r.status_int == 200 assert "

Hello, Jonathan!

" in r.body - + r = app.get('/index.html?name=World') assert r.status_int == 200 assert "

Hello, World!

" in r.body - + error_msg = None try: r = app.get('/badtemplate.html') @@ -1054,20 +1120,23 @@ class TestEngines(TestCase): if error_msg: break assert error_msg is not None - + def test_json(self): try: from simplejson import loads except: - from json import loads - - expected_result = dict(name='Jonathan', age=30, nested=dict(works=True)) - + from json import loads # noqa + + expected_result = dict( + name='Jonathan', + age=30, nested=dict(works=True) + ) + class RootController(object): @expose('json') def index(self): return expected_result - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 @@ -1084,21 +1153,24 @@ class TestEngines(TestCase): app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 - assert 'Override' in r.body + assert 'Override' in r.body assert r.content_type == 'text/plain' def test_render(self): - + #if 'mako' not in builtin_renderers: # return - + class RootController(object): @expose() def index(self, name='Jonathan'): return render('mako.html', dict(name=name)) return dict(name=name) - - app = TestApp(Pecan(RootController(), template_path=self.template_path)) + + app = TestApp( + Pecan(RootController(), + template_path=self.template_path) + ) r = app.get('/') assert r.status_int == 200 assert "

Hello, Jonathan!

" in r.body diff --git a/pecan/tests/test_conf.py b/pecan/tests/test_conf.py index 49d076c..58182b5 100644 --- a/pecan/tests/test_conf.py +++ b/pecan/tests/test_conf.py @@ -6,11 +6,12 @@ from pecan import conf as _runtime_conf __here__ = os.path.dirname(__file__) + class TestConf(TestCase): def test_update_config_fail_identifier(self): """Fail when naming does not pass correctness""" - bad_dict = {'bad name':'value'} + bad_dict = {'bad name': 'value'} self.assertRaises(ValueError, configuration.Config, bad_dict) def test_update_set_config(self): @@ -46,7 +47,7 @@ class TestConf(TestCase): self.assertEqual(conf.server.host, '0.0.0.0') self.assertEqual(conf.server.port, '8080') - + def test_update_force_dict(self): """Update an empty configuration with the default values""" conf = configuration.initconf() @@ -66,18 +67,21 @@ class TestConf(TestCase): self.assertTrue(isinstance(conf.beaker, dict)) self.assertEqual(conf.beaker['session.key'], 'key') self.assertEqual(conf.beaker['session.type'], 'cookie') - self.assertEqual(conf.beaker['session.validate_key'], '1a971a7df182df3e1dec0af7c6913ec7') + self.assertEqual( + conf.beaker['session.validate_key'], + '1a971a7df182df3e1dec0af7c6913ec7' + ) self.assertEqual(conf.beaker.get('__force_dict__'), None) def test_update_config_with_dict(self): conf = configuration.initconf() - d = {'attr':True} + d = {'attr': True} conf['attr'] = d self.assertTrue(conf.attr.attr) def test_config_repr(self): - conf = configuration.Config({'a':1}) - self.assertEqual(repr(conf),"Config({'a': 1})") + conf = configuration.Config({'a': 1}) + self.assertEqual(repr(conf), "Config({'a': 1})") def test_config_from_dict(self): conf = configuration.conf_from_dict({}) @@ -85,7 +89,9 @@ class TestConf(TestCase): self.assertTrue(os.path.samefile(conf['path'], os.getcwd())) def test_config_from_file(self): - path = os.path.join(os.path.dirname(__file__), 'test_config', 'config.py') + path = os.path.join( + os.path.dirname(__file__), 'test_config', 'config.py' + ) conf = configuration.conf_from_file(path) self.assertTrue(conf.app.debug) @@ -99,7 +105,7 @@ class TestConf(TestCase): def test_config_missing_file(self): path = ('doesnotexist.py',) - conf = configuration.Config({}) + configuration.Config({}) self.assertRaises(IOError, configuration.conf_from_file, os.path.join( __here__, 'test_config', @@ -108,7 +114,7 @@ class TestConf(TestCase): def test_config_missing_file_on_path(self): path = ('bad', 'bad', 'doesnotexist.py',) - conf = configuration.Config({}) + configuration.Config({}) self.assertRaises(IOError, configuration.conf_from_file, os.path.join( __here__, @@ -118,34 +124,41 @@ class TestConf(TestCase): def test_config_with_syntax_error(self): path = ('bad', 'syntaxerror.py') - conf = configuration.Config({}) + configuration.Config({}) - self.assertRaises(SyntaxError, configuration.conf_from_file, os.path.join( - __here__, - 'test_config', - *path - )) + self.assertRaises( + SyntaxError, + configuration.conf_from_file, + os.path.join(__here__, 'test_config', *path) + ) def test_config_with_bad_import(self): path = ('bad', 'importerror.py') - conf = configuration.Config({}) + configuration.Config({}) - self.assertRaises(ImportError, configuration.conf_from_file, os.path.join( - __here__, - 'test_config', - *path - )) + self.assertRaises( + ImportError, + configuration.conf_from_file, + os.path.join( + __here__, + 'test_config', + *path + ) + ) def test_config_set_from_file(self): - path = os.path.join(os.path.dirname(__file__), 'test_config', 'empty.py') + path = os.path.join( + os.path.dirname(__file__), 'test_config', 'empty.py' + ) configuration.set_config(path) - assert list(_runtime_conf.server) == list(configuration.initconf().server) + res = list(configuration.initconf().server) + assert list(_runtime_conf.server) == res def test_config_dir(self): if sys.version_info >= (2, 6): conf = configuration.Config({}) self.assertEqual([], dir(conf)) - conf = configuration.Config({'a':1}) + conf = configuration.Config({'a': 1}) self.assertEqual(['a'], dir(conf)) def test_config_bad_key(self): @@ -173,36 +186,35 @@ class TestConf(TestCase): as_dict = conf.as_dict() assert isinstance(as_dict, dict) - assert as_dict['server']['host'] == '0.0.0.0' - assert as_dict['server']['port'] == '8080' - assert as_dict['app']['debug'] == False - assert as_dict['app']['errors'] == {} + assert as_dict['server']['host'] == '0.0.0.0' + assert as_dict['server']['port'] == '8080' + assert as_dict['app']['debug'] == False + assert as_dict['app']['errors'] == {} assert as_dict['app']['force_canonical'] == True - assert as_dict['app']['modules'] == [] - assert as_dict['app']['root'] == None - assert as_dict['app']['static_root'] == 'public' - assert as_dict['app']['template_path'] == '' + assert as_dict['app']['modules'] == [] + assert as_dict['app']['root'] == None + assert as_dict['app']['static_root'] == 'public' + assert as_dict['app']['template_path'] == '' def test_config_as_dict_nested(self): """have more than one level nesting and convert to dict""" conf = configuration.initconf() - nested = {'one':{'two':2}} + nested = {'one': {'two': 2}} conf['nested'] = nested as_dict = conf.as_dict() assert isinstance(as_dict, dict) - assert as_dict['server']['host'] == '0.0.0.0' - assert as_dict['server']['port'] == '8080' - assert as_dict['app']['debug'] == False - assert as_dict['app']['errors'] == {} + assert as_dict['server']['host'] == '0.0.0.0' + assert as_dict['server']['port'] == '8080' + assert as_dict['app']['debug'] == False + assert as_dict['app']['errors'] == {} assert as_dict['app']['force_canonical'] == True - assert as_dict['app']['modules'] == [] - assert as_dict['app']['root'] == None - assert as_dict['app']['static_root'] == 'public' - assert as_dict['app']['template_path'] == '' - assert as_dict['nested']['one']['two'] == 2 - + assert as_dict['app']['modules'] == [] + assert as_dict['app']['root'] == None + assert as_dict['app']['static_root'] == 'public' + assert as_dict['app']['template_path'] == '' + assert as_dict['nested']['one']['two'] == 2 def test_config_as_dict_prefixed(self): """Add a prefix for keys""" @@ -213,13 +225,12 @@ class TestConf(TestCase): as_dict = conf.as_dict('prefix_') assert isinstance(as_dict, dict) - assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0' - assert as_dict['prefix_server']['prefix_port'] == '8080' - assert as_dict['prefix_app']['prefix_debug'] == False - assert as_dict['prefix_app']['prefix_errors'] == {} + assert as_dict['prefix_server']['prefix_host'] == '0.0.0.0' + assert as_dict['prefix_server']['prefix_port'] == '8080' + assert as_dict['prefix_app']['prefix_debug'] == False + assert as_dict['prefix_app']['prefix_errors'] == {} assert as_dict['prefix_app']['prefix_force_canonical'] == True - assert as_dict['prefix_app']['prefix_modules'] == [] - assert as_dict['prefix_app']['prefix_root'] == None - assert as_dict['prefix_app']['prefix_static_root'] == 'public' - assert as_dict['prefix_app']['prefix_template_path'] == '' - + assert as_dict['prefix_app']['prefix_modules'] == [] + assert as_dict['prefix_app']['prefix_root'] == None + assert as_dict['prefix_app']['prefix_static_root'] == 'public' + assert as_dict['prefix_app']['prefix_template_path'] == '' diff --git a/pecan/tests/test_config/bad/syntaxerror.py b/pecan/tests/test_config/bad/syntaxerror.py index 8352b37..18dc1d0 100644 --- a/pecan/tests/test_config/bad/syntaxerror.py +++ b/pecan/tests/test_config/bad/syntaxerror.py @@ -1,3 +1,3 @@ if false var = 3 - + diff --git a/pecan/tests/test_config/config.py b/pecan/tests/test_config/config.py index cb83442..bba299b 100644 --- a/pecan/tests/test_config/config.py +++ b/pecan/tests/test_config/config.py @@ -2,21 +2,21 @@ # Server Specific Configurations server = { - 'port' : '8081', - 'host' : '1.1.1.1', + 'port': '8081', + 'host': '1.1.1.1', 'hostport': '{pecan.conf.server.host}:{pecan.conf.server.port}' } # Pecan Application Configurations app = { - 'static_root' : 'public', - 'template_path' : 'myproject/templates', - 'debug' : True + 'static_root': 'public', + 'template_path': 'myproject/templates', + 'debug': True } # Custom Configurations must be in Python dictionary format:: # # foo = {'bar':'baz'} -# +# # All configurations are accessible at:: # pecan.conf diff --git a/pecan/tests/test_config/forcedict.py b/pecan/tests/test_config/forcedict.py index 604cc1d..4e2c83a 100644 --- a/pecan/tests/test_config/forcedict.py +++ b/pecan/tests/test_config/forcedict.py @@ -1,14 +1,14 @@ # Pecan Application Configurations beaker = { - 'session.key' : 'key', - 'session.type' : 'cookie', - 'session.validate_key' : '1a971a7df182df3e1dec0af7c6913ec7', - '__force_dict__' : True + 'session.key': 'key', + 'session.type': 'cookie', + 'session.validate_key': '1a971a7df182df3e1dec0af7c6913ec7', + '__force_dict__': True } # Custom Configurations must be in Python dictionary format:: # # foo = {'bar':'baz'} -# +# # All configurations are accessible at:: # pecan.conf diff --git a/pecan/tests/test_generic.py b/pecan/tests/test_generic.py index 72c5af8..a77338e 100644 --- a/pecan/tests/test_generic.py +++ b/pecan/tests/test_generic.py @@ -1,36 +1,36 @@ -from pecan import Pecan, expose, request, response, redirect +from pecan import Pecan, expose from unittest import TestCase from webtest import TestApp try: from simplejson import dumps except: - from json import dumps + from json import dumps # noqa class TestGeneric(TestCase): - - def test_simple_generic(self): + + def test_simple_generic(self): class RootController(object): @expose(generic=True) def index(self): pass - + @index.when(method='POST', template='json') def do_post(self): return dict(result='POST') - + @index.when(method='GET') def do_get(self): return 'GET' - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 assert r.body == 'GET' - + r = app.post('/') assert r.status_int == 200 assert r.body == dumps(dict(result='POST')) - + r = app.get('/do_get', status=404) assert r.status_int == 404 diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py index bf479ff..94b52c5 100644 --- a/pecan/tests/test_hooks.py +++ b/pecan/tests/test_hooks.py @@ -1,30 +1,31 @@ -from cStringIO import StringIO -from pecan import make_app, expose, request, redirect, abort -from pecan.core import state -from pecan.hooks import PecanHook, TransactionHook, HookController, RequestViewerHook +from cStringIO import StringIO +from pecan import make_app, expose, request, redirect, abort +from pecan.core import state +from pecan.hooks import ( + PecanHook, TransactionHook, HookController, RequestViewerHook +) 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 +from pecan.decorators import transactional, after_commit, after_rollback +from unittest import TestCase +from formencode import Schema, validators +from webtest import TestApp class TestHooks(TestCase): - + def test_basic_single_hook(self): run_hook = [] - + class RootController(object): @expose() def index(self): run_hook.append('inside') return 'Hello, World!' - + class SimpleHook(PecanHook): def on_route(self, state): run_hook.append('on_route') - + def before(self, state): run_hook.append('before') @@ -33,50 +34,50 @@ class TestHooks(TestCase): def on_error(self, state, e): run_hook.append('error') - + app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 4 assert run_hook[0] == 'on_route' assert run_hook[1] == 'before' assert run_hook[2] == 'inside' assert run_hook[3] == 'after' - + def test_basic_multi_hook(self): run_hook = [] - + class RootController(object): @expose() def index(self): run_hook.append('inside') return 'Hello, World!' - + class SimpleHook(PecanHook): def __init__(self, id): self.id = str(id) - + def on_route(self, state): - run_hook.append('on_route'+self.id) - + run_hook.append('on_route' + self.id) + def before(self, state): - run_hook.append('before'+self.id) + run_hook.append('before' + self.id) def after(self, state): - run_hook.append('after'+self.id) + run_hook.append('after' + self.id) def on_error(self, state, e): - run_hook.append('error'+self.id) - + run_hook.append('error' + self.id) + app = TestApp(make_app(RootController(), hooks=[ SimpleHook(1), SimpleHook(2), SimpleHook(3) ])) response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 10 assert run_hook[0] == 'on_route1' assert run_hook[1] == 'on_route2' @@ -134,29 +135,29 @@ class TestHooks(TestCase): def test_prioritized_hooks(self): run_hook = [] - + class RootController(object): @expose() def index(self): run_hook.append('inside') return 'Hello, World!' - + class SimpleHook(PecanHook): def __init__(self, id): self.id = str(id) - + def on_route(self, state): - run_hook.append('on_route'+self.id) - + run_hook.append('on_route' + self.id) + def before(self, state): - run_hook.append('before'+self.id) + run_hook.append('before' + self.id) def after(self, state): - run_hook.append('after'+self.id) + run_hook.append('after' + self.id) def on_error(self, state, e): - run_hook.append('error'+self.id) - + run_hook.append('error' + self.id) + papp = make_app(RootController(), hooks=[ SimpleHook(1), SimpleHook(2), SimpleHook(3) ]) @@ -164,7 +165,7 @@ class TestHooks(TestCase): response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 10 assert run_hook[0] == 'on_route1' assert run_hook[1] == 'on_route2' @@ -176,17 +177,17 @@ class TestHooks(TestCase): assert run_hook[7] == 'after3' assert run_hook[8] == 'after2' assert run_hook[9] == 'after1' - + run_hook = [] - + state.app.hooks[0].priority = 3 state.app.hooks[1].priority = 2 state.app.hooks[2].priority = 1 - + response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 10 assert run_hook[0] == 'on_route3' assert run_hook[1] == 'on_route2' @@ -198,14 +199,14 @@ class TestHooks(TestCase): assert run_hook[7] == 'after1' assert run_hook[8] == 'after2' assert run_hook[9] == 'after3' - + def test_basic_isolated_hook(self): run_hook = [] - + class SimpleHook(PecanHook): def on_route(self, state): run_hook.append('on_route') - + def before(self, state): run_hook.append('before') @@ -220,39 +221,39 @@ class TestHooks(TestCase): def index(self): run_hook.append('inside_sub_sub') return 'Deep inside here!' - + class SubController(HookController): __hooks__ = [SimpleHook()] - + @expose() def index(self): run_hook.append('inside_sub') return 'Inside here!' - + sub = SubSubController() - + class RootController(object): @expose() def index(self): run_hook.append('inside') return 'Hello, World!' - + sub = SubController() - + app = TestApp(make_app(RootController())) response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 1 assert run_hook[0] == 'inside' - + run_hook = [] - + response = app.get('/sub/') assert response.status_int == 200 assert response.body == 'Inside here!' - + assert len(run_hook) == 3 assert run_hook[0] == 'before' assert run_hook[1] == 'inside_sub' @@ -262,64 +263,64 @@ class TestHooks(TestCase): response = app.get('/sub/sub/') assert response.status_int == 200 assert response.body == 'Deep inside here!' - + assert len(run_hook) == 3 assert run_hook[0] == 'before' assert run_hook[1] == 'inside_sub_sub' assert run_hook[2] == 'after' - + def test_isolated_hook_with_global_hook(self): run_hook = [] - + class SimpleHook(PecanHook): def __init__(self, id): self.id = str(id) - + def on_route(self, state): - run_hook.append('on_route'+self.id) - + run_hook.append('on_route' + self.id) + def before(self, state): - run_hook.append('before'+self.id) + run_hook.append('before' + self.id) def after(self, state): - run_hook.append('after'+self.id) + run_hook.append('after' + self.id) def on_error(self, state, e): - run_hook.append('error'+self.id) - + run_hook.append('error' + self.id) + class SubController(HookController): __hooks__ = [SimpleHook(2)] - + @expose() def index(self): run_hook.append('inside_sub') return 'Inside here!' - + class RootController(object): @expose() def index(self): run_hook.append('inside') return 'Hello, World!' - + sub = SubController() - + app = TestApp(make_app(RootController(), hooks=[SimpleHook(1)])) response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 4 assert run_hook[0] == 'on_route1' assert run_hook[1] == 'before1' assert run_hook[2] == 'inside' assert run_hook[3] == 'after1' - + run_hook = [] - + response = app.get('/sub/') assert response.status_int == 200 assert response.body == 'Inside here!' - + assert len(run_hook) == 6 assert run_hook[0] == 'on_route1' assert run_hook[1] == 'before2' @@ -327,26 +328,26 @@ class TestHooks(TestCase): assert run_hook[3] == 'inside_sub' assert run_hook[4] == 'after1' assert run_hook[5] == 'after2' - + def test_hooks_with_validation(self): run_hook = [] - + class RegistrationSchema(Schema): - first_name = validators.String(not_empty=True) - last_name = validators.String(not_empty=True) - email = validators.Email() - username = validators.PlainText() - password = validators.String() - password_confirm = validators.String() - age = validators.Int() + first_name = validators.String(not_empty=True) + last_name = validators.String(not_empty=True) + email = validators.Email() + username = validators.PlainText() + password = validators.String() + password_confirm = validators.String() + age = validators.Int() chained_validators = [ validators.FieldsMatch('password', 'password_confirm') ] - + class SimpleHook(PecanHook): def on_route(self, state): run_hook.append('on_route') - + def before(self, state): run_hook.append('before') @@ -355,45 +356,45 @@ class TestHooks(TestCase): def on_error(self, state, e): run_hook.append('error') - - class RootController(object): + + class RootController(object): @expose() def errors(self, *args, **kwargs): run_hook.append('inside') return 'errors' - + @expose(schema=RegistrationSchema()) - def index(self, first_name, - last_name, - email, - username, + def index(self, first_name, + last_name, + email, + username, password, password_confirm, age): run_hook.append('inside') return str(len(request.pecan['validation_errors']) > 0) - + @expose(schema=RegistrationSchema(), error_handler='/errors') - def with_handler(self, first_name, - last_name, - email, - username, + def with_handler(self, first_name, + last_name, + email, + username, password, password_confirm, age): run_hook.append('inside') return str(len(request.pecan['validation_errors']) > 0) - - # test that the hooks get properly run with no validation errors + + # test that the hooks get properly run with no validation errors app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) r = app.post('/', dict( - first_name = 'Jonathan', - last_name = 'LaCour', - email = 'jonathan@cleverdevil.org', - username = 'jlacour', - password = '123456', - password_confirm = '123456', - age = '31' + first_name='Jonathan', + last_name='LaCour', + email='jonathan@cleverdevil.org', + username='jlacour', + password='123456', + password_confirm='123456', + age='31' )) assert r.status_int == 200 assert r.body == 'False' @@ -403,17 +404,17 @@ class TestHooks(TestCase): assert run_hook[2] == 'inside' assert run_hook[3] == 'after' run_hook = [] - - # test that the hooks get properly run with validation errors + + # test that the hooks get properly run with validation errors app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) r = app.post('/', dict( - first_name = 'Jonathan', - last_name = 'LaCour', - email = 'jonathan@cleverdevil.org', - username = 'jlacour', - password = '654321', - password_confirm = '123456', - age = '31' + first_name='Jonathan', + last_name='LaCour', + email='jonathan@cleverdevil.org', + username='jlacour', + password='654321', + password_confirm='123456', + age='31' )) assert r.status_int == 200 assert r.body == 'True' @@ -423,18 +424,18 @@ class TestHooks(TestCase): assert run_hook[2] == 'inside' assert run_hook[3] == 'after' run_hook = [] - - # test that the hooks get properly run with validation errors - # and an error handler + + # test that the hooks get properly run with validation errors + # and an error handler app = TestApp(make_app(RootController(), hooks=[SimpleHook()])) r = app.post('/with_handler', dict( - first_name = 'Jonathan', - last_name = 'LaCour', - email = 'jonathan@cleverdevil.org', - username = 'jlacour', - password = '654321', - password_confirm = '123456', - age = '31' + first_name='Jonathan', + last_name='LaCour', + email='jonathan@cleverdevil.org', + username='jlacour', + password='654321', + password_confirm='123456', + age='31' )) assert r.status_int == 200 assert r.body == 'errors' @@ -447,101 +448,101 @@ class TestHooks(TestCase): assert run_hook[5] == 'inside' assert run_hook[6] == 'after' + class TestTransactionHook(TestCase): def test_transaction_hook(self): run_hook = [] - + class RootController(object): @expose() def index(self): run_hook.append('inside') return 'Hello, World!' - + @expose() def redirect(self): - redirect('/') - + redirect('/') + @expose() def error(self): return [][1] - + def gen(event): return lambda: run_hook.append(event) - + app = TestApp(make_app(RootController(), hooks=[ TransactionHook( - start = gen('start'), - start_ro = gen('start_ro'), - commit = gen('commit'), - rollback = gen('rollback'), - clear = gen('clear') + start=gen('start'), + start_ro=gen('start_ro'), + commit=gen('commit'), + rollback=gen('rollback'), + clear=gen('clear') ) ])) - + response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 3 assert run_hook[0] == 'start_ro' assert run_hook[1] == 'inside' assert run_hook[2] == 'clear' - + run_hook = [] - + response = app.post('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + assert len(run_hook) == 4 assert run_hook[0] == 'start' assert run_hook[1] == 'inside' assert run_hook[2] == 'commit' assert run_hook[3] == 'clear' - + # # test hooks for GET /redirect # This controller should always be non-transactional # run_hook = [] - + response = app.get('/redirect') assert response.status_int == 302 assert len(run_hook) == 2 assert run_hook[0] == 'start_ro' assert run_hook[1] == 'clear' - + # # test hooks for POST /redirect # This controller should always be transactional, # even in the case of redirects # - + run_hook = [] - + response = app.post('/redirect') assert response.status_int == 302 assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'commit' - assert run_hook[2] == 'clear' - + assert run_hook[2] == 'clear' + run_hook = [] try: response = app.post('/error') except IndexError: pass - + assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - def test_transaction_hook_with_after_actions(self): run_hook = [] - + def action(name): def action_impl(): run_hook.append(name) @@ -571,17 +572,17 @@ class TestTransactionHook(TestCase): @after_rollback(action('action-four')) def rollback_decorated(self): abort(500) - + def gen(event): return lambda: run_hook.append(event) app = TestApp(make_app(RootController(), hooks=[ TransactionHook( - start = gen('start'), - start_ro = gen('start_ro'), - commit = gen('commit'), - rollback = gen('rollback'), - clear = gen('clear') + start=gen('start'), + start_ro=gen('start_ro'), + commit=gen('commit'), + rollback=gen('rollback'), + clear=gen('clear') ) ])) @@ -600,20 +601,20 @@ class TestTransactionHook(TestCase): assert response.status_int == 200 assert response.body == 'Index Method!' - assert len(run_hook) == 5 + assert len(run_hook) == 5 assert run_hook[0] == 'start' assert run_hook[1] == 'inside' assert run_hook[2] == 'commit' assert run_hook[3] == 'action-one' assert run_hook[4] == 'clear' - + run_hook = [] - + response = app.get('/decorated') assert response.status_int == 200 assert response.body == 'Decorated Method!' - - assert len(run_hook) == 7 + + assert len(run_hook) == 7 assert run_hook[0] == 'start_ro' assert run_hook[1] == 'clear' assert run_hook[2] == 'start' @@ -636,18 +637,18 @@ class TestTransactionHook(TestCase): response = app.post('/rollback', expect_errors=True) assert response.status_int == 500 - assert len(run_hook) == 4 + assert len(run_hook) == 4 assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'action-three' assert run_hook[3] == 'clear' - + run_hook = [] - + response = app.get('/rollback_decorated', expect_errors=True) assert response.status_int == 500 - - assert len(run_hook) == 6 + + assert len(run_hook) == 6 assert run_hook[0] == 'start_ro' assert run_hook[1] == 'clear' assert run_hook[2] == 'start' @@ -676,12 +677,12 @@ class TestTransactionHook(TestCase): @expose() def redirect(self): redirect('/') - + @expose() @transactional() def redirect_transactional(self): redirect('/') - + @expose() @transactional(False) def redirect_rollback(self): @@ -690,27 +691,27 @@ class TestTransactionHook(TestCase): @expose() def error(self): return [][1] - + @expose() @transactional(False) def error_rollback(self): - return [][1] - + return [][1] + @expose() @transactional() def error_transactional(self): - return [][1] + return [][1] def gen(event): return lambda: run_hook.append(event) app = TestApp(make_app(RootController(), hooks=[ TransactionHook( - start = gen('start'), - start_ro = gen('start_ro'), - commit = gen('commit'), - rollback = gen('rollback'), - clear = gen('clear') + start=gen('start'), + start_ro=gen('start_ro'), + commit=gen('commit'), + rollback=gen('rollback'), + clear=gen('clear') ) ])) @@ -743,97 +744,97 @@ class TestTransactionHook(TestCase): # run_hook = [] - + response = app.get('/redirect') assert response.status_int == 302 assert len(run_hook) == 2 assert run_hook[0] == 'start_ro' assert run_hook[1] == 'clear' - + # # test hooks for POST /redirect # This controller should always be transactional, # even in the case of redirects # - + run_hook = [] - + response = app.post('/redirect') assert response.status_int == 302 assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'commit' assert run_hook[2] == 'clear' - + # # test hooks for GET /redirect_transactional # This controller should always be transactional, # even in the case of redirects # - + run_hook = [] - + response = app.get('/redirect_transactional') assert response.status_int == 302 assert len(run_hook) == 5 assert run_hook[0] == 'start_ro' - assert run_hook[1] == 'clear' + assert run_hook[1] == 'clear' assert run_hook[2] == 'start' assert run_hook[3] == 'commit' assert run_hook[4] == 'clear' - + # # test hooks for POST /redirect_transactional # This controller should always be transactional, # even in the case of redirects # - + run_hook = [] - + response = app.post('/redirect_transactional') assert response.status_int == 302 assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'commit' assert run_hook[2] == 'clear' - + # # test hooks for GET /redirect_rollback # This controller should always be transactional, # *except* in the case of redirects # run_hook = [] - + response = app.get('/redirect_rollback') assert response.status_int == 302 assert len(run_hook) == 5 assert run_hook[0] == 'start_ro' - assert run_hook[1] == 'clear' - assert run_hook[2] == 'start' + assert run_hook[1] == 'clear' + assert run_hook[2] == 'start' assert run_hook[3] == 'rollback' assert run_hook[4] == 'clear' - + # # test hooks for POST /redirect_rollback # This controller should always be transactional, # *except* in the case of redirects # - + run_hook = [] - + response = app.post('/redirect_rollback') assert response.status_int == 302 assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - + # # Exceptions (other than HTTPFound) should *always* # rollback no matter what # - run_hook = [] - + run_hook = [] + try: response = app.post('/error') except IndexError: @@ -843,9 +844,9 @@ class TestTransactionHook(TestCase): assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - - run_hook = [] - + + run_hook = [] + try: response = app.get('/error') except IndexError: @@ -854,9 +855,9 @@ class TestTransactionHook(TestCase): assert len(run_hook) == 2 assert run_hook[0] == 'start_ro' assert run_hook[1] == 'clear' - - run_hook = [] - + + run_hook = [] + try: response = app.post('/error_transactional') except IndexError: @@ -866,9 +867,9 @@ class TestTransactionHook(TestCase): assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - - run_hook = [] - + + run_hook = [] + try: response = app.get('/error_transactional') except IndexError: @@ -880,9 +881,9 @@ class TestTransactionHook(TestCase): assert run_hook[2] == 'start' assert run_hook[3] == 'rollback' assert run_hook[4] == 'clear' - - run_hook = [] - + + run_hook = [] + try: response = app.post('/error_rollback') except IndexError: @@ -892,9 +893,9 @@ class TestTransactionHook(TestCase): assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - - run_hook = [] - + + run_hook = [] + try: response = app.get('/error_rollback') except IndexError: @@ -909,7 +910,7 @@ class TestTransactionHook(TestCase): def test_transaction_hook_with_transactional_class_decorator(self): run_hook = [] - + @transactional() class RootController(object): @expose() @@ -920,7 +921,7 @@ class TestTransactionHook(TestCase): @expose() def redirect(self): redirect('/') - + @expose() @transactional(False) def redirect_rollback(self): @@ -943,17 +944,17 @@ class TestTransactionHook(TestCase): def generic_post(self): run_hook.append('inside') return 'generic post' - + def gen(event): return lambda: run_hook.append(event) app = TestApp(make_app(RootController(), hooks=[ TransactionHook( - start = gen('start'), - start_ro = gen('start_ro'), - commit = gen('commit'), - rollback = gen('rollback'), - clear = gen('clear') + start=gen('start'), + start_ro=gen('start_ro'), + commit=gen('commit'), + rollback=gen('rollback'), + clear=gen('clear') ) ])) @@ -988,69 +989,69 @@ class TestTransactionHook(TestCase): # This controller should always be transactional, # even in the case of redirects # - + run_hook = [] response = app.get('/redirect') assert response.status_int == 302 assert len(run_hook) == 5 assert run_hook[0] == 'start_ro' - assert run_hook[1] == 'clear' + assert run_hook[1] == 'clear' assert run_hook[2] == 'start' assert run_hook[3] == 'commit' assert run_hook[4] == 'clear' - + # # test hooks for POST /redirect # This controller should always be transactional, # even in the case of redirects # - + run_hook = [] - + response = app.post('/redirect') assert response.status_int == 302 assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'commit' assert run_hook[2] == 'clear' - + # # test hooks for GET /redirect_rollback # This controller should always be transactional, # *except* in the case of redirects # run_hook = [] - + response = app.get('/redirect_rollback') assert response.status_int == 302 assert len(run_hook) == 5 assert run_hook[0] == 'start_ro' - assert run_hook[1] == 'clear' - assert run_hook[2] == 'start' + assert run_hook[1] == 'clear' + assert run_hook[2] == 'start' assert run_hook[3] == 'rollback' assert run_hook[4] == 'clear' - + # # test hooks for POST /redirect_rollback # This controller should always be transactional, # *except* in the case of redirects # - + run_hook = [] - + response = app.post('/redirect_rollback') assert response.status_int == 302 assert len(run_hook) == 3 assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - + # # Exceptions (other than HTTPFound) should *always* # rollback no matter what # - run_hook = [] - + run_hook = [] + try: response = app.post('/error') except IndexError: @@ -1060,9 +1061,9 @@ class TestTransactionHook(TestCase): assert run_hook[0] == 'start' assert run_hook[1] == 'rollback' assert run_hook[2] == 'clear' - - run_hook = [] - + + run_hook = [] + try: response = app.get('/error') except IndexError: @@ -1079,9 +1080,9 @@ class TestTransactionHook(TestCase): # test hooks for GET /generic # This controller should always be transactional, # - + run_hook = [] - + response = app.get('/generic') assert response.status_int == 200 assert response.body == 'generic get' @@ -1092,14 +1093,14 @@ class TestTransactionHook(TestCase): assert run_hook[3] == 'inside' assert run_hook[4] == 'commit' assert run_hook[5] == 'clear' - + # # test hooks for POST /generic # This controller should always be transactional, # - + run_hook = [] - + response = app.post('/generic') assert response.status_int == 200 assert response.body == 'generic post' @@ -1108,7 +1109,7 @@ class TestTransactionHook(TestCase): assert run_hook[1] == 'inside' assert run_hook[2] == 'commit' assert run_hook[3] == 'clear' - + class TestRequestViewerHook(TestCase): @@ -1117,167 +1118,180 @@ class TestRequestViewerHook(TestCase): conf['requestviewer'] = { 'blacklist': ['/favicon.ico'] } - + class RootController(object): pass - + app = make_app(RootController()) while hasattr(app, 'application'): app = app.application del conf.__values__['requestviewer'] assert app.hooks - + def test_basic_single_default_hook(self): - + _stdout = StringIO() class RootController(object): @expose() def index(self): return 'Hello, World!' - - app = TestApp(make_app(RootController(), hooks=[RequestViewerHook(writer=_stdout)])) - response = app.get('/') + + app = TestApp( + make_app( + RootController(), hooks=[RequestViewerHook(writer=_stdout)] + ) + ) + response = app.get('/') out = _stdout.getvalue() assert response.status_int == 200 - assert response.body == 'Hello, World!' - assert 'path' in out - assert 'method' in out - assert 'status' in out - assert 'method' in out - assert 'params' in out - assert 'hooks' in out - assert '200 OK' in out + assert response.body == 'Hello, World!' + assert 'path' in out + assert 'method' in out + assert 'status' in out + assert 'method' in out + assert 'params' in out + assert 'hooks' in out + assert '200 OK' in out assert "['RequestViewerHook']" in out - assert '/' in out + assert '/' in out def test_bad_response_from_app(self): """When exceptions are raised the hook deals with them properly""" - + _stdout = StringIO() class RootController(object): @expose() def index(self): return 'Hello, World!' - - app = TestApp(make_app(RootController(), hooks=[RequestViewerHook(writer=_stdout)])) - response = app.get('/404', expect_errors=True) + + app = TestApp( + make_app( + RootController(), hooks=[RequestViewerHook(writer=_stdout)] + ) + ) + response = app.get('/404', expect_errors=True) out = _stdout.getvalue() assert response.status_int == 404 - assert 'path' in out - assert 'method' in out - assert 'status' in out - assert 'method' in out - assert 'params' in out - assert 'hooks' in out - assert '404 Not Found' in out + assert 'path' in out + assert 'method' in out + assert 'status' in out + assert 'method' in out + assert 'params' in out + assert 'hooks' in out + assert '404 Not Found' in out assert "['RequestViewerHook']" in out - assert '/' in out - + assert '/' in out def test_single_item(self): - + _stdout = StringIO() class RootController(object): @expose() def index(self): return 'Hello, World!' - - app = TestApp( - make_app(RootController(), - hooks=[ - RequestViewerHook(config={'items':['path']}, writer=_stdout) - ] + + app = TestApp( + make_app(RootController(), + hooks=[ + RequestViewerHook( + config={'items':['path']}, writer=_stdout ) - ) - response = app.get('/') + ] + ) + ) + response = app.get('/') out = _stdout.getvalue() assert response.status_int == 200 - assert response.body == 'Hello, World!' - assert '/' in out - assert 'path' in out - assert 'method' not in out - assert 'status' not in out - assert 'method' not in out - assert 'params' not in out - assert 'hooks' not in out - assert '200 OK' not in out + assert response.body == 'Hello, World!' + assert '/' in out + assert 'path' in out + assert 'method' not in out + assert 'status' not in out + assert 'method' not in out + assert 'params' not in out + assert 'hooks' not in out + assert '200 OK' not in out assert "['RequestViewerHook']" not in out def test_single_blacklist_item(self): - + _stdout = StringIO() class RootController(object): @expose() def index(self): return 'Hello, World!' - - app = TestApp( - make_app(RootController(), - hooks=[ - RequestViewerHook(config={'blacklist':['/']}, writer=_stdout) - ] + + app = TestApp( + make_app(RootController(), + hooks=[ + RequestViewerHook( + config={'blacklist':['/']}, writer=_stdout ) - ) - response = app.get('/') + ] + ) + ) + response = app.get('/') out = _stdout.getvalue() assert response.status_int == 200 - assert response.body == 'Hello, World!' + assert response.body == 'Hello, World!' assert out == '' def test_item_not_in_defaults(self): - + _stdout = StringIO() class RootController(object): @expose() def index(self): return 'Hello, World!' - - app = TestApp( - make_app(RootController(), - hooks=[ - RequestViewerHook(config={'items':['date']}, writer=_stdout) - ] + + app = TestApp( + make_app(RootController(), + hooks=[ + RequestViewerHook( + config={'items':['date']}, writer=_stdout ) - ) - response = app.get('/') + ] + ) + ) + response = app.get('/') out = _stdout.getvalue() assert response.status_int == 200 - assert response.body == 'Hello, World!' - assert 'date' in out - assert 'method' not in out - assert 'status' not in out - assert 'method' not in out - assert 'params' not in out - assert 'hooks' not in out - assert '200 OK' not in out + assert response.body == 'Hello, World!' + assert 'date' in out + assert 'method' not in out + assert 'status' not in out + assert 'method' not in out + assert 'params' not in out + assert 'hooks' not in out + assert '200 OK' not in out assert "['RequestViewerHook']" not in out - assert '/' not in out + assert '/' not in out def test_hook_formatting(self): - hooks = [''] - viewer = RequestViewerHook() + hooks = [''] + viewer = RequestViewerHook() formatted = viewer.format_hooks(hooks) assert formatted == ['RequestViewerHook'] def test_deal_with_pecan_configs(self): """If config comes from pecan.conf convert it to dict""" - conf = Config(conf_dict={'items':['url']}) + conf = Config(conf_dict={'items': ['url']}) viewer = RequestViewerHook(conf) assert viewer.items == ['url'] diff --git a/pecan/tests/test_jsonify.py b/pecan/tests/test_jsonify.py index bd77515..3ddf55e 100644 --- a/pecan/tests/test_jsonify.py +++ b/pecan/tests/test_jsonify.py @@ -1,47 +1,48 @@ -from datetime import datetime, date -from decimal import Decimal +from datetime import datetime, date +from decimal import Decimal try: - from simplejson import loads + from simplejson import loads except: - from json import loads + from json import loads # noqa try: - from sqlalchemy import orm, schema, types + from sqlalchemy import orm, schema, types from sqlalchemy.engine import create_engine except ImportError: - create_engine = None -from unittest import TestCase + create_engine = None # noqa +from unittest import TestCase -from pecan.jsonify import jsonify, encode, ResultProxy, RowProxy -from pecan import Pecan, expose, request -from webtest import TestApp +from pecan.jsonify import jsonify, encode, ResultProxy, RowProxy +from pecan import Pecan, expose +from webtest import TestApp from webob.multidict import MultiDict + def make_person(): class Person(object): def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name - + @property def name(self): return '%s %s' % (self.first_name, self.last_name) return Person -def test_simple_rule(): +def test_simple_rule(): Person = make_person() - + # create a Person instance p = Person('Jonathan', 'LaCour') - + # register a generic JSON rule @jsonify.when_type(Person) def jsonify_person(obj): return dict( name=obj.name ) - + # encode the object using our new rule result = loads(encode(p)) assert result['name'] == 'Jonathan LaCour' @@ -49,40 +50,42 @@ def test_simple_rule(): class TestJsonify(TestCase): - + def test_simple_jsonify(self): Person = make_person() - + # register a generic JSON rule @jsonify.when_type(Person) def jsonify_person(obj): return dict( name=obj.name ) - + class RootController(object): @expose('json') def index(self): # create a Person instance p = Person('Jonathan', 'LaCour') return p - + app = TestApp(Pecan(RootController())) r = app.get('/') assert r.status_int == 200 - assert loads(r.body) == {'name':'Jonathan LaCour'} + assert loads(r.body) == {'name': 'Jonathan LaCour'} + class TestJsonifyGenericEncoder(TestCase): def test_json_callable(self): class JsonCallable(object): def __init__(self, arg): self.arg = arg + def __json__(self): - return {"arg":self.arg} + return {"arg": self.arg} result = encode(JsonCallable('foo')) - assert loads(result) == {'arg':'foo'} + assert loads(result) == {'arg': 'foo'} def test_datetime(self): today = date.today() @@ -109,20 +112,22 @@ class TestJsonifyGenericEncoder(TestCase): assert loads(result) == {'arg': ['foo', 'bar']} def test_fallback_to_builtin_encoder(self): - class Foo(object): pass + class Foo(object): + pass self.assertRaises(TypeError, encode, Foo()) + class TestJsonifySQLAlchemyGenericEncoder(TestCase): - + def setUp(self): if not create_engine: self.create_fake_proxies() else: self.create_sa_proxies() - + def create_fake_proxies(self): - + # create a fake SA object class FakeSAObject(object): def __init__(self): @@ -131,74 +136,94 @@ class TestJsonifySQLAlchemyGenericEncoder(TestCase): self.id = 1 self.first_name = 'Jonathan' self.last_name = 'LaCour' - + # create a fake result proxy class FakeResultProxy(ResultProxy): def __init__(self): self.rowcount = -1 self.rows = [] + def __iter__(self): return iter(self.rows) + def append(self, row): self.rows.append(row) - + # create a fake row proxy class FakeRowProxy(RowProxy): def __init__(self, arg=None): self.row = dict(arg) + def __getitem__(self, key): return self.row.__getitem__(key) + def keys(self): return self.row.keys() - + # get the SA objects self.sa_object = FakeSAObject() self.result_proxy = FakeResultProxy() - self.result_proxy.append(FakeRowProxy([('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour')])) - self.result_proxy.append(FakeRowProxy([('id', 2), ('first_name', 'Yoann'), ('last_name', 'Roman')])) - self.row_proxy = FakeRowProxy([('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour')]) - + self.result_proxy.append( + FakeRowProxy([ + ('id', 1), + ('first_name', 'Jonathan'), + ('last_name', 'LaCour') + ]) + ) + self.result_proxy.append( + FakeRowProxy([ + ('id', 2), ('first_name', 'Yoann'), ('last_name', 'Roman') + ])) + self.row_proxy = FakeRowProxy([ + ('id', 1), ('first_name', 'Jonathan'), ('last_name', 'LaCour') + ]) + def create_sa_proxies(self): - + # create the table and mapper metadata = schema.MetaData() - user_table = schema.Table('user', metadata, + user_table = schema.Table('user', metadata, schema.Column('id', types.Integer, primary_key=True), schema.Column('first_name', types.Unicode(25)), schema.Column('last_name', types.Unicode(25))) + class User(object): pass orm.mapper(User, user_table) - + # create the session engine = create_engine('sqlite:///:memory:') metadata.bind = engine metadata.create_all() session = orm.sessionmaker(bind=engine)() - + # add some dummy data user_table.insert().execute([ {'first_name': u'Jonathan', 'last_name': u'LaCour'}, {'first_name': u'Yoann', 'last_name': u'Roman'} ]) - + # get the SA objects self.sa_object = session.query(User).first() select = user_table.select() self.result_proxy = select.execute() self.row_proxy = select.execute().fetchone() - + def test_sa_object(self): result = encode(self.sa_object) - assert loads(result) == {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'} - + assert loads(result) == { + 'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour' + } + def test_result_proxy(self): result = encode(self.result_proxy) assert loads(result) == {'count': 2, 'rows': [ {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'}, {'id': 2, 'first_name': 'Yoann', 'last_name': 'Roman'} ]} - + def test_row_proxy(self): result = encode(self.row_proxy) - assert loads(result) == {'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour'} + assert loads(result) == { + 'id': 1, 'first_name': 'Jonathan', 'last_name': 'LaCour' + } diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py index a5c88cd..6dcfac7 100644 --- a/pecan/tests/test_rest.py +++ b/pecan/tests/test_rest.py @@ -5,218 +5,218 @@ from webtest import TestApp try: from simplejson import dumps, loads except: - from json import dumps, loads + from json import dumps, loads # noqa import formencode class TestRestController(TestCase): - + def test_basic_rest(self): - + class OthersController(object): - + @expose() def index(self): return 'OTHERS' - + @expose() def echo(self, value): return str(value) - + class ThingsController(RestController): data = ['zero', 'one', 'two', 'three'] - + _custom_actions = {'count': ['GET'], 'length': ['GET', 'POST']} - + others = OthersController() - + @expose() def get_one(self, id): return self.data[int(id)] - + @expose('json') def get_all(self): return dict(items=self.data) - + @expose() def length(self, id, value=None): length = len(self.data[int(id)]) if value: length += len(value) return str(length) - + @expose() def get_count(self): return str(len(self.data)) - + @expose() def new(self): return 'NEW' - + @expose() def post(self, value): self.data.append(value) response.status = 302 return 'CREATED' - + @expose() def edit(self, id): return 'EDIT %s' % self.data[int(id)] - + @expose() def put(self, id, value): self.data[int(id)] = value return 'UPDATED' - + @expose() def get_delete(self, id): return 'DELETE %s' % self.data[int(id)] - + @expose() def delete(self, id): del self.data[int(id)] return 'DELETED' - + @expose() def reset(self): return 'RESET' - + @expose() def post_options(self): return 'OPTIONS' - + @expose() def options(self): abort(500) - + @expose() def other(self): abort(500) - + class RootController(object): things = ThingsController() - + # create the app app = TestApp(make_app(RootController())) - + # test get_all r = app.get('/things') assert r.status_int == 200 assert r.body == dumps(dict(items=ThingsController.data)) - + # test get_one for i, value in enumerate(ThingsController.data): r = app.get('/things/%d' % i) assert r.status_int == 200 assert r.body == value - + # test post - r = app.post('/things', {'value':'four'}) + r = app.post('/things', {'value': 'four'}) assert r.status_int == 302 assert r.body == 'CREATED' - + # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == 'four' - + # test edit r = app.get('/things/3/edit') assert r.status_int == 200 assert r.body == 'EDIT three' - + # test put - r = app.put('/things/4', {'value':'FOUR'}) + r = app.put('/things/4', {'value': 'FOUR'}) assert r.status_int == 200 assert r.body == 'UPDATED' - + # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == 'FOUR' - + # test put with _method parameter and GET - r = app.get('/things/4?_method=put', {'value':'FOUR!'}, status=405) + r = app.get('/things/4?_method=put', {'value': 'FOUR!'}, status=405) assert r.status_int == 405 - + # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == 'FOUR' - + # test put with _method parameter and POST - r = app.post('/things/4?_method=put', {'value':'FOUR!'}) + r = app.post('/things/4?_method=put', {'value': 'FOUR!'}) assert r.status_int == 200 assert r.body == 'UPDATED' - + # make sure it works r = app.get('/things/4') assert r.status_int == 200 assert r.body == 'FOUR!' - + # test get delete r = app.get('/things/4/delete') assert r.status_int == 200 assert r.body == 'DELETE FOUR!' - + # test delete r = app.delete('/things/4') assert r.status_int == 200 assert r.body == 'DELETED' - + # make sure it works r = app.get('/things') assert r.status_int == 200 assert len(loads(r.body)['items']) == 4 - + # test delete with _method parameter and GET r = app.get('/things/3?_method=DELETE', status=405) assert r.status_int == 405 - + # make sure it works r = app.get('/things') assert r.status_int == 200 assert len(loads(r.body)['items']) == 4 - + # test delete with _method parameter and POST r = app.post('/things/3?_method=DELETE') assert r.status_int == 200 assert r.body == 'DELETED' - + # make sure it works r = app.get('/things') assert r.status_int == 200 assert len(loads(r.body)['items']) == 3 - + # test "RESET" custom action r = app.request('/things', method='RESET') assert r.status_int == 200 assert r.body == 'RESET' - + # test "RESET" custom action with _method parameter r = app.get('/things?_method=RESET') assert r.status_int == 200 assert r.body == 'RESET' - + # test the "OPTIONS" custom action r = app.request('/things', method='OPTIONS') assert r.status_int == 200 assert r.body == 'OPTIONS' - + # test the "OPTIONS" custom action with the _method parameter r = app.post('/things', {'_method': 'OPTIONS'}) assert r.status_int == 200 assert r.body == 'OPTIONS' - + # test the "other" custom action r = app.request('/things/other', method='MISC', status=405) assert r.status_int == 405 - + # test the "other" custom action with the _method parameter r = app.post('/things/other', {'_method': 'MISC'}, status=405) assert r.status_int == 405 - + # test the "others" custom action r = app.request('/things/others/', method='MISC') assert r.status_int == 200 @@ -225,59 +225,59 @@ class TestRestController(TestCase): # test the "others" custom action missing trailing slash r = app.request('/things/others', method='MISC', status=302) assert r.status_int == 302 - + # test the "others" custom action with the _method parameter r = app.get('/things/others/?_method=MISC') assert r.status_int == 200 assert r.body == 'OTHERS' - + # test an invalid custom action r = app.get('/things?_method=BAD', status=404) assert r.status_int == 404 - + # test custom "GET" request "count" r = app.get('/things/count') assert r.status_int == 200 assert r.body == '3' - + # test custom "GET" request "length" r = app.get('/things/1/length') assert r.status_int == 200 assert r.body == str(len('one')) - + # test custom "GET" request through subcontroller r = app.get('/things/others/echo?value=test') assert r.status_int == 200 assert r.body == 'test' - + # test custom "POST" request "length" - r = app.post('/things/1/length', {'value':'test'}) + r = app.post('/things/1/length', {'value': 'test'}) assert r.status_int == 200 assert r.body == str(len('onetest')) - + # test custom "POST" request through subcontroller - r = app.post('/things/others/echo', {'value':'test'}) + r = app.post('/things/others/echo', {'value': 'test'}) assert r.status_int == 200 assert r.body == 'test' - + def test_nested_rest(self): - + class BarsController(RestController): - + data = [['zero-zero', 'zero-one'], ['one-zero', 'one-one']] - + @expose() def get_one(self, foo_id, id): return self.data[int(foo_id)][int(id)] - + @expose('json') def get_all(self, foo_id): return dict(items=self.data[int(foo_id)]) - + @expose() def new(self, foo_id): return 'NEW FOR %s' % foo_id - + @expose() def post(self, foo_id, value): foo_id = int(foo_id) @@ -286,31 +286,31 @@ class TestRestController(TestCase): self.data[foo_id].append(value) response.status = 302 return 'CREATED FOR %s' % foo_id - + @expose() def edit(self, foo_id, id): return 'EDIT %s' % self.data[int(foo_id)][int(id)] - + @expose() def put(self, foo_id, id, value): self.data[int(foo_id)][int(id)] = value return 'UPDATED' - + @expose() def get_delete(self, foo_id, id): return 'DELETE %s' % self.data[int(foo_id)][int(id)] - + @expose() def delete(self, foo_id, id): del self.data[int(foo_id)][int(id)] return 'DELETED' - + class FoosController(RestController): - + data = ['zero', 'one'] - + bars = BarsController() - + @expose() def get_one(self, id): return self.data[int(id)] @@ -318,15 +318,15 @@ class TestRestController(TestCase): @expose('json') def get_all(self): return dict(items=self.data) - + @expose() def new(self): return 'NEW' - + @expose() def edit(self, id): return 'EDIT %s' % self.data[int(id)] - + @expose() def post(self, value): self.data.append(value) @@ -337,393 +337,396 @@ class TestRestController(TestCase): def put(self, id, value): self.data[int(id)] = value return 'UPDATED' - + @expose() def get_delete(self, id): return 'DELETE %s' % self.data[int(id)] - + @expose() def delete(self, id): del self.data[int(id)] return 'DELETED' - + class RootController(object): foos = FoosController() - + # create the app app = TestApp(make_app(RootController())) - + # test get_all r = app.get('/foos') assert r.status_int == 200 assert r.body == dumps(dict(items=FoosController.data)) - + # test nested get_all r = app.get('/foos/1/bars') assert r.status_int == 200 assert r.body == dumps(dict(items=BarsController.data[1])) - + # test get_one for i, value in enumerate(FoosController.data): r = app.get('/foos/%d' % i) assert r.status_int == 200 assert r.body == value - + # test nested get_one for i, value in enumerate(FoosController.data): for j, value in enumerate(BarsController.data[i]): r = app.get('/foos/%s/bars/%s' % (i, j)) assert r.status_int == 200 assert r.body == value - + # test post - r = app.post('/foos', {'value':'two'}) + r = app.post('/foos', {'value': 'two'}) assert r.status_int == 302 assert r.body == 'CREATED' - + # make sure it works r = app.get('/foos/2') assert r.status_int == 200 assert r.body == 'two' - + # test nested post - r = app.post('/foos/2/bars', {'value':'two-zero'}) + r = app.post('/foos/2/bars', {'value': 'two-zero'}) assert r.status_int == 302 assert r.body == 'CREATED FOR 2' - + # make sure it works r = app.get('/foos/2/bars/0') assert r.status_int == 200 assert r.body == 'two-zero' - + # test edit r = app.get('/foos/1/edit') assert r.status_int == 200 assert r.body == 'EDIT one' - + # test nested edit r = app.get('/foos/1/bars/1/edit') assert r.status_int == 200 assert r.body == 'EDIT one-one' - + # test put - r = app.put('/foos/2', {'value':'TWO'}) + r = app.put('/foos/2', {'value': 'TWO'}) assert r.status_int == 200 assert r.body == 'UPDATED' - + # make sure it works r = app.get('/foos/2') assert r.status_int == 200 assert r.body == 'TWO' - + # test nested put - r = app.put('/foos/2/bars/0', {'value':'TWO-ZERO'}) + r = app.put('/foos/2/bars/0', {'value': 'TWO-ZERO'}) assert r.status_int == 200 assert r.body == 'UPDATED' - + # make sure it works r = app.get('/foos/2/bars/0') assert r.status_int == 200 assert r.body == 'TWO-ZERO' - + # test put with _method parameter and GET - r = app.get('/foos/2?_method=put', {'value':'TWO!'}, status=405) + r = app.get('/foos/2?_method=put', {'value': 'TWO!'}, status=405) assert r.status_int == 405 - + # make sure it works r = app.get('/foos/2') assert r.status_int == 200 assert r.body == 'TWO' - + # test nested put with _method parameter and GET - r = app.get('/foos/2/bars/0?_method=put', {'value':'ZERO-TWO!'}, status=405) + r = app.get( + '/foos/2/bars/0?_method=put', + {'value': 'ZERO-TWO!'}, status=405 + ) assert r.status_int == 405 - + # make sure it works r = app.get('/foos/2/bars/0') assert r.status_int == 200 assert r.body == 'TWO-ZERO' - + # test put with _method parameter and POST - r = app.post('/foos/2?_method=put', {'value':'TWO!'}) + r = app.post('/foos/2?_method=put', {'value': 'TWO!'}) assert r.status_int == 200 assert r.body == 'UPDATED' - + # make sure it works r = app.get('/foos/2') assert r.status_int == 200 assert r.body == 'TWO!' - + # test nested put with _method parameter and POST - r = app.post('/foos/2/bars/0?_method=put', {'value':'TWO-ZERO!'}) + r = app.post('/foos/2/bars/0?_method=put', {'value': 'TWO-ZERO!'}) assert r.status_int == 200 assert r.body == 'UPDATED' - + # make sure it works r = app.get('/foos/2/bars/0') assert r.status_int == 200 assert r.body == 'TWO-ZERO!' - + # test get delete r = app.get('/foos/2/delete') assert r.status_int == 200 assert r.body == 'DELETE TWO!' - + # test nested get delete r = app.get('/foos/2/bars/0/delete') assert r.status_int == 200 assert r.body == 'DELETE TWO-ZERO!' - + # test nested delete r = app.delete('/foos/2/bars/0') assert r.status_int == 200 assert r.body == 'DELETED' - + # make sure it works r = app.get('/foos/2/bars') assert r.status_int == 200 assert len(loads(r.body)['items']) == 0 - + # test delete r = app.delete('/foos/2') assert r.status_int == 200 assert r.body == 'DELETED' - + # make sure it works r = app.get('/foos') assert r.status_int == 200 assert len(loads(r.body)['items']) == 2 - + # test nested delete with _method parameter and GET r = app.get('/foos/1/bars/1?_method=DELETE', status=405) assert r.status_int == 405 - + # make sure it works r = app.get('/foos/1/bars') assert r.status_int == 200 assert len(loads(r.body)['items']) == 2 - + # test delete with _method parameter and GET r = app.get('/foos/1?_method=DELETE', status=405) assert r.status_int == 405 - + # make sure it works r = app.get('/foos') assert r.status_int == 200 assert len(loads(r.body)['items']) == 2 - + # test nested delete with _method parameter and POST r = app.post('/foos/1/bars/1?_method=DELETE') assert r.status_int == 200 assert r.body == 'DELETED' - + # make sure it works r = app.get('/foos/1/bars') assert r.status_int == 200 assert len(loads(r.body)['items']) == 1 - + # test delete with _method parameter and POST r = app.post('/foos/1?_method=DELETE') assert r.status_int == 200 assert r.body == 'DELETED' - + # make sure it works r = app.get('/foos') assert r.status_int == 200 assert len(loads(r.body)['items']) == 1 def test_bad_rest(self): - + class ThingsController(RestController): pass - + class RootController(object): things = ThingsController() - + # create the app app = TestApp(make_app(RootController())) - + # test get_all r = app.get('/things', status=404) assert r.status_int == 404 - + # test get_one r = app.get('/things/1', status=404) assert r.status_int == 404 - + # test post - r = app.post('/things', {'value':'one'}, status=404) + r = app.post('/things', {'value': 'one'}, status=404) assert r.status_int == 404 - + # test edit r = app.get('/things/1/edit', status=404) assert r.status_int == 404 - + # test put - r = app.put('/things/1', {'value':'ONE'}, status=404) - + r = app.put('/things/1', {'value': 'ONE'}, status=404) + # test put with _method parameter and GET - r = app.get('/things/1?_method=put', {'value':'ONE!'}, status=405) + r = app.get('/things/1?_method=put', {'value': 'ONE!'}, status=405) assert r.status_int == 405 - + # test put with _method parameter and POST - r = app.post('/things/1?_method=put', {'value':'ONE!'}, status=404) + r = app.post('/things/1?_method=put', {'value': 'ONE!'}, status=404) assert r.status_int == 404 - + # test get delete r = app.get('/things/1/delete', status=404) assert r.status_int == 404 - + # test delete r = app.delete('/things/1', status=404) assert r.status_int == 404 - + # test delete with _method parameter and GET r = app.get('/things/1?_method=DELETE', status=405) assert r.status_int == 405 - + # test delete with _method parameter and POST r = app.post('/things/1?_method=DELETE', status=404) assert r.status_int == 404 - + # test "RESET" custom action r = app.request('/things', method='RESET', status=404) assert r.status_int == 404 - + def test_custom_delete(self): - + class OthersController(object): - + @expose() def index(self): return 'DELETE' - + @expose() def reset(self, id): return str(id) - + class ThingsController(RestController): - + others = OthersController() - + @expose() def delete_fail(self): abort(500) - + class RootController(object): things = ThingsController() - + # create the app app = TestApp(make_app(RootController())) - + # test bad delete r = app.delete('/things/delete_fail', status=405) assert r.status_int == 405 - + # test bad delete with _method parameter and GET r = app.get('/things/delete_fail?_method=delete', status=405) assert r.status_int == 405 - + # test bad delete with _method parameter and POST - r = app.post('/things/delete_fail', {'_method':'delete'}, status=405) + r = app.post('/things/delete_fail', {'_method': 'delete'}, status=405) assert r.status_int == 405 - + # test custom delete without ID r = app.delete('/things/others/') assert r.status_int == 200 assert r.body == 'DELETE' - + # test custom delete without ID with _method parameter and GET r = app.get('/things/others/?_method=delete', status=405) assert r.status_int == 405 - + # test custom delete without ID with _method parameter and POST - r = app.post('/things/others/', {'_method':'delete'}) + r = app.post('/things/others/', {'_method': 'delete'}) assert r.status_int == 200 assert r.body == 'DELETE' - + # test custom delete with ID r = app.delete('/things/others/reset/1') assert r.status_int == 200 assert r.body == '1' - + # test custom delete with ID with _method parameter and GET r = app.get('/things/others/reset/1?_method=delete', status=405) assert r.status_int == 405 - + # test custom delete with ID with _method parameter and POST - r = app.post('/things/others/reset/1', {'_method':'delete'}) + r = app.post('/things/others/reset/1', {'_method': 'delete'}) assert r.status_int == 200 assert r.body == '1' - + def test_get_with_var_args(self): - + class OthersController(object): - + @expose() def index(self, one, two, three): return 'NESTED: %s, %s, %s' % (one, two, three) - + class ThingsController(RestController): - + others = OthersController() - + @expose() def get_one(self, *args): return ', '.join(args) - + class RootController(object): things = ThingsController() - + # create the app app = TestApp(make_app(RootController())) - + # test get request r = app.get('/things/one/two/three') assert r.status_int == 200 assert r.body == 'one, two, three' - + # test nested get request r = app.get('/things/one/two/three/others/') assert r.status_int == 200 assert r.body == 'NESTED: one, two, three' - + def test_sub_nested_rest(self): - + class BazsController(RestController): - + data = [[['zero-zero-zero']]] - + @expose() def get_one(self, foo_id, bar_id, id): return self.data[int(foo_id)][int(bar_id)][int(id)] - + class BarsController(RestController): - + data = [['zero-zero']] - + bazs = BazsController() - + @expose() def get_one(self, foo_id, id): return self.data[int(foo_id)][int(id)] - + class FoosController(RestController): - + data = ['zero'] - + bars = BarsController() - + @expose() def get_one(self, id): return self.data[int(id)] - + class RootController(object): foos = FoosController() - + # create the app app = TestApp(make_app(RootController())) - + # test sub-nested get_one r = app.get('/foos/0/bars/0/bazs/0') assert r.status_int == 200 @@ -740,13 +743,13 @@ class TestRestController(TestCase): @expose() def named(self): return 'NAMED' - + class BazsController(RestController): - + data = [[['zero-zero-zero']]] final = FinalController() - + @expose() def get_one(self, foo_id, bar_id, id): return self.data[int(foo_id)][int(bar_id)][int(id)] @@ -758,13 +761,13 @@ class TestRestController(TestCase): @expose() def put(self, id): return 'PUT-GRAND-CHILD' - + class BarsController(RestController): - + data = [['zero-zero']] - + bazs = BazsController() - + @expose() def get_one(self, foo_id, id): return self.data[int(foo_id)][int(id)] @@ -778,11 +781,11 @@ class TestRestController(TestCase): return 'PUT-CHILD' class FoosController(RestController): - + data = ['zero'] - + bars = BarsController() - + @expose() def get_one(self, id): return self.data[int(id)] @@ -794,13 +797,13 @@ class TestRestController(TestCase): @expose() def put(self, id): return 'PUT' - + class RootController(object): foos = FoosController() - + # create the app app = TestApp(make_app(RootController())) - + r = app.post('/foos') assert r.status_int == 200 assert r.body == 'POST' @@ -849,36 +852,36 @@ class TestRestController(TestCase): class SampleSchema(formencode.Schema): name = formencode.validators.String() - + class UserController(RestController): - + @expose() def get_one(self, id): return "FORM VALIDATION FAILED" - - @expose( - schema = SampleSchema(), - error_handler = lambda: request.path - ) - def put(self, id): - raise AssertionError, "Schema validation should fail." @expose( - schema = SampleSchema(), - error_handler = lambda: request.path + schema=SampleSchema(), + error_handler=lambda: request.path + ) + def put(self, id): + raise AssertionError("Schema validation should fail.") + + @expose( + schema=SampleSchema(), + error_handler=lambda: request.path ) def delete(self, id): - raise AssertionError, "Schema validation should fail." + raise AssertionError("Schema validation should fail.") class RootController(object): users = UserController() - + # create the app app = TestApp(make_app(RootController())) - + # create the app app = TestApp(make_app(RootController())) - + # test proper internal redirection r = app.post('/users/1?_method=put') assert r.status_int == 200 diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py index b16bbf0..4b48877 100644 --- a/pecan/tests/test_secure.py +++ b/pecan/tests/test_secure.py @@ -1,7 +1,7 @@ from unittest import TestCase from pecan import expose, make_app -from pecan.secure import secure, unlocked, SecureController, Protected +from pecan.secure import secure, unlocked, SecureController from webtest import TestApp try: @@ -9,6 +9,7 @@ try: except: from sets import Set as set + class TestSecure(TestCase): def test_simple_secure(self): authorized = False @@ -17,49 +18,48 @@ class TestSecure(TestCase): @expose() def index(self): return 'Index' - + @expose() @unlocked def allowed(self): return 'Allowed!' - + @classmethod def check_permissions(cls): return authorized - + class RootController(object): @expose() def index(self): return 'Hello, World!' - + @expose() @secure(lambda: False) def locked(self): return 'No dice!' - + @expose() @secure(lambda: True) def unlocked(self): return 'Sure thing' - + secret = SecretController() - app = TestApp(make_app(RootController(), static_root='tests/static')) response = app.get('/') assert response.status_int == 200 assert response.body == 'Hello, World!' - + response = app.get('/unlocked') assert response.status_int == 200 assert response.body == 'Sure thing' - + response = app.get('/locked', expect_errors=True) assert response.status_int == 401 - + response = app.get('/secret/', expect_errors=True) assert response.status_int == 401 - + response = app.get('/secret/allowed') assert response.status_int == 200 assert response.body == 'Allowed!' @@ -70,7 +70,7 @@ class TestSecure(TestCase): def index(self): return 'Index' - @expose() + @expose() def allowed(self): return 'Allowed!' @@ -103,7 +103,6 @@ class TestSecure(TestCase): secret = SecretController() - app = TestApp(make_app(RootController(), static_root='tests/static')) response = app.get('/') assert response.status_int == 200 @@ -122,11 +121,11 @@ class TestSecure(TestCase): response = app.get('/secret/allowed') assert response.status_int == 200 assert response.body == 'Allowed!' - + response = app.get('/secret/authorized/') assert response.status_int == 200 - assert response.body == 'Index' - + assert response.body == 'Index' + response = app.get('/secret/authorized/allowed') assert response.status_int == 200 assert response.body == 'Allowed!' @@ -168,26 +167,29 @@ class TestSecure(TestCase): assert bool(Protected) is True def test_secure_obj_only_failure(self): - class Foo(object): pass + class Foo(object): + pass try: secure(Foo()) except Exception, e: assert isinstance(e, TypeError) + class TestObjectPathSecurity(TestCase): def setUp(self): permissions_checked = set() class DeepSecretController(SecureController): authorized = False + @expose() @unlocked def _lookup(self, someID, *remainder): if someID == 'notfound': return None return SubController(someID), remainder - + @expose() def index(self): return 'Deep Secret' @@ -224,7 +226,10 @@ class TestObjectPathSecurity(TestCase): def independent(self): return 'Independent Security' - wrapped = secure(SubController('wrapped'), 'independent_check_permissions') + wrapped = secure( + SubController('wrapped'), 'independent_check_permissions' + ) + @classmethod def check_permissions(cls): permissions_checked.add('secretcontroller') @@ -244,7 +249,6 @@ class TestObjectPathSecurity(TestCase): unlocked = unlocked(SubController('unlocked')) - class RootController(object): secret = SecretController() notsecret = NotSecretController() @@ -253,7 +257,9 @@ class TestObjectPathSecurity(TestCase): self.secret_cls = SecretController self.permissions_checked = permissions_checked - self.app = TestApp(make_app(RootController(), static_root='tests/static')) + self.app = TestApp( + make_app(RootController(), static_root='tests/static') + ) def tearDown(self): self.permissions_checked.clear() @@ -280,7 +286,9 @@ class TestObjectPathSecurity(TestCase): assert response.status_int == 404 def test_secret_through_lookup(self): - response = self.app.get('/notsecret/hi/deepsecret/', expect_errors=True) + response = self.app.get( + '/notsecret/hi/deepsecret/', expect_errors=True + ) assert response.status_int == 401 def test_layered_protection(self): @@ -316,13 +324,17 @@ class TestObjectPathSecurity(TestCase): assert response.body == 'Index 2' assert 'deepsecret' not in self.permissions_checked - response = self.app.get('/notsecret/1/deepsecret/notfound/', expect_errors=True) + response = self.app.get( + '/notsecret/1/deepsecret/notfound/', expect_errors=True + ) assert response.status_int == 404 assert 'deepsecret' not in self.permissions_checked def test_mixed_protection(self): self.secret_cls.authorized = True - response = self.app.get('/secret/1/deepsecret/notfound/', expect_errors=True) + response = self.app.get( + '/secret/1/deepsecret/notfound/', expect_errors=True + ) assert response.status_int == 404 assert 'secretcontroller' in self.permissions_checked assert 'deepsecret' not in self.permissions_checked diff --git a/pecan/tests/test_static.py b/pecan/tests/test_static.py index fc2bc50..b423707 100644 --- a/pecan/tests/test_static.py +++ b/pecan/tests/test_static.py @@ -3,14 +3,15 @@ from pecan import expose, make_app from unittest import TestCase from webtest import TestApp + class TestStatic(TestCase): - - def test_simple_static(self): + + def test_simple_static(self): class RootController(object): @expose() def index(self): return 'Hello, World!' - + # make sure Cascade is working properly text = os.path.join(os.path.dirname(__file__), 'static/text.txt') static_root = os.path.join(os.path.dirname(__file__), 'static') @@ -19,7 +20,7 @@ class TestStatic(TestCase): response = app.get('/index.html') assert response.status_int == 200 assert response.body == 'Hello, World!' - + # get a static resource response = app.get('/text.txt') assert response.status_int == 200 diff --git a/pecan/tests/test_templating.py b/pecan/tests/test_templating.py index 0ba03f5..567a6b8 100644 --- a/pecan/tests/test_templating.py +++ b/pecan/tests/test_templating.py @@ -2,9 +2,9 @@ from unittest import TestCase from pecan.templating import RendererFactory, format_line_context -import os import tempfile + class TestTemplate(TestCase): def setUp(self): self.rf = RendererFactory() @@ -21,14 +21,14 @@ class TestTemplate(TestCase): self.assertEqual(extra_vars.make_ns({}), {}) extra_vars.update({'foo': 1}) - self.assertEqual(extra_vars.make_ns({}), {'foo':1}) + self.assertEqual(extra_vars.make_ns({}), {'foo': 1}) def test_update_extra_vars(self): extra_vars = self.rf.extra_vars extra_vars.update({'foo': 1}) - self.assertEqual(extra_vars.make_ns({'bar':2}), {'foo':1, 'bar':2}) - self.assertEqual(extra_vars.make_ns({'foo':2}), {'foo':2}) + self.assertEqual(extra_vars.make_ns({'bar': 2}), {'foo': 1, 'bar': 2}) + self.assertEqual(extra_vars.make_ns({'foo': 2}), {'foo': 2}) class TestTemplateLineFormat(TestCase): diff --git a/pecan/tests/test_util.py b/pecan/tests/test_util.py index 109e0be..3294eec 100644 --- a/pecan/tests/test_util.py +++ b/pecan/tests/test_util.py @@ -1,5 +1,6 @@ from pecan.util import compat_splitext + def test_compat_splitext(): assert ('foo', '.bar') == compat_splitext('foo.bar') assert ('/foo/bar', '.txt') == compat_splitext('/foo/bar.txt') diff --git a/pecan/tests/test_validation.py b/pecan/tests/test_validation.py index a978dd1..df138a9 100644 --- a/pecan/tests/test_validation.py +++ b/pecan/tests/test_validation.py @@ -4,39 +4,38 @@ from unittest import TestCase import os.path -from pecan import make_app, expose, request, response, redirect, ValidationException +from pecan import make_app, expose, request, ValidationException from pecan.templating import _builtin_renderers as builtin_renderers try: from simplejson import dumps except ImportError: - from json import dumps + from json import dumps # noqa class TestValidation(TestCase): - + template_path = os.path.join(os.path.dirname(__file__), 'templates') - + def test_simple_validation(self): class RegistrationSchema(Schema): - first_name = validators.String(not_empty=True) - last_name = validators.String(not_empty=True) - email = validators.Email() - username = validators.PlainText() - password = validators.String() - password_confirm = validators.String() - age = validators.Int() + first_name = validators.String(not_empty=True) + last_name = validators.String(not_empty=True) + email = validators.Email() + username = validators.PlainText() + password = validators.String() + password_confirm = validators.String() + age = validators.Int() chained_validators = [ validators.FieldsMatch('password', 'password_confirm') ] - class RootController(object): @expose(schema=RegistrationSchema()) - def index(self, first_name, - last_name, - email, - username, + def index(self, first_name, + last_name, + email, + username, password, password_confirm, age): @@ -45,14 +44,13 @@ class TestValidation(TestCase): assert age == 31 assert isinstance(age, int) return 'Success!' - + @expose(json_schema=RegistrationSchema()) def json(self, data): assert data['age'] == 31 assert isinstance(data['age'], int) return 'Success!' - # test form submissions app = TestApp(make_app(RootController())) r = app.post('/', dict( @@ -66,7 +64,7 @@ class TestValidation(TestCase): )) assert r.status_int == 200 assert r.body == 'Success!' - + # test JSON submissions r = app.post('/json', dumps(dict( first_name='Jonathan', @@ -88,9 +86,9 @@ class TestValidation(TestCase): from this scenario. """ class RegistrationSchema(Schema): - if_empty = None - first_name = validators.String(not_empty=True) - last_name = validators.String(not_empty=True) + if_empty = None + first_name = validators.String(not_empty=True) + last_name = validators.String(not_empty=True) class RootController(object): @expose(schema=RegistrationSchema()) @@ -104,20 +102,19 @@ class TestValidation(TestCase): r = app.post('/', dict()) assert r.status_int == 200 assert r.body == 'Success!' - + def test_simple_failure(self): class RegistrationSchema(Schema): - first_name = validators.String(not_empty=True) - last_name = validators.String(not_empty=True) - email = validators.Email() - username = validators.PlainText() - password = validators.String() - password_confirm = validators.String() - age = validators.Int() + first_name = validators.String(not_empty=True) + last_name = validators.String(not_empty=True) + email = validators.Email() + username = validators.PlainText() + password = validators.String() + password_confirm = validators.String() + age = validators.Int() chained_validators = [ validators.FieldsMatch('password', 'password_confirm') ] - class RootController(object): @@ -125,39 +122,38 @@ class TestValidation(TestCase): def errors(self, *args, **kwargs): assert len(request.pecan['validation_errors']) > 0 return 'There was an error!' - + @expose(schema=RegistrationSchema()) - def index(self, first_name, - last_name, - email, - username, + def index(self, first_name, + last_name, + email, + username, password, password_confirm, age): assert len(request.pecan['validation_errors']) > 0 return 'Success!' - + @expose(schema=RegistrationSchema(), error_handler='/errors') - def with_handler(self, first_name, - last_name, - email, - username, + def with_handler(self, first_name, + last_name, + email, + username, password, password_confirm, age): assert len(request.pecan['validation_errors']) > 0 return 'Success!' - + @expose(json_schema=RegistrationSchema()) def json(self, data): assert len(request.pecan['validation_errors']) > 0 return 'Success!' - + @expose(json_schema=RegistrationSchema(), error_handler='/errors') def json_with_handler(self, data): assert len(request.pecan['validation_errors']) > 0 return 'Success!' - # test without error handler app = TestApp(make_app(RootController())) @@ -172,7 +168,7 @@ class TestValidation(TestCase): )) assert r.status_int == 200 assert r.body == 'Success!' - + # test with error handler r = app.post('/with_handler', dict( first_name='Jonathan', @@ -185,7 +181,7 @@ class TestValidation(TestCase): )) assert r.status_int == 200 assert r.body == 'There was an error!' - + # test JSON without error handler r = app.post('/json', dumps(dict( first_name='Jonathan', @@ -198,7 +194,7 @@ class TestValidation(TestCase): )), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test JSON with error handler r = app.post('/json_with_handler', dumps(dict( first_name='Jonathan', @@ -213,50 +209,51 @@ class TestValidation(TestCase): assert r.body == 'There was an error!' def test_with_variable_decode(self): - + class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) - + class RootController(object): - + @expose() def errors(self, *args, **kwargs): - return 'Error with %s!' % ', '.join(request.pecan['validation_errors'].keys()) - - @expose(schema=ColorSchema(), + errs = ', '.join(request.pecan['validation_errors'].keys()) + return 'Error with %s!' % errs + + @expose(schema=ColorSchema(), variable_decode=True) def index(self, **kwargs): if request.pecan['validation_errors']: return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - - @expose(schema=ColorSchema(), - error_handler='/errors', + + @expose(schema=ColorSchema(), + error_handler='/errors', variable_decode=True) def with_handler(self, **kwargs): if request.pecan['validation_errors']: return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - - @expose(json_schema=ColorSchema(), + + @expose(json_schema=ColorSchema(), variable_decode=True) def json(self, data): if request.pecan['validation_errors']: return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - - @expose(json_schema=ColorSchema(), - error_handler='/errors', + + @expose(json_schema=ColorSchema(), + error_handler='/errors', variable_decode=True) def json_with_handler(self, data): if request.pecan['validation_errors']: return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - + @expose(schema=ColorSchema(), variable_decode=dict()) def custom(self, **kwargs): @@ -264,7 +261,7 @@ class TestValidation(TestCase): return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - + @expose(schema=ColorSchema(), error_handler='/errors', variable_decode=dict()) @@ -273,7 +270,7 @@ class TestValidation(TestCase): return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - + @expose(json_schema=ColorSchema(), variable_decode=dict()) def custom_json(self, data): @@ -298,7 +295,7 @@ class TestValidation(TestCase): return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - + @expose(schema=ColorSchema(), error_handler='/errors', variable_decode=dict(dict_char='-', list_char='.')) @@ -307,7 +304,7 @@ class TestValidation(TestCase): return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - + @expose(json_schema=ColorSchema(), variable_decode=dict(dict_char='-', list_char='.')) def alternate_json(self, data): @@ -324,335 +321,339 @@ class TestValidation(TestCase): return ', '.join(request.pecan['validation_errors'].keys()) else: return 'Success!' - + # test without error handler app = TestApp(make_app(RootController())) r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test failure without error handler r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == 'colors-1' - + # test with error handler r = app.post('/with_handler', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test failure with error handler r = app.post('/with_handler', { - 'colors-0' : '', - 'colors-1' : 'red' + 'colors-0': '', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Error with colors-0!' - + # test JSON without error handler r = app.post('/json', dumps({ - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test JSON failure without error handler r = app.post('/json', dumps({ - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'colors-1' - + # test JSON with error handler r = app.post('/json_with_handler', dumps({ - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test JSON failure with error handler r = app.post('/json_with_handler', dumps({ - 'colors-0' : '', - 'colors-1' : 'red' + 'colors-0': '', + 'colors-1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Error with colors-0!' - + # test custom without error handler r = app.post('/custom', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test custom failure without error handler r = app.post('/custom', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == 'colors-1' - + # test custom with error handler r = app.post('/custom_with_handler', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test custom failure with error handler r = app.post('/custom_with_handler', { - 'colors-0' : '', - 'colors-1' : 'red' + 'colors-0': '', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Error with colors-0!' - + # test custom JSON without error handler r = app.post('/custom_json', dumps({ - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test custom JSON failure without error handler r = app.post('/custom_json', dumps({ - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'colors-1' - + # test custom JSON with error handler r = app.post('/custom_json_with_handler', dumps({ - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test custom JSON failure with error handler r = app.post('/custom_json_with_handler', dumps({ - 'colors-0' : '', - 'colors-1' : 'red' + 'colors-0': '', + 'colors-1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Error with colors-0!' - + # test alternate without error handler r = app.post('/alternate', { - 'colors.0' : 'blue', - 'colors.1' : 'red' + 'colors.0': 'blue', + 'colors.1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test alternate failure without error handler r = app.post('/alternate', { - 'colors.0' : 'blue', - 'colors.1' : '' + 'colors.0': 'blue', + 'colors.1': '' }) assert r.status_int == 200 assert r.body == 'colors.1' - + # test alternate with error handler r = app.post('/alternate_with_handler', { - 'colors.0' : 'blue', - 'colors.1' : 'red' + 'colors.0': 'blue', + 'colors.1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test alternate failure with error handler r = app.post('/alternate_with_handler', { - 'colors.0' : '', - 'colors.1' : 'red' + 'colors.0': '', + 'colors.1': 'red' }) assert r.status_int == 200 assert r.body == 'Error with colors.0!' - + # test alternate JSON without error handler r = app.post('/alternate_json', dumps({ - 'colors.0' : 'blue', - 'colors.1' : 'red' + 'colors.0': 'blue', + 'colors.1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test alternate JSON failure without error handler r = app.post('/alternate_json', dumps({ - 'colors.0' : 'blue', - 'colors.1' : '' + 'colors.0': 'blue', + 'colors.1': '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'colors.1' - + # test alternate JSON with error handler r = app.post('/alternate_json_with_handler', dumps({ - 'colors.0' : 'blue', - 'colors.1' : 'red' + 'colors.0': 'blue', + 'colors.1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Success!' - + # test alternate JSON failure with error handler r = app.post('/alternate_json_with_handler', dumps({ - 'colors.0' : '', - 'colors.1' : 'red' + 'colors.0': '', + 'colors.1': 'red' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Error with colors.0!' - + def test_htmlfill(self): - + if 'mako' not in builtin_renderers: return - + class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) - + class NameSchema(Schema): name = validators.String(not_empty=True) - + class RootController(object): - + @expose(template='mako:form_colors.html', - schema=ColorSchema(), + schema=ColorSchema(), variable_decode=True) def index(self, **kwargs): if request.pecan['validation_errors']: return dict() else: return dict(data=kwargs) - + @expose(schema=ColorSchema(), error_handler='/errors_with_handler', variable_decode=True) def with_handler(self, **kwargs): return ', '.join(kwargs['colors']) - + @expose('mako:form_colors.html') def errors_with_handler(self): return dict() - + @expose(template='mako:form_name.html', schema=NameSchema(), htmlfill=dict(auto_insert_errors=True)) def with_errors(self, **kwargs): return kwargs - + @expose(schema=NameSchema(), error_handler='/errors_with_handler_and_errors', htmlfill=dict(auto_insert_errors=True)) def with_handler_and_errors(self, **kwargs): return kwargs['name'] - + @expose('mako:form_name.html') def errors_with_handler_and_errors(self): return dict() - + @expose(template='json', schema=NameSchema(), htmlfill=dict(auto_insert_errors=True)) def json(self, **kwargs): if request.pecan['validation_errors']: - return dict(error_with=request.pecan['validation_errors'].keys()) + return dict( + error_with=request.pecan['validation_errors'].keys() + ) else: return kwargs - + def _get_contents(filename): return open(os.path.join(self.template_path, filename), 'r').read() - + # test without error handler - app = TestApp(make_app(RootController(), template_path=self.template_path)) + app = TestApp( + make_app(RootController(), template_path=self.template_path) + ) r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == _get_contents('form_colors_valid.html') - + # test failure without error handler r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == _get_contents('form_colors_invalid.html') - + # test with error handler r = app.post('/with_handler', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'blue, red' - + # test failure with error handler r = app.post('/with_handler', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == _get_contents('form_colors_invalid.html') - + # test with errors r = app.post('/with_errors', { - 'name' : 'Yoann' + 'name': 'Yoann' }) assert r.status_int == 200 assert r.body == _get_contents('form_name_valid.html') - + # test failure with errors r = app.post('/with_errors', { - 'name' : '' + 'name': '' }) assert r.status_int == 200 assert r.body == _get_contents('form_name_invalid.html') - + # test with error handler and errors r = app.post('/with_handler_and_errors', { - 'name' : 'Yoann' + 'name': 'Yoann' }) assert r.status_int == 200 assert r.body == 'Yoann' - + # test failure with error handler and errors r = app.post('/with_handler_and_errors', { - 'name' : '' + 'name': '' }) assert r.status_int == 200 assert r.body == _get_contents('form_name_invalid.html') - + # test JSON r = app.post('/json', { - 'name' : 'Yoann' + 'name': 'Yoann' }) assert r.status_int == 200 assert r.body == dumps(dict(name='Yoann')) - + # test JSON failure r = app.post('/json', { - 'name' : '' + 'name': '' }) assert r.status_int == 200 assert r.body == dumps(dict(error_with=['name'])) - + def test_htmlfill_static(self): if 'mako' not in builtin_renderers: @@ -660,7 +661,7 @@ class TestValidation(TestCase): class LoginSchema(Schema): username = validators.String(not_empty=True) - password = validators.String(not_empty=True) + password = validators.String(not_empty=True) class RootController(object): @@ -679,197 +680,205 @@ class TestValidation(TestCase): @expose('mako:form_login.html') def errors_with_handler(self): - return dict() + return dict() def _get_contents(filename): return open(os.path.join(self.template_path, filename), 'r').read() # test without error handler - app = TestApp(make_app(RootController(), template_path=self.template_path)) + app = TestApp( + make_app(RootController(), template_path=self.template_path) + ) r = app.post('/', { - 'username' : 'ryan', - 'password' : 'password' + 'username': 'ryan', + 'password': 'password' }) assert r.status_int == 200 assert r.body == _get_contents('form_login_valid.html') # test failure without error handler r = app.post('/', { - 'username' : 'ryan', - 'password' : '' + 'username': 'ryan', + 'password': '' }) assert r.status_int == 200 assert r.body == _get_contents('form_login_invalid.html') # test with error handler r = app.post('/with_handler', { - 'username' : 'ryan', - 'password' : 'password' + 'username': 'ryan', + 'password': 'password' }) assert r.status_int == 200 assert r.body == 'ryan:password' # test failure with error handler r = app.post('/with_handler', { - 'username' : 'ryan', - 'password' : '' + 'username': 'ryan', + 'password': '' }) assert r.status_int == 200 assert r.body == _get_contents('form_login_invalid.html') - + def test_error_for(self): - + if 'mako' not in builtin_renderers: return - + class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) - + class RootController(object): - + @expose(template='mako:error_for.html') def errors(self, *args, **kwargs): return dict() - + @expose(template='mako:error_for.html', - schema=ColorSchema(), + schema=ColorSchema(), variable_decode=True) def index(self, **kwargs): return dict() - - @expose(schema=ColorSchema(), - error_handler='/errors', + + @expose(schema=ColorSchema(), + error_handler='/errors', variable_decode=True) def with_handler(self, **kwargs): return dict() - + @expose(template='mako:error_for.html', - json_schema=ColorSchema(), + json_schema=ColorSchema(), variable_decode=True) def json(self, data): return dict() - - @expose(json_schema=ColorSchema(), - error_handler='/errors', + + @expose(json_schema=ColorSchema(), + error_handler='/errors', variable_decode=True) def json_with_handler(self, data): return dict() - + # test without error handler - app = TestApp(make_app(RootController(), template_path=self.template_path)) + app = TestApp( + make_app(RootController(), template_path=self.template_path) + ) r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == '' - + # test failure without error handler r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == 'Please enter a value' - + # test failure with error handler r = app.post('/with_handler', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == 'Please enter a value' - + # test JSON failure without error handler r = app.post('/json', dumps({ - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Please enter a value' - + # test JSON failure with error handler r = app.post('/json_with_handler', dumps({ - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }), [('content-type', 'application/json')]) assert r.status_int == 200 assert r.body == 'Please enter a value' - + def test_callable_error_handler(self): - + class ColorSchema(Schema): colors = ForEach(validators.String(not_empty=True)) - + class RootController(object): - + @expose() def errors(self, *args, **kwargs): return 'There was an error!' - - @expose(schema=ColorSchema(), + + @expose(schema=ColorSchema(), error_handler=lambda: '/errors', variable_decode=True) def index(self, **kwargs): return 'Success!' - + # test with error handler app = TestApp(make_app(RootController())) r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : 'red' + 'colors-0': 'blue', + 'colors-1': 'red' }) assert r.status_int == 200 assert r.body == 'Success!' - + # test with error handler r = app.post('/', { - 'colors-0' : 'blue', - 'colors-1' : '' + 'colors-0': 'blue', + 'colors-1': '' }) assert r.status_int == 200 assert r.body == 'There was an error!' - + def test_validation_exception(self): - + if 'mako' not in builtin_renderers: return - + class NameSchema(Schema): name = validators.String(not_empty=True) - + class SubController(object): @expose() def _route(self, *args): raise ValidationException('/success') - + class RootController(object): - + sub = SubController() - + @expose('mako:form_name.html') def errors_name(self): return dict() - - @expose(schema=NameSchema(), + + @expose(schema=NameSchema(), error_handler='/errors_name', htmlfill=dict(auto_insert_errors=True)) def name(self, name): - raise ValidationException(errors={'name': 'Names must be unique'}) - + raise ValidationException( + errors={'name': 'Names must be unique'} + ) + @expose() def success(self): return 'Success!' - + def _get_contents(filename): return open(os.path.join(self.template_path, filename), 'r').read() - + # test exception with no controller - app = TestApp(make_app(RootController(), template_path=self.template_path)) + app = TestApp( + make_app(RootController(), template_path=self.template_path) + ) r = app.get('/sub') assert r.status_int == 200 assert r.body == 'Success!' - + # test exception with additional errors r = app.post('/name', {'name': 'Yoann'}) assert r.status_int == 200 diff --git a/pecan/util.py b/pecan/util.py index 44c6145..7f29a91 100644 --- a/pecan/util.py +++ b/pecan/util.py @@ -1,13 +1,17 @@ import sys import os + def iscontroller(obj): return getattr(obj, 'exposed', False) + def _cfg(f): - if not hasattr(f, '_pecan'): f._pecan = {} + if not hasattr(f, '_pecan'): + f._pecan = {} return f._pecan + def compat_splitext(path): """ This method emulates the behavior os.path.splitext introduced in python 2.6 @@ -18,20 +22,24 @@ def compat_splitext(path): if index > 0: root = basename[:index] if root.count('.') != index: - return (os.path.join(os.path.dirname(path), root), basename[index:]) + return ( + os.path.join(os.path.dirname(path), root), + basename[index:] + ) return (path, '') + # use the builtin splitext unless we're python 2.5 -if sys.version_info >= (2,6): +if sys.version_info >= (2, 6): from os.path import splitext -else: #pragma no cover - splitext = compat_splitext +else: # pragma no cover + splitext = compat_splitext # noqa -if sys.version_info >=(2,6,5): +if sys.version_info >= (2, 6, 5): def encode_if_needed(s): return s else: - def encode_if_needed(s): + def encode_if_needed(s): # noqa return s.encode('utf-8')