summaryrefslogtreecommitdiff
path: root/pecan
diff options
context:
space:
mode:
Diffstat (limited to 'pecan')
-rw-r--r--pecan/__init__.py134
-rw-r--r--pecan/commands/__init__.py4
-rw-r--r--pecan/commands/base.py166
-rw-r--r--pecan/commands/create.py60
-rw-r--r--pecan/commands/serve.py223
-rw-r--r--pecan/commands/shell.py177
-rw-r--r--pecan/compat/__init__.py20
-rw-r--r--pecan/configuration.py254
-rw-r--r--pecan/core.py854
-rw-r--r--pecan/decorators.py175
-rw-r--r--pecan/deploy.py9
-rw-r--r--pecan/ext/__init__.py6
-rw-r--r--pecan/extensions.py83
-rw-r--r--pecan/hooks.py375
-rw-r--r--pecan/jsonify.py137
-rw-r--r--pecan/log.py54
-rw-r--r--pecan/middleware/__init__.py3
-rw-r--r--pecan/middleware/debug.py96
-rw-r--r--pecan/middleware/errordocument.py76
-rw-r--r--pecan/middleware/recursive.py184
-rw-r--r--pecan/middleware/static.py166
-rw-r--r--pecan/rest.py412
-rw-r--r--pecan/routing.py325
-rw-r--r--pecan/scaffolds/__init__.py142
-rw-r--r--pecan/scaffolds/base/+package+/__init__.py0
-rw-r--r--pecan/scaffolds/base/+package+/app.py_tmpl14
-rw-r--r--pecan/scaffolds/base/+package+/controllers/__init__.py0
-rw-r--r--pecan/scaffolds/base/+package+/controllers/root.py22
-rw-r--r--pecan/scaffolds/base/+package+/model/__init__.py15
-rw-r--r--pecan/scaffolds/base/+package+/templates/error.html12
-rw-r--r--pecan/scaffolds/base/+package+/templates/index.html34
-rw-r--r--pecan/scaffolds/base/+package+/templates/layout.html22
-rw-r--r--pecan/scaffolds/base/+package+/tests/__init__.py_tmpl22
-rw-r--r--pecan/scaffolds/base/+package+/tests/config.py_tmpl25
-rw-r--r--pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl22
-rw-r--r--pecan/scaffolds/base/+package+/tests/test_units.py7
-rw-r--r--pecan/scaffolds/base/MANIFEST.in1
-rw-r--r--pecan/scaffolds/base/config.py_tmpl54
-rw-r--r--pecan/scaffolds/base/public/css/style.css43
-rw-r--r--pecan/scaffolds/base/public/images/logo.pngbin20596 -> 0 bytes
-rw-r--r--pecan/scaffolds/base/setup.cfg_tmpl6
-rw-r--r--pecan/scaffolds/base/setup.py_tmpl22
-rw-r--r--pecan/scaffolds/rest-api/+package+/__init__.py0
-rw-r--r--pecan/scaffolds/rest-api/+package+/app.py_tmpl16
-rw-r--r--pecan/scaffolds/rest-api/+package+/controllers/__init__.py0
-rw-r--r--pecan/scaffolds/rest-api/+package+/controllers/root.py53
-rw-r--r--pecan/scaffolds/rest-api/+package+/errors.py18
-rw-r--r--pecan/scaffolds/rest-api/+package+/model/__init__.py15
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl22
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl19
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl37
-rw-r--r--pecan/scaffolds/rest-api/+package+/tests/test_units.py7
-rw-r--r--pecan/scaffolds/rest-api/config.py_tmpl48
-rw-r--r--pecan/scaffolds/rest-api/setup.cfg_tmpl6
-rw-r--r--pecan/scaffolds/rest-api/setup.py_tmpl22
-rw-r--r--pecan/secure.py232
-rw-r--r--pecan/templating.py286
-rw-r--r--pecan/testing.py35
-rw-r--r--pecan/tests/__init__.py20
-rw-r--r--pecan/tests/config_fixtures/bad/importerror.py1
-rw-r--r--pecan/tests/config_fixtures/bad/module_and_underscore.py4
-rw-r--r--pecan/tests/config_fixtures/config.py22
-rw-r--r--pecan/tests/config_fixtures/empty.py2
-rw-r--r--pecan/tests/config_fixtures/foobar.py1
-rw-r--r--pecan/tests/config_fixtures/forcedict.py14
-rw-r--r--pecan/tests/middleware/__init__.py0
-rw-r--r--pecan/tests/middleware/static_fixtures/self.pngbin6976 -> 0 bytes
-rw-r--r--pecan/tests/middleware/static_fixtures/text.txt9
-rw-r--r--pecan/tests/middleware/test_errordocument.py92
-rw-r--r--pecan/tests/middleware/test_recursive.py142
-rw-r--r--pecan/tests/middleware/test_static.py68
-rw-r--r--pecan/tests/scaffold_builder.py172
-rw-r--r--pecan/tests/scaffold_fixtures/__init__.py0
-rw-r--r--pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl1
-rw-r--r--pecan/tests/scaffold_fixtures/content_sub/foo_tmpl1
-rw-r--r--pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt1
-rw-r--r--pecan/tests/scaffold_fixtures/file_sub/foo_+package+1
-rw-r--r--pecan/tests/scaffold_fixtures/simple/bar/spam.txt1
-rw-r--r--pecan/tests/scaffold_fixtures/simple/foo1
-rw-r--r--pecan/tests/templates/__init__.py0
-rw-r--r--pecan/tests/templates/form_colors.html2
-rw-r--r--pecan/tests/templates/form_colors_invalid.html2
-rw-r--r--pecan/tests/templates/form_colors_valid.html2
-rw-r--r--pecan/tests/templates/form_login_invalid.html2
-rw-r--r--pecan/tests/templates/form_login_valid.html2
-rw-r--r--pecan/tests/templates/form_name.html1
-rw-r--r--pecan/tests/templates/form_name_invalid.html3
-rw-r--r--pecan/tests/templates/form_name_invalid_custom.html3
-rw-r--r--pecan/tests/templates/form_name_valid.html1
-rw-r--r--pecan/tests/templates/genshi.html16
-rw-r--r--pecan/tests/templates/genshi_bad.html18
-rw-r--r--pecan/tests/templates/jinja.html11
-rw-r--r--pecan/tests/templates/jinja_bad.html13
-rw-r--r--pecan/tests/templates/kajiki.html11
-rw-r--r--pecan/tests/templates/mako.html11
-rw-r--r--pecan/tests/templates/mako_bad.html6
-rw-r--r--pecan/tests/test_base.py2227
-rw-r--r--pecan/tests/test_commands.py56
-rw-r--r--pecan/tests/test_conf.py346
-rw-r--r--pecan/tests/test_generic.py111
-rw-r--r--pecan/tests/test_hooks.py1711
-rw-r--r--pecan/tests/test_jsonify.py233
-rw-r--r--pecan/tests/test_no_thread_locals.py1440
-rw-r--r--pecan/tests/test_rest.py1640
-rw-r--r--pecan/tests/test_scaffolds.py160
-rw-r--r--pecan/tests/test_secure.py563
-rw-r--r--pecan/tests/test_templating.py50
-rw-r--r--pecan/tests/test_util.py95
-rw-r--r--pecan/util.py53
109 files changed, 0 insertions, 14614 deletions
diff --git a/pecan/__init__.py b/pecan/__init__.py
deleted file mode 100644
index 1adcc71..0000000
--- a/pecan/__init__.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from .core import (
- abort, override_template, Pecan, Request, Response, load_app,
- redirect, render, request, response
-)
-from .decorators import expose
-from .hooks import RequestViewerHook
-
-from .middleware.debug import DebugMiddleware
-from .middleware.errordocument import ErrorDocumentMiddleware
-from .middleware.recursive import RecursiveMiddleware
-from .middleware.static import StaticFileMiddleware
-from .routing import route
-
-from .configuration import set_config, Config
-from .configuration import _runtime_conf as conf
-from . import middleware
-
-try:
- from logging.config import dictConfig as load_logging_config
-except ImportError:
- from logutils.dictconfig import dictConfig as load_logging_config # noqa
-
-import warnings
-
-
-__all__ = [
- 'make_app', 'load_app', 'Pecan', 'Request', 'Response', 'request',
- 'response', 'override_template', 'expose', 'conf', 'set_config', 'render',
- 'abort', 'redirect', 'route'
-]
-
-
-def make_app(root, **kw):
- '''
- Utility for creating the Pecan application object. This function should
- generally be called from the ``setup_app`` function in your project's
- ``app.py`` file.
-
- :param root: A string representing a root controller object (e.g.,
- "myapp.controller.root.RootController")
- :param static_root: The relative path to a directory containing static
- files. Serving static files is only enabled when
- debug mode is set.
- :param debug: A flag to enable debug mode. This enables the debug
- middleware and serving static files.
- :param wrap_app: A function or middleware class to wrap the Pecan app.
- This must either be a wsgi middleware class or a
- function that returns a wsgi application. This wrapper
- is applied first before wrapping the application in
- other middlewares such as Pecan's debug middleware.
- This should be used if you want to use middleware to
- perform authentication or intercept all requests before
- they are routed to the root controller.
- :param logging: A dictionary used to configure logging. This uses
- ``logging.config.dictConfig``.
-
- All other keyword arguments are passed in to the Pecan app constructor.
-
- :returns: a ``Pecan`` object.
- '''
- # Pass logging configuration (if it exists) on to the Python logging module
- logging = kw.get('logging', {})
- debug = kw.get('debug', False)
- if logging:
- if debug:
- try:
- #
- # By default, Python 2.7+ silences DeprecationWarnings.
- # However, if conf.app.debug is True, we should probably ensure
- # that users see these types of warnings.
- #
- from logging import captureWarnings
- captureWarnings(True)
- warnings.simplefilter("default", DeprecationWarning)
- except ImportError:
- # No captureWarnings on Python 2.6, DeprecationWarnings are on
- pass
-
- if isinstance(logging, Config):
- logging = logging.to_dict()
- if 'version' not in logging:
- logging['version'] = 1
- load_logging_config(logging)
-
- # Instantiate the WSGI app by passing **kw onward
- app = Pecan(root, **kw)
-
- # Optionally wrap the app in another WSGI app
- wrap_app = kw.get('wrap_app', None)
- if wrap_app:
- app = wrap_app(app)
-
- # Configuration for serving custom error messages
- errors = kw.get('errors', getattr(conf.app, 'errors', {}))
- if errors:
- app = middleware.errordocument.ErrorDocumentMiddleware(app, errors)
-
- # Included for internal redirect support
- app = middleware.recursive.RecursiveMiddleware(app)
-
- # When in debug mode, load exception debugging middleware
- static_root = kw.get('static_root', None)
- if debug:
- debug_kwargs = getattr(conf, 'debug', {})
- debug_kwargs.setdefault('context_injectors', []).append(
- lambda environ: {
- 'request': environ.get('pecan.locals', {}).get('request')
- }
- )
- app = DebugMiddleware(
- app,
- **debug_kwargs
- )
-
- # Support for serving static files (for development convenience)
- if static_root:
- app = middleware.static.StaticFileMiddleware(app, static_root)
-
- elif static_root:
- warnings.warn(
- "`static_root` is only used when `debug` is True, ignoring",
- RuntimeWarning
- )
-
- if hasattr(conf, 'requestviewer'):
- warnings.warn(''.join([
- "`pecan.conf.requestviewer` is deprecated. To apply the ",
- "`RequestViewerHook` to your application, add it to ",
- "`pecan.conf.app.hooks` or manually in your project's `app.py` ",
- "file."]),
- DeprecationWarning
- )
-
- return app
diff --git a/pecan/commands/__init__.py b/pecan/commands/__init__.py
deleted file mode 100644
index da02464..0000000
--- a/pecan/commands/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .base import CommandRunner, BaseCommand # noqa
-from .serve import ServeCommand # noqa
-from .shell import ShellCommand # noqa
-from .create import CreateCommand # noqa
diff --git a/pecan/commands/base.py b/pecan/commands/base.py
deleted file mode 100644
index 441d577..0000000
--- a/pecan/commands/base.py
+++ /dev/null
@@ -1,166 +0,0 @@
-import pkg_resources
-import argparse
-import logging
-import sys
-from warnings import warn
-
-import six
-
-log = logging.getLogger(__name__)
-
-
-class HelpfulArgumentParser(argparse.ArgumentParser):
-
- def error(self, message): # pragma: nocover
- """error(message: string)
-
- Prints a usage message incorporating the message to stderr and
- exits.
-
- If you override this in a subclass, it should not return -- it
- should either exit or raise an exception.
- """
- self.print_help(sys.stderr)
- self._print_message('\n')
- self.exit(2, '%s: %s\n' % (self.prog, message))
-
-
-class CommandManager(object):
- """ Used to discover `pecan.command` entry points. """
-
- def __init__(self):
- self.commands = {}
- self.load_commands()
-
- def load_commands(self):
- for ep in pkg_resources.iter_entry_points('pecan.command'):
- log.debug('%s loading plugin %s', self.__class__.__name__, ep)
- if ep.name in self.commands:
- warn(
- "Duplicate entry points found on `%s` - ignoring %s" % (
- ep.name,
- ep
- ),
- RuntimeWarning
- )
- continue
- try:
- cmd = ep.load()
- cmd.run # ensure existance; catch AttributeError otherwise
- except Exception as e: # pragma: nocover
- warn("Unable to load plugin %s: %s" % (ep, e), RuntimeWarning)
- continue
- self.add({ep.name: cmd})
-
- def add(self, cmd):
- self.commands.update(cmd)
-
-
-class CommandRunner(object):
- """ Dispatches `pecan` command execution requests. """
-
- def __init__(self):
- self.manager = CommandManager()
- self.parser = HelpfulArgumentParser(add_help=True)
- self.parser.add_argument(
- '--version',
- action='version',
- version='Pecan %s' % self.version
- )
- self.parse_sub_commands()
-
- def parse_sub_commands(self):
- subparsers = self.parser.add_subparsers(
- dest='command_name',
- metavar='command'
- )
- for name, cmd in self.commands.items():
- sub = subparsers.add_parser(
- name,
- help=cmd.summary
- )
- for arg in getattr(cmd, 'arguments', tuple()):
- arg = arg.copy()
- if isinstance(arg.get('name'), six.string_types):
- sub.add_argument(arg.pop('name'), **arg)
- elif isinstance(arg.get('name'), list):
- sub.add_argument(*arg.pop('name'), **arg)
-
- def run(self, args):
- ns = self.parser.parse_args(args)
- self.commands[ns.command_name]().run(ns)
-
- @classmethod
- def handle_command_line(cls): # pragma: nocover
- runner = CommandRunner()
- runner.run(sys.argv[1:])
-
- @property
- def version(self):
- return pkg_resources.get_distribution('pecan').version
-
- @property
- def commands(self):
- return self.manager.commands
-
-
-class BaseCommandMeta(type):
-
- @property
- def summary(cls):
- """
- This is used to populate the --help argument on the command line.
-
- This provides a default behavior which takes the first sentence of the
- command's docstring and uses it.
- """
- return cls.__doc__.strip().splitlines()[0].rstrip('.')
-
-
-class BaseCommandParent(object):
- """
- A base interface for Pecan commands.
-
- Can be extended to support ``pecan`` command extensions in individual Pecan
- projects, e.g.,
-
- $ ``pecan my-custom-command config.py``
-
- ::
-
- # myapp/myapp/custom_command.py
- class CustomCommand(pecan.commands.base.BaseCommand):
- '''
- (First) line of the docstring is used to summarize the command.
- '''
-
- arguments = ({
- 'name': '--extra_arg',
- 'help': 'an extra command line argument',
- 'optional': True
- })
-
- def run(self, args):
- super(SomeCommand, self).run(args)
- if args.extra_arg:
- pass
- """
-
- arguments = ({
- 'name': 'config_file',
- 'help': 'a Pecan configuration file',
- 'nargs': '?',
- 'default': None,
- },)
-
- def run(self, args):
- """To be implemented by subclasses."""
- self.args = args
-
- def load_app(self):
- from pecan import load_app
- return load_app(self.args.config_file)
-
-BaseCommand = BaseCommandMeta('BaseCommand', (BaseCommandParent,), {
- '__doc__': BaseCommandParent.__doc__
-})
diff --git a/pecan/commands/create.py b/pecan/commands/create.py
deleted file mode 100644
index 1187489..0000000
--- a/pecan/commands/create.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""
-Create command for Pecan
-"""
-import pkg_resources
-import logging
-from warnings import warn
-from pecan.commands import BaseCommand
-from pecan.scaffolds import DEFAULT_SCAFFOLD
-
-log = logging.getLogger(__name__)
-
-
-class ScaffoldManager(object):
- """ Used to discover `pecan.scaffold` entry points. """
-
- def __init__(self):
- self.scaffolds = {}
- self.load_scaffolds()
-
- def load_scaffolds(self):
- for ep in pkg_resources.iter_entry_points('pecan.scaffold'):
- log.debug('%s loading scaffold %s', self.__class__.__name__, ep)
- try:
- cmd = ep.load()
- cmd.copy_to # ensure existance; catch AttributeError otherwise
- except Exception as e: # pragma: nocover
- warn(
- "Unable to load scaffold %s: %s" % (ep, e), RuntimeWarning
- )
- continue
- self.add({ep.name: cmd})
-
- def add(self, cmd):
- self.scaffolds.update(cmd)
-
-
-class CreateCommand(BaseCommand):
- """
- Creates the file layout for a new Pecan scaffolded project.
- """
-
- manager = ScaffoldManager()
-
- arguments = ({
- 'name': 'project_name',
- 'help': 'the (package) name of the new project'
- }, {
- 'name': 'template_name',
- 'metavar': 'template_name',
- 'help': 'a registered Pecan template',
- 'nargs': '?',
- 'default': DEFAULT_SCAFFOLD,
- 'choices': manager.scaffolds.keys()
- })
-
- def run(self, args):
- super(CreateCommand, self).run(args)
- self.manager.scaffolds[args.template_name]().copy_to(
- args.project_name
- )
diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py
deleted file mode 100644
index 72a536d..0000000
--- a/pecan/commands/serve.py
+++ /dev/null
@@ -1,223 +0,0 @@
-"""
-Serve command for Pecan.
-"""
-from __future__ import print_function
-import logging
-import os
-import sys
-import time
-import subprocess
-from wsgiref.simple_server import WSGIRequestHandler
-
-
-from pecan.commands import BaseCommand
-from pecan import util
-
-
-logger = logging.getLogger(__name__)
-
-
-class ServeCommand(BaseCommand):
- """
- Serves a Pecan web application.
-
- This command serves a Pecan web application using the provided
- configuration file for the server and application.
- """
-
- arguments = BaseCommand.arguments + ({
- 'name': '--reload',
- 'help': 'Watch for changes and automatically reload.',
- 'default': False,
- 'action': 'store_true'
- },)
-
- def run(self, args):
- super(ServeCommand, self).run(args)
- app = self.load_app()
- self.serve(app, app.config)
-
- def create_subprocess(self):
- self.server_process = subprocess.Popen(
- [arg for arg in sys.argv if arg != '--reload'],
- stdout=sys.stdout, stderr=sys.stderr
- )
-
- def watch_and_spawn(self, conf):
- from watchdog.observers import Observer
- from watchdog.events import (
- FileSystemEventHandler, FileSystemMovedEvent, FileModifiedEvent,
- DirModifiedEvent
- )
-
- print('Monitoring for changes...')
- self.create_subprocess()
-
- parent = self
-
- class AggressiveEventHandler(FileSystemEventHandler):
- def should_reload(self, event):
- for t in (
- FileSystemMovedEvent, FileModifiedEvent, DirModifiedEvent
- ):
- if isinstance(event, t):
- return True
- return False
-
- def on_modified(self, event):
- if self.should_reload(event):
- parent.server_process.kill()
- parent.create_subprocess()
-
- # Determine a list of file paths to monitor
- paths = self.paths_to_monitor(conf)
-
- event_handler = AggressiveEventHandler()
- for path, recurse in paths:
- observer = Observer()
- observer.schedule(
- event_handler,
- path=path,
- recursive=recurse
- )
- observer.start()
-
- try:
- while True:
- time.sleep(1)
- except KeyboardInterrupt:
- pass
-
- def paths_to_monitor(self, conf):
- paths = []
-
- for package_name in getattr(conf.app, 'modules', []):
- module = __import__(package_name, fromlist=['app'])
- if hasattr(module, 'app') and hasattr(module.app, 'setup_app'):
- paths.append((
- os.path.dirname(module.__file__),
- True
- ))
- break
-
- paths.append((os.path.dirname(conf.__file__), False))
- return paths
-
- def _serve(self, app, conf):
- from wsgiref.simple_server import make_server
-
- host, port = conf.server.host, int(conf.server.port)
- srv = make_server(
- host,
- port,
- app,
- handler_class=PecanWSGIRequestHandler,
- )
-
- print('Starting server in PID %s' % os.getpid())
-
- if host == '0.0.0.0':
- print(
- 'serving on 0.0.0.0:%s, view at http://127.0.0.1:%s' %
- (port, port)
- )
- else:
- print("serving on http://%s:%s" % (host, port))
-
- try:
- srv.serve_forever()
- except KeyboardInterrupt:
- # allow CTRL+C to shutdown
- pass
-
- def serve(self, app, conf):
- """
- A very simple approach for a WSGI server.
- """
-
- if self.args.reload:
- try:
- self.watch_and_spawn(conf)
- except ImportError:
- print('The `--reload` option requires `watchdog` to be '
- 'installed.')
- print(' $ pip install watchdog')
- else:
- self._serve(app, conf)
-
-
-def gunicorn_run():
- """
- The ``gunicorn_pecan`` command for launching ``pecan`` applications
- """
- try:
- from gunicorn.app.wsgiapp import WSGIApplication
- except ImportError as exc:
- args = exc.args
- arg0 = args[0] if args else ''
- arg0 += ' (are you sure `gunicorn` is installed?)'
- exc.args = (arg0,) + args[1:]
- raise
-
- class PecanApplication(WSGIApplication):
-
- def init(self, parser, opts, args):
- if len(args) != 1:
- parser.error("No configuration file was specified.")
-
- self.cfgfname = os.path.normpath(
- os.path.join(os.getcwd(), args[0])
- )
- self.cfgfname = os.path.abspath(self.cfgfname)
- if not os.path.exists(self.cfgfname):
- parser.error("Config file not found: %s" % self.cfgfname)
-
- from pecan.configuration import _runtime_conf, set_config
- set_config(self.cfgfname, overwrite=True)
-
- # If available, use the host and port from the pecan config file
- cfg = {}
- if _runtime_conf.get('server'):
- server = _runtime_conf['server']
- if hasattr(server, 'host') and hasattr(server, 'port'):
- cfg['bind'] = '%s:%s' % (
- server.host, server.port
- )
- return cfg
-
- def load(self):
- from pecan.deploy import deploy
- return deploy(self.cfgfname)
-
- PecanApplication("%(prog)s [OPTIONS] config.py").run()
-
-
-class PecanWSGIRequestHandler(WSGIRequestHandler, object):
- """
- A wsgiref request handler class that allows actual log output depending on
- the application configuration.
- """
-
- def __init__(self, *args, **kwargs):
- # We set self.path to avoid crashes in log_message() on unsupported
- # requests (like "OPTIONS").
- self.path = ''
- super(PecanWSGIRequestHandler, self).__init__(*args, **kwargs)
-
- def log_message(self, format, *args):
- """
- overrides the ``log_message`` method from the wsgiref server so that
- normal logging works with whatever configuration the application has
- been set to.
-
- Levels are inferred from the HTTP status code, 4XX codes are treated as
- warnings, 5XX as errors and everything else as INFO level.
- """
- code = args[1][0]
- levels = {
- '4': 'warning',
- '5': 'error'
- }
-
- log_handler = getattr(logger, levels.get(code, 'info'))
- log_handler(format % args)
diff --git a/pecan/commands/shell.py b/pecan/commands/shell.py
deleted file mode 100644
index 968ded0..0000000
--- a/pecan/commands/shell.py
+++ /dev/null
@@ -1,177 +0,0 @@
-"""
-Shell command for Pecan.
-"""
-from pecan.commands import BaseCommand
-from webtest import TestApp
-from warnings import warn
-import sys
-
-
-class NativePythonShell(object):
- """
- Open an interactive python shell with the Pecan app loaded.
- """
-
- @classmethod
- def invoke(cls, ns, banner): # pragma: nocover
- """
- :param ns: local namespace
- :param banner: interactive shell startup banner
-
- Embed an interactive native python shell.
- """
- import code
- py_prefix = sys.platform.startswith('java') and 'J' or 'P'
- shell_banner = 'Pecan Interactive Shell\n%sython %s\n\n' % \
- (py_prefix, sys.version)
- shell = code.InteractiveConsole(locals=ns)
- try:
- import readline # noqa
- except ImportError:
- pass
- shell.interact(shell_banner + banner)
-
-
-class IPythonShell(object):
- """
- Open an interactive ipython shell with the Pecan app loaded.
- """
-
- @classmethod
- def invoke(cls, ns, banner): # pragma: nocover
- """
- :param ns: local namespace
- :param banner: interactive shell startup banner
-
- Embed an interactive ipython shell.
- Try the InteractiveShellEmbed API first, fall back on
- IPShellEmbed for older IPython versions.
- """
- try:
- from IPython.frontend.terminal.embed import (
- InteractiveShellEmbed
- )
- # try and load their default profile
- from IPython.frontend.terminal.ipapp import (
- load_default_config
- )
- config = load_default_config()
- shell = InteractiveShellEmbed(config=config, banner2=banner)
- shell(local_ns=ns)
- except ImportError:
- # Support for the IPython <= 0.10 shell API
- from IPython.Shell import IPShellEmbed
- shell = IPShellEmbed(argv=[])
- shell.set_banner(shell.IP.BANNER + '\n\n' + banner)
- shell(local_ns=ns, global_ns={})
-
-
-class BPythonShell(object):
- """
- Open an interactive bpython shell with the Pecan app loaded.
- """
-
- @classmethod
- def invoke(cls, ns, banner): # pragma: nocover
- """
- :param ns: local namespace
- :param banner: interactive shell startup banner
-
- Embed an interactive bpython shell.
- """
- from bpython import embed
- embed(ns, ['-i'], banner)
-
-
-class ShellCommand(BaseCommand):
- """
- Open an interactive shell with the Pecan app loaded.
- Attempt to invoke the specified python shell flavor
- (ipython, bpython, etc.). Fall back on the native
- python shell if the requested flavor variance is not
- installed.
- """
-
- SHELLS = {
- 'python': NativePythonShell,
- 'ipython': IPythonShell,
- 'bpython': BPythonShell,
- }
-
- arguments = BaseCommand.arguments + ({
- 'name': ['--shell', '-s'],
- 'help': 'which Python shell to use',
- 'choices': SHELLS.keys(),
- 'default': 'python'
- },)
-
- def run(self, args):
- """
- Load the pecan app, prepare the locals, sets the
- banner, and invokes the python shell.
- """
- super(ShellCommand, self).run(args)
-
- # 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
- from pecan import abort, conf, redirect, request, response
- locs['abort'] = abort
- locs['conf'] = conf
- locs['redirect'] = redirect
- locs['request'] = request
- locs['response'] = response
-
- # 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')
- )
- banner += ' %-10s - Models from %s\n' % ('model', model_name)
-
- self.invoke_shell(locs, banner)
-
- def invoke_shell(self, locs, banner):
- """
- Invokes the appropriate flavor of the python shell.
- Falls back on the native python shell if the requested
- flavor (ipython, bpython,etc) is not installed.
- """
- shell = self.SHELLS[self.args.shell]
- try:
- shell().invoke(locs, banner)
- except ImportError as e:
- warn((
- "%s is not installed, `%s`, "
- "falling back to native shell") % (self.args.shell, e),
- RuntimeWarning
- )
- if shell == NativePythonShell:
- raise
- NativePythonShell().invoke(locs, banner)
-
- def load_model(self, config):
- """
- Load the model extension module
- """
- for package_name in getattr(config.app, 'modules', []):
- module = __import__(package_name, fromlist=['model'])
- if hasattr(module, 'model'):
- return module.model
- return None
diff --git a/pecan/compat/__init__.py b/pecan/compat/__init__.py
deleted file mode 100644
index c929f58..0000000
--- a/pecan/compat/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import inspect
-
-import six
-
-if six.PY3:
- import urllib.parse as urlparse
- from urllib.parse import quote, unquote_plus
- from urllib.request import urlopen, URLError
- from html import escape
- izip = zip
-else:
- import urlparse # noqa
- from urllib import quote, unquote_plus # noqa
- from urllib2 import urlopen, URLError # noqa
- from cgi import escape # noqa
- from itertools import izip
-
-
-def is_bound_method(ob):
- return inspect.ismethod(ob) and six.get_method_self(ob) is not None
diff --git a/pecan/configuration.py b/pecan/configuration.py
deleted file mode 100644
index 6260723..0000000
--- a/pecan/configuration.py
+++ /dev/null
@@ -1,254 +0,0 @@
-import re
-import inspect
-import os
-import sys
-
-import six
-
-if six.PY3:
- from importlib.machinery import SourceFileLoader
-else:
- import imp
-
-
-IDENTIFIER = re.compile(r'[a-z_](\w)*$', re.IGNORECASE)
-
-DEFAULT = {
- # Server Specific Configurations
- 'server': {
- 'port': '8080',
- 'host': '0.0.0.0'
- },
-
- # Pecan Application Configurations
- 'app': {
- 'root': None,
- 'modules': [],
- 'static_root': 'public',
- 'template_path': '',
- 'force_canonical': True
- }
-}
-
-
-class ConfigDict(dict):
- pass
-
-
-class Config(object):
- '''
- Base class for Pecan configurations.
-
- 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.
- '''
-
- def __init__(self, conf_dict={}, filename=''):
-
- self.__values__ = {}
- self.__file__ = filename
- self.update(conf_dict)
-
- def empty(self):
- self.__values__ = {}
-
- def update(self, conf_dict):
- '''
- Updates this configuration with a dictionary.
-
- :param conf_dict: A python dictionary to update this configuration
- with.
- '''
-
- if isinstance(conf_dict, dict):
- iterator = six.iteritems(conf_dict)
- else:
- iterator = iter(conf_dict)
-
- for k, v in iterator:
- if not IDENTIFIER.match(k):
- raise ValueError('\'%s\' is not a valid indentifier' % k)
-
- cur_val = self.__values__.get(k)
-
- if isinstance(cur_val, Config):
- cur_val.update(conf_dict[k])
- else:
- self[k] = conf_dict[k]
-
- def get(self, attribute, default=None):
- try:
- return self[attribute]
- except KeyError:
- return default
-
- def __dictify__(self, obj, prefix):
- '''
- Private helper method for to_dict.
- '''
- for k, v in obj.copy().items():
- if prefix:
- del obj[k]
- k = "%s%s" % (prefix, k)
- if isinstance(v, Config):
- v = self.__dictify__(dict(v), prefix)
- obj[k] = v
- return obj
-
- def to_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
- returned dictonary.
- '''
-
- conf_obj = dict(self)
- return self.__dictify__(conf_obj, prefix)
-
- def __getattr__(self, name):
- try:
- return self.__values__[name]
- except KeyError:
- msg = "'pecan.conf' object has no attribute '%s'" % name
- raise AttributeError(msg)
-
- def __getitem__(self, key):
- return self.__values__[key]
-
- def __setitem__(self, key, value):
- if isinstance(value, dict) and not isinstance(value, ConfigDict):
- if value.get('__force_dict__'):
- del value['__force_dict__']
- self.__values__[key] = ConfigDict(value)
- else:
- self.__values__[key] = Config(value, filename=self.__file__)
- elif isinstance(value, six.string_types) and '%(confdir)s' in value:
- confdir = os.path.dirname(self.__file__) or os.getcwd()
- self.__values__[key] = value.replace('%(confdir)s', confdir)
- else:
- self.__values__[key] = value
-
- def __iter__(self):
- return six.iteritems(self.__values__)
-
- 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.
- """
- return list(self.__values__.keys())
-
- def __repr__(self):
- return 'Config(%s)' % str(self.__values__)
-
-
-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 = {}
- if not os.path.isfile(abspath):
- raise RuntimeError('`%s` is not a file.' % abspath)
-
- # First, make sure the code will actually compile (and has no SyntaxErrors)
- with open(abspath, 'rb') as f:
- compiled = compile(f.read(), abspath, 'exec')
-
- # Next, attempt to actually import the file as a module.
- # This provides more verbose import-related error reporting than exec()
- absname, _ = os.path.splitext(abspath)
- basepath, module_name = absname.rsplit(os.sep, 1)
- if six.PY3:
- SourceFileLoader(module_name, abspath).load_module(module_name)
- else:
- imp.load_module(
- module_name,
- *imp.find_module(module_name, [basepath])
- )
-
- # If we were able to import as a module, actually exec the compiled code
- exec(compiled, globals(), conf_dict)
- conf_dict['__file__'] = abspath
-
- return conf_from_dict(conf_dict)
-
-
-def get_conf_path_from_env():
- '''
- If the ``PECAN_CONFIG`` environment variable exists and it points to
- a valid path it will return that, otherwise it will raise
- a ``RuntimeError``.
- '''
- config_path = os.environ.get('PECAN_CONFIG')
- if not config_path:
- error = "PECAN_CONFIG is not set and " \
- "no config file was passed as an argument."
- elif not os.path.isfile(config_path):
- error = "PECAN_CONFIG was set to an invalid path: %s" % config_path
- else:
- return config_path
-
- raise RuntimeError(error)
-
-
-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 six.iteritems(conf_dict):
- 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``.
- '''
- return conf_from_dict(DEFAULT)
-
-
-def set_config(config, overwrite=False):
- '''
- Updates the global configuration.
-
- :param config: Can be a dictionary containing configuration, or a string
- which represents a (relative) configuration filename.
- '''
-
- if config is None:
- config = get_conf_path_from_env()
-
- # must be after the fallback other a bad fallback will incorrectly clear
- if overwrite is True:
- _runtime_conf.empty()
-
- if isinstance(config, six.string_types):
- config = conf_from_file(config)
- _runtime_conf.update(config)
- if config.__file__:
- _runtime_conf.__file__ = config.__file__
- elif isinstance(config, dict):
- _runtime_conf.update(conf_from_dict(config))
- else:
- raise TypeError('%s is neither a dictionary or a string.' % config)
-
-
-_runtime_conf = initconf()
diff --git a/pecan/core.py b/pecan/core.py
deleted file mode 100644
index 854d948..0000000
--- a/pecan/core.py
+++ /dev/null
@@ -1,854 +0,0 @@
-try:
- from simplejson import dumps, loads
-except ImportError: # pragma: no cover
- from json import dumps, loads # noqa
-from inspect import Arguments
-from itertools import chain, tee
-from mimetypes import guess_type, add_type
-from os.path import splitext
-import logging
-import operator
-import sys
-import types
-
-import six
-
-from webob import (Request as WebObRequest, Response as WebObResponse, exc,
- acceptparse)
-from webob.multidict import NestedMultiDict
-
-from .compat import urlparse, izip
-from .secure import handle_security
-from .templating import RendererFactory
-from .routing import lookup_controller, NonCanonicalPath
-from .util import _cfg, encode_if_needed, getargspec
-from .middleware.recursive import ForwardRequestException
-
-if six.PY3:
- from .compat import is_bound_method as ismethod
-else:
- from inspect import ismethod
-
-# make sure that json is defined in mimetypes
-add_type('application/json', '.json', True)
-
-state = None
-logger = logging.getLogger(__name__)
-
-
-class RoutingState(object):
-
- def __init__(self, request, response, app, hooks=[], controller=None,
- arguments=None):
- self.request = request
- self.response = response
- self.app = app
- self.hooks = hooks
- self.controller = controller
- self.arguments = arguments
-
-
-class Request(WebObRequest):
-
- def __getattribute__(self, name):
- try:
- return WebObRequest.__getattribute__(self, name)
- except UnicodeDecodeError as e:
- logger.exception(e)
- abort(400)
-
-
-class Response(WebObResponse):
- pass
-
-
-def proxy(key):
- class ObjectProxy(object):
-
- explanation_ = AttributeError(
- "`pecan.state` is not bound to a context-local context.\n"
- "Ensure that you're accessing `pecan.request` or `pecan.response` "
- "from within the context of a WSGI `__call__` and that "
- "`use_context_locals` = True."
- )
-
- def __getattr__(self, attr):
- try:
- obj = getattr(state, key)
- except AttributeError:
- raise self.explanation_
- 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)
-
- def __dir__(self):
- obj = getattr(state, key)
- return dir(obj)
-
- return ObjectProxy()
-
-
-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.func_closure
- '''
-
- request.pecan['override_template'] = template
- if 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.
- '''
-
- # If there is a traceback, we need to catch it for a re-raise
- try:
- _, _, traceback = sys.exc_info()
- webob_exception = exc.status_map[status_code](
- detail=detail,
- headers=headers,
- comment=comment,
- **kw
- )
-
- if six.PY3:
- raise webob_exception.with_traceback(traceback)
- else:
- # Using exec to avoid python 3 parsers from crashing
- exec('raise webob_exception, None, traceback')
- finally:
- # Per the suggestion of the Python docs, delete the traceback object
- del traceback
-
-
-def redirect(location=None, internal=False, code=None, headers={},
- add_slash=False, request=None):
- '''
- 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 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 request: The :class:`pecan.Request` instance to use.
- '''
- request = request or state.request
-
- if add_slash:
- if location is None:
- split_url = list(urlparse.urlsplit(request.url))
- new_proto = request.environ.get(
- 'HTTP_X_FORWARDED_PROTO', split_url[0]
- )
- split_url[0] = new_proto
- else:
- split_url = urlparse.urlsplit(location)
-
- split_url[2] = split_url[2].rstrip('/') + '/'
- location = urlparse.urlunsplit(split_url)
-
- if not headers:
- headers = {}
- if internal:
- if code is not None:
- raise ValueError('Cannot specify a code for internal redirects')
- request.environ['pecan.recursive.context'] = request.context
- raise ForwardRequestException(location)
- if code is None:
- code = 302
- raise exc.status_map[code](location=location, headers=headers)
-
-
-def render(template, namespace, app=None):
- '''
- 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 app: The instance of :class:`pecan.Pecan` to use
- '''
- app = app or state.app
- return app.render(template, namespace)
-
-
-def load_app(config, **kwargs):
- '''
- Used to load a ``Pecan`` application and its environment based on passed
- configuration.
-
- :param config: Can be a dictionary containing configuration, a string which
- represents a (relative) configuration filename
-
- returns a pecan.Pecan object
- '''
- from .configuration import _runtime_conf, set_config
- set_config(config, overwrite=True)
-
- for package_name in getattr(_runtime_conf.app, 'modules', []):
- module = __import__(package_name, fromlist=['app'])
- if hasattr(module, 'app') and hasattr(module.app, 'setup_app'):
- app = module.app.setup_app(_runtime_conf, **kwargs)
- app.config = _runtime_conf
- return app
- raise RuntimeError(
- 'No app.setup_app found in any of the configured app.modules'
- )
-
-
-class PecanBase(object):
-
- SIMPLEST_CONTENT_TYPES = (
- ['text/html'],
- ['text/plain']
- )
-
- def __init__(self, root, default_renderer='mako',
- template_path='templates', hooks=lambda: [],
- custom_renderers={}, extra_template_vars={},
- force_canonical=True, guess_content_type_from_ext=True,
- context_local_factory=None, request_cls=Request,
- response_cls=Response, **kw):
- if isinstance(root, six.string_types):
- root = self.__translate_root__(root)
-
- self.root = root
- self.request_cls = request_cls
- self.response_cls = response_cls
- self.renderers = RendererFactory(custom_renderers, extra_template_vars)
- self.default_renderer = default_renderer
-
- # pre-sort these so we don't have to do it per-request
- if six.callable(hooks):
- hooks = hooks()
-
- self.hooks = list(sorted(
- hooks,
- key=operator.attrgetter('priority')
- ))
- self.template_path = template_path
- self.force_canonical = force_canonical
- self.guess_content_type_from_ext = guess_content_type_from_ext
-
- def __translate_root__(self, item):
- '''
- Creates a root controller instance from a string root, e.g.,
-
- > __translate_root__("myproject.controllers.RootController")
- myproject.controllers.RootController()
-
- :param item: The string to the item
- '''
-
- if '.' in item:
- parts = item.split('.')
- name = '.'.join(parts[:-1])
- fromlist = parts[-1:]
-
- module = __import__(name, fromlist=fromlist)
- kallable = getattr(module, parts[-1])
- msg = "%s does not represent a callable class or function."
- if not six.callable(kallable):
- raise TypeError(msg % item)
- return kallable()
-
- raise ImportError('No item named %s' % item)
-
- def route(self, req, 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, req)
- return node, remainder
- except NonCanonicalPath as e:
- if self.force_canonical and \
- not _cfg(e.controller).get('accept_noncanonical', False):
- if req.method == 'POST':
- 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" %
- (req.pecan['routing_path'],
- req.pecan['routing_path'])
- )
- redirect(code=302, add_slash=True, request=req)
- 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.
- '''
-
- controller_hooks = []
- if controller:
- controller_hooks = _cfg(controller).get('hooks', [])
- if controller_hooks:
- return list(
- sorted(
- chain(controller_hooks, self.hooks),
- key=operator.attrgetter('priority')
- )
- )
- return self.hooks
-
- def handle_hooks(self, hooks, 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 \*args: Arguments to pass to the hooks.
- '''
- if hook_type not in ['before', 'on_route']:
- hooks = reversed(hooks)
-
- for hook in hooks:
- result = getattr(hook, hook_type)(*args)
- # on_error hooks can choose to return a Response, which will
- # be used instead of the standard error pages.
- if hook_type == 'on_error' and isinstance(result, WebObResponse):
- return result
-
- def get_args(self, state, all_params, remainder, argspec, im_self):
- '''
- Determines the arguments for a controller based upon parameters
- passed the argument specification for the controller.
- '''
- args = []
- varargs = []
- kwargs = dict()
- valid_args = argspec.args[:]
- if ismethod(state.controller) or im_self:
- valid_args.pop(0) # pop off `self`
- pecan_state = state.request.pecan
-
- remainder = [x for x in remainder if x]
-
- if im_self is not None:
- args.append(im_self)
-
- # grab the routing args from nested REST controllers
- if 'routing_args' in pecan_state:
- remainder = pecan_state['routing_args'] + list(remainder)
- del pecan_state['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 [i for i in remainder if i]:
- if not argspec[1]:
- abort(404)
- varargs.extend(remainder)
-
- # get the default positional arguments
- if argspec[3]:
- defaults = dict(izip(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:
- args.append(all_params.pop(name))
- elif name in defaults:
- args.append(defaults[name])
- else:
- break
-
- # handle wildcard GET/POST params
- if argspec[2]:
- for name, value in six.iteritems(all_params):
- if name not in argspec[0]:
- kwargs[encode_if_needed(name)] = value
-
- return args, varargs, kwargs
-
- def render(self, template, namespace):
- if template == 'json':
- renderer = self.renderers.get('json', self.template_path)
- elif ':' in template:
- renderer_name, template = template.split(':', 1)
- renderer = self.renderers.get(
- renderer_name,
- self.template_path
- )
- else:
- renderer = self.renderers.get(
- self.default_renderer,
- self.template_path
- )
- return renderer.render(template, namespace)
-
- def find_controller(self, state):
- '''
- The main request handler for Pecan applications.
- '''
- # get a sorted list of hooks, by priority (no controller hooks yet)
- req = state.request
- pecan_state = req.pecan
-
- # store the routing path for the current application to allow hooks to
- # modify it
- pecan_state['routing_path'] = path = req.path_info
-
- # handle "on_route" hooks
- self.handle_hooks(self.hooks, 'on_route', state)
-
- # lookup the controller, respecting content-type as requested
- # by the file extension on the URI
- pecan_state['extension'] = None
-
- # attempt to guess the content type based on the file extension
- if self.guess_content_type_from_ext \
- and not pecan_state['content_type'] \
- and '.' in path:
- new_path, extension = splitext(path)
-
- # preface with a letter to ensure compat for 2.5
- potential_type = guess_type('x' + extension)[0]
-
- if potential_type is not None:
- path = new_path
- pecan_state['extension'] = extension
- pecan_state['content_type'] = potential_type
-
- controller, remainder = self.route(req, self.root, path)
- cfg = _cfg(controller)
-
- if cfg.get('generic_handler'):
- raise exc.HTTPNotFound
-
- # handle generic controllers
- im_self = None
- if cfg.get('generic'):
- im_self = six.get_method_self(controller)
- handlers = cfg['generic_handlers']
- controller = handlers.get(req.method, handlers['DEFAULT'])
- handle_security(controller, im_self)
- 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
- content_types = cfg.get('content_types', {})
- if not pecan_state['content_type']:
- # attempt to find a best match based on accept headers (if they
- # exist)
- accept = getattr(req.accept, 'header_value', '*/*')
- if accept == '*/*' or (
- accept.startswith('text/html,') and
- list(content_types.keys()) in self.SIMPLEST_CONTENT_TYPES):
- pecan_state['content_type'] = cfg.get(
- 'content_type',
- 'text/html'
- )
- else:
- best_default = acceptparse.MIMEAccept(
- accept
- ).best_match(
- content_types.keys()
- )
-
- if best_default is None:
- msg = "Controller '%s' defined does not support " + \
- "content_type '%s'. Supported type(s): %s"
- logger.error(
- msg % (
- controller.__name__,
- pecan_state['content_type'],
- content_types.keys()
- )
- )
- raise exc.HTTPNotAcceptable()
-
- pecan_state['content_type'] = best_default
- elif cfg.get('content_type') is not None and \
- pecan_state['content_type'] not in content_types:
-
- msg = "Controller '%s' defined does not support content_type " + \
- "'%s'. Supported type(s): %s"
- logger.error(
- msg % (
- controller.__name__,
- pecan_state['content_type'],
- content_types.keys()
- )
- )
- raise exc.HTTPNotFound
-
- # fetch any parameters
- if req.method == 'GET':
- params = req.GET
- elif req.content_type in ('application/json',
- 'application/javascript'):
- try:
- if not isinstance(req.json, dict):
- raise TypeError('%s is not a dict' % req.json)
- params = NestedMultiDict(req.GET, req.json)
- except (TypeError, ValueError):
- params = req.params
- else:
- params = req.params
-
- # fetch the arguments for the controller
- args, varargs, kwargs = self.get_args(
- state,
- params.mixed(),
- remainder,
- cfg['argspec'],
- im_self
- )
- state.arguments = Arguments(args, varargs, kwargs)
-
- # handle "before" hooks
- self.handle_hooks(self.determine_hooks(controller), 'before', state)
-
- return controller, args + varargs, kwargs
-
- def invoke_controller(self, controller, args, kwargs, state):
- '''
- The main request handler for Pecan applications.
- '''
- cfg = _cfg(controller)
- content_types = cfg.get('content_types', {})
- req = state.request
- resp = state.response
- pecan_state = req.pecan
-
- # If a keyword is supplied via HTTP GET or POST arguments, but the
- # function signature does not allow it, just drop it (rather than
- # generating a TypeError).
- argspec = getargspec(controller)
- keys = kwargs.keys()
- for key in keys:
- if key not in argspec.args and not argspec.keywords:
- kwargs.pop(key)
-
- # get the result from the controller
- result = controller(*args, **kwargs)
-
- # a controller can return the response object which means they've taken
- # care of filling it out
- if result is response:
- return
- elif isinstance(result, WebObResponse):
- state.response = result
- return
-
- raw_namespace = result
-
- # pull the template out based upon content type and handle overrides
- template = content_types.get(pecan_state['content_type'])
-
- # check if for controller override of template
- template = pecan_state.get('override_template', template)
- if template is None and cfg['explicit_content_type'] is False:
- if self.default_renderer == 'json':
- template = 'json'
-
- pecan_state['content_type'] = pecan_state.get(
- 'override_content_type',
- pecan_state['content_type']
- )
-
- # if there is a template, render it
- if template:
- if template == 'json':
- pecan_state['content_type'] = 'application/json'
- result = self.render(template, result)
-
- # If we are in a test request put the namespace where it can be
- # accessed directly
- if req.environ.get('paste.testing'):
- testing_variables = req.environ['paste.testing_variables']
- testing_variables['namespace'] = raw_namespace
- testing_variables['template_name'] = template
- testing_variables['controller_output'] = result
-
- # set the body content
- if result and isinstance(result, six.text_type):
- resp.text = result
- elif result:
- resp.body = result
-
- if pecan_state['content_type']:
- # set the content type
- resp.content_type = pecan_state['content_type']
-
- def _handle_empty_response_body(self, state):
- # Enforce HTTP 204 for responses which contain no body
- if state.response.status_int == 200:
- # If the response is a generator...
- if isinstance(state.response.app_iter, types.GeneratorType):
- # Split the generator into two so we can peek at one of them
- # and determine if there is any response body content
- a, b = tee(state.response.app_iter)
- try:
- next(a)
- except StopIteration:
- # If we hit StopIteration, the body is empty
- state.response.status = 204
- finally:
- state.response.app_iter = b
- else:
- text = None
- if state.response.charset:
- # `response.text` cannot be accessed without a valid
- # charset (because we don't know which encoding to use)
- try:
- text = state.response.text
- except UnicodeDecodeError:
- # If a valid charset is not specified, don't bother
- # trying to guess it (because there's obviously
- # content, so we know this shouldn't be a 204)
- pass
- if not any((state.response.body, text)):
- state.response.status = 204
-
- if state.response.status_int in (204, 304):
- state.response.content_type = None
-
- def __call__(self, environ, start_response):
- '''
- Implements the WSGI specification for Pecan applications, utilizing
- ``WebOb``.
- '''
-
- # create the request and response object
- req = self.request_cls(environ)
- resp = self.response_cls()
- state = RoutingState(req, resp, self)
- environ['pecan.locals'] = {
- 'request': req,
- 'response': resp
- }
- controller = None
-
- # handle the request
- try:
- # add context and environment to the request
- req.context = environ.get('pecan.recursive.context', {})
- req.pecan = dict(content_type=None)
-
- controller, args, kwargs = self.find_controller(state)
- self.invoke_controller(controller, args, kwargs, state)
- except Exception as e:
- # if this is an HTTP Exception, set it as the response
- if isinstance(e, exc.HTTPException):
- # if the client asked for JSON, do our best to provide it
- best_match = acceptparse.MIMEAccept(
- getattr(req.accept, 'header_value', '*/*')
- ).best_match(('text/plain', 'text/html', 'application/json'))
- state.response = e
- if best_match == 'application/json':
- json_body = dumps({
- 'code': e.status_int,
- 'title': e.title,
- 'description': e.detail
- })
- if isinstance(json_body, six.text_type):
- e.text = json_body
- else:
- e.body = json_body
- state.response.content_type = best_match
- environ['pecan.original_exception'] = e
-
- # if this is not an internal redirect, run error hooks
- on_error_result = None
- if not isinstance(e, ForwardRequestException):
- on_error_result = self.handle_hooks(
- self.determine_hooks(state.controller),
- 'on_error',
- state,
- e
- )
-
- # if the on_error handler returned a Response, use it.
- if isinstance(on_error_result, WebObResponse):
- state.response = on_error_result
- else:
- if not isinstance(e, exc.HTTPException):
- raise
-
- # if this is an HTTP 405, attempt to specify an Allow header
- if isinstance(e, exc.HTTPMethodNotAllowed) and controller:
- allowed_methods = _cfg(controller).get('allowed_methods', [])
- if allowed_methods:
- state.response.allow = sorted(allowed_methods)
- finally:
- # handle "after" hooks
- self.handle_hooks(
- self.determine_hooks(state.controller), 'after', state
- )
-
- self._handle_empty_response_body(state)
-
- # get the response
- return state.response(environ, start_response)
-
-
-class ExplicitPecan(PecanBase):
-
- def get_args(self, state, all_params, remainder, argspec, im_self):
- # When comparing the argspec of the method to GET/POST params,
- # ignore the implicit (req, resp) at the beginning of the function
- # signature
- if hasattr(state.controller, '__self__'):
- _repr = '.'.join((
- state.controller.__self__.__class__.__module__,
- state.controller.__self__.__class__.__name__,
- state.controller.__name__
- ))
- else:
- _repr = '.'.join((
- state.controller.__module__,
- state.controller.__name__
- ))
-
- signature_error = TypeError(
- 'When `use_context_locals` is `False`, pecan passes an explicit '
- 'reference to the request and response as the first two arguments '
- 'to the controller.\nChange the `%s` signature to accept exactly '
- '2 initial arguments (req, resp)' % _repr
- )
- try:
- positional = argspec.args[:]
- positional.pop(1) # req
- positional.pop(1) # resp
- argspec = argspec._replace(args=positional)
- except IndexError:
- raise signature_error
-
- args, varargs, kwargs = super(ExplicitPecan, self).get_args(
- state, all_params, remainder, argspec, im_self
- )
-
- if ismethod(state.controller):
- args = [state.request, state.response] + args
- else:
- # generic controllers have an explicit self *first*
- # (because they're decorated functions, not instance methods)
- args[1:1] = [state.request, state.response]
- return args, varargs, kwargs
-
-
-class Pecan(PecanBase):
- '''
- Pecan application object. Generally created using ``pecan.make_app``,
- rather than being created manually.
-
- 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 template rendering engine to use.
- Defaults to mako.
- :param template_path: A relative file system path (from the project root)
- where template files live. Defaults to 'templates'.
- :param hooks: A callable which returns a list of
- :class:`pecan.hooks.PecanHook`
- :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 guess_content_type_from_ext: A boolean indicating if this project
- should use the extension in the URL for guessing
- the content type to return.
- :param use_context_locals: When `True`, `pecan.request` and
- `pecan.response` will be available as
- thread-local references.
- :param request_cls: Can be used to specify a custom `pecan.request` object.
- Defaults to `pecan.Request`.
- :param response_cls: Can be used to specify a custom `pecan.response`
- object. Defaults to `pecan.Response`.
- '''
-
- def __new__(cls, *args, **kw):
- if kw.get('use_context_locals') is False:
- self = super(Pecan, cls).__new__(ExplicitPecan, *args, **kw)
- self.__init__(*args, **kw)
- return self
- return super(Pecan, cls).__new__(cls)
-
- def __init__(self, *args, **kw):
- self.init_context_local(kw.get('context_local_factory'))
- super(Pecan, self).__init__(*args, **kw)
-
- def __call__(self, environ, start_response):
- try:
- state.hooks = []
- state.app = self
- state.controller = None
- state.arguments = None
- return super(Pecan, self).__call__(environ, start_response)
- finally:
- del state.hooks
- del state.request
- del state.response
- del state.controller
- del state.arguments
- del state.app
-
- def init_context_local(self, local_factory):
- global state
- if local_factory is None:
- from threading import local as local_factory
- state = local_factory()
-
- def find_controller(self, _state):
- state.request = _state.request
- state.response = _state.response
- controller, args, kw = super(Pecan, self).find_controller(_state)
- state.controller = controller
- state.arguments = _state.arguments
- return controller, args, kw
-
- def handle_hooks(self, hooks, *args, **kw):
- state.hooks = hooks
- return super(Pecan, self).handle_hooks(hooks, *args, **kw)
diff --git a/pecan/decorators.py b/pecan/decorators.py
deleted file mode 100644
index 15808d4..0000000
--- a/pecan/decorators.py
+++ /dev/null
@@ -1,175 +0,0 @@
-from inspect import getmembers, isclass, ismethod, isfunction
-
-import six
-
-from .util import _cfg, getargspec
-
-__all__ = [
- 'expose', 'transactional', 'accept_noncanonical', 'after_commit',
- 'after_rollback'
-]
-
-
-def when_for(controller):
- def when(method=None, **kw):
- def decorate(f):
- _cfg(f)['generic_handler'] = True
- controller._pecan['generic_handlers'][method.upper()] = f
- controller._pecan['allowed_methods'].append(method.upper())
- expose(**kw)(f)
- return f
- return decorate
- return when
-
-
-def expose(template=None,
- generic=False,
- route=None,
- **kw):
-
- '''
- 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 content_type: The content-type to use for this template.
- :param generic: A boolean which flags this as a "generic" controller,
- which uses generic functions based upon
- ``functools.singledispatch`` generic functions. Allows you
- to split a single controller into multiple paths based upon
- HTTP method.
- :param route: The name of the path segment to match (excluding
- separator characters, like `/`). Defaults to the name of
- the function itself, but this can be used to resolve paths
- which are not valid Python function names, e.g., if you
- wanted to route a function to `some-special-path'.
- '''
-
- content_type = kw.get('content_type', 'text/html')
-
- if template == 'json':
- content_type = 'application/json'
-
- def decorate(f):
- # flag the method as exposed
- f.exposed = True
-
- cfg = _cfg(f)
- cfg['explicit_content_type'] = 'content_type' in kw
-
- if route:
- # This import is here to avoid a circular import issue
- from pecan import routing
- if cfg.get('generic_handler'):
- raise ValueError(
- 'Path segments cannot be overridden for generic '
- 'controllers.'
- )
- routing.route(route, f)
-
- # set a "pecan" attribute, where we will store details
- cfg['content_type'] = content_type
- cfg.setdefault('template', []).append(template)
- cfg.setdefault('content_types', {})[content_type] = template
-
- # handle generic controllers
- if generic:
- if f.__name__ in ('_default', '_lookup', '_route'):
- raise ValueError(
- 'The special method %s cannot be used as a generic '
- 'controller' % f.__name__
- )
- cfg['generic'] = True
- cfg['generic_handlers'] = dict(DEFAULT=f)
- cfg['allowed_methods'] = []
- f.when = when_for(f)
-
- # store the arguments for this controller method
- cfg['argspec'] = getargspec(f)
-
- 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.
- '''
-
- def deco(f):
- if isclass(f):
- for meth in [
- m[1] for m in getmembers(f)
- if (isfunction if six.PY3 else 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
- return f
- return deco
-
-
-def after_action(action_type, action):
- '''
- If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you
- 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. '''
-
- if action_type not in ('commit', 'rollback'):
- 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.
- '''
- return after_action('commit', action)
-
-
-def after_rollback(action):
- '''
- If utilizing the :mod:`pecan.hooks` ``TransactionHook``, allows you
- 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.
- '''
- return after_action('rollback', action)
-
-
-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
deleted file mode 100644
index be6b359..0000000
--- a/pecan/deploy.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from .core import load_app
-
-
-def deploy(config):
- """
- Given a config (dictionary of relative filename), returns a configured
- WSGI app.
- """
- return load_app(config)
diff --git a/pecan/ext/__init__.py b/pecan/ext/__init__.py
deleted file mode 100644
index c768f9c..0000000
--- a/pecan/ext/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def install():
- from pecan.extensions import PecanExtensionImporter
- PecanExtensionImporter().install()
-
-install()
-del install
diff --git a/pecan/extensions.py b/pecan/extensions.py
deleted file mode 100644
index 87b9925..0000000
--- a/pecan/extensions.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import sys
-import pkg_resources
-import inspect
-import logging
-
-log = logging.getLogger(__name__)
-
-
-class PecanExtensionMissing(ImportError):
- pass
-
-
-class PecanExtensionImporter(object):
- """
- Short circuits imports for extensions.
-
- This is used in combination with ``pecan.ext`` so that when a user does
- ``from pecan.ext import foo``, it will attempt to map ``foo`` to a
- registered setuptools entry point in some other (Pecan extension) project.
-
- Conversely, an extension developer may define an entry point in his
- ``setup.py``, e.g.,
-
- setup(
- ...
- entry_points='''
- [pecan.extension]
- celery = pecancelery.lib.core
- '''
- )
-
- This is mostly for convenience and consistency. In this way, Pecan can
- maintain an ecosystem of extensions that share a common namespace,
- ``pecan.ext``, while still maintaining backwards compatibility for simple
- package names (e.g., ``pecancelery``).
- """
-
- extension_module = 'pecan.ext'
- prefix = extension_module + '.'
-
- def install(self):
- if self not in sys.meta_path:
- sys.meta_path.append(self)
-
- def __eq__(self, b):
- return self.__class__.__module__ == b.__class__.__module__ and \
- self.__class__.__name__ == b.__class__.__name__
-
- def __ne__(self, b):
- return not self.__eq__(b)
-
- def find_module(self, fullname, path=None):
- if fullname.startswith(self.prefix):
- return self
-
- def load_module(self, fullname):
- if fullname in sys.modules:
- return self
- extname = fullname.split(self.prefix)[1]
- module = self.find_module_for_extension(extname)
- realname = module.__name__
- try:
- __import__(realname)
- except ImportError:
- raise sys.exc_info()
- module = sys.modules[fullname] = sys.modules[realname]
- if '.' not in extname:
- setattr(sys.modules[self.extension_module], extname, module)
- return module
-
- def find_module_for_extension(self, name):
- for ep in pkg_resources.iter_entry_points('pecan.extension'):
- if ep.name != name:
- continue
- log.debug('%s loading extension %s', self.__class__.__name__, ep)
- module = ep.load()
- if not inspect.ismodule(module):
- log.debug('%s is not a module, skipping...' % module)
- continue
- return module
- raise PecanExtensionMissing(
- 'The `pecan.ext.%s` extension is not installed.' % name
- )
diff --git a/pecan/hooks.py b/pecan/hooks.py
deleted file mode 100644
index 0b666d3..0000000
--- a/pecan/hooks.py
+++ /dev/null
@@ -1,375 +0,0 @@
-import types
-import sys
-from inspect import getmembers
-
-import six
-from webob.exc import HTTPFound
-
-from .util import iscontroller, _cfg
-
-__all__ = [
- 'PecanHook', 'TransactionHook', 'HookController',
- 'RequestViewerHook'
-]
-
-
-def walk_controller(root_class, controller, hooks, seen=None):
- seen = seen or set()
- if type(controller) not in vars(six.moves.builtins).values():
- # Avoid recursion loops
- try:
- if controller in seen:
- return
- seen.add(controller)
- except TypeError:
- # If we discover an unhashable item (like a list), it's not
- # something that we want to traverse because it's not the sort of
- # thing we would add a hook to
- return
-
- for hook in getattr(controller, '__hooks__', []):
- # Append hooks from controller class definition
- hooks.add(hook)
-
- for name, value in getmembers(controller):
- if name == 'controller':
- continue
- if name.startswith('__') and name.endswith('__'):
- continue
-
- if iscontroller(value):
- for hook in hooks:
- value._pecan.setdefault('hooks', set()).add(hook)
- elif hasattr(value, '__class__'):
- # Skip non-exposed methods that are defined in parent classes;
- # they're internal implementation details of that class, and
- # not actual routable controllers, so we shouldn't bother
- # assigning hooks to them.
- if (
- isinstance(value, types.MethodType) and
- any(filter(lambda c: value.__func__ in c.__dict__.values(),
- value.im_class.mro()[1:]))
- ):
- continue
- walk_controller(root_class, value, hooks, seen)
-
-
-class HookControllerMeta(type):
- '''
- A base class for controllers that would like to specify hooks on
- their controller methods. Simply create a list of hook objects
- called ``__hooks__`` as a member of the controller's namespace.
- '''
-
- def __init__(cls, name, bases, dict_):
- hooks = set(dict_.get('__hooks__', []))
- for base in bases:
- # Add hooks from parent class and mixins
- for hook in getattr(base, '__hooks__', []):
- hooks.add(hook)
- walk_controller(cls, cls, hooks)
-
-
-HookController = HookControllerMeta(
- 'HookController',
- (object,),
- {'__doc__': ("A base class for controllers that would like to specify "
- "hooks on their controller methods. Simply create a list "
- "of hook objects called ``__hooks__`` as a class attribute "
- "of your controller.")}
-)
-
-
-class PecanHook(object):
- '''
- A base class for Pecan hooks. Inherit from this class to create your
- 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.
- '''
- return
-
-
-class TransactionHook(PecanHook):
- '''
- :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 clear: A callable that will clear your current context.
-
- A basic framework hook for supporting wrapping requests in
- transactions. By default, it will wrap all but ``GET`` and ``HEAD``
- 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):
-
- self.start = start
- self.start_ro = start_ro
- self.commit = commit
- self.rollback = rollback
- self.clear = clear
-
- def is_transactional(self, state):
- '''
- Decide if a request should be wrapped in a transaction, based
- 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)
- else:
- force_transactional = False
-
- if state.request.method not in ('GET', 'HEAD') or force_transactional:
- return True
- return False
-
- def on_route(self, state):
- state.request.error = False
- if self.is_transactional(state):
- state.request.transactional = True
- self.start()
- else:
- state.request.transactional = False
- self.start_ro()
-
- def before(self, state):
- if self.is_transactional(state) \
- and not getattr(state.request, 'transactional', False):
- self.clear()
- state.request.transactional = True
- self.start()
-
- def on_error(self, state, e):
- #
- # If we should ignore redirects,
- # (e.g., shouldn't consider them rollback-worthy)
- # don't set `state.request.error = True`.
- #
- trans_ignore_redirects = (
- state.request.method not in ('GET', 'HEAD')
- )
- if state.controller is not None:
- 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 getattr(state.request, 'transactional', False):
- action_name = None
- if state.request.error:
- action_name = 'after_rollback'
- self.rollback()
- else:
- action_name = 'after_commit'
- self.commit()
-
- #
- # If a controller was routed to, find any
- # after_* actions it may have registered, and perform
- # them.
- #
- if action_name:
- controller = getattr(state, 'controller', None)
- if controller is not None:
- actions = _cfg(controller).get(action_name, [])
- for action in actions:
- action()
-
- self.clear()
-
-
-class RequestViewerHook(PecanHook):
- '''
- :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)
- :param headers: Sets values to the X-HTTP headers
-
- 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.
-
- **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:
-
- * path
- * status
- * method
- * controller
- * params
- * hooks
-
- .. note::
- This key should always use a ``list`` of items to use.
-
- **blacklist**
-
- This key holds items that will be blacklisted based on ``url``. If
- there is a need to omit 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 omitted.
-
- .. note::
- This key should always use a ``list`` of items to use.
-
- 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):
-
- if not config:
- self.config = {'items': self.available}
- else:
- if config.__class__.__name__ == 'Config':
- self.config = config.to_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
-
- 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),
- }
-
- is_available = [
- i for i in self.items
- 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)
- ]
-
- if will_skip:
- return
-
- for request_info in is_available:
- try:
- value = responses.get(request_info)
- if not value:
- value = getattr(state.request, request_info)
- else:
- value = value(self, state)
- except Exception as e:
- value = e
-
- terminal.append('%-12s - %s\n' % (request_info, value))
- headers.append((request_info, value))
-
- if self.terminal:
- self.writer.write(''.join(terminal))
- self.writer.write('\n\n')
-
- if self.headers:
- for h in headers:
- key = str(h[0])
- value = str(h[1])
- name = 'X-Pecan-%s' % key
- state.response.headers[name] = value
-
- def get_controller(self, state):
- '''
- Retrieves the actual controller name from the application
- Specific to Pecan (not available in the request object)
- '''
- path = state.request.pecan['routing_path'].split('/')[1:]
- return state.controller.__str__().split()[2]
-
- def format_hooks(self, hooks):
- '''
- Tries to format the hook objects to be more readable
- Specific to Pecan (not available in the request object)
- '''
- 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
deleted file mode 100644
index 8eb310e..0000000
--- a/pecan/jsonify.py
+++ /dev/null
@@ -1,137 +0,0 @@
-try:
- from simplejson import JSONEncoder
-except ImportError: # pragma: no cover
- from json import JSONEncoder # noqa
-
-from datetime import datetime, date
-from decimal import Decimal
-
-# depending on the version WebOb might have 2 types of dicts
-try:
- # WebOb <= 1.1.1
- from webob.multidict import MultiDict, UnicodeMultiDict
- webob_dicts = (MultiDict, UnicodeMultiDict) # pragma: no cover
-except ImportError: # pragma no cover
- # WebOb >= 1.2
- from webob.multidict import MultiDict
- webob_dicts = (MultiDict,)
-
-import six
-try:
- from functools import singledispatch
-except ImportError: # pragma: no cover
- from singledispatch import singledispatch
-
-try:
- from sqlalchemy.engine.result import ResultProxy, RowProxy
-except ImportError: # pragma no cover
- try:
- from sqlalchemy.engine.base import ResultProxy, RowProxy
- except ImportError: # pragma no cover
- # dummy classes since we don't have SQLAlchemy installed
-
- class ResultProxy(object): # noqa
- pass
-
- class RowProxy(object): # noqa
- pass
-
-
-#
-# encoders
-#
-
-def is_saobject(obj):
- return hasattr(obj, '_sa_class_manager')
-
-
-class GenericJSON(JSONEncoder):
- '''
- Generic JSON encoder. Makes several attempts to correctly JSONify
- requested response objects.
- '''
- def default(self, obj):
- '''
- Converts an object and returns a ``JSON``-friendly structure.
-
- :param obj: object or structure to be converted into a
- ``JSON``-ifiable structure
-
- Considers the following special cases in order:
-
- * object has a callable __json__() attribute defined
- returns the result of the call to __json__()
- * date and datetime objects
- returns the object cast to str
- * Decimal objects
- returns the object cast to float
- * SQLAlchemy objects
- returns a copy of the object.__dict__ with internal SQLAlchemy
- parameters removed
- * SQLAlchemy ResultProxy objects
- Casts the iterable ResultProxy into a list of tuples containing
- the entire resultset data, returns the list in a dictionary
- along with the resultset "row" count.
-
- .. note:: {'count': 5, 'rows': [('Ed Jones',), ('Pete Jones',),
- ('Wendy Williams',), ('Mary Contrary',), ('Fred Smith',)]}
-
- * SQLAlchemy RowProxy objects
- Casts the RowProxy cursor object into a dictionary, probably
- losing its ordered dictionary behavior in the process but
- making it JSON-friendly.
- * webob_dicts objects
- returns webob_dicts.mixed() dictionary, which is guaranteed
- to be JSON-friendly.
- '''
- if hasattr(obj, '__json__') and six.callable(obj.__json__):
- return obj.__json__()
- elif isinstance(obj, (date, datetime)):
- return str(obj)
- elif isinstance(obj, Decimal):
- # XXX What to do about JSONEncoder crappy handling of Decimals?
- # SimpleJSON has better Decimal encoding than the std lib
- # but only in recent versions
- return float(obj)
- elif is_saobject(obj):
- props = {}
- for key in obj.__dict__:
- if not key.startswith('_sa_'):
- props[key] = getattr(obj, key)
- return props
- elif isinstance(obj, ResultProxy):
- props = dict(rows=list(obj), count=obj.rowcount)
- if props['count'] < 0:
- props['count'] = len(props['rows'])
- return props
- elif isinstance(obj, RowProxy):
- return dict(obj)
- elif isinstance(obj, webob_dicts):
- return obj.mixed()
- else:
- return JSONEncoder.default(self, obj)
-
-_default = GenericJSON()
-
-
-def with_when_type(f):
- # Add some backwards support for simplegeneric's API
- f.when_type = f.register
- return f
-
-
-@with_when_type
-@singledispatch
-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/log.py b/pecan/log.py
deleted file mode 100644
index 133fdf5..0000000
--- a/pecan/log.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import logging
-
-from logutils.colorize import ColorizingStreamHandler
-
-
-class DefaultColorizer(ColorizingStreamHandler):
-
- level_map = {
- logging.DEBUG: (None, 'blue', True),
- logging.INFO: (None, None, True),
- logging.WARNING: (None, 'yellow', True),
- logging.ERROR: (None, 'red', True),
- logging.CRITICAL: (None, 'red', True),
- }
-
-
-class ColorFormatter(logging.Formatter):
- """
- A very basic logging formatter that not only applies color to the
- levels of the ouput but can also add padding to the the level names so that
- they do not alter the visuals of logging when presented on the terminal.
-
- The padding is provided by a convenient keyword that adds padding to the
- ``levelname`` so that log output is easier to follow::
-
- %(padded_color_levelname)s
-
- Which would result in log level output that looks like::
-
- [INFO ]
- [WARNING ]
- [ERROR ]
- [DEBUG ]
- [CRITICAL]
-
- If colored output is not supported, it falls back to non-colored output
- without any extra settings.
- """
-
- def __init__(self, _logging=None, colorizer=None, *a, **kw):
- self.logging = _logging or logging
- self.color = colorizer or DefaultColorizer()
- logging.Formatter.__init__(self, *a, **kw)
-
- def format(self, record):
- levelname = record.levelname
- padded_level = '%-8s' % levelname
-
- record.color_levelname = self.color.colorize(levelname, record)
- record.padded_color_levelname = self.color.colorize(
- padded_level,
- record
- )
- return self.logging.Formatter.format(self, record)
diff --git a/pecan/middleware/__init__.py b/pecan/middleware/__init__.py
deleted file mode 100644
index 0c82911..0000000
--- a/pecan/middleware/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from . import errordocument
-from . import recursive
-from . import static
diff --git a/pecan/middleware/debug.py b/pecan/middleware/debug.py
deleted file mode 100644
index 1a6a482..0000000
--- a/pecan/middleware/debug.py
+++ /dev/null
@@ -1,96 +0,0 @@
-__CONFIG_HELP__ = '''
-<div class="traceback">
- <b>To disable this interface, set </b>
- <a target="window"
- href="https://pecan.readthedocs.org/en/latest/deployment.html#disabling-debug-mode">
- <pre>conf.app.debug = False</pre>
- </a>
-</div>
-''' # noqa
-
-try:
- import re
- from backlash.debug import DebuggedApplication
-
- class DebugMiddleware(DebuggedApplication):
-
- body_re = re.compile('(<body[^>]*>)', re.I)
-
- def debug_application(self, environ, start_response):
- for part in super(DebugMiddleware, self).debug_application(
- environ, start_response
- ):
- yield self.body_re.sub('\g<1>%s' % __CONFIG_HELP__, part)
-
-
-except ImportError:
- from traceback import print_exc
- from pprint import pformat
-
- from mako.template import Template
- from six.moves import cStringIO as StringIO
- from webob import Response
- from webob.exc import HTTPException
-
- debug_template_raw = '''<html>
- <head>
- <title>Pecan - Application Error</title>
- <body>
- <header>
- <h1>
- An error occurred!
- </h1>
- </header>
- <div id="error-content">
- <p>
- %(config_help)s
- Pecan offers support for interactive debugging by installing the <a href="https://pypi.python.org/pypi/backlash" target="window">backlash</a> package:
- <br />
- <b><pre>pip install backlash</pre></b>
- ...and reloading this page.
- </p>
- <h2>Traceback</h2>
- <div id="traceback">
- <pre>${traceback}</pre>
- </div>
- <h2>WSGI Environment</h2>
- <div id="environ">
- <pre>${environment}</pre>
- </div>
- </div>
- </body>
- </html>
- ''' % {'config_help': __CONFIG_HELP__} # noqa
-
- debug_template = Template(debug_template_raw)
-
- class DebugMiddleware(object):
-
- def __init__(self, app, *args, **kwargs):
- self.app = app
-
- def __call__(self, environ, start_response):
- try:
- return self.app(environ, start_response)
- except Exception as exc:
- # get a formatted exception
- out = StringIO()
- print_exc(file=out)
-
- # get formatted WSGI environment
- formatted_environ = pformat(environ)
-
- # render our template
- result = debug_template.render(
- traceback=out.getvalue(),
- environment=formatted_environ
- )
-
- # construct and return our response
- response = Response()
- if isinstance(exc, HTTPException):
- response.status_int = exc.status
- else:
- response.status_int = 500
- response.unicode_body = result
- return response(environ, start_response)
diff --git a/pecan/middleware/errordocument.py b/pecan/middleware/errordocument.py
deleted file mode 100644
index 12a3c9e..0000000
--- a/pecan/middleware/errordocument.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import sys
-
-from six import b as b_
-from .recursive import ForwardRequestException, RecursionLoop
-
-
-class StatusPersist(object):
-
- def __init__(self, app, status, url):
- self.app = app
- self.status = status
- self.url = url
-
- def __call__(self, environ, start_response):
- def keep_status_start_response(status, headers, exc_info=None):
- return start_response(self.status, headers, exc_info)
- parts = self.url.split('?')
- environ['PATH_INFO'] = parts[0]
- if len(parts) > 1:
- environ['QUERY_STRING'] = parts[1]
- else:
- environ['QUERY_STRING'] = ''
-
- try:
- return self.app(environ, keep_status_start_response)
- except RecursionLoop as e:
- environ['wsgi.errors'].write(
- 'Recursion error getting error page: %s\n' % e
- )
- keep_status_start_response(
- '500 Server Error',
- [('Content-type', 'text/plain')],
- sys.exc_info()
- )
- return [b_(
- 'Error: %s. (Error page could not be fetched)' % self.status
- )]
-
-
-class ErrorDocumentMiddleware(object):
- '''
- Intersects HTTP response status code, looks it up in the error map defined
- in the Pecan app config.py, and routes to the controller assigned to that
- status.
- '''
- def __init__(self, app, error_map):
- self.app = app
- self.error_map = error_map
-
- def __call__(self, environ, start_response):
-
- def replacement_start_response(status, headers, exc_info=None):
- '''
- Overrides the default response if the status is defined in the
- Pecan app error map configuration.
- '''
- try:
- status_code = int(status.split(' ')[0])
- except (ValueError, TypeError): # pragma: nocover
- raise Exception((
- 'ErrorDocumentMiddleware received an invalid '
- 'status %s' % status
- ))
-
- if status_code in self.error_map:
- def factory(app):
- return StatusPersist(
- app,
- status,
- self.error_map[status_code]
- )
- raise ForwardRequestException(factory=factory)
- return start_response(status, headers, exc_info)
-
- app_iter = self.app(environ, replacement_start_response)
- return app_iter
diff --git a/pecan/middleware/recursive.py b/pecan/middleware/recursive.py
deleted file mode 100644
index fb9d19c..0000000
--- a/pecan/middleware/recursive.py
+++ /dev/null
@@ -1,184 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste
-# Licensed under the MIT license:
-# http://www.opensource.org/licenses/mit-license.php
-
-"""
-Middleware to make internal requests and forward requests internally.
-
-Raise ``ForwardRequestException(new_path_info)`` to do a forward
-(aborting the current request).
-"""
-
-__all__ = ['RecursiveMiddleware']
-
-
-class RecursionLoop(AssertionError):
- # Subclasses AssertionError for legacy reasons
- """Raised when a recursion enters into a loop"""
-
-
-class CheckForRecursionMiddleware(object):
- def __init__(self, app, env):
- self.app = app
- self.env = env
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO', '')
- if path_info in self.env.get('pecan.recursive.old_path_info', []):
- raise RecursionLoop(
- "Forwarding loop detected; %r visited twice (internal "
- "redirect path: %s)"
- % (path_info, self.env['pecan.recursive.old_path_info'])
- )
- old_path_info = self.env.setdefault(
- 'pecan.recursive.old_path_info', []
- )
- old_path_info.append(self.env.get('PATH_INFO', ''))
- return self.app(environ, start_response)
-
-
-class RecursiveMiddleware(object):
-
- """
- A WSGI middleware that allows for recursive and forwarded calls.
- All these calls go to the same 'application', but presumably that
- application acts differently with different URLs. The forwarded
- URLs must be relative to this container.
- """
-
- def __init__(self, application, global_conf=None):
- self.application = application
-
- def __call__(self, environ, start_response):
- my_script_name = environ.get('SCRIPT_NAME', '')
- environ['pecan.recursive.script_name'] = my_script_name
- try:
- return self.application(environ, start_response)
- except ForwardRequestException as e:
- middleware = CheckForRecursionMiddleware(
- e.factory(self), environ)
- return middleware(environ, start_response)
-
-
-class ForwardRequestException(Exception):
- """
- Used to signal that a request should be forwarded to a different location.
-
- ``url``
- The URL to forward to starting with a ``/`` and relative to
- ``RecursiveMiddleware``. URL fragments can also contain query strings
- so ``/error?code=404`` would be a valid URL fragment.
-
- ``environ``
- An altertative WSGI environment dictionary to use for the forwarded
- request. If specified is used *instead* of the ``url_fragment``
-
- ``factory``
- If specifed ``factory`` is used instead of ``url`` or ``environ``.
- ``factory`` is a callable that takes a WSGI application object
- as the first argument and returns an initialised WSGI middleware
- which can alter the forwarded response.
-
- Basic usage (must have ``RecursiveMiddleware`` present) :
-
- .. code-block:: python
-
- from pecan.middleware.recursive import ForwardRequestException
- def app(environ, start_response):
- if environ['PATH_INFO'] == '/hello':
- start_response("200 OK", [('Content-type', 'text/plain')])
- return ['Hello World!']
- elif environ['PATH_INFO'] == '/error':
- start_response("404 Not Found",
- [('Content-type', 'text/plain')]
- )
- return ['Page not found']
- else:
- raise ForwardRequestException('/error')
-
- from pecan.middleware.recursive import RecursiveMiddleware
- app = RecursiveMiddleware(app)
-
- If you ran this application and visited ``/hello`` you would get a
- ``Hello World!`` message. If you ran the application and visited
- ``/not_found`` a ``ForwardRequestException`` would be raised and the caught
- by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then
- return the headers and response from the ``/error`` URL but would display
- a ``404 Not found`` status message.
-
- You could also specify an ``environ`` dictionary instead of a url. Using
- the same example as before:
-
- .. code-block:: python
-
- def app(environ, start_response):
- ... same as previous example ...
- else:
- new_environ = environ.copy()
- new_environ['PATH_INFO'] = '/error'
- raise ForwardRequestException(environ=new_environ)
- """
-
- def __init__(self, url=None, environ={}, factory=None, path_info=None):
- # Check no incompatible options have been chosen
- if factory and url:
- raise TypeError(
- 'You cannot specify factory and a url in '
- 'ForwardRequestException'
- ) # pragma: nocover
- elif factory and environ:
- raise TypeError(
- 'You cannot specify factory and environ in '
- 'ForwardRequestException'
- ) # pragma: nocover
- if url and environ:
- raise TypeError(
- 'You cannot specify environ and url in '
- 'ForwardRequestException'
- ) # pragma: nocover
-
- # set the path_info or warn about its use.
- if path_info:
- self.path_info = path_info
-
- # If the url can be treated as a path_info do that
- if url and '?' not in str(url):
- self.path_info = url
-
- # Base middleware
- class ForwardRequestExceptionMiddleware(object):
- def __init__(self, app):
- self.app = app
-
- # Otherwise construct the appropriate middleware factory
- if hasattr(self, 'path_info'):
- p = self.path_info
-
- def factory_pi(app):
- class PathInfoForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ, start_response):
- environ['PATH_INFO'] = p
- return self.app(environ, start_response)
- return PathInfoForward(app)
-
- self.factory = factory_pi
- elif url:
- def factory_url(app):
- class URLForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ, start_response):
- environ['PATH_INFO'] = url.split('?')[0]
- environ['QUERY_STRING'] = url.split('?')[1]
- return self.app(environ, start_response)
- return URLForward(app)
-
- self.factory = factory_url
- elif environ:
- def factory_env(app):
- class EnvironForward(ForwardRequestExceptionMiddleware):
- def __call__(self, environ_, start_response):
- return self.app(environ, start_response)
- return EnvironForward(app)
-
- self.factory = factory_env
- else:
- self.factory = factory
diff --git a/pecan/middleware/static.py b/pecan/middleware/static.py
deleted file mode 100644
index ab6dd4e..0000000
--- a/pecan/middleware/static.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-This code is adapted from the Werkzeug project, under the BSD license.
-
-:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
-:license: BSD, see LICENSE for more details.
-"""
-
-import os
-import mimetypes
-from datetime import datetime
-from time import gmtime
-
-import six
-
-
-class FileWrapper(object):
- """This class can be used to convert a :class:`file`-like object into
- an iterable. It yields `buffer_size` blocks until the file is fully
- read.
-
- You should not use this class directly but rather use the
- :func:`wrap_file` function that uses the WSGI server's file wrapper
- support if it's available.
-
- :param file: a :class:`file`-like object with a :meth:`~file.read` method.
- :param buffer_size: number of bytes for one iteration.
- """
-
- def __init__(self, file, buffer_size=8192):
- self.file = file
- self.buffer_size = buffer_size
-
- def close(self):
- if hasattr(self.file, 'close'):
- self.file.close()
-
- def __iter__(self):
- return self
-
- def next(self):
- data = self.file.read(self.buffer_size)
- if data:
- return data
- raise StopIteration()
-
-
-if six.PY3:
- FileWrapper.__next__ = FileWrapper.next
-
-
-def wrap_file(environ, file, buffer_size=8192):
- """Wraps a file. This uses the WSGI server's file wrapper if available
- or otherwise the generic :class:`FileWrapper`.
-
- If the file wrapper from the WSGI server is used it's important to not
- iterate over it from inside the application but to pass it through
- unchanged.
-
- More information about file wrappers are available in :pep:`333`.
-
- :param file: a :class:`file`-like object with a :meth:`~file.read` method.
- :param buffer_size: number of bytes for one iteration.
- """
- return environ.get('wsgi.file_wrapper', FileWrapper)(file, buffer_size)
-
-
-def _dump_date(d, delim):
- """Used for `http_date` and `cookie_date`."""
- if d is None:
- d = gmtime()
- elif isinstance(d, datetime):
- d = d.utctimetuple()
- elif isinstance(d, (int, float)):
- d = gmtime(d)
- return '%s, %02d%s%s%s%s %02d:%02d:%02d GMT' % (
- ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')[d.tm_wday],
- d.tm_mday, delim,
- ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
- 'Oct', 'Nov', 'Dec')[d.tm_mon - 1],
- delim, str(d.tm_year), d.tm_hour, d.tm_min, d.tm_sec
- )
-
-
-def http_date(timestamp=None):
- """Formats the time to match the RFC1123 date format.
-
- Accepts a floating point number expressed in seconds since the epoch in, a
- datetime object or a timetuple. All times in UTC.
-
- Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``.
-
- :param timestamp: If provided that date is used, otherwise the current.
- """
- return _dump_date(timestamp, ' ')
-
-
-class StaticFileMiddleware(object):
- """A WSGI middleware that provides static content for development
- environments.
-
- Currently the middleware does not support non ASCII filenames. If the
- encoding on the file system happens to be the encoding of the URI it may
- work but this could also be by accident. We strongly suggest using ASCII
- only file names for static files.
-
- The middleware will guess the mimetype using the Python `mimetype`
- module. If it's unable to figure out the charset it will fall back
- to `fallback_mimetype`.
-
- :param app: the application to wrap. If you don't want to wrap an
- application you can pass it :exc:`NotFound`.
- :param directory: the directory to serve up.
- :param fallback_mimetype: the fallback mimetype for unknown files.
- """
-
- def __init__(self, app, directory, fallback_mimetype='text/plain'):
- self.app = app
- self.loader = self.get_directory_loader(directory)
- self.fallback_mimetype = fallback_mimetype
-
- def _opener(self, filename):
- return lambda: (
- open(filename, 'rb'),
- datetime.utcfromtimestamp(os.path.getmtime(filename)),
- int(os.path.getsize(filename))
- )
-
- def get_directory_loader(self, directory):
- def loader(path):
- path = path or directory
- if path is not None:
- path = os.path.join(directory, path)
- if os.path.isfile(path):
- return os.path.basename(path), self._opener(path)
- return None, None
- return loader
-
- def __call__(self, environ, start_response):
- # sanitize the path for non unix systems
- cleaned_path = environ.get('PATH_INFO', '').strip('/')
- for sep in os.sep, os.altsep:
- if sep and sep != '/':
- cleaned_path = cleaned_path.replace(sep, '/')
- path = '/'.join([''] + [x for x in cleaned_path.split('/')
- if x and x != '..'])
-
- # attempt to find a loader for the file
- real_filename, file_loader = self.loader(path[1:])
- if file_loader is None:
- return self.app(environ, start_response)
-
- # serve the file with the appropriate name if we found it
- guessed_type = mimetypes.guess_type(real_filename)
- mime_type = guessed_type[0] or self.fallback_mimetype
- f, mtime, file_size = file_loader()
-
- headers = [('Date', http_date())]
- headers.append(('Cache-Control', 'public'))
- headers.extend((
- ('Content-Type', mime_type),
- ('Content-Length', str(file_size)),
- ('Last-Modified', http_date(mtime))
- ))
-
- start_response('200 OK', headers)
- return wrap_file(environ, f)
diff --git a/pecan/rest.py b/pecan/rest.py
deleted file mode 100644
index e231c71..0000000
--- a/pecan/rest.py
+++ /dev/null
@@ -1,412 +0,0 @@
-from inspect import ismethod, getmembers
-import warnings
-
-from webob import exc
-import six
-
-from .core import abort
-from .decorators import expose
-from .routing import lookup_controller, handle_lookup_traversal
-from .util import iscontroller, getargspec
-
-
-class RestController(object):
- '''
- A base class for ``REST`` based controllers. Inherit from this class
- to implement a REST controller.
-
- ``RestController`` implements a set of routing functions which override
- the default pecan routing with behavior consistent with RESTful routing.
- This functionality covers navigation to the requested resource
- controllers, and the appropriate handling of both the common (``GET``,
- ``POST``, ``PUT``, ``DELETE``) as well as custom-defined REST action
- methods.
-
- For more on developing **RESTful** web applications with Pecan, see
- :ref:`rest`.
- '''
- _custom_actions = {}
-
- def __new__(cls, *args, **kwargs):
- """
- RestController does not support the `route` argument to
- :func:`~pecan.decorators.expose`
-
- Implement this with __new__ rather than a metaclass, because it's very
- common for pecan users to mixin RestController (with other bases that
- have their own metaclasses).
- """
- for name, value in getmembers(cls):
- if iscontroller(value) and getattr(value, 'custom_route', None):
- raise ValueError(
- 'Path segments cannot be used in combination with '
- 'pecan.rest.RestController. Remove the `route` argument '
- 'to @pecan.expose on %s.%s.%s' % (
- cls.__module__, cls.__name__, value.__name__
- )
- )
-
- # object.__new__ will error if called with extra arguments, and either
- # __new__ is overridden or __init__ is not overridden;
- # https://hg.python.org/cpython/file/78d36d54391c/Objects/typeobject.c#l3034
- # In PY3, this is actually a TypeError (in PY2, it just raises
- # a DeprecationWarning)
- new = super(RestController, cls).__new__
- if new is object.__new__:
- return new(cls)
- return new(cls, *args, **kwargs)
-
- def _get_args_for_controller(self, controller):
- """
- Retrieve the arguments we actually care about. For Pecan applications
- that utilize thread locals, we should truncate the first argument,
- `self`. For applications that explicitly pass request/response
- references as the first controller arguments, we should truncate the
- first three arguments, `self, req, resp`.
- """
- argspec = getargspec(controller)
- from pecan import request
- try:
- request.path
- except AttributeError:
- return argspec.args[3:]
- return argspec.args[1:]
-
- def _handle_bad_rest_arguments(self, controller, remainder, request):
- """
- Ensure that the argspec for a discovered controller actually matched
- the positional arguments in the request path. If not, raise
- a webob.exc.HTTPBadRequest.
- """
- argspec = self._get_args_for_controller(controller)
- fixed_args = len(argspec) - len(
- request.pecan.get('routing_args', [])
- )
- if len(remainder) < fixed_args:
- # For controllers that are missing intermediate IDs
- # (e.g., /authors/books vs /authors/1/books), return a 404 for an
- # invalid path.
- abort(404)
-
- def _lookup_child(self, remainder):
- """
- Lookup a child controller with a named path (handling Unicode paths
- properly for Python 2).
- """
- try:
- controller = getattr(self, remainder, None)
- except UnicodeEncodeError:
- return None
- return controller
-
- @expose()
- def _route(self, args, request=None):
- '''
- Routes a request to the appropriate controller and returns its result.
-
- Performs a bit of validation - refuses to route delete and put actions
- via a GET request).
- '''
- if request is None:
- from pecan import request
- # convention uses "_method" to handle browser-unsupported methods
- 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)
-
- # check for nested controllers
- result = self._find_sub_controllers(args, request)
- if result:
- return result
-
- # handle the request
- handler = getattr(
- self,
- '_handle_%s' % method,
- self._handle_unknown_method
- )
-
- try:
- if len(getargspec(handler).args) == 3:
- result = handler(method, args)
- else:
- result = handler(method, args, request)
-
- #
- # If the signature of the handler does not match the number
- # of remaining positional arguments, attempt to handle
- # a _lookup method (if it exists)
- #
- argspec = self._get_args_for_controller(result[0])
- num_args = len(argspec)
- if num_args < len(args):
- _lookup_result = self._handle_lookup(args, request)
- if _lookup_result:
- return _lookup_result
- except (exc.HTTPClientError, exc.HTTPNotFound,
- exc.HTTPMethodNotAllowed) as e:
- #
- # If the matching handler results in a 400, 404, or 405, attempt to
- # handle a _lookup method (if it exists)
- #
- _lookup_result = self._handle_lookup(args, request)
- if _lookup_result:
- return _lookup_result
-
- # Build a correct Allow: header
- if isinstance(e, exc.HTTPMethodNotAllowed):
-
- def method_iter():
- for func in ('get', 'get_one', 'get_all', 'new', 'edit',
- 'get_delete'):
- if self._find_controller(func):
- yield 'GET'
- break
- for method in ('HEAD', 'POST', 'PUT', 'DELETE', 'TRACE',
- 'PATCH'):
- func = method.lower()
- if self._find_controller(func):
- yield method
-
- e.allow = sorted(method_iter())
-
- raise
-
- # return the result
- return result
-
- def _handle_lookup(self, args, request=None):
- if request is None:
- self._raise_method_deprecation_warning(self.handle_lookup)
-
- # filter empty strings from the arg list
- args = list(six.moves.filter(bool, args))
-
- # check for lookup controllers
- lookup = getattr(self, '_lookup', None)
- if args and iscontroller(lookup):
- result = handle_lookup_traversal(lookup, args)
- if result:
- obj, remainder = result
- return lookup_controller(obj, remainder, request)
-
- def _find_controller(self, *args):
- '''
- Returns the appropriate controller for routing a custom action.
- '''
- for name in args:
- obj = self._lookup_child(name)
- if obj and iscontroller(obj):
- return obj
- return None
-
- def _find_sub_controllers(self, remainder, request):
- '''
- Identifies the correct controller to route to by analyzing the
- request URI.
- '''
- # need either a get_one or get to parse args
- method = None
- for name in ('get_one', 'get'):
- if hasattr(self, name):
- method = name
- break
- if not method:
- return
-
- # get the args to figure out how much to chop off
- args = self._get_args_for_controller(getattr(self, method))
- fixed_args = len(args) - len(
- request.pecan.get('routing_args', [])
- )
- var_args = getargspec(getattr(self, method)).varargs
-
- # attempt to locate a sub-controller
- if var_args:
- for i, item in enumerate(remainder):
- controller = self._lookup_child(item)
- if controller and not ismethod(controller):
- self._set_routing_args(request, remainder[:i])
- return lookup_controller(controller, remainder[i + 1:],
- request)
- elif fixed_args < len(remainder) and hasattr(
- self, remainder[fixed_args]
- ):
- controller = self._lookup_child(remainder[fixed_args])
- if not ismethod(controller):
- self._set_routing_args(request, remainder[:fixed_args])
- return lookup_controller(
- controller,
- remainder[fixed_args + 1:],
- request
- )
-
- def _handle_unknown_method(self, method, remainder, request=None):
- '''
- Routes undefined actions (like RESET) to the appropriate controller.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_unknown_method)
-
- # 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
- # since this isn't a safe GET verb, any local exposes are 405'd
- if remainder:
- if self._find_controller(remainder[0]):
- abort(405)
- sub_controller = self._lookup_child(remainder[0])
- if sub_controller:
- return lookup_controller(sub_controller, remainder[1:],
- request)
-
- abort(405)
-
- def _handle_get(self, method, remainder, request=None):
- '''
- Routes ``GET`` actions to the appropriate controller.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_get)
-
- # route to a get_all or get if no additional parts are available
- if not remainder or remainder == ['']:
- remainder = list(six.moves.filter(bool, remainder))
- controller = self._find_controller('get_all', 'get')
- if controller:
- self._handle_bad_rest_arguments(controller, remainder, request)
- return controller, []
- abort(405)
-
- method_name = remainder[-1]
- # check for new/edit/delete GET requests
- if method_name in ('new', 'edit', 'delete'):
- if method_name == 'delete':
- method_name = 'get_delete'
- controller = self._find_controller(method_name)
- if controller:
- return controller, remainder[:-1]
-
- match = self._handle_custom_action(method, remainder, request)
- if match:
- return match
-
- controller = self._lookup_child(remainder[0])
- if controller and not ismethod(controller):
- return lookup_controller(controller, remainder[1:], request)
-
- # finally, check for the regular get_one/get requests
- controller = self._find_controller('get_one', 'get')
- if controller:
- self._handle_bad_rest_arguments(controller, remainder, request)
- return controller, remainder
-
- abort(405)
-
- def _handle_delete(self, method, remainder, request=None):
- '''
- Routes ``DELETE`` actions to the appropriate controller.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_delete)
-
- if remainder:
- match = self._handle_custom_action(method, remainder, request)
- if match:
- return match
-
- controller = self._lookup_child(remainder[0])
- if controller and not ismethod(controller):
- return lookup_controller(controller, remainder[1:], request)
-
- # 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
- # since this is a DELETE verb, any local exposes are 405'd
- if remainder:
- if self._find_controller(remainder[0]):
- abort(405)
- sub_controller = self._lookup_child(remainder[0])
- if sub_controller:
- return lookup_controller(sub_controller, remainder[1:],
- request)
-
- abort(405)
-
- def _handle_post(self, method, remainder, request=None):
- '''
- Routes ``POST`` requests.
- '''
- if request is None:
- self._raise_method_deprecation_warning(self._handle_post)
-
- # check for custom POST/PUT requests
- if remainder:
- match = self._handle_custom_action(method, remainder, request)
- if match:
- return match
-
- controller = self._lookup_child(remainder[0])
- if controller and not ismethod(controller):
- return lookup_controller(controller, remainder[1:], request)
-
- # check for regular POST/PUT requests
- controller = self._find_controller(method)
- if controller:
- return controller, remainder
-
- abort(405)
-
- def _handle_put(self, method, remainder, request=None):
- return self._handle_post(method, remainder, request)
-
- def _handle_custom_action(self, method, remainder, request=None):
- if request is None:
- self._raise_method_deprecation_warning(self._handle_custom_action)
-
- remainder = [r for r in remainder if r]
- if remainder:
- if method in ('put', 'delete'):
- # For PUT and DELETE, additional arguments are supplied, e.g.,
- # DELETE /foo/XYZ
- method_name = remainder[0]
- remainder = remainder[1:]
- else:
- method_name = remainder[-1]
- remainder = remainder[:-1]
- if method.upper() in self._custom_actions.get(method_name, []):
- controller = self._find_controller(
- '%s_%s' % (method, method_name),
- method_name
- )
- if controller:
- return controller, remainder
-
- def _set_routing_args(self, request, args):
- '''
- Sets default routing arguments.
- '''
- request.pecan.setdefault('routing_args', []).extend(args)
-
- def _raise_method_deprecation_warning(self, handler):
- warnings.warn(
- (
- "The function signature for %s.%s.%s is changing "
- "in the next version of pecan.\nPlease update to: "
- "`%s(self, method, remainder, request)`." % (
- self.__class__.__module__,
- self.__class__.__name__,
- handler.__name__,
- handler.__name__
- )
- ),
- DeprecationWarning
- )
diff --git a/pecan/routing.py b/pecan/routing.py
deleted file mode 100644
index aed8ad5..0000000
--- a/pecan/routing.py
+++ /dev/null
@@ -1,325 +0,0 @@
-import re
-import warnings
-from inspect import getmembers, ismethod
-
-from webob import exc
-import six
-
-from .secure import handle_security, cross_boundary
-from .util import iscontroller, getargspec, _cfg
-
-__all__ = ['lookup_controller', 'find_object', 'route']
-__observed_controllers__ = set()
-__custom_routes__ = {}
-
-
-def route(*args):
- """
- This function is used to define an explicit route for a path segment.
-
- You generally only want to use this in situations where your desired path
- segment is not a valid Python variable/function name.
-
- For example, if you wanted to be able to route to:
-
- /path/with-dashes/
-
- ...the following is invalid Python syntax::
-
- class Controller(object):
-
- with-dashes = SubController()
-
- ...so you would instead define the route explicitly::
-
- class Controller(object):
- pass
-
- pecan.route(Controller, 'with-dashes', SubController())
- """
-
- def _validate_route(route):
- if not isinstance(route, six.string_types):
- raise TypeError('%s must be a string' % route)
-
- if route in ('.', '..') or not re.match(
- '^[0-9a-zA-Z-_$\(\)\.~!,;:*+@=]+$', route
- ):
- raise ValueError(
- '%s must be a valid path segment. Keep in mind '
- 'that path segments should not contain path separators '
- '(e.g., /) ' % route
- )
-
- if len(args) == 2:
- # The handler in this situation is a @pecan.expose'd callable,
- # and is generally only used by the @expose() decorator itself.
- #
- # This sets a special attribute, `custom_route` on the callable, which
- # pecan's routing logic knows how to make use of (as a special case)
- route, handler = args
- if ismethod(handler):
- handler = handler.__func__
- if not iscontroller(handler):
- raise TypeError(
- '%s must be a callable decorated with @pecan.expose' % handler
- )
- obj, attr, value = handler, 'custom_route', route
-
- if handler.__name__ in ('_lookup', '_default', '_route'):
- raise ValueError(
- '%s is a special method in pecan and cannot be used in '
- 'combination with custom path segments.' % handler.__name__
- )
- elif len(args) == 3:
- # This is really just a setattr on the parent controller (with some
- # additional validation for the path segment itself)
- _, route, handler = args
- obj, attr, value = args
-
- if hasattr(obj, attr):
- raise RuntimeError(
- (
- "%(module)s.%(class)s already has an "
- "existing attribute named \"%(route)s\"." % {
- 'module': obj.__module__,
- 'class': obj.__name__,
- 'route': attr
- }
- ),
- )
- else:
- raise TypeError(
- 'pecan.route should be called in the format '
- 'route(ParentController, "path-segment", SubController())'
- )
-
- _validate_route(route)
- setattr(obj, attr, value)
-
-
-class PecanNotFound(Exception):
- pass
-
-
-class NonCanonicalPath(Exception):
- '''
- Exception Raised when a non-canonical path is encountered when 'walking'
- the URI. This is typically a ``POST`` request which requires a trailing
- slash.
- '''
- def __init__(self, controller, remainder):
- self.controller = controller
- self.remainder = remainder
-
-
-def lookup_controller(obj, remainder, request=None):
- '''
- Traverses the requested url path and returns the appropriate controller
- object, including default routes.
-
- Handles common errors gracefully.
- '''
- if request is None:
- warnings.warn(
- (
- "The function signature for %s.lookup_controller is changing "
- "in the next version of pecan.\nPlease update to: "
- "`lookup_controller(self, obj, remainder, request)`." % (
- __name__,
- )
- ),
- DeprecationWarning
- )
-
- notfound_handlers = []
- while True:
- try:
- obj, remainder = find_object(obj, remainder, notfound_handlers,
- request)
- handle_security(obj)
- return obj, remainder
- except (exc.HTTPNotFound, exc.HTTPMethodNotAllowed,
- PecanNotFound) as e:
- if isinstance(e, PecanNotFound):
- e = exc.HTTPNotFound()
- while notfound_handlers:
- name, obj, remainder = notfound_handlers.pop()
- if name == '_default':
- # Notfound handler is, in fact, a controller, so stop
- # traversal
- return obj, remainder
- else:
- # Notfound handler is an internal redirect, so continue
- # traversal
- result = handle_lookup_traversal(obj, remainder)
- if result:
- # If no arguments are passed to the _lookup, yet the
- # argspec requires at least one, raise a 404
- if (
- remainder == [''] and
- len(obj._pecan['argspec'].args) > 1
- ):
- raise e
- obj_, remainder_ = result
- return lookup_controller(obj_, remainder_, request)
- else:
- raise e
-
-
-def handle_lookup_traversal(obj, args):
- try:
- result = obj(*args)
- if result:
- prev_obj = obj
- obj, remainder = result
- # crossing controller boundary
- cross_boundary(prev_obj, obj)
- return result
- except TypeError as te:
- msg = 'Got exception calling lookup(): %s (%s)'
- warnings.warn(
- msg % (te, te.args),
- RuntimeWarning
- )
-
-
-def find_object(obj, remainder, notfound_handlers, request):
- '''
- 'Walks' the url path in search of an action for which a controller is
- implemented and returns that controller object along with what's left
- of the remainder.
- '''
- prev_obj = None
- while True:
- if obj is None:
- raise PecanNotFound
- if iscontroller(obj):
- if getattr(obj, 'custom_route', None) is None:
- return obj, remainder
-
- _detect_custom_path_segments(obj)
-
- if remainder:
- custom_route = __custom_routes__.get((obj.__class__, remainder[0]))
- if custom_route:
- return getattr(obj, custom_route), remainder[1:]
-
- # are we traversing to another controller
- cross_boundary(prev_obj, obj)
- try:
- next_obj, rest = remainder[0], remainder[1:]
- if next_obj == '':
- index = getattr(obj, 'index', None)
- if iscontroller(index):
- return index, rest
- except IndexError:
- # the URL has hit an index method without a trailing slash
- index = getattr(obj, 'index', None)
- if iscontroller(index):
- raise NonCanonicalPath(index, [])
-
- default = getattr(obj, '_default', None)
- if iscontroller(default):
- notfound_handlers.append(('_default', default, remainder))
-
- lookup = getattr(obj, '_lookup', None)
- if iscontroller(lookup):
- notfound_handlers.append(('_lookup', lookup, remainder))
-
- route = getattr(obj, '_route', None)
- if iscontroller(route):
- if len(getargspec(route).args) == 2:
- warnings.warn(
- (
- "The function signature for %s.%s._route is changing "
- "in the next version of pecan.\nPlease update to: "
- "`def _route(self, args, request)`." % (
- obj.__class__.__module__,
- obj.__class__.__name__
- )
- ),
- DeprecationWarning
- )
- next_obj, next_remainder = route(remainder)
- else:
- next_obj, next_remainder = route(remainder, request)
- cross_boundary(route, next_obj)
- return next_obj, next_remainder
-
- if not remainder:
- raise PecanNotFound
-
- prev_remainder = remainder
- prev_obj = obj
- remainder = rest
- try:
- obj = getattr(obj, next_obj, None)
- except UnicodeEncodeError:
- obj = None
-
- # Last-ditch effort: if there's not a matching subcontroller, no
- # `_default`, no `_lookup`, and no `_route`, look to see if there's
- # an `index` that has a generic method defined for the current request
- # method.
- if not obj and not notfound_handlers and hasattr(prev_obj, 'index'):
- if request.method in _cfg(prev_obj.index).get('generic_handlers',
- {}):
- return prev_obj.index, prev_remainder
-
-
-def _detect_custom_path_segments(obj):
- # Detect custom controller routes (on the initial traversal)
- if obj.__class__.__module__ == '__builtin__':
- return
-
- attrs = set(dir(obj))
-
- if obj.__class__ not in __observed_controllers__:
- for key, val in getmembers(obj):
- if iscontroller(val) and isinstance(
- getattr(val, 'custom_route', None),
- six.string_types
- ):
- route = val.custom_route
-
- # Detect class attribute name conflicts
- for conflict in attrs.intersection(set((route,))):
- raise RuntimeError(
- (
- "%(module)s.%(class)s.%(function)s has "
- "a custom path segment, \"%(route)s\", "
- "but %(module)s.%(class)s already has an "
- "existing attribute named \"%(route)s\"." % {
- 'module': obj.__class__.__module__,
- 'class': obj.__class__.__name__,
- 'function': val.__name__,
- 'route': conflict
- }
- ),
- )
-
- existing = __custom_routes__.get(
- (obj.__class__, route)
- )
- if existing:
- # Detect custom path conflicts between functions
- raise RuntimeError(
- (
- "%(module)s.%(class)s.%(function)s and "
- "%(module)s.%(class)s.%(other)s have a "
- "conflicting custom path segment, "
- "\"%(route)s\"." % {
- 'module': obj.__class__.__module__,
- 'class': obj.__class__.__name__,
- 'function': val.__name__,
- 'other': existing,
- 'route': route
- }
- ),
- )
-
- __custom_routes__[
- (obj.__class__, route)
- ] = key
- __observed_controllers__.add(obj.__class__)
diff --git a/pecan/scaffolds/__init__.py b/pecan/scaffolds/__init__.py
deleted file mode 100644
index 2dbe46f..0000000
--- a/pecan/scaffolds/__init__.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import sys
-import os
-import re
-import pkg_resources
-from string import Template
-
-import six
-
-DEFAULT_SCAFFOLD = 'base'
-_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
-
-
-class PecanScaffold(object):
- """
- A base Pecan scaffold. New scaffolded implementations should extend this
- class and define a ``_scaffold_dir`` attribute, e.g.,
-
- class CoolAddOnScaffold(PecanScaffold):
-
- _scaffold_dir = ('package', os.path.join('scaffolds', 'scaffold_name'))
-
- ...where...
-
- pkg_resources.resource_listdir(_scaffold_dir[0], _scaffold_dir[1]))
-
- ...points to some scaffold directory root.
- """
-
- def normalize_output_dir(self, dest):
- return os.path.abspath(os.path.normpath(dest))
-
- def normalize_pkg_name(self, dest):
- return _bad_chars_re.sub('', dest.lower())
-
- def copy_to(self, dest, **kw):
- output_dir = self.normalize_output_dir(dest)
- pkg_name = self.normalize_pkg_name(dest)
- copy_dir(self._scaffold_dir, output_dir, {'package': pkg_name}, **kw)
-
-
-class BaseScaffold(PecanScaffold):
- _scaffold_dir = ('pecan', os.path.join('scaffolds', 'base'))
-
-
-class RestAPIScaffold(PecanScaffold):
- _scaffold_dir = ('pecan', os.path.join('scaffolds', 'rest-api'))
-
-
-def copy_dir(source, dest, variables, out_=sys.stdout, i=0):
- """
- Copies the ``source`` directory to the ``dest`` directory, where
- ``source`` is some tuple representing an installed package and a
- subdirectory in the package, e.g.,
-
- ('pecan', os.path.join('scaffolds', 'base'))
- ('pecan_extension', os.path.join('scaffolds', 'scaffold_name'))
-
- ``variables``: A dictionary of variables to use in any substitutions.
- Substitution is performed via ``string.Template``.
-
- ``out_``: File object to write to (default is sys.stdout).
- """
- def out(msg):
- out_.write('%s%s' % (' ' * (i * 2), msg))
- out_.write('\n')
- out_.flush()
-
- names = sorted(pkg_resources.resource_listdir(source[0], source[1]))
- if not os.path.exists(dest):
- out('Creating %s' % dest)
- makedirs(dest)
- else:
- out('%s already exists' % dest)
- return
-
- for name in names:
-
- full = '/'.join([source[1], name])
- dest_full = os.path.join(dest, substitute_filename(name, variables))
-
- sub_file = False
- if dest_full.endswith('_tmpl'):
- dest_full = dest_full[:-5]
- sub_file = True
-
- if pkg_resources.resource_isdir(source[0], full):
- out('Recursing into %s' % os.path.basename(full))
- copy_dir((source[0], full), dest_full, variables, out_, i + 1)
- continue
- else:
- content = pkg_resources.resource_string(source[0], full)
-
- if sub_file:
- content = render_template(content, variables)
- if content is None:
- continue # pragma: no cover
-
- out('Copying %s to %s' % (full, dest_full))
-
- f = open(dest_full, 'wb')
- f.write(content)
- f.close()
-
-
-def makedirs(directory):
- """ Resursively create a named directory. """
- parent = os.path.dirname(os.path.abspath(directory))
- if not os.path.exists(parent):
- makedirs(parent)
- os.mkdir(directory)
-
-
-def substitute_filename(fn, variables):
- """ Substitute +variables+ in file directory names. """
- for var, value in variables.items():
- fn = fn.replace('+%s+' % var, str(value))
- return fn
-
-
-def render_template(content, variables):
- """
- Return a bytestring representing a templated file based on the
- input (content) and the variable names defined (vars).
- """
- fsenc = sys.getfilesystemencoding()
-
- def to_native(s, encoding='latin-1', errors='strict'):
- if six.PY3:
- if isinstance(s, six.text_type):
- return s
- return str(s, encoding, errors)
- else:
- if isinstance(s, six.text_type):
- return s.encode(encoding, errors)
- return str(s)
-
- output = Template(
- to_native(content, fsenc)
- ).substitute(variables)
- if isinstance(output, six.text_type):
- output = output.encode(fsenc, 'strict')
- return output
diff --git a/pecan/scaffolds/base/+package+/__init__.py b/pecan/scaffolds/base/+package+/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/base/+package+/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/base/+package+/app.py_tmpl b/pecan/scaffolds/base/+package+/app.py_tmpl
deleted file mode 100644
index bf904b6..0000000
--- a/pecan/scaffolds/base/+package+/app.py_tmpl
+++ /dev/null
@@ -1,14 +0,0 @@
-from pecan import make_app
-from ${package} import model
-
-
-def setup_app(config):
-
- model.init_model()
- app_conf = dict(config.app)
-
- return make_app(
- app_conf.pop('root'),
- logging=getattr(config, 'logging', {}),
- **app_conf
- )
diff --git a/pecan/scaffolds/base/+package+/controllers/__init__.py b/pecan/scaffolds/base/+package+/controllers/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/base/+package+/controllers/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/base/+package+/controllers/root.py b/pecan/scaffolds/base/+package+/controllers/root.py
deleted file mode 100644
index bc1e72b..0000000
--- a/pecan/scaffolds/base/+package+/controllers/root.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from pecan import expose, redirect
-from webob.exc import status_map
-
-
-class RootController(object):
-
- @expose(generic=True, template='index.html')
- def index(self):
- return dict()
-
- @index.when(method='POST')
- def index_post(self, q):
- redirect('http://pecan.readthedocs.org/en/latest/search.html?q=%s' % q)
-
- @expose('error.html')
- def error(self, status):
- try:
- status = int(status)
- except ValueError: # pragma: no cover
- status = 500
- message = getattr(status_map.get(status), 'explanation', '')
- return dict(status=status, message=message)
diff --git a/pecan/scaffolds/base/+package+/model/__init__.py b/pecan/scaffolds/base/+package+/model/__init__.py
deleted file mode 100644
index d983f7b..0000000
--- a/pecan/scaffolds/base/+package+/model/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pecan import conf # noqa
-
-
-def init_model():
- """
- This is a stub method which is called at application startup time.
-
- If you need to bind to a parsed database configuration, set up tables or
- ORM classes, or perform any database initialization, this is the
- recommended place to do it.
-
- For more information working with databases, and some common recipes,
- see http://pecan.readthedocs.org/en/latest/databases.html
- """
- pass
diff --git a/pecan/scaffolds/base/+package+/templates/error.html b/pecan/scaffolds/base/+package+/templates/error.html
deleted file mode 100644
index f2d9796..0000000
--- a/pecan/scaffolds/base/+package+/templates/error.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<%inherit file="layout.html" />
-
-## provide definitions for blocks we want to redefine
-<%def name="title()">
- Server Error ${status}
-</%def>
-
-## now define the body of the template
- <header>
- <h1>Server Error ${status}</h1>
- </header>
- <p>${message}</p>
diff --git a/pecan/scaffolds/base/+package+/templates/index.html b/pecan/scaffolds/base/+package+/templates/index.html
deleted file mode 100644
index f17c386..0000000
--- a/pecan/scaffolds/base/+package+/templates/index.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<%inherit file="layout.html" />
-
-## provide definitions for blocks we want to redefine
-<%def name="title()">
- Welcome to Pecan!
-</%def>
-
-## now define the body of the template
- <header>
- <h1><img src="/images/logo.png" /></h1>
- </header>
-
- <div id="content">
-
- <p>This is a sample Pecan project.</p>
-
- <p>
- Instructions for getting started can be found online at <a
- href="http://pecanpy.org" target="window">pecanpy.org</a>
- </p>
-
- <p>
- ...or you can search the documentation here:
- </p>
-
- <form method="POST" action="/">
- <fieldset>
- <input name="q" />
- <input type="submit" value="Search" />
- </fieldset>
- <small>Enter search terms or a module, class or function name.</small>
- </form>
-
- </div>
diff --git a/pecan/scaffolds/base/+package+/templates/layout.html b/pecan/scaffolds/base/+package+/templates/layout.html
deleted file mode 100644
index 4090859..0000000
--- a/pecan/scaffolds/base/+package+/templates/layout.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<html>
- <head>
- <title>${self.title()}</title>
- ${self.style()}
- ${self.javascript()}
- </head>
- <body>
- ${self.body()}
- </body>
-</html>
-
-<%def name="title()">
- Default Title
-</%def>
-
-<%def name="style()">
- <link rel="stylesheet" type="text/css" media="screen" href="/css/style.css" />
-</%def>
-
-<%def name="javascript()">
- <script language="text/javascript" src="/javascript/shared.js"></script>
-</%def>
diff --git a/pecan/scaffolds/base/+package+/tests/__init__.py_tmpl b/pecan/scaffolds/base/+package+/tests/__init__.py_tmpl
deleted file mode 100644
index 78ea527..0000000
--- a/pecan/scaffolds/base/+package+/tests/__init__.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-from unittest import TestCase
-from pecan import set_config
-from pecan.testing import load_test_app
-
-__all__ = ['FunctionalTest']
-
-
-class FunctionalTest(TestCase):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
- """
-
- def setUp(self):
- self.app = load_test_app(os.path.join(
- os.path.dirname(__file__),
- 'config.py'
- ))
-
- def tearDown(self):
- set_config({}, overwrite=True)
diff --git a/pecan/scaffolds/base/+package+/tests/config.py_tmpl b/pecan/scaffolds/base/+package+/tests/config.py_tmpl
deleted file mode 100644
index b745a8c..0000000
--- a/pecan/scaffolds/base/+package+/tests/config.py_tmpl
+++ /dev/null
@@ -1,25 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'static_root': '%(confdir)s/../../public',
- 'template_path': '%(confdir)s/../templates',
- 'debug': True,
- 'errors': {
- '404': '/error/404',
- '__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/scaffolds/base/+package+/tests/test_functional.py_tmpl b/pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl
deleted file mode 100644
index 2d7c6f0..0000000
--- a/pecan/scaffolds/base/+package+/tests/test_functional.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-from unittest import TestCase
-from webtest import TestApp
-from ${package}.tests import FunctionalTest
-
-
-class TestRootController(FunctionalTest):
-
- def test_get(self):
- response = self.app.get('/')
- assert response.status_int == 200
-
- def test_search(self):
- response = self.app.post('/', params={'q': 'RestController'})
- assert response.status_int == 302
- assert response.headers['Location'] == (
- 'http://pecan.readthedocs.org/en/latest/search.html'
- '?q=RestController'
- )
-
- def test_get_not_found(self):
- response = self.app.get('/a/bogus/url', expect_errors=True)
- assert response.status_int == 404
diff --git a/pecan/scaffolds/base/+package+/tests/test_units.py b/pecan/scaffolds/base/+package+/tests/test_units.py
deleted file mode 100644
index 573fb68..0000000
--- a/pecan/scaffolds/base/+package+/tests/test_units.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from unittest import TestCase
-
-
-class TestUnits(TestCase):
-
- def test_units(self):
- assert 5 * 5 == 25
diff --git a/pecan/scaffolds/base/MANIFEST.in b/pecan/scaffolds/base/MANIFEST.in
deleted file mode 100644
index c922f11..0000000
--- a/pecan/scaffolds/base/MANIFEST.in
+++ /dev/null
@@ -1 +0,0 @@
-recursive-include public *
diff --git a/pecan/scaffolds/base/config.py_tmpl b/pecan/scaffolds/base/config.py_tmpl
deleted file mode 100644
index 1578f19..0000000
--- a/pecan/scaffolds/base/config.py_tmpl
+++ /dev/null
@@ -1,54 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'static_root': '%(confdir)s/public',
- 'template_path': '%(confdir)s/${package}/templates',
- 'debug': True,
- 'errors': {
- 404: '/error/404',
- '__force_dict__': True
- }
-}
-
-logging = {
- 'root': {'level': 'INFO', 'handlers': ['console']},
- 'loggers': {
- '${package}': {'level': 'DEBUG', 'handlers': ['console']},
- 'pecan': {'level': 'DEBUG', 'handlers': ['console']},
- 'py.warnings': {'handlers': ['console']},
- '__force_dict__': True
- },
- 'handlers': {
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler',
- 'formatter': 'color'
- }
- },
- 'formatters': {
- 'simple': {
- 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
- '[%(threadName)s] %(message)s')
- },
- 'color': {
- '()': 'pecan.log.ColorFormatter',
- 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]'
- '[%(threadName)s] %(message)s'),
- '__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/scaffolds/base/public/css/style.css b/pecan/scaffolds/base/public/css/style.css
deleted file mode 100644
index 55c9db5..0000000
--- a/pecan/scaffolds/base/public/css/style.css
+++ /dev/null
@@ -1,43 +0,0 @@
-body {
- background: #311F00;
- color: white;
- font-family: 'Helvetica Neue', 'Helvetica', 'Verdana', sans-serif;
- padding: 1em 2em;
-}
-
-a {
- color: #FAFF78;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
-}
-
-div#content {
- width: 800px;
- margin: 0 auto;
-}
-
-form {
- margin: 0;
- padding: 0;
- border: 0;
-}
-
-fieldset {
- border: 0;
-}
-
-input.error {
- background: #FAFF78;
-}
-
-header {
- text-align: center;
-}
-
-h1, h2, h3, h4, h5, h6 {
- font-family: 'Futura-CondensedExtraBold', 'Futura', 'Helvetica', sans-serif;
- text-transform: uppercase;
-}
diff --git a/pecan/scaffolds/base/public/images/logo.png b/pecan/scaffolds/base/public/images/logo.png
deleted file mode 100644
index a8f403e..0000000
--- a/pecan/scaffolds/base/public/images/logo.png
+++ /dev/null
Binary files differ
diff --git a/pecan/scaffolds/base/setup.cfg_tmpl b/pecan/scaffolds/base/setup.cfg_tmpl
deleted file mode 100644
index 111f7cc..0000000
--- a/pecan/scaffolds/base/setup.cfg_tmpl
+++ /dev/null
@@ -1,6 +0,0 @@
-[nosetests]
-match=^test
-where=${package}
-nocapture=1
-cover-package=${package}
-cover-erase=1
diff --git a/pecan/scaffolds/base/setup.py_tmpl b/pecan/scaffolds/base/setup.py_tmpl
deleted file mode 100644
index ec47896..0000000
--- a/pecan/scaffolds/base/setup.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-try:
- from setuptools import setup, find_packages
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup, find_packages
-
-setup(
- name='${package}',
- version='0.1',
- description='',
- author='',
- author_email='',
- install_requires=[
- "pecan",
- ],
- test_suite='${package}',
- zip_safe=False,
- include_package_data=True,
- packages=find_packages(exclude=['ez_setup'])
-)
diff --git a/pecan/scaffolds/rest-api/+package+/__init__.py b/pecan/scaffolds/rest-api/+package+/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/rest-api/+package+/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/rest-api/+package+/app.py_tmpl b/pecan/scaffolds/rest-api/+package+/app.py_tmpl
deleted file mode 100644
index 3eb5edf..0000000
--- a/pecan/scaffolds/rest-api/+package+/app.py_tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
-from pecan import make_app
-from ${package} import model
-from ${package}.errors import JSONErrorHook
-
-
-def setup_app(config):
-
- model.init_model()
- app_conf = dict(config.app)
-
- return make_app(
- app_conf.pop('root'),
- logging=getattr(config, 'logging', {}),
- hooks=[JSONErrorHook()],
- **app_conf
- )
diff --git a/pecan/scaffolds/rest-api/+package+/controllers/__init__.py b/pecan/scaffolds/rest-api/+package+/controllers/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/scaffolds/rest-api/+package+/controllers/__init__.py
+++ /dev/null
diff --git a/pecan/scaffolds/rest-api/+package+/controllers/root.py b/pecan/scaffolds/rest-api/+package+/controllers/root.py
deleted file mode 100644
index f106bb2..0000000
--- a/pecan/scaffolds/rest-api/+package+/controllers/root.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from pecan import expose, response, abort
-
-people = {
- 1: 'Luke',
- 2: 'Leia',
- 3: 'Han',
- 4: 'Anakin'
-}
-
-
-class PersonController(object):
-
- def __init__(self, person_id):
- self.person_id = person_id
-
- @expose(generic=True)
- def index(self):
- return people.get(self.person_id) or abort(404)
-
- @index.when(method='PUT')
- def put(self):
- # TODO: Idempotent PUT (returns 200 or 204)
- response.status = 204
-
- @index.when(method='DELETE')
- def delete(self):
- # TODO: Idempotent DELETE
- response.status = 204
-
-
-class PeopleController(object):
-
- @expose()
- def _lookup(self, person_id, *remainder):
- return PersonController(int(person_id)), remainder
-
- @expose(generic=True, template='json')
- def index(self):
- return people
-
- @index.when(method='POST', template='json')
- def post(self):
- # TODO: Create a new person
- response.status = 201
-
-
-class RootController(object):
-
- people = PeopleController()
-
- @expose()
- def index(self):
- return "Hello, World!"
diff --git a/pecan/scaffolds/rest-api/+package+/errors.py b/pecan/scaffolds/rest-api/+package+/errors.py
deleted file mode 100644
index 4d4d06c..0000000
--- a/pecan/scaffolds/rest-api/+package+/errors.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import json
-import webob
-from pecan.hooks import PecanHook
-
-
-class JSONErrorHook(PecanHook):
- """
- A pecan hook that translates webob HTTP errors into a JSON format.
- """
-
- def on_error(self, state, exc):
- if isinstance(exc, webob.exc.HTTPError):
- return webob.Response(
- body=json.dumps({'reason': str(exc)}),
- status=exc.status,
- headerlist=exc.headerlist,
- content_type='application/json'
- )
diff --git a/pecan/scaffolds/rest-api/+package+/model/__init__.py b/pecan/scaffolds/rest-api/+package+/model/__init__.py
deleted file mode 100644
index d983f7b..0000000
--- a/pecan/scaffolds/rest-api/+package+/model/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from pecan import conf # noqa
-
-
-def init_model():
- """
- This is a stub method which is called at application startup time.
-
- If you need to bind to a parsed database configuration, set up tables or
- ORM classes, or perform any database initialization, this is the
- recommended place to do it.
-
- For more information working with databases, and some common recipes,
- see http://pecan.readthedocs.org/en/latest/databases.html
- """
- pass
diff --git a/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
deleted file mode 100644
index 78ea527..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/__init__.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-from unittest import TestCase
-from pecan import set_config
-from pecan.testing import load_test_app
-
-__all__ = ['FunctionalTest']
-
-
-class FunctionalTest(TestCase):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
- """
-
- def setUp(self):
- self.app = load_test_app(os.path.join(
- os.path.dirname(__file__),
- 'config.py'
- ))
-
- def tearDown(self):
- set_config({}, overwrite=True)
diff --git a/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
deleted file mode 100644
index 09efcb7..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/config.py_tmpl
+++ /dev/null
@@ -1,19 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'debug': True
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl b/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl
deleted file mode 100644
index db414db..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/test_functional.py_tmpl
+++ /dev/null
@@ -1,37 +0,0 @@
-import json
-from ${package}.tests import FunctionalTest
-
-
-class TestRootController(FunctionalTest):
-
- def test_get_all(self):
- response = self.app.get('/people/')
- assert response.status_int == 200
- assert response.namespace[1] == 'Luke'
- assert response.namespace[2] == 'Leia'
- assert response.namespace[3] == 'Han'
- assert response.namespace[4] == 'Anakin'
-
- def test_get_one(self):
- response = self.app.get('/people/1/')
- assert response.status_int == 200
- assert response.body.decode() == 'Luke'
-
- def test_post(self):
- response = self.app.post('/people/')
- assert response.status_int == 201
-
- def test_put(self):
- response = self.app.put('/people/1/')
- assert response.status_int == 204
-
- def test_delete(self):
- response = self.app.delete('/people/1/')
- assert response.status_int == 204
-
- def test_not_found(self):
- response = self.app.get('/missing/', expect_errors=True)
- assert response.status_int == 404
- assert json.loads(response.body.decode()) == {
- 'reason': 'The resource could not be found.'
- }
diff --git a/pecan/scaffolds/rest-api/+package+/tests/test_units.py b/pecan/scaffolds/rest-api/+package+/tests/test_units.py
deleted file mode 100644
index 573fb68..0000000
--- a/pecan/scaffolds/rest-api/+package+/tests/test_units.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from unittest import TestCase
-
-
-class TestUnits(TestCase):
-
- def test_units(self):
- assert 5 * 5 == 25
diff --git a/pecan/scaffolds/rest-api/config.py_tmpl b/pecan/scaffolds/rest-api/config.py_tmpl
deleted file mode 100644
index a865c6c..0000000
--- a/pecan/scaffolds/rest-api/config.py_tmpl
+++ /dev/null
@@ -1,48 +0,0 @@
-# Server Specific Configurations
-server = {
- 'port': '8080',
- 'host': '0.0.0.0'
-}
-
-# Pecan Application Configurations
-app = {
- 'root': '${package}.controllers.root.RootController',
- 'modules': ['${package}'],
- 'debug': True
-}
-
-logging = {
- 'root': {'level': 'INFO', 'handlers': ['console']},
- 'loggers': {
- '${package}': {'level': 'DEBUG', 'handlers': ['console']},
- 'pecan': {'level': 'DEBUG', 'handlers': ['console']},
- 'py.warnings': {'handlers': ['console']},
- '__force_dict__': True
- },
- 'handlers': {
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler',
- 'formatter': 'color'
- }
- },
- 'formatters': {
- 'simple': {
- 'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
- '[%(threadName)s] %(message)s')
- },
- 'color': {
- '()': 'pecan.log.ColorFormatter',
- 'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]'
- '[%(threadName)s] %(message)s'),
- '__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/scaffolds/rest-api/setup.cfg_tmpl b/pecan/scaffolds/rest-api/setup.cfg_tmpl
deleted file mode 100644
index 111f7cc..0000000
--- a/pecan/scaffolds/rest-api/setup.cfg_tmpl
+++ /dev/null
@@ -1,6 +0,0 @@
-[nosetests]
-match=^test
-where=${package}
-nocapture=1
-cover-package=${package}
-cover-erase=1
diff --git a/pecan/scaffolds/rest-api/setup.py_tmpl b/pecan/scaffolds/rest-api/setup.py_tmpl
deleted file mode 100644
index ec47896..0000000
--- a/pecan/scaffolds/rest-api/setup.py_tmpl
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-try:
- from setuptools import setup, find_packages
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
- from setuptools import setup, find_packages
-
-setup(
- name='${package}',
- version='0.1',
- description='',
- author='',
- author_email='',
- install_requires=[
- "pecan",
- ],
- test_suite='${package}',
- zip_safe=False,
- include_package_data=True,
- packages=find_packages(exclude=['ez_setup'])
-)
diff --git a/pecan/secure.py b/pecan/secure.py
deleted file mode 100644
index ea9fd51..0000000
--- a/pecan/secure.py
+++ /dev/null
@@ -1,232 +0,0 @@
-from functools import wraps
-from inspect import getmembers, isfunction
-from webob import exc
-
-import six
-
-from .decorators import expose
-from .util import _cfg, iscontroller
-
-__all__ = ['unlocked', 'secure', 'SecureController']
-
-if six.PY3:
- from .compat import is_bound_method as ismethod
-else:
- from inspect import ismethod
-
-
-class _SecureState(object):
- def __init__(self, desc, boolean_value):
- self.description = desc
- self.boolean_value = boolean_value
-
- def __repr__(self):
- return '<SecureState %s>' % self.description
-
- def __nonzero__(self):
- return self.boolean_value
-
- def __bool__(self):
- return self.__nonzero__()
-
-Any = _SecureState('Any', False)
-Protected = _SecureState('Protected', True)
-
-
-# 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)
- cfg['secured'] = Protected
- cfg['check_permissions'] = check_permissions_func
- return func
- return wrap
-
-
-# classes to assist with wrapping attributes
-class _UnlockedAttribute(object):
- def __init__(self, obj):
- self.obj = obj
-
- @_unlocked_method
- @expose()
- def _lookup(self, *remainder):
- return self.obj, remainder
-
-
-class _SecuredAttribute(object):
- def __init__(self, obj, check_permissions):
- self.obj = obj
- self.check_permissions = check_permissions
- self._parent = None
-
- def _check_permissions(self):
- if isinstance(self.check_permissions, six.string_types):
- return getattr(self.parent, self.check_permissions)()
- else:
- return self.check_permissions()
-
- def __get_parent(self):
- return self._parent
-
- def __set_parent(self, parent):
- if ismethod(parent):
- self._parent = six.get_method_self(parent)
- else:
- self._parent = parent
- parent = property(__get_parent, __set_parent)
-
- @_secure_method('_check_permissions')
- @expose()
- 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
- isinstance(x, six.string_types)
- )
-
-
-# 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):
- """
- This method unlocks method or class attribute on a SecureController. Can
- be used to decorate or wrap an attribute
- """
- if ismethod(func_or_obj) or isfunction(func_or_obj):
- return _unlocked_method(func_or_obj)
- else:
- return _UnlockedAttribute(func_or_obj)
-
-
-def secure(func_or_obj, check_permissions_for_obj=None):
- """
- This method secures a method or class depending on invocation.
-
- To decorate a method use one argument:
- @secure(<check_permissions_method>)
-
- To secure a class, invoke with two arguments:
- secure(<obj instance>, <check_permissions_method>)
- """
- if _allowed_check_permissions_types(func_or_obj):
- return _secure_method(func_or_obj)
- else:
- if not _allowed_check_permissions_types(check_permissions_for_obj):
- 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 SecureControllerMeta(type):
- """
- 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
- to the controller).
- """
- def __init__(cls, name, bases, dict_):
- cls._pecan = dict(
- secured=Protected,
- check_permissions=cls.check_permissions,
- unlocked=[]
- )
-
- for name, value in getmembers(cls)[:]:
- if (isfunction if six.PY3 else ismethod)(value):
- if iscontroller(value) and value._pecan.get(
- 'secured'
- ) is None:
- # Wrap the function so that the security context is
- # local to this class definition. This works around
- # the fact that unbound method attributes are shared
- # across classes with the same bases.
- wrapped = _make_wrapper(value)
- wrapped._pecan['secured'] = Protected
- wrapped._pecan['check_permissions'] = \
- cls.check_permissions
- setattr(cls, name, wrapped)
- elif hasattr(value, '__class__'):
- if name.startswith('__') and name.endswith('__'):
- continue
- if isinstance(value, _UnlockedAttribute):
- # mark it as unlocked and remove wrapper
- cls._pecan['unlocked'].append(value.obj)
- setattr(cls, name, value.obj)
- 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
- # it is using a check_permissions function embedded in
- # the _SecuredAttribute wrapper
- cls._pecan['unlocked'].append(value)
-
-
-class SecureControllerBase(object):
-
- @classmethod
- def check_permissions(cls):
- """
- Returns `True` or `False` to grant access. Implemented in subclasses
- of :class:`SecureController`.
- """
- return False
-
-
-SecureController = SecureControllerMeta(
- 'SecureController',
- (SecureControllerBase,),
- {'__doc__': SecureControllerMeta.__doc__}
-)
-
-
-def _make_wrapper(f):
- """return a wrapped function with a copy of the _pecan context"""
- @wraps(f)
- def wrapper(*args, **kwargs):
- return f(*args, **kwargs)
- wrapper._pecan = f._pecan.copy()
- return wrapper
-
-
-# methods to evaluate security during routing
-def handle_security(controller, im_self=None):
- """ Checks the security of a controller. """
- if controller._pecan.get('secured', False):
- check_permissions = controller._pecan['check_permissions']
-
- if isinstance(check_permissions, six.string_types):
- check_permissions = getattr(
- im_self or six.get_method_self(controller),
- check_permissions
- )
-
- 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:
- return
-
- if isinstance(obj, _SecuredAttribute):
- # a secure attribute can live in unsecure class so we have to set
- # while we walk the route
- obj.parent = prev_obj
-
- if hasattr(prev_obj, '_pecan'):
- if obj not in prev_obj._pecan.get('unlocked', []):
- handle_security(prev_obj)
diff --git a/pecan/templating.py b/pecan/templating.py
deleted file mode 100644
index 81a60a0..0000000
--- a/pecan/templating.py
+++ /dev/null
@@ -1,286 +0,0 @@
-from .compat import escape
-from .jsonify import encode
-
-_builtin_renderers = {}
-error_formatters = []
-
-#
-# JSON rendering engine
-#
-
-
-class JsonRenderer(object):
- '''
- Defines the builtin ``JSON`` renderer.
- '''
- def __init__(self, path, extra_vars):
- pass
-
- def render(self, template_path, namespace):
- '''
- Implements ``JSON`` rendering.
- '''
- return encode(namespace)
-
- # TODO: add error formatter for json (pass it through json lint?)
-
-_builtin_renderers['json'] = JsonRenderer
-
-#
-# Genshi rendering engine
-#
-
-try:
- from genshi.template import (TemplateLoader,
- TemplateError as gTemplateError)
-
- class GenshiRenderer(object):
- '''
- Defines the builtin ``Genshi`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.loader = TemplateLoader([path], auto_reload=True)
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Genshi`` rendering.
- '''
- 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):
- '''
- Implements ``Genshi`` renderer error formatting.
- '''
- if isinstance(exc_value, (gTemplateError)):
- retval = '<h4>Genshi error %s</h4>' % escape(
- exc_value.args[0],
- True
- )
- retval += format_line_context(exc_value.filename, exc_value.lineno)
- return retval
- error_formatters.append(format_genshi_error)
-except ImportError: # pragma no cover
- pass
-
-
-#
-# Mako rendering engine
-#
-
-try:
- from mako.lookup import TemplateLookup
- from mako.exceptions import (CompileException, SyntaxException,
- html_error_template)
-
- class MakoRenderer(object):
- '''
- Defines the builtin ``Mako`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.loader = TemplateLookup(
- directories=[path],
- output_encoding='utf-8'
- )
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Mako`` rendering.
- '''
- tmpl = self.loader.get_template(template_path)
- return tmpl.render(**self.extra_vars.make_ns(namespace))
-
- _builtin_renderers['mako'] = MakoRenderer
-
- def format_mako_error(exc_value):
- '''
- Implements ``Mako`` renderer error formatting.
- '''
- if isinstance(exc_value, (CompileException, SyntaxException)):
- return html_error_template().render(full=False, css=False)
-
- error_formatters.append(format_mako_error)
-except ImportError: # pragma no cover
- pass
-
-
-#
-# Kajiki rendering engine
-#
-
-try:
- from kajiki.loader import FileLoader
-
- class KajikiRenderer(object):
- '''
- Defines the builtin ``Kajiki`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.loader = FileLoader(path, reload=True)
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Kajiki`` rendering.
- '''
- Template = self.loader.import_(template_path)
- stream = Template(self.extra_vars.make_ns(namespace))
- return stream.render()
- _builtin_renderers['kajiki'] = KajikiRenderer
- # TODO: add error formatter for kajiki
-except ImportError: # pragma no cover
- pass
-
-#
-# Jinja2 rendering engine
-#
-try:
- from jinja2 import Environment, FileSystemLoader
- from jinja2.exceptions import TemplateSyntaxError as jTemplateSyntaxError
-
- class JinjaRenderer(object):
- '''
- Defines the builtin ``Jinja`` renderer.
- '''
- def __init__(self, path, extra_vars):
- self.env = Environment(loader=FileSystemLoader(path))
- self.extra_vars = extra_vars
-
- def render(self, template_path, namespace):
- '''
- Implements ``Jinja`` rendering.
- '''
- template = self.env.get_template(template_path)
- return template.render(self.extra_vars.make_ns(namespace))
- _builtin_renderers['jinja'] = JinjaRenderer
-
- def format_jinja_error(exc_value):
- '''
- Implements ``Jinja`` renderer error formatting.
- '''
- retval = '<h4>Jinja2 error in \'%s\' on line %d</h4><div>%s</div>'
- if isinstance(exc_value, (jTemplateSyntaxError)):
- 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):
- '''
- Formats the the line context for error rendering.
-
- :param filename: the location of the file, within which the error occurred
- :param lineno: the offending line number
- :param context: number of lines of code to display before and after the
- offending line.
- '''
- lines = open(filename).readlines()
-
- lineno = lineno - 1 # files are indexed by 1 not 0
- if lineno > 0:
- start_lineno = max(lineno - context, 0)
- end_lineno = lineno + context
-
- lines = [escape(l, True) for l in lines[start_lineno:end_lineno]]
- i = lineno - start_lineno
- lines[i] = '<strong>%s</strong>' % lines[i]
-
- else:
- lines = [escape(l, True) for l in lines[:context]]
- msg = '<pre style="background-color:#ccc;padding:2em;">%s</pre>'
- return msg % ''.join(lines)
-
-
-#
-# Extra Vars Rendering
-#
-class ExtraNamespace(object):
- '''
- Extra variables for the template namespace to pass to the renderer as named
- parameters.
-
- :param extras: dictionary of extra parameters. Defaults to an empty dict.
- '''
- def __init__(self, extras={}):
- self.namespace = dict(extras)
-
- def update(self, d):
- '''
- Updates the extra variable dictionary for the namespace.
- '''
- self.namespace.update(d)
-
- def make_ns(self, ns):
- '''
- Returns the `lazily` created template namespace.
- '''
- if self.namespace:
- val = {}
- val.update(self.namespace)
- val.update(ns)
- return val
- else:
- return ns
-
-
-#
-# Rendering Factory
-#
-class RendererFactory(object):
- '''
- Manufactures known Renderer objects.
-
- :param custom_renderers: custom-defined renderers to manufacture
- :param extra_vars: extra vars for the template namespace
- '''
- def __init__(self, custom_renderers={}, extra_vars={}):
- self._renderers = {}
- self._renderer_classes = dict(_builtin_renderers)
- self.add_renderers(custom_renderers)
- self.extra_vars = ExtraNamespace(extra_vars)
-
- def add_renderers(self, custom_dict):
- '''
- Adds a custom renderer.
-
- :param custom_dict: a dictionary of custom renderers to add
- '''
- self._renderer_classes.update(custom_dict)
-
- def available(self, name):
- '''
- Returns true if queried renderer class is available.
-
- :param name: renderer name
- '''
- return name in self._renderer_classes
-
- def get(self, name, template_path):
- '''
- Returns the renderer object.
-
- :param name: name of the requested renderer
- :param template_path: path to the template
- '''
- if name not in self._renderers:
- cls = self._renderer_classes.get(name)
- if cls is None:
- return None
- else:
- self._renderers[name] = cls(template_path, self.extra_vars)
- return self._renderers[name]
diff --git a/pecan/testing.py b/pecan/testing.py
deleted file mode 100644
index 14986ff..0000000
--- a/pecan/testing.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from pecan import load_app
-from webtest import TestApp
-
-
-def load_test_app(config=None, **kwargs):
- """
- Used for functional tests where you need to test your
- literal application and its integration with the framework.
-
- :param config: Can be a dictionary containing configuration, a string which
- represents a (relative) configuration filename or ``None``
- which will fallback to get the ``PECAN_CONFIG`` env
- variable.
-
- returns a pecan.Pecan WSGI application wrapped in a webtest.TestApp
- instance.
-
- ::
- app = load_test_app('path/to/some/config.py')
-
- resp = app.get('/path/to/some/resource').status_int
- assert resp.status_int == 200
-
- resp = app.post('/path/to/some/resource', params={'param': 'value'})
- assert resp.status_int == 302
-
- Alternatively you could call ``load_test_app`` with no parameters if the
- environment variable is set ::
-
- app = load_test_app()
-
- resp = app.get('/path/to/some/resource').status_int
- assert resp.status_int == 200
- """
- return TestApp(load_app(config, **kwargs))
diff --git a/pecan/tests/__init__.py b/pecan/tests/__init__.py
deleted file mode 100644
index 4d2df74..0000000
--- a/pecan/tests/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import sys
-import os
-if sys.version_info < (2, 7):
- from unittest2 import TestCase # pragma: nocover
-else:
- from unittest import TestCase # pragma: nocover
-
-
-class PecanTestCase(TestCase):
-
- def setUp(self):
- self.addCleanup(self._reset_global_config)
-
- def _reset_global_config(self):
- from pecan import configuration
- configuration.set_config(
- dict(configuration.initconf()),
- overwrite=True
- )
- os.environ.pop('PECAN_CONFIG', None)
diff --git a/pecan/tests/config_fixtures/bad/importerror.py b/pecan/tests/config_fixtures/bad/importerror.py
deleted file mode 100644
index 592fa5b..0000000
--- a/pecan/tests/config_fixtures/bad/importerror.py
+++ /dev/null
@@ -1 +0,0 @@
-import pecan.thismoduledoesnotexist
diff --git a/pecan/tests/config_fixtures/bad/module_and_underscore.py b/pecan/tests/config_fixtures/bad/module_and_underscore.py
deleted file mode 100644
index e53f416..0000000
--- a/pecan/tests/config_fixtures/bad/module_and_underscore.py
+++ /dev/null
@@ -1,4 +0,0 @@
-import sys
-
-__badattr__ = True
-moduleattr = sys
diff --git a/pecan/tests/config_fixtures/config.py b/pecan/tests/config_fixtures/config.py
deleted file mode 100644
index bba299b..0000000
--- a/pecan/tests/config_fixtures/config.py
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-# Server Specific Configurations
-server = {
- '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
-}
-
-# Custom Configurations must be in Python dictionary format::
-#
-# foo = {'bar':'baz'}
-#
-# All configurations are accessible at::
-# pecan.conf
diff --git a/pecan/tests/config_fixtures/empty.py b/pecan/tests/config_fixtures/empty.py
deleted file mode 100644
index b4a9332..0000000
--- a/pecan/tests/config_fixtures/empty.py
+++ /dev/null
@@ -1,2 +0,0 @@
-app = {}
-server = {}
diff --git a/pecan/tests/config_fixtures/foobar.py b/pecan/tests/config_fixtures/foobar.py
deleted file mode 100644
index 5abc475..0000000
--- a/pecan/tests/config_fixtures/foobar.py
+++ /dev/null
@@ -1 +0,0 @@
-foo = "bar"
diff --git a/pecan/tests/config_fixtures/forcedict.py b/pecan/tests/config_fixtures/forcedict.py
deleted file mode 100644
index 4e2c83a..0000000
--- a/pecan/tests/config_fixtures/forcedict.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Pecan Application Configurations
-beaker = {
- '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/middleware/__init__.py b/pecan/tests/middleware/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/middleware/__init__.py
+++ /dev/null
diff --git a/pecan/tests/middleware/static_fixtures/self.png b/pecan/tests/middleware/static_fixtures/self.png
deleted file mode 100644
index 9b30321..0000000
--- a/pecan/tests/middleware/static_fixtures/self.png
+++ /dev/null
Binary files differ
diff --git a/pecan/tests/middleware/static_fixtures/text.txt b/pecan/tests/middleware/static_fixtures/text.txt
deleted file mode 100644
index c6defe5..0000000
--- a/pecan/tests/middleware/static_fixtures/text.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-This is a test text file.
-
-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
-veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
-commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
-velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
-occaecat cupidatat non proident, sunt in culpa qui officia deserunt
-mollit anim id est laborum. \ No newline at end of file
diff --git a/pecan/tests/middleware/test_errordocument.py b/pecan/tests/middleware/test_errordocument.py
deleted file mode 100644
index 29f46e5..0000000
--- a/pecan/tests/middleware/test_errordocument.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import json
-
-from webtest import TestApp
-from six import b as b_
-
-import pecan
-from pecan.middleware.errordocument import ErrorDocumentMiddleware
-from pecan.middleware.recursive import RecursiveMiddleware
-from pecan.tests import PecanTestCase
-
-
-def four_oh_four_app(environ, start_response):
- if environ['PATH_INFO'].startswith('/error'):
- code = environ['PATH_INFO'].split('/')[2]
- start_response("200 OK", [('Content-type', 'text/plain')])
-
- body = "Error: %s" % code
- if environ['QUERY_STRING']:
- body += "\nQS: %s" % environ['QUERY_STRING']
- return [b_(body)]
- start_response("404 Not Found", [('Content-type', 'text/plain')])
- return []
-
-
-class TestErrorDocumentMiddleware(PecanTestCase):
-
- def setUp(self):
- super(TestErrorDocumentMiddleware, self).setUp()
- self.app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
- four_oh_four_app, {404: '/error/404'}
- )))
-
- def test_hit_error_page(self):
- r = self.app.get('/error/404')
- assert r.status_int == 200
- assert r.body == b_('Error: 404')
-
- def test_middleware_routes_to_404_message(self):
- r = self.app.get('/', expect_errors=True)
- assert r.status_int == 404
- assert r.body == b_('Error: 404')
-
- def test_error_endpoint_with_query_string(self):
- app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
- four_oh_four_app, {404: '/error/404?foo=bar'}
- )))
- r = app.get('/', expect_errors=True)
- assert r.status_int == 404
- assert r.body == b_('Error: 404\nQS: foo=bar')
-
- def test_error_with_recursion_loop(self):
- app = TestApp(RecursiveMiddleware(ErrorDocumentMiddleware(
- four_oh_four_app, {404: '/'}
- )))
- r = app.get('/', expect_errors=True)
- assert r.status_int == 404
- assert r.body == b_(
- 'Error: 404 Not Found. (Error page could not be fetched)'
- )
-
- def test_original_exception(self):
-
- class RootController(object):
-
- @pecan.expose()
- def index(self):
- if pecan.request.method != 'POST':
- pecan.abort(405, 'You have to POST, dummy!')
- return 'Hello, World!'
-
- @pecan.expose('json')
- def error(self, status):
- return dict(
- status=int(status),
- reason=pecan.request.environ[
- 'pecan.original_exception'
- ].detail
- )
-
- app = pecan.Pecan(RootController())
- app = RecursiveMiddleware(ErrorDocumentMiddleware(app, {
- 405: '/error/405'
- }))
- app = TestApp(app)
-
- assert app.post('/').status_int == 200
- r = app.get('/', expect_errors=405)
- assert r.status_int == 405
-
- resp = json.loads(r.body.decode())
- assert resp['status'] == 405
- assert resp['reason'] == 'You have to POST, dummy!'
diff --git a/pecan/tests/middleware/test_recursive.py b/pecan/tests/middleware/test_recursive.py
deleted file mode 100644
index ed95d50..0000000
--- a/pecan/tests/middleware/test_recursive.py
+++ /dev/null
@@ -1,142 +0,0 @@
-from webtest import TestApp
-from six import b as b_
-
-from pecan.middleware.recursive import (RecursiveMiddleware,
- ForwardRequestException)
-from pecan.tests import PecanTestCase
-
-
-def simple_app(environ, start_response):
- start_response("200 OK", [('Content-type', 'text/plain')])
- return [b_('requested page returned')]
-
-
-def error_docs_app(environ, start_response):
- if environ['PATH_INFO'] == '/not_found':
- start_response("404 Not found", [('Content-type', 'text/plain')])
- return [b_('Not found')]
- elif environ['PATH_INFO'] == '/error':
- start_response("200 OK", [('Content-type', 'text/plain')])
- return [b_('Page not found')]
- elif environ['PATH_INFO'] == '/recurse':
- raise ForwardRequestException('/recurse')
- else:
- return simple_app(environ, start_response)
-
-
-class Middleware(object):
- def __init__(self, app, url='/error'):
- self.app = app
- self.url = url
-
- def __call__(self, environ, start_response):
- raise ForwardRequestException(self.url)
-
-
-def forward(app):
- app = TestApp(RecursiveMiddleware(app))
- res = app.get('')
-
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'requested page returned' in res
- res = app.get('/error')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'Page not found' in res
- res = app.get('/not_found')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'Page not found' in res
- try:
- res = app.get('/recurse')
- except AssertionError as e:
- if str(e).startswith('Forwarding loop detected'):
- pass
- else:
- raise AssertionError('Failed to detect forwarding loop')
-
-
-class TestRecursiveMiddleware(PecanTestCase):
-
- def test_ForwardRequest_url(self):
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- raise ForwardRequestException(self.url)
- forward(TestForwardRequestMiddleware(error_docs_app))
-
- def test_ForwardRequest_url_with_params(self):
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- raise ForwardRequestException(self.url + '?q=1')
- forward(TestForwardRequestMiddleware(error_docs_app))
-
- def test_ForwardRequest_environ(self):
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- environ['PATH_INFO'] = self.url
- raise ForwardRequestException(environ=environ)
- forward(TestForwardRequestMiddleware(error_docs_app))
-
- def test_ForwardRequest_factory(self):
-
- class TestForwardRequestMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- environ['PATH_INFO'] = self.url
-
- def factory(app):
-
- class WSGIApp(object):
-
- def __init__(self, app):
- self.app = app
-
- def __call__(self, e, start_response):
- def keep_status_start_response(status, headers,
- exc_info=None):
- return start_response(
- '404 Not Found', headers, exc_info
- )
- return self.app(e, keep_status_start_response)
-
- return WSGIApp(app)
-
- raise ForwardRequestException(factory=factory)
-
- app = TestForwardRequestMiddleware(error_docs_app)
- app = TestApp(RecursiveMiddleware(app))
- res = app.get('')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'requested page returned' in res
- res = app.get('/error')
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '200 OK'
- assert 'Page not found' in res
- res = app.get('/not_found', status=404)
- assert res.headers['content-type'] == 'text/plain'
- assert res.status == '404 Not Found' # Different status
- assert 'Page not found' in res
- try:
- res = app.get('/recurse')
- except AssertionError as e:
- if str(e).startswith('Forwarding loop detected'):
- pass
- else:
- raise AssertionError('Failed to detect forwarding loop')
-
- def test_ForwardRequestException(self):
- class TestForwardRequestExceptionMiddleware(Middleware):
- def __call__(self, environ, start_response):
- if environ['PATH_INFO'] != '/not_found':
- return self.app(environ, start_response)
- raise ForwardRequestException(path_info=self.url)
- forward(TestForwardRequestExceptionMiddleware(error_docs_app))
diff --git a/pecan/tests/middleware/test_static.py b/pecan/tests/middleware/test_static.py
deleted file mode 100644
index 9a0c08c..0000000
--- a/pecan/tests/middleware/test_static.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from pecan.middleware.static import (StaticFileMiddleware, FileWrapper,
- _dump_date)
-from pecan.tests import PecanTestCase
-
-import os
-
-
-class TestStaticFileMiddleware(PecanTestCase):
-
- def setUp(self):
- super(TestStaticFileMiddleware, self).setUp()
-
- def app(environ, start_response):
- response_headers = [('Content-type', 'text/plain')]
- start_response('200 OK', response_headers)
- return ['Hello world!\n']
-
- self.app = StaticFileMiddleware(
- app, os.path.dirname(__file__)
- )
-
- self._status = None
- self._response_headers = None
-
- def _request(self, path):
- def start_response(status, response_headers, exc_info=None):
- self._status = status
- self._response_headers = response_headers
- return self.app(
- dict(PATH_INFO=path),
- start_response
- )
-
- def _get_response_header(self, header):
- for k, v in self._response_headers:
- if k.upper() == header.upper():
- return v
- return None
-
- def test_file_can_be_found(self):
- result = self._request('/static_fixtures/text.txt')
- assert isinstance(result, FileWrapper)
-
- def test_no_file_found_causes_passthrough(self):
- result = self._request('/static_fixtures/nosuchfile.txt')
- assert not isinstance(result, FileWrapper)
- assert result == ['Hello world!\n']
-
- def test_mime_type_works_for_png_files(self):
- self._request('/static_fixtures/self.png')
- assert self._get_response_header('Content-Type') == 'image/png'
-
- def test_file_can_be_closed(self):
- result = self._request('/static_fixtures/text.txt')
- assert result.close() is None
-
- def test_file_can_be_iterated_over(self):
- result = self._request('/static_fixtures/text.txt')
- assert len([x for x in result])
-
- def test_date_dumping_on_unix_timestamps(self):
- result = _dump_date(1331755274.59, ' ')
- assert result == 'Wed, 14 Mar 2012 20:01:14 GMT'
-
- def test_separator_sanitization_still_finds_file(self):
- os.altsep = ':'
- result = self._request(':static_fixtures:text.txt')
- assert isinstance(result, FileWrapper)
diff --git a/pecan/tests/scaffold_builder.py b/pecan/tests/scaffold_builder.py
deleted file mode 100644
index dc039da..0000000
--- a/pecan/tests/scaffold_builder.py
+++ /dev/null
@@ -1,172 +0,0 @@
-import os
-import sys
-import subprocess
-import time
-
-from six import b as b_
-
-from pecan.compat import urlopen, URLError
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest
-else:
- import unittest # noqa
-
-
-if __name__ == '__main__':
-
- class TestTemplateBuilds(PecanTestCase):
- """
- Used to test the templated quickstart project(s).
- """
-
- @property
- def bin(self):
- return os.path.dirname(sys.executable)
-
- def poll(self, proc):
- limit = 30
- for i in range(limit):
- proc.poll()
-
- # Make sure it's running
- if proc.returncode is None:
- break
- elif i == limit: # pragma: no cover
- raise RuntimeError("Server process didn't start.")
- time.sleep(.1)
-
- def test_project_pecan_serve_command(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'pecan'),
- 'serve',
- 'testing123/config.py'
- ])
-
- try:
- self.poll(proc)
- retries = 30
- while True:
- retries -= 1
- if retries < 0: # pragma: nocover
- raise RuntimeError(
- "The HTTP server has not replied within 3 seconds."
- )
- try:
- # ...and that it's serving (valid) content...
- resp = urlopen('http://localhost:8080/')
- assert resp.getcode()
- assert len(resp.read().decode())
- except URLError:
- pass
- else:
- break
- time.sleep(.1)
- finally:
- proc.terminate()
-
- def test_project_pecan_shell_command(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'pecan'),
- 'shell',
- 'testing123/config.py'
- ],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
-
- self.poll(proc)
-
- out, _ = proc.communicate(
- b_('{"model" : model, "conf" : conf, "app" : app}')
- )
- assert 'testing123.model' in out.decode(), out
- assert 'Config(' in out.decode(), out
- assert 'webtest.app.TestApp' in out.decode(), out
-
- try:
- # just in case stdin doesn't close
- proc.terminate()
- except:
- pass
-
- class TestThirdPartyServe(TestTemplateBuilds):
-
- def poll_http(self, name, proc, port):
- try:
- self.poll(proc)
- retries = 30
- while True:
- retries -= 1
- if retries < 0: # pragma: nocover
- raise RuntimeError(
- "The %s server has not replied within"
- " 3 seconds." % name
- )
- try:
- # ...and that it's serving (valid) content...
- resp = urlopen('http://localhost:%d/' % port)
- assert resp.getcode()
- assert len(resp.read().decode())
- except URLError:
- pass
- else:
- break
- time.sleep(.1)
- finally:
- proc.terminate()
-
- class TestGunicornServeCommand(TestThirdPartyServe):
-
- def test_serve_from_config(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'gunicorn_pecan'),
- 'testing123/config.py'
- ])
-
- self.poll_http('gunicorn', proc, 8080)
-
- def test_serve_with_custom_bind(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'gunicorn_pecan'),
- '--bind=0.0.0.0:9191',
- 'testing123/config.py'
- ])
-
- self.poll_http('gunicorn', proc, 9191)
-
- class TestUWSGIServiceCommand(TestThirdPartyServe):
-
- def test_serve_from_config(self):
- # Start the server
- proc = subprocess.Popen([
- os.path.join(self.bin, 'uwsgi'),
- '--http-socket',
- ':8080',
- '--venv',
- sys.prefix,
- '--pecan',
- 'testing123/config.py'
- ])
-
- self.poll_http('uwsgi', proc, 8080)
-
- # First, ensure that the `testing123` package has been installed
- args = [
- os.path.join(os.path.dirname(sys.executable), 'pip'),
- 'install',
- '-U',
- '-e',
- './testing123'
- ]
- process = subprocess.Popen(args)
- _, unused_err = process.communicate()
- assert not process.poll()
-
- unittest.main()
diff --git a/pecan/tests/scaffold_fixtures/__init__.py b/pecan/tests/scaffold_fixtures/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/scaffold_fixtures/__init__.py
+++ /dev/null
diff --git a/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl b/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl
deleted file mode 100644
index 95a9c91..0000000
--- a/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl
+++ /dev/null
@@ -1 +0,0 @@
-Pecan ${package}
diff --git a/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl b/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl
deleted file mode 100644
index 25591f3..0000000
--- a/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl
+++ /dev/null
@@ -1 +0,0 @@
-YAR ${package}
diff --git a/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt b/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt
deleted file mode 100644
index 02c61ad..0000000
--- a/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt
+++ /dev/null
@@ -1 +0,0 @@
-Pecan
diff --git a/pecan/tests/scaffold_fixtures/file_sub/foo_+package+ b/pecan/tests/scaffold_fixtures/file_sub/foo_+package+
deleted file mode 100644
index 035599b..0000000
--- a/pecan/tests/scaffold_fixtures/file_sub/foo_+package+
+++ /dev/null
@@ -1 +0,0 @@
-YAR
diff --git a/pecan/tests/scaffold_fixtures/simple/bar/spam.txt b/pecan/tests/scaffold_fixtures/simple/bar/spam.txt
deleted file mode 100644
index 02c61ad..0000000
--- a/pecan/tests/scaffold_fixtures/simple/bar/spam.txt
+++ /dev/null
@@ -1 +0,0 @@
-Pecan
diff --git a/pecan/tests/scaffold_fixtures/simple/foo b/pecan/tests/scaffold_fixtures/simple/foo
deleted file mode 100644
index 035599b..0000000
--- a/pecan/tests/scaffold_fixtures/simple/foo
+++ /dev/null
@@ -1 +0,0 @@
-YAR
diff --git a/pecan/tests/templates/__init__.py b/pecan/tests/templates/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pecan/tests/templates/__init__.py
+++ /dev/null
diff --git a/pecan/tests/templates/form_colors.html b/pecan/tests/templates/form_colors.html
deleted file mode 100644
index cd4fea9..0000000
--- a/pecan/tests/templates/form_colors.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="colors-0" name="colors-0" value="${data['colors'][0] if data else 'A color'}" />
-<input type="text" id="colors-1" name="colors-1" value="${data['colors'][1] if data else 'A color'}" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_colors_invalid.html b/pecan/tests/templates/form_colors_invalid.html
deleted file mode 100644
index d2d4ed7..0000000
--- a/pecan/tests/templates/form_colors_invalid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="colors-0" name="colors-0" value="blue" />
-<input type="text" id="colors-1" name="colors-1" value="" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_colors_valid.html b/pecan/tests/templates/form_colors_valid.html
deleted file mode 100644
index adecfbf..0000000
--- a/pecan/tests/templates/form_colors_valid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="colors-0" name="colors-0" value="blue" />
-<input type="text" id="colors-1" name="colors-1" value="red" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_login_invalid.html b/pecan/tests/templates/form_login_invalid.html
deleted file mode 100644
index cede816..0000000
--- a/pecan/tests/templates/form_login_invalid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="username" name="username" value="ryan" />
-<input type="password" id="password" name="password" value="" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_login_valid.html b/pecan/tests/templates/form_login_valid.html
deleted file mode 100644
index 0587754..0000000
--- a/pecan/tests/templates/form_login_valid.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<input type="text" id="username" name="username" />
-<input type="password" id="password" name="password" value="" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name.html b/pecan/tests/templates/form_name.html
deleted file mode 100644
index b46181d..0000000
--- a/pecan/tests/templates/form_name.html
+++ /dev/null
@@ -1 +0,0 @@
-<input type="text" id="name" name="name" value="${name if name else ''}" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name_invalid.html b/pecan/tests/templates/form_name_invalid.html
deleted file mode 100644
index bcbd035..0000000
--- a/pecan/tests/templates/form_name_invalid.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<!-- for: name -->
-<span class="error-message">Please enter a value</span><br />
-<input type="text" id="name" name="name" value="" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name_invalid_custom.html b/pecan/tests/templates/form_name_invalid_custom.html
deleted file mode 100644
index cbb631f..0000000
--- a/pecan/tests/templates/form_name_invalid_custom.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<!-- for: name -->
-<span class="error-message">Names must be unique</span><br />
-<input type="text" id="name" name="name" value="Yoann" class="error" /> \ No newline at end of file
diff --git a/pecan/tests/templates/form_name_valid.html b/pecan/tests/templates/form_name_valid.html
deleted file mode 100644
index fc7d693..0000000
--- a/pecan/tests/templates/form_name_valid.html
+++ /dev/null
@@ -1 +0,0 @@
-<input type="text" id="name" name="name" value="Yoann" /> \ No newline at end of file
diff --git a/pecan/tests/templates/genshi.html b/pecan/tests/templates/genshi.html
deleted file mode 100644
index a42aa32..0000000
--- a/pecan/tests/templates/genshi.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude">
-
-<head>
- <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
- <title>Hello, ${name}!</title>
-</head>
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/genshi_bad.html b/pecan/tests/templates/genshi_bad.html
deleted file mode 100644
index 557bc55..0000000
--- a/pecan/tests/templates/genshi_bad.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude">
-
-<head>
- <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
- <title>Hello, ${name}!</title>
-<!-- comment out close tag to cause error
-</head>
--->
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/jinja.html b/pecan/tests/templates/jinja.html
deleted file mode 100644
index e96835a..0000000
--- a/pecan/tests/templates/jinja.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-
-<head>
- <title>Hello, {{name}}!</title>
-</head>
-
-<body>
- <h1>Hello, {{name}}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/jinja_bad.html b/pecan/tests/templates/jinja_bad.html
deleted file mode 100644
index 3513871..0000000
--- a/pecan/tests/templates/jinja_bad.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<html>
-
-<head>
- <title>Hello, {{name}}!</title>
-</head>
-
-<body>
- <h1>Hello, {{name}}!</h1>
-</body>
-{# open a block without and name #}
-{% block %}
-
-</html>
diff --git a/pecan/tests/templates/kajiki.html b/pecan/tests/templates/kajiki.html
deleted file mode 100644
index 41c308c..0000000
--- a/pecan/tests/templates/kajiki.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-
-<head>
- <title>Hello, ${name}!</title>
-</head>
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html>
diff --git a/pecan/tests/templates/mako.html b/pecan/tests/templates/mako.html
deleted file mode 100644
index db60fec..0000000
--- a/pecan/tests/templates/mako.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<html>
-
-<head>
- <title>Hello, ${name}!</title>
-</head>
-
-<body>
- <h1>Hello, ${name}!</h1>
-</body>
-
-</html> \ No newline at end of file
diff --git a/pecan/tests/templates/mako_bad.html b/pecan/tests/templates/mako_bad.html
deleted file mode 100644
index e6caea2..0000000
--- a/pecan/tests/templates/mako_bad.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<%
-
- def bad_indentation:
-return None
-
-%>
diff --git a/pecan/tests/test_base.py b/pecan/tests/test_base.py
deleted file mode 100644
index 8558f82..0000000
--- a/pecan/tests/test_base.py
+++ /dev/null
@@ -1,2227 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-import os
-import json
-import traceback
-import warnings
-
-import webob
-from webob.exc import HTTPNotFound
-import mock
-from webtest import TestApp
-import six
-from six import b as b_
-from six import u as u_
-from six.moves import cStringIO as StringIO
-
-from pecan import (
- Pecan, Request, Response, expose, request, response, redirect,
- abort, make_app, override_template, render, route
-)
-from pecan.templating import (
- _builtin_renderers as builtin_renderers, error_formatters
-)
-from pecan.decorators import accept_noncanonical
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest # pragma: nocover
-else:
- import unittest # pragma: nocover
-
-
-class SampleRootController(object):
- pass
-
-
-class TestAppRoot(PecanTestCase):
-
- def test_controller_lookup_by_string_path(self):
- app = Pecan('pecan.tests.test_base.SampleRootController')
- assert app.root and isinstance(app.root, SampleRootController)
-
-
-class TestEmptyContent(PecanTestCase):
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- pass
-
- @expose()
- def explicit_body(self):
- response.body = b_('Hello, World!')
-
- @expose()
- def empty_body(self):
- response.body = b_('')
-
- @expose()
- def explicit_text(self):
- response.text = six.text_type('Hello, World!')
-
- @expose()
- def empty_text(self):
- response.text = six.text_type('')
-
- @expose()
- def explicit_json(self):
- response.json = {'foo': 'bar'}
-
- @expose()
- def explicit_json_body(self):
- response.json_body = {'foo': 'bar'}
-
- @expose()
- def non_unicode(self):
- return chr(0xc0)
-
- return TestApp(Pecan(RootController()))
-
- def test_empty_index(self):
- r = self.app_.get('/')
- self.assertEqual(r.status_int, 204)
- self.assertNotIn('Content-Type', r.headers)
- self.assertEqual(r.headers['Content-Length'], '0')
- self.assertEqual(len(r.body), 0)
-
- def test_index_with_non_unicode(self):
- r = self.app_.get('/non_unicode/')
- self.assertEqual(r.status_int, 200)
-
- def test_explicit_body(self):
- r = self.app_.get('/explicit_body/')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_('Hello, World!'))
-
- def test_empty_body(self):
- r = self.app_.get('/empty_body/')
- self.assertEqual(r.status_int, 204)
- self.assertEqual(r.body, b_(''))
-
- def test_explicit_text(self):
- r = self.app_.get('/explicit_text/')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_('Hello, World!'))
-
- def test_empty_text(self):
- r = self.app_.get('/empty_text/')
- self.assertEqual(r.status_int, 204)
- self.assertEqual(r.body, b_(''))
-
- def test_explicit_json(self):
- r = self.app_.get('/explicit_json/')
- self.assertEqual(r.status_int, 200)
- json_resp = json.loads(r.body.decode())
- assert json_resp == {'foo': 'bar'}
-
- def test_explicit_json_body(self):
- r = self.app_.get('/explicit_json_body/')
- self.assertEqual(r.status_int, 200)
- json_resp = json.loads(r.body.decode())
- assert json_resp == {'foo': 'bar'}
-
-
-class TestAppIterFile(PecanTestCase):
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- body = six.BytesIO(b_('Hello, World!'))
- response.body_file = body
-
- @expose()
- def empty(self):
- body = six.BytesIO(b_(''))
- response.body_file = body
-
- return TestApp(Pecan(RootController()))
-
- def test_body_generator(self):
- r = self.app_.get('/')
- self.assertEqual(r.status_int, 200)
- assert r.body == b_('Hello, World!')
-
- def test_empty_body_generator(self):
- r = self.app_.get('/empty')
- self.assertEqual(r.status_int, 204)
- assert len(r.body) == 0
-
-
-class TestInvalidURLEncoding(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
-
- @expose()
- def _route(self, args, request):
- assert request.path
-
- return TestApp(Pecan(RootController()))
-
- def test_rest_with_non_utf_8_body(self):
- r = self.app_.get('/%aa/', expect_errors=True)
- assert r.status_int == 400
-
-
-class TestIndexRouting(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- return TestApp(Pecan(RootController()))
-
- def test_empty_root(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index(self):
- r = self.app_.get('/index')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index_html(self):
- r = self.app_.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
-
-class TestObjectDispatch(PecanTestCase):
-
- @property
- def app_(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()
-
- return TestApp(Pecan(RootController()))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_one_level(self):
- r = self.app_.get('/deeper')
- assert r.status_int == 200
- assert r.body == b_('/deeper')
-
- def test_one_level_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert r.body == b_('/sub/')
-
- def test_two_levels(self):
- r = self.app_.get('/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/deeper')
-
- def test_two_levels_with_trailing(self):
- r = self.app_.get('/sub/sub/')
- assert r.status_int == 200
-
- def test_three_levels(self):
- r = self.app_.get('/sub/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/sub/deeper')
-
-
-@unittest.skipIf(not six.PY3, "tests are Python3 specific")
-class TestUnicodePathSegments(PecanTestCase):
-
- def test_unicode_methods(self):
- class RootController(object):
- pass
- setattr(RootController, '🌰', expose()(lambda self: 'Hello, World!'))
- app = TestApp(Pecan(RootController()))
-
- resp = app.get('/%F0%9F%8C%B0/')
- assert resp.status_int == 200
- assert resp.body == b_('Hello, World!')
-
- def test_unicode_child(self):
- class ChildController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- class RootController(object):
- pass
- setattr(RootController, '🌰', ChildController())
- app = TestApp(Pecan(RootController()))
-
- resp = app.get('/%F0%9F%8C%B0/')
- assert resp.status_int == 200
- assert resp.body == b_('Hello, World!')
-
-
-class TestLookups(PecanTestCase):
-
- @property
- def app_(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
-
- return TestApp(Pecan(RootController()))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_lookup(self):
- r = self.app_.get('/100/')
- assert r.status_int == 200
- assert r.body == b_('/100')
-
- def test_lookup_with_method(self):
- r = self.app_.get('/100/name')
- assert r.status_int == 200
- assert r.body == b_('/100/name')
-
- def test_lookup_with_wrong_argspec(self):
- class RootController(object):
- @expose()
- def _lookup(self, someID):
- return 'Bad arg spec' # pragma: nocover
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- app = TestApp(Pecan(RootController()))
- r = app.get('/foo/bar', expect_errors=True)
- assert r.status_int == 404
-
-
-class TestCanonicalLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self):
- return self.someID
-
- class UserController(object):
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- class RootController(object):
- users = UserController()
-
- return TestApp(Pecan(RootController()))
-
- def test_canonical_lookup(self):
- assert self.app_.get('/users', expect_errors=404).status_int == 404
- assert self.app_.get('/users/', expect_errors=404).status_int == 404
- assert self.app_.get('/users/100').status_int == 302
- assert self.app_.get('/users/100/').body == b_('100')
-
-
-class TestControllerArguments(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @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())
- ]
- 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())
- ]
- 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)
- )
-
- @staticmethod
- @expose()
- def static(id):
- return "id is %s" % id
-
- @expose()
- def _route(self, args, request):
- if hasattr(self, args[0]):
- return getattr(self, args[0]), args[1:]
- else:
- return self.index, args
-
- return TestApp(Pecan(RootController()))
-
- def test_required_argument(self):
- try:
- r = self.app_.get('/')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "index() takes exactly 2 arguments (1 given)",
- "index() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_single_argument(self):
- r = self.app_.get('/1')
- assert r.status_int == 200
- assert r.body == b_('index: 1')
-
- def test_single_argument_with_encoded_url(self):
- r = self.app_.get('/This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_single_argument_with_plus(self):
- r = self.app_.get('/foo+bar')
- assert r.status_int == 200
- assert r.body == b_('index: foo+bar')
-
- def test_single_argument_with_encoded_plus(self):
- r = self.app_.get('/foo%2Bbar')
- assert r.status_int == 200
- assert r.body == b_('index: foo+bar')
-
- def test_two_arguments(self):
- r = self.app_.get('/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_keyword_argument(self):
- r = self.app_.get('/?id=2')
- assert r.status_int == 200
- assert r.body == b_('index: 2')
-
- def test_keyword_argument_with_encoded_url(self):
- r = self.app_.get('/?id=This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_keyword_argument_with_plus(self):
- r = self.app_.get('/?id=foo+bar')
- assert r.status_int == 200
- assert r.body == b_('index: foo bar')
-
- def test_keyword_argument_with_encoded_plus(self):
- r = self.app_.get('/?id=foo%2Bbar')
- assert r.status_int == 200
- assert r.body == b_('index: foo+bar')
-
- def test_argument_and_keyword_argument(self):
- r = self.app_.get('/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: 3')
-
- def test_encoded_argument_and_keyword_argument(self):
- r = self.app_.get('/This%20is%20a%20test%21?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_explicit_kwargs(self):
- r = self.app_.post('/', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_path_with_explicit_kwargs(self):
- r = self.app_.post('/4', {'id': 'four'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_explicit_json_kwargs(self):
- r = self.app_.post_json('/', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_path_with_explicit_json_kwargs(self):
- r = self.app_.post_json('/4', {'id': 'four'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_multiple_kwargs(self):
- r = self.app_.get('/?id=5&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('index: 5')
-
- def test_kwargs_from_root(self):
- r = self.app_.post('/', {'id': '6', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('index: 6')
-
- def test_json_kwargs_from_root(self):
- r = self.app_.post_json('/', {'id': '6', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('index: 6')
-
- # multiple args
-
- def test_multiple_positional_arguments(self):
- r = self.app_.get('/multiple/one/two')
- assert r.status_int == 200
- assert r.body == b_('multiple: one, two')
-
- def test_multiple_positional_arguments_with_url_encode(self):
- r = self.app_.get('/multiple/One%20/Two%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: One , Two!')
-
- def test_multiple_positional_arguments_with_kwargs(self):
- r = self.app_.get('/multiple?one=three&two=four')
- assert r.status_int == 200
- assert r.body == b_('multiple: three, four')
-
- def test_multiple_positional_arguments_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple?one=Three%20&two=Four%20%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: Three , Four !')
-
- def test_positional_args_with_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'five', 'two': 'six'})
- assert r.status_int == 200
- assert r.body == b_('multiple: five, six')
-
- def test_positional_args_with_json_kwargs(self):
- r = self.app_.post_json('/multiple', {'one': 'five', 'two': 'six'})
- assert r.status_int == 200
- assert r.body == b_('multiple: five, six')
-
- def test_positional_args_with_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'Five%20', 'two': 'Six%20%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple: Five%20, Six%20%21')
-
- # optional arg
- def test_optional_arg(self):
- r = self.app_.get('/optional')
- assert r.status_int == 200
- assert r.body == b_('optional: None')
-
- def test_multiple_optional(self):
- r = self.app_.get('/optional/1')
- assert r.status_int == 200
- assert r.body == b_('optional: 1')
-
- def test_multiple_optional_url_encoded(self):
- r = self.app_.get('/optional/Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_optional_missing(self):
- r = self.app_.get('/optional/2/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_with_kwargs(self):
- r = self.app_.get('/optional?id=2')
- assert r.status_int == 200
- assert r.body == b_('optional: 2')
-
- def test_multiple_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: 3')
-
- def test_url_encoded_positional_args(self):
- r = self.app_.get('/optional/Some%20Number?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_kwargs(self):
- r = self.app_.post('/optional', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('optional: 4')
-
- def test_optional_arg_with_json_kwargs(self):
- r = self.app_.post_json('/optional', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('optional: 4')
-
- def test_optional_arg_with_url_encoded_kwargs(self):
- r = self.app_.post('/optional', {'id': 'Some%20Number'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- def test_multiple_positional_arguments_with_dictionary_kwargs(self):
- r = self.app_.post('/optional/5', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: 5')
-
- def test_multiple_positional_arguments_with_json_kwargs(self):
- r = self.app_.post_json('/optional/5', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: 5')
-
- def test_multiple_positional_url_encoded_arguments_with_kwargs(self):
- r = self.app_.post('/optional/Some%20Number', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_kwargs(self):
- r = self.app_.get('/optional?id=6&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: 6')
-
- def test_optional_arg_with_multiple_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_dictionary_kwargs(self):
- r = self.app_.post('/optional', {'id': '7', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('optional: 7')
-
- def test_optional_arg_with_multiple_json_kwargs(self):
- r = self.app_.post_json('/optional', {'id': '7', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('optional: 7')
-
- def test_optional_arg_with_multiple_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/optional', {
- 'id': 'Some%20Number',
- 'dummy': 'dummy'
- })
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- # multiple optional args
-
- def test_multiple_optional_positional_args(self):
- r = self.app_.get('/multiple_optional')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, None')
-
- def test_multiple_optional_positional_args_one_arg(self):
- r = self.app_.get('/multiple_optional/1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_one_url_encoded_arg(self):
- r = self.app_.get('/multiple_optional/One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_all_args(self):
- r = self.app_.get('/multiple_optional/1/2/3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_positional_args_all_url_encoded_args(self):
- r = self.app_.get('/multiple_optional/One%21/Two%21/Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_positional_args_too_many_args(self):
- r = self.app_.get('/multiple_optional/1/2/3/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_optional_positional_args_with_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple_optional?one=One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_string_kwargs(self):
- r = self.app_.get('/multiple_optional/1?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_str_kwargs(self):
- r = self.app_.get('/multiple_optional/One%21?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': '1'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_json_kwargs(self):
- r = self.app_.post_json('/multiple_optional', {'one': '1'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': 'One%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, None, None')
-
- def test_multiple_optional_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/1', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_and_json_kwargs(self):
- r = self.app_.post_json('/multiple_optional/1', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_encoded_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/One%21', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_args_with_multiple_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1&two=2&three=3&four=4')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_kwargs(self):
- r = self.app_.get(
- '/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4'
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_args_with_multiple_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {'one': '1', 'two': '2', 'three': '3', 'four': '4'}
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_json_kwargs(self):
- r = self.app_.post_json(
- '/multiple_optional',
- {'one': '1', 'two': '2', 'three': '3', 'four': '4'}
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {
- 'one': 'One%21',
- 'two': 'Two%21',
- 'three': 'Three%21',
- 'four': '4'
- }
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, Two%21, Three%21')
-
- def test_multiple_optional_args_with_last_kwarg(self):
- r = self.app_.get('/multiple_optional?three=3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, 3')
-
- def test_multiple_optional_args_with_last_encoded_kwarg(self):
- r = self.app_.get('/multiple_optional?three=Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, Three!')
-
- def test_multiple_optional_args_with_middle_arg(self):
- r = self.app_.get('/multiple_optional', {'two': '2'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, 2, None')
-
- def test_variable_args(self):
- r = self.app_.get('/variable_args')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_multiple_variable_args(self):
- r = self.app_.get('/variable_args/1/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: 1, dummy')
-
- def test_multiple_encoded_variable_args(self):
- r = self.app_.get('/variable_args/Testing%20One%20Two/Three%21')
- assert r.status_int == 200
- assert r.body == b_('variable_args: Testing One Two, Three!')
-
- def test_variable_args_with_kwargs(self):
- r = self.app_.get('/variable_args?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_args_with_dict_kwargs(self):
- r = self.app_.post('/variable_args', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_args_with_json_kwargs(self):
- r = self.app_.post_json(
- '/variable_args',
- {'id': '3', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: ')
-
- def test_multiple_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_variable_kwargs_with_explicit_kwargs(self):
- r = self.app_.get('/variable_kwargs?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=2')
-
- def test_multiple_variable_kwargs_with_explicit_encoded_kwargs(self):
- r = self.app_.get(
- '/variable_kwargs?id=Two%21&dummy=This%20is%20a%20test'
- )
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=This is a test, id=Two!')
-
- def test_multiple_variable_kwargs_with_dict_kwargs(self):
- r = self.app_.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
-
- def test_multiple_variable_kwargs_with_json_kwargs(self):
- r = self.app_.post_json(
- '/variable_kwargs',
- {'id': '3', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
-
- def test_multiple_variable_kwargs_with_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/variable_kwargs',
- {'id': 'Three%21', 'dummy': 'This%20is%20a%20test'}
- )
- assert r.status_int == 200
- result = 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21'
- assert r.body == b_(result)
-
- def test_variable_all(self):
- r = self.app_.get('/variable_all')
- assert r.status_int == 200
- assert r.body == b_('variable_all: ')
-
- def test_variable_all_with_one_extra(self):
- r = self.app_.get('/variable_all/1')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 1')
-
- def test_variable_all_with_two_extras(self):
- r = self.app_.get('/variable_all/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 2, dummy')
-
- def test_variable_mixed(self):
- r = self.app_.get('/variable_all/3?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 3, day=12, month=1')
-
- def test_variable_mixed_explicit(self):
- r = self.app_.get('/variable_all/4?id=four&month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 4, day=12, id=four, month=1')
-
- def test_variable_post(self):
- r = self.app_.post('/variable_all/5/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 5, dummy')
-
- def test_variable_post_with_kwargs(self):
- r = self.app_.post('/variable_all/6', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('variable_all: 6, day=12, month=1')
-
- def test_variable_post_with_json_kwargs(self):
- r = self.app_.post_json(
- '/variable_all/6',
- {'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 6, day=12, month=1')
-
- def test_variable_post_mixed(self):
- r = self.app_.post(
- '/variable_all/7',
- {'id': 'seven', 'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
-
- def test_variable_post_mixed_with_json(self):
- r = self.app_.post_json(
- '/variable_all/7',
- {'id': 'seven', 'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
-
- def test_duplicate_query_parameters_GET(self):
- r = self.app_.get('/variable_kwargs?list=1&list=2')
- l = [u_('1'), u_('2')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_duplicate_query_parameters_POST(self):
- r = self.app_.post('/variable_kwargs',
- {'list': ['1', '2']})
- l = [u_('1'), u_('2')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_duplicate_query_parameters_POST_mixed(self):
- r = self.app_.post('/variable_kwargs?list=1&list=2',
- {'list': ['3', '4']})
- l = [u_('1'), u_('2'), u_('3'), u_('4')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_duplicate_query_parameters_POST_mixed_json(self):
- r = self.app_.post('/variable_kwargs?list=1&list=2',
- {'list': 3})
- l = [u_('1'), u_('2'), u_('3')]
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: list=%s' % l)
-
- def test_staticmethod(self):
- r = self.app_.get('/static/foobar')
- assert r.status_int == 200
- assert r.body == b_('id is foobar')
-
- def test_no_remainder(self):
- try:
- r = self.app_.get('/eater')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "eater() takes at least 2 arguments (1 given)",
- "eater() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_one_remainder(self):
- r = self.app_.get('/eater/1')
- assert r.status_int == 200
- assert r.body == b_('eater: 1, None, ')
-
- def test_two_remainders(self):
- r = self.app_.get('/eater/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 2, dummy, ')
-
- def test_many_remainders(self):
- r = self.app_.get('/eater/3/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 3, dummy, foo, bar')
-
- def test_remainder_with_kwargs(self):
- r = self.app_.get('/eater/4?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('eater: 4, None, day=12, month=1')
-
- def test_remainder_with_many_kwargs(self):
- r = self.app_.get('/eater/5?id=five&month=1&day=12&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 5, dummy, day=12, month=1')
-
- def test_post_remainder(self):
- r = self.app_.post('/eater/6')
- assert r.status_int == 200
- assert r.body == b_('eater: 6, None, ')
-
- def test_post_three_remainders(self):
- r = self.app_.post('/eater/7/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 7, dummy, ')
-
- def test_post_many_remainders(self):
- r = self.app_.post('/eater/8/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 8, dummy, foo, bar')
-
- def test_post_remainder_with_kwargs(self):
- r = self.app_.post('/eater/9', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_empty_remainder_with_json_kwargs(self):
- r = self.app_.post_json('/eater/9/', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_remainder_with_json_kwargs(self):
- r = self.app_.post_json('/eater/9', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_many_remainders_with_many_kwargs(self):
- r = self.app_.post(
- '/eater/10',
- {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('eater: 10, dummy, day=12, month=1')
-
- def test_post_many_remainders_with_many_json_kwargs(self):
- r = self.app_.post_json(
- '/eater/10',
- {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('eater: 10, dummy, day=12, month=1')
-
-
-class TestDefaultErrorRendering(PecanTestCase):
-
- def test_plain_error(self):
- class RootController(object):
- pass
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', status=404)
- assert r.status_int == 404
- assert r.content_type == 'text/plain'
- assert r.body == b_(HTTPNotFound().plain_body({}))
-
- def test_html_error(self):
- class RootController(object):
- pass
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', headers={'Accept': 'text/html'}, status=404)
- assert r.status_int == 404
- assert r.content_type == 'text/html'
- assert r.body == b_(HTTPNotFound().html_body({}))
-
- def test_json_error(self):
- class RootController(object):
- pass
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/', headers={'Accept': 'application/json'}, status=404)
- assert r.status_int == 404
- json_resp = json.loads(r.body.decode())
- assert json_resp['code'] == 404
- assert json_resp['description'] is None
- assert json_resp['title'] == 'Not Found'
- assert r.content_type == 'application/json'
-
-
-class TestAbort(PecanTestCase):
-
- 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
-
- def test_abort_keeps_traceback(self):
- last_exc, last_traceback = None, None
-
- try:
- try:
- raise Exception('Bottom Exception')
- except:
- abort(404)
- except Exception:
- last_exc, _, last_traceback = sys.exc_info()
-
- assert last_exc is HTTPNotFound
- assert 'Bottom Exception' in traceback.format_tb(last_traceback)[-1]
-
-
-class TestScriptName(PecanTestCase):
-
- def setUp(self):
- super(TestScriptName, self).setUp()
- self.environ = {'SCRIPT_NAME': '/foo'}
-
- def test_handle_script_name(self):
- class RootController(object):
- @expose()
- def index(self):
- return 'Root Index'
-
- app = TestApp(Pecan(RootController()), extra_environ=self.environ)
- r = app.get('/foo/')
- assert r.status_int == 200
-
-
-class TestRedirect(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @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)
-
- @expose()
- def testing(self):
- return 'it worked!'
-
- return TestApp(make_app(RootController(), debug=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 302
- r = r.follow()
- assert r.status_int == 200
- assert r.body == b_('it worked!')
-
- def test_internal(self):
- r = self.app_.get('/internal')
- assert r.status_int == 200
- assert r.body == b_('it worked!')
-
- def test_internal_with_301(self):
- self.assertRaises(ValueError, self.app_.get, '/bad_internal')
-
- def test_permanent_redirect(self):
- r = self.app_.get('/permanent')
- assert r.status_int == 301
- r = r.follow()
- assert r.status_int == 200
- assert r.body == b_('it worked!')
-
- def test_x_forward_proto(self):
- class ChildController(object):
- @expose()
- def index(self):
- redirect('/testing') # pragma: nocover
-
- class RootController(object):
- @expose()
- def index(self):
- redirect('/testing') # pragma: nocover
-
- @expose()
- def testing(self):
- return 'it worked!' # pragma: nocover
- child = ChildController()
-
- app = TestApp(make_app(RootController(), debug=True))
- 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 res.status_int == 302
- # should add trailing / and changes location to https
- assert res.location == 'https://localhost/child/'
- assert res.request.environ['HTTP_X_FORWARDED_PROTO'] == 'https'
-
-
-class TestInternalRedirectContext(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
-
- @expose()
- def redirect_with_context(self):
- request.context['foo'] = 'bar'
- redirect('/testing')
-
- @expose()
- def internal_with_context(self):
- request.context['foo'] = 'bar'
- redirect('/testing', internal=True)
-
- @expose('json')
- def testing(self):
- return request.context
-
- return TestApp(make_app(RootController(), debug=False))
-
- def test_internal_with_request_context(self):
- r = self.app_.get('/internal_with_context')
- assert r.status_int == 200
- assert json.loads(r.body.decode()) == {'foo': 'bar'}
-
- def test_context_does_not_bleed(self):
- r = self.app_.get('/redirect_with_context').follow()
- assert r.status_int == 200
- assert json.loads(r.body.decode()) == {}
-
-
-class TestStreamedResponse(PecanTestCase):
-
- def test_streaming_response(self):
-
- class RootController(object):
- @expose(content_type='text/plain')
- def test(self, foo):
- if foo == 'stream':
- # mimic large file
- contents = six.BytesIO(b_('stream'))
- response.content_type = 'application/octet-stream'
- contents.seek(0, os.SEEK_END)
- response.content_length = contents.tell()
- contents.seek(0, os.SEEK_SET)
- response.app_iter = contents
- return response
- else:
- return 'plain text'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/test/stream')
- assert r.content_type == 'application/octet-stream'
- assert r.body == b_('stream')
-
- r = app.get('/test/plain')
- assert r.content_type == 'text/plain'
- assert r.body == b_('plain text')
-
-
-class TestManualResponse(PecanTestCase):
-
- def test_manual_response(self):
-
- class RootController(object):
- @expose()
- def index(self):
- resp = webob.Response(response.environ)
- resp.body = b_('Hello, World!')
- return resp
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.body == b_('Hello, World!')
-
-
-class TestCustomResponseandRequest(PecanTestCase):
-
- def test_custom_objects(self):
-
- class CustomRequest(Request):
-
- @property
- def headers(self):
- headers = super(CustomRequest, self).headers
- headers['X-Custom-Request'] = 'ABC'
- return headers
-
- class CustomResponse(Response):
-
- @property
- def headers(self):
- headers = super(CustomResponse, self).headers
- headers['X-Custom-Response'] = 'XYZ'
- return headers
-
- class RootController(object):
- @expose()
- def index(self):
- return request.headers.get('X-Custom-Request')
-
- app = TestApp(Pecan(
- RootController(),
- request_cls=CustomRequest,
- response_cls=CustomResponse
- ))
- r = app.get('/')
- assert r.body == b_('ABC')
- assert r.headers.get('X-Custom-Response') == 'XYZ'
-
-
-class TestThreadLocalState(PecanTestCase):
-
- def test_thread_local_dir(self):
- """
- Threadlocal proxies for request and response should properly
- proxy ``dir()`` calls to the underlying webob class.
- """
- class RootController(object):
- @expose()
- def index(self):
- assert 'method' in dir(request)
- assert 'status' in dir(response)
- return '/'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- 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 == b_('/')
-
- assert state.__dict__ == {}
-
-
-class TestFileTypeExtensions(PecanTestCase):
-
- @property
- def app_(self):
- """
- Test extension splits
- """
- class RootController(object):
- @expose(content_type=None)
- def _default(self, *args):
- ext = request.pecan['extension']
- assert len(args) == 1
- if ext:
- assert ext not in args[0]
- return ext or ''
-
- return TestApp(Pecan(RootController()))
-
- def test_html_extension(self):
- r = self.app_.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('.html')
-
- def test_image_extension(self):
- r = self.app_.get('/image.png')
- assert r.status_int == 200
- assert r.body == b_('.png')
-
- def test_hidden_file(self):
- r = self.app_.get('/.vimrc')
- assert r.status_int == 204
- assert r.body == b_('')
-
- def test_multi_dot_extension(self):
- r = self.app_.get('/gradient.min.js')
- assert r.status_int == 200
- assert r.body == b_('.js')
-
- 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 == b_('/')
-
- r = app.get('/index.html', expect_errors=True)
- assert r.status_int == 200
- assert r.body == b_('/')
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.get('/index.txt', expect_errors=True)
- assert r.status_int == 404
-
- def test_unknown_file_extension(self):
- class RootController(object):
- @expose(content_type=None)
- def _default(self, *args):
- assert 'example:x.tiny' in args
- assert request.pecan['extension'] is None
- return 'SOME VALUE'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/example:x.tiny')
- assert r.status_int == 200
- assert r.body == b_('SOME VALUE')
-
- def test_guessing_disabled(self):
- class RootController(object):
- @expose(content_type=None)
- def _default(self, *args):
- assert 'index.html' in args
- assert request.pecan['extension'] is None
- return 'SOME VALUE'
-
- app = TestApp(Pecan(RootController(),
- guess_content_type_from_ext=False))
-
- r = app.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('SOME VALUE')
-
-
-class TestContentTypeByAcceptHeaders(PecanTestCase):
-
- @property
- def app_(self):
- """
- Test that content type is set appropriately based on Accept headers.
- """
- class RootController(object):
-
- @expose(content_type='text/html')
- @expose(content_type='application/json')
- def index(self, *args):
- return 'Foo'
-
- return TestApp(Pecan(RootController()))
-
- def test_quality(self):
- r = self.app_.get('/', headers={
- 'Accept': 'text/html,application/json;q=0.9,*/*;q=0.8'
- })
- assert r.status_int == 200
- assert r.content_type == 'text/html'
-
- r = self.app_.get('/', headers={
- 'Accept': 'application/json,text/html;q=0.9,*/*;q=0.8'
- })
- assert r.status_int == 200
- assert r.content_type == 'application/json'
-
- def test_file_extension_has_higher_precedence(self):
- r = self.app_.get('/index.html', headers={
- 'Accept': 'application/json,text/html;q=0.9,*/*;q=0.8'
- })
- assert r.status_int == 200
- assert r.content_type == 'text/html'
-
- def test_not_acceptable(self):
- r = self.app_.get('/', headers={
- 'Accept': 'application/xml',
- }, status=406)
- assert r.status_int == 406
-
- def test_accept_header_missing(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.content_type == 'text/html'
-
-
-class TestCanonicalRouting(PecanTestCase):
-
- @property
- def app_(self):
- class ArgSubController(object):
- @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):
- return 'index'
-
- sub = SubController()
- arg = ArgSubController()
- accept = AcceptController()
-
- return TestApp(Pecan(RootController()))
-
- def test_root(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert b_('index') in r.body
-
- def test_index(self):
- r = self.app_.get('/index')
- assert r.status_int == 200
- assert b_('index') in r.body
-
- def test_broken_clients(self):
- # for broken clients
- r = self.app_.get('', status=302)
- assert r.status_int == 302
- assert r.location == 'http://localhost/'
-
- def test_sub_controller_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_sub_controller_redirect(self):
- r = self.app_.get('/sub', status=302)
- assert r.status_int == 302
- assert r.location == 'http://localhost/sub/'
-
- def test_with_query_string(self):
- # try with query string
- r = self.app_.get('/sub?foo=bar', status=302)
- assert r.status_int == 302
- assert r.location == 'http://localhost/sub/?foo=bar'
-
- def test_posts_fail(self):
- try:
- self.app_.post('/sub', dict(foo=1))
- raise Exception("Post should fail") # pragma: nocover
- except Exception as e:
- assert isinstance(e, RuntimeError)
-
- def test_with_args(self):
- r = self.app_.get('/arg/index/foo')
- assert r.status_int == 200
- assert r.body == b_('foo')
-
- def test_accept_noncanonical(self):
- r = self.app_.get('/accept/')
- assert r.status_int == 200
- assert r.body == b_('accept')
-
- def test_accept_noncanonical_no_trailing_slash(self):
- r = self.app_.get('/accept')
- assert r.status_int == 200
- assert r.body == b_('accept')
-
-
-class TestNonCanonical(PecanTestCase):
-
- @property
- def app_(self):
- class ArgSubController(object):
- @expose()
- def index(self, arg):
- return arg # pragma: nocover
-
- class AcceptController(object):
- @accept_noncanonical
- @expose()
- def index(self):
- return 'accept' # pragma: nocover
-
- class SubController(object):
- @expose()
- def index(self, **kw):
- return 'subindex'
-
- class RootController(object):
- @expose()
- def index(self):
- return 'index'
-
- sub = SubController()
- arg = ArgSubController()
- accept = AcceptController()
-
- return TestApp(Pecan(RootController(), force_canonical=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert b_('index') in r.body
-
- def test_subcontroller(self):
- r = self.app_.get('/sub')
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_subcontroller_with_kwargs(self):
- r = self.app_.post('/sub', dict(foo=1))
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_sub_controller_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert b_('subindex') in r.body
-
- def test_proxy(self):
- class RootController(object):
- @expose()
- def index(self):
- request.testing = True
- assert request.testing is True
- del request.testing
- assert hasattr(request, 'testing') is False
- return '/'
-
- app = TestApp(make_app(RootController(), debug=True))
- r = app.get('/')
- assert r.status_int == 200
-
- def test_app_wrap(self):
- class RootController(object):
- pass
-
- wrapped_apps = []
-
- def wrap(app):
- wrapped_apps.append(app)
- return app
-
- make_app(RootController(), wrap_app=wrap, debug=True)
- assert len(wrapped_apps) == 1
-
-
-class TestLogging(PecanTestCase):
-
- def test_logging_setup(self):
- class RootController(object):
- @expose()
- def index(self):
- import logging
- logging.getLogger('pecantesting').info('HELLO WORLD')
- return "HELLO WORLD"
-
- f = StringIO()
-
- app = TestApp(make_app(RootController(), logging={
- 'loggers': {
- 'pecantesting': {
- 'level': 'INFO', 'handlers': ['memory']
- }
- },
- 'handlers': {
- 'memory': {
- 'level': 'INFO',
- 'class': 'logging.StreamHandler',
- 'stream': f
- }
- }
- }))
-
- app.get('/')
- assert f.getvalue() == 'HELLO WORLD\n'
-
- def test_logging_setup_with_config_obj(self):
- class RootController(object):
- @expose()
- def index(self):
- import logging
- logging.getLogger('pecantesting').info('HELLO WORLD')
- return "HELLO WORLD"
-
- f = StringIO()
-
- from pecan.configuration import conf_from_dict
- app = TestApp(make_app(RootController(), logging=conf_from_dict({
- 'loggers': {
- 'pecantesting': {
- 'level': 'INFO', 'handlers': ['memory']
- }
- },
- 'handlers': {
- 'memory': {
- 'level': 'INFO',
- 'class': 'logging.StreamHandler',
- 'stream': f
- }
- }
- })))
-
- app.get('/')
- assert f.getvalue() == 'HELLO WORLD\n'
-
-
-class TestEngines(PecanTestCase):
-
- template_path = os.path.join(os.path.dirname(__file__), 'templates')
-
- @unittest.skipIf('genshi' not in builtin_renderers, 'Genshi not installed')
- def test_genshi(self):
-
- class RootController(object):
- @expose('genshi:genshi.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- @expose('genshi:genshi_bad.html')
- def badtemplate(self):
- return dict()
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- r = app.get('/index.html?name=World')
- assert r.status_int == 200
- assert b_("<h1>Hello, World!</h1>") in r.body
-
- error_msg = None
- try:
- r = app.get('/badtemplate.html')
- except Exception as e:
- for error_f in error_formatters:
- error_msg = error_f(e)
- if error_msg:
- break
- assert error_msg is not None
-
- @unittest.skipIf('kajiki' not in builtin_renderers, 'Kajiki not installed')
- def test_kajiki(self):
-
- class RootController(object):
- @expose('kajiki:kajiki.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- r = app.get('/index.html?name=World')
- assert r.status_int == 200
- assert b_("<h1>Hello, World!</h1>") in r.body
-
- @unittest.skipIf('jinja' not in builtin_renderers, 'Jinja not installed')
- def test_jinja(self):
-
- class RootController(object):
- @expose('jinja:jinja.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- @expose('jinja:jinja_bad.html')
- def badtemplate(self):
- return dict()
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- error_msg = None
- try:
- r = app.get('/badtemplate.html')
- except Exception as e:
- for error_f in error_formatters:
- error_msg = error_f(e)
- if error_msg:
- break
- assert error_msg is not None
-
- @unittest.skipIf('mako' not in builtin_renderers, 'Mako not installed')
- def test_mako(self):
-
- class RootController(object):
- @expose('mako:mako.html')
- def index(self, name='Jonathan'):
- return dict(name=name)
-
- @expose('mako:mako_bad.html')
- def badtemplate(self):
- return dict()
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- r = app.get('/index.html?name=World')
- assert r.status_int == 200
- assert b_("<h1>Hello, World!</h1>") in r.body
-
- error_msg = None
- try:
- r = app.get('/badtemplate.html')
- except Exception as e:
- for error_f in error_formatters:
- error_msg = error_f(e)
- if error_msg:
- break
- assert error_msg is not None
-
- def test_json(self):
- try:
- from simplejson import loads
- except:
- 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
- result = dict(loads(r.body.decode()))
- assert result == expected_result
-
- def test_override_template(self):
- class RootController(object):
- @expose('foo.html')
- def index(self):
- override_template(None, content_type='text/plain')
- return 'Override'
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/')
- assert r.status_int == 200
- assert b_('Override') in r.body
- assert r.content_type == 'text/plain'
-
- def test_render(self):
- class RootController(object):
- @expose()
- def index(self, name='Jonathan'):
- return render('mako.html', dict(name=name))
-
- app = TestApp(
- Pecan(RootController(), template_path=self.template_path)
- )
- r = app.get('/')
- assert r.status_int == 200
- assert b_("<h1>Hello, Jonathan!</h1>") in r.body
-
- def test_default_json_renderer(self):
-
- class RootController(object):
- @expose()
- def index(self, name='Bill'):
- return dict(name=name)
-
- app = TestApp(Pecan(RootController(), default_renderer='json'))
- r = app.get('/')
- assert r.status_int == 200
- result = dict(json.loads(r.body.decode()))
- assert result == {'name': 'Bill'}
-
- def test_default_json_renderer_with_explicit_content_type(self):
-
- class RootController(object):
- @expose(content_type='text/plain')
- def index(self, name='Bill'):
- return name
-
- app = TestApp(Pecan(RootController(), default_renderer='json'))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_("Bill")
-
-
-class TestDeprecatedRouteMethod(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
-
- @expose()
- def index(self, *args):
- return ', '.join(args)
-
- @expose()
- def _route(self, args):
- return self.index, args
-
- return TestApp(Pecan(RootController()))
-
- def test_required_argument(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.get('/foo/bar/')
- assert r.status_int == 200
- assert b_('foo, bar') in r.body
-
-
-class TestExplicitRoute(PecanTestCase):
-
- def test_alternate_route(self):
-
- class RootController(object):
-
- @expose(route='some-path')
- def some_path(self):
- return 'Hello, World!'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/some_path/', expect_errors=True)
- assert r.status_int == 404
-
- def test_manual_route(self):
-
- class SubController(object):
-
- @expose(route='some-path')
- def some_path(self):
- return 'Hello, World!'
-
- class RootController(object):
- pass
-
- route(RootController, 'some-controller', SubController())
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-controller/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/some-controller/some_path/', expect_errors=True)
- assert r.status_int == 404
-
- def test_manual_route_conflict(self):
-
- class SubController(object):
- pass
-
- class RootController(object):
-
- @expose()
- def hello(self):
- return 'Hello, World!'
-
- self.assertRaises(
- RuntimeError,
- route,
- RootController,
- 'hello',
- SubController()
- )
-
- def test_custom_route_on_index(self):
-
- class RootController(object):
-
- @expose(route='some-path')
- def index(self):
- return 'Hello, World!'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/index/', expect_errors=True)
- assert r.status_int == 404
-
- def test_custom_route_with_attribute_conflict(self):
-
- class RootController(object):
-
- @expose(route='mock')
- def greet(self):
- return 'Hello, World!'
-
- @expose()
- def mock(self):
- return 'You are not worthy!'
-
- app = TestApp(Pecan(RootController()))
-
- self.assertRaises(
- RuntimeError,
- app.get,
- '/mock/'
- )
-
- def test_conflicting_custom_routes(self):
-
- class RootController(object):
-
- @expose(route='testing')
- def foo(self):
- return 'Foo!'
-
- @expose(route='testing')
- def bar(self):
- return 'Bar!'
-
- app = TestApp(Pecan(RootController()))
-
- self.assertRaises(
- RuntimeError,
- app.get,
- '/testing/'
- )
-
- def test_conflicting_custom_routes_in_subclass(self):
-
- class BaseController(object):
-
- @expose(route='testing')
- def foo(self):
- return request.path
-
- class ChildController(BaseController):
- pass
-
- class RootController(BaseController):
- child = ChildController()
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/testing/')
- assert r.body == b_('/testing/')
-
- r = app.get('/child/testing/')
- assert r.body == b_('/child/testing/')
-
- def test_custom_route_prohibited_on_lookup(self):
- try:
- class RootController(object):
-
- @expose(route='some-path')
- def _lookup(self):
- return 'Hello, World!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- '_lookup cannot be used with a custom path segment'
- )
-
- def test_custom_route_prohibited_on_default(self):
- try:
- class RootController(object):
-
- @expose(route='some-path')
- def _default(self):
- return 'Hello, World!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- '_default cannot be used with a custom path segment'
- )
-
- def test_custom_route_prohibited_on_route(self):
- try:
- class RootController(object):
-
- @expose(route='some-path')
- def _route(self):
- return 'Hello, World!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- '_route cannot be used with a custom path segment'
- )
-
- def test_custom_route_with_generic_controllers(self):
-
- class RootController(object):
-
- @expose(route='some-path', generic=True)
- def foo(self):
- return 'Hello, World!'
-
- @foo.when(method='POST')
- def handle_post(self):
- return 'POST!'
-
- app = TestApp(Pecan(RootController()))
-
- r = app.get('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- r = app.get('/foo/', expect_errors=True)
- assert r.status_int == 404
-
- r = app.post('/some-path/')
- assert r.status_int == 200
- assert r.body == b_('POST!')
-
- r = app.post('/foo/', expect_errors=True)
- assert r.status_int == 404
-
- def test_custom_route_prohibited_on_generic_controllers(self):
- try:
- class RootController(object):
-
- @expose(generic=True)
- def foo(self):
- return 'Hello, World!'
-
- @foo.when(method='POST', route='some-path')
- def handle_post(self):
- return 'POST!'
- except ValueError:
- pass
- else:
- raise AssertionError(
- 'generic controllers cannot be used with a custom path segment'
- )
-
- def test_invalid_route_arguments(self):
- class C(object):
-
- def secret(self):
- return {}
-
- self.assertRaises(TypeError, route)
- self.assertRaises(TypeError, route, 'some-path', lambda x: x)
- self.assertRaises(TypeError, route, 'some-path', C.secret)
- self.assertRaises(TypeError, route, C, {}, C())
-
- for path in (
- 'VARIED-case-PATH',
- 'this,custom,path',
- '123-path',
- 'path(with-parens)',
- 'path;with;semicolons',
- 'path:with:colons',
- 'v2.0',
- '~username',
- 'somepath!',
- 'four*four',
- 'one+two',
- '@twitterhandle',
- 'package=pecan'
- ):
- handler = C()
- route(C, path, handler)
- assert getattr(C, path, handler)
-
- self.assertRaises(ValueError, route, C, '/path/', C())
- self.assertRaises(ValueError, route, C, '.', C())
- self.assertRaises(ValueError, route, C, '..', C())
- self.assertRaises(ValueError, route, C, 'path?', C())
- self.assertRaises(ValueError, route, C, 'percent%20encoded', C())
diff --git a/pecan/tests/test_commands.py b/pecan/tests/test_commands.py
deleted file mode 100644
index e9d6ead..0000000
--- a/pecan/tests/test_commands.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from pecan.tests import PecanTestCase
-
-
-class TestCommandManager(PecanTestCase):
-
- def test_commands(self):
- from pecan.commands import ServeCommand, ShellCommand, CreateCommand
- from pecan.commands.base import CommandManager
- m = CommandManager()
- assert m.commands['serve'] == ServeCommand
- assert m.commands['shell'] == ShellCommand
- assert m.commands['create'] == CreateCommand
-
-
-class TestCommandRunner(PecanTestCase):
-
- def test_commands(self):
- from pecan.commands import (
- ServeCommand, ShellCommand, CreateCommand, CommandRunner
- )
- runner = CommandRunner()
- assert runner.commands['serve'] == ServeCommand
- assert runner.commands['shell'] == ShellCommand
- assert runner.commands['create'] == CreateCommand
-
- def test_run(self):
- from pecan.commands import CommandRunner
- runner = CommandRunner()
- self.assertRaises(
- RuntimeError,
- runner.run,
- ['serve', 'missing_file.py']
- )
-
-
-class TestCreateCommand(PecanTestCase):
-
- def test_run(self):
- from pecan.commands import CreateCommand
-
- class FakeArg(object):
- project_name = 'default'
- template_name = 'default'
-
- class FakeScaffold(object):
- def copy_to(self, project_name):
- assert project_name == 'default'
-
- class FakeManager(object):
- scaffolds = {
- 'default': FakeScaffold
- }
-
- c = CreateCommand()
- c.manager = FakeManager()
- c.run(FakeArg())
diff --git a/pecan/tests/test_conf.py b/pecan/tests/test_conf.py
deleted file mode 100644
index e682885..0000000
--- a/pecan/tests/test_conf.py
+++ /dev/null
@@ -1,346 +0,0 @@
-import os
-import sys
-import tempfile
-
-from pecan.tests import PecanTestCase
-from six import b as b_
-
-
-__here__ = os.path.dirname(__file__)
-
-
-class TestConf(PecanTestCase):
-
- def test_update_config_fail_identifier(self):
- """Fail when naming does not pass correctness"""
- from pecan import configuration
- bad_dict = {'bad name': 'value'}
- self.assertRaises(ValueError, configuration.Config, bad_dict)
-
- def test_update_set_config(self):
- """Update an empty configuration with the default values"""
- from pecan import configuration
-
- conf = configuration.initconf()
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/config.py'
- )))
-
- self.assertEqual(conf.app.root, None)
- self.assertEqual(conf.app.template_path, 'myproject/templates')
- self.assertEqual(conf.app.static_root, 'public')
-
- self.assertEqual(conf.server.host, '1.1.1.1')
- self.assertEqual(conf.server.port, '8081')
-
- def test_update_set_default_config(self):
- """Update an empty configuration with the default values"""
- from pecan import configuration
-
- conf = configuration.initconf()
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/empty.py'
- )))
-
- self.assertEqual(conf.app.root, None)
- self.assertEqual(conf.app.template_path, '')
- self.assertEqual(conf.app.static_root, 'public')
-
- 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"""
- from pecan import configuration
- conf = configuration.initconf()
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/forcedict.py'
- )))
-
- self.assertEqual(conf.app.root, None)
- self.assertEqual(conf.app.template_path, '')
- self.assertEqual(conf.app.static_root, 'public')
-
- self.assertEqual(conf.server.host, '0.0.0.0')
- self.assertEqual(conf.server.port, '8080')
-
- 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.get('__force_dict__'), None)
-
- def test_update_config_with_dict(self):
- from pecan import configuration
- conf = configuration.initconf()
- d = {'attr': True}
- conf['attr'] = d
- self.assertTrue(conf.attr.attr)
-
- def test_config_repr(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- self.assertEqual(repr(conf), "Config({'a': 1})")
-
- def test_config_from_dict(self):
- from pecan import configuration
- conf = configuration.conf_from_dict({})
- conf['path'] = '%(confdir)s'
- self.assertTrue(os.path.samefile(conf['path'], os.getcwd()))
-
- def test_config_from_file(self):
- from pecan import configuration
- path = os.path.join(
- os.path.dirname(__file__), 'config_fixtures', 'config.py'
- )
- configuration.conf_from_file(path)
-
- def test_config_illegal_ids(self):
- from pecan import configuration
- conf = configuration.Config({})
- conf.update(configuration.conf_from_file(os.path.join(
- __here__,
- 'config_fixtures/bad/module_and_underscore.py'
- )))
- self.assertEqual([], list(conf))
-
- def test_config_missing_file(self):
- from pecan import configuration
- path = ('doesnotexist.py',)
- configuration.Config({})
- self.assertRaises(
- RuntimeError,
- configuration.conf_from_file,
- os.path.join(__here__, 'config_fixtures', *path)
- )
-
- def test_config_missing_file_on_path(self):
- from pecan import configuration
- path = ('bad', 'bad', 'doesnotexist.py',)
- configuration.Config({})
-
- self.assertRaises(
- RuntimeError,
- configuration.conf_from_file,
- os.path.join(__here__, 'config_fixtures', *path)
- )
-
- def test_config_with_syntax_error(self):
- from pecan import configuration
- with tempfile.NamedTemporaryFile('wb') as f:
- f.write(b_('\n'.join(['if false', 'var = 3'])))
- f.flush()
- configuration.Config({})
-
- self.assertRaises(
- SyntaxError,
- configuration.conf_from_file,
- f.name
- )
-
- def test_config_with_non_package_relative_import(self):
- from pecan import configuration
- with tempfile.NamedTemporaryFile('wb', suffix='.py') as f:
- f.write(b_('\n'.join(['from . import variables'])))
- f.flush()
- configuration.Config({})
-
- try:
- configuration.conf_from_file(f.name)
- except (ValueError, SystemError) as e:
- assert 'relative import' in str(e)
- else:
- raise AssertionError(
- "A relative import-related error should have been raised"
- )
-
- def test_config_with_bad_import(self):
- from pecan import configuration
- path = ('bad', 'importerror.py')
- configuration.Config({})
-
- self.assertRaises(
- ImportError,
- configuration.conf_from_file,
- os.path.join(
- __here__,
- 'config_fixtures',
- *path
- )
- )
-
- def test_config_dir(self):
- from pecan import configuration
- if sys.version_info >= (2, 6):
- conf = configuration.Config({})
- self.assertEqual([], dir(conf))
- conf = configuration.Config({'a': 1})
- self.assertEqual(['a'], dir(conf))
-
- def test_config_bad_key(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.a == 1
- self.assertRaises(AttributeError, getattr, conf, 'b')
-
- def test_config_get_valid_key(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.get('a') == 1
-
- def test_config_get_invalid_key(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.get('b') is None
-
- def test_config_get_invalid_key_return_default(self):
- from pecan import configuration
- conf = configuration.Config({'a': 1})
- assert conf.get('b', True) is True
-
- def test_config_to_dict(self):
- from pecan import configuration
- conf = configuration.initconf()
-
- assert isinstance(conf, configuration.Config)
-
- to_dict = conf.to_dict()
-
- assert isinstance(to_dict, dict)
- assert to_dict['server']['host'] == '0.0.0.0'
- assert to_dict['server']['port'] == '8080'
- assert to_dict['app']['modules'] == []
- assert to_dict['app']['root'] is None
- assert to_dict['app']['static_root'] == 'public'
- assert to_dict['app']['template_path'] == ''
-
- def test_config_to_dict_nested(self):
- from pecan import configuration
- """have more than one level nesting and convert to dict"""
- conf = configuration.initconf()
- nested = {'one': {'two': 2}}
- conf['nested'] = nested
-
- to_dict = conf.to_dict()
-
- assert isinstance(to_dict, dict)
- assert to_dict['server']['host'] == '0.0.0.0'
- assert to_dict['server']['port'] == '8080'
- assert to_dict['app']['modules'] == []
- assert to_dict['app']['root'] is None
- assert to_dict['app']['static_root'] == 'public'
- assert to_dict['app']['template_path'] == ''
- assert to_dict['nested']['one']['two'] == 2
-
- def test_config_to_dict_prefixed(self):
- from pecan import configuration
- """Add a prefix for keys"""
- conf = configuration.initconf()
-
- assert isinstance(conf, configuration.Config)
-
- to_dict = conf.to_dict('prefix_')
-
- assert isinstance(to_dict, dict)
- assert to_dict['prefix_server']['prefix_host'] == '0.0.0.0'
- assert to_dict['prefix_server']['prefix_port'] == '8080'
- assert to_dict['prefix_app']['prefix_modules'] == []
- assert to_dict['prefix_app']['prefix_root'] is None
- assert to_dict['prefix_app']['prefix_static_root'] == 'public'
- assert to_dict['prefix_app']['prefix_template_path'] == ''
-
-
-class TestGlobalConfig(PecanTestCase):
-
- def tearDown(self):
- from pecan import configuration
- configuration.set_config(
- dict(configuration.initconf()),
- overwrite=True
- )
-
- def test_paint_from_dict(self):
- from pecan import configuration
- configuration.set_config({'foo': 'bar'})
- assert dict(configuration._runtime_conf) != {'foo': 'bar'}
- self.assertEqual(configuration._runtime_conf.foo, 'bar')
-
- def test_overwrite_from_dict(self):
- from pecan import configuration
- configuration.set_config({'foo': 'bar'}, overwrite=True)
- assert dict(configuration._runtime_conf) == {'foo': 'bar'}
-
- def test_paint_from_file(self):
- from pecan import configuration
- configuration.set_config(os.path.join(
- __here__,
- 'config_fixtures/foobar.py'
- ))
- assert dict(configuration._runtime_conf) != {'foo': 'bar'}
- assert configuration._runtime_conf.foo == 'bar'
-
- def test_overwrite_from_file(self):
- from pecan import configuration
- configuration.set_config(
- os.path.join(
- __here__,
- 'config_fixtures/foobar.py',
- ),
- overwrite=True
- )
- assert dict(configuration._runtime_conf) == {'foo': 'bar'}
-
- def test_set_config_none_type(self):
- from pecan import configuration
- self.assertRaises(RuntimeError, configuration.set_config, None)
-
- def test_set_config_to_dir(self):
- from pecan import configuration
- self.assertRaises(RuntimeError, configuration.set_config, '/')
-
-
-class TestConfFromEnv(PecanTestCase):
- #
- # Note that there is a good chance of pollution if ``tearDown`` does not
- # reset the configuration like this class does. If implementing new classes
- # for configuration this tearDown **needs to be implemented**
- #
-
- def setUp(self):
- super(TestConfFromEnv, self).setUp()
- self.addCleanup(self._remove_config_key)
-
- from pecan import configuration
- self.get_conf_path_from_env = configuration.get_conf_path_from_env
-
- def _remove_config_key(self):
- os.environ.pop('PECAN_CONFIG', None)
-
- def test_invalid_path(self):
- os.environ['PECAN_CONFIG'] = '/'
- msg = "PECAN_CONFIG was set to an invalid path: /"
- self.assertRaisesRegexp(
- RuntimeError,
- msg,
- self.get_conf_path_from_env
- )
-
- def test_is_not_set(self):
- msg = "PECAN_CONFIG is not set and " \
- "no config file was passed as an argument."
- self.assertRaisesRegexp(
- RuntimeError,
- msg,
- self.get_conf_path_from_env
- )
-
- def test_return_valid_path(self):
- __here__ = os.path.abspath(__file__)
- os.environ['PECAN_CONFIG'] = __here__
- assert self.get_conf_path_from_env() == __here__
diff --git a/pecan/tests/test_generic.py b/pecan/tests/test_generic.py
deleted file mode 100644
index 0478799..0000000
--- a/pecan/tests/test_generic.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from webtest import TestApp
-try:
- from simplejson import dumps
-except:
- from json import dumps # noqa
-
-from six import b as b_
-
-from pecan import Pecan, expose, abort
-from pecan.tests import PecanTestCase
-
-
-class TestGeneric(PecanTestCase):
-
- 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 == b_('GET')
-
- r = app.post('/')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(result='POST')))
-
- r = app.get('/do_get', status=404)
- assert r.status_int == 404
-
- def test_generic_allow_header(self):
- class RootController(object):
- @expose(generic=True)
- def index(self):
- abort(405)
-
- @index.when(method='POST', template='json')
- def do_post(self):
- return dict(result='POST')
-
- @index.when(method='GET')
- def do_get(self):
- return 'GET'
-
- @index.when(method='PATCH')
- def do_patch(self):
- return 'PATCH'
-
- app = TestApp(Pecan(RootController()))
- r = app.delete('/', expect_errors=True)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET, PATCH, POST'
-
- def test_nested_generic(self):
-
- class SubSubController(object):
- @expose(generic=True)
- def index(self):
- return 'GET'
-
- @index.when(method='DELETE', template='json')
- def do_delete(self, name, *args):
- return dict(result=name, args=', '.join(args))
-
- class SubController(object):
- sub = SubSubController()
-
- class RootController(object):
- sub = SubController()
-
- app = TestApp(Pecan(RootController()))
- r = app.get('/sub/sub/')
- assert r.status_int == 200
- assert r.body == b_('GET')
-
- r = app.delete('/sub/sub/joe/is/cool')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(result='joe', args='is, cool')))
-
-
-class TestGenericWithSpecialMethods(PecanTestCase):
-
- def test_generics_not_allowed(self):
-
- class C(object):
-
- def _default(self):
- pass
-
- def _lookup(self):
- pass
-
- def _route(self):
- pass
-
- for method in (C._default, C._lookup, C._route):
- self.assertRaises(
- ValueError,
- expose(generic=True),
- getattr(method, '__func__', method)
- )
diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py
deleted file mode 100644
index a15368e..0000000
--- a/pecan/tests/test_hooks.py
+++ /dev/null
@@ -1,1711 +0,0 @@
-import inspect
-import operator
-
-from webtest import TestApp
-from six import PY3
-from six import b as b_
-from six import u as u_
-from six.moves import cStringIO as StringIO
-
-from pecan import make_app, expose, redirect, abort, rest, Request, Response
-from pecan.hooks import (
- PecanHook, TransactionHook, HookController, RequestViewerHook
-)
-from pecan.configuration import Config
-from pecan.decorators import transactional, after_commit, after_rollback
-from pecan.tests import PecanTestCase
-
-# The `inspect.Arguments` namedtuple is different between PY2/3
-kwargs = operator.attrgetter('varkw' if PY3 else 'keywords')
-
-
-class TestHooks(PecanTestCase):
-
- 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')
-
- def after(self, state):
- run_hook.append('after')
-
- 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 == b_('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)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- 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 == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route3'
- assert run_hook[3] == 'before1'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before3'
- assert run_hook[6] == 'inside'
- assert run_hook[7] == 'after3'
- assert run_hook[8] == 'after2'
- assert run_hook[9] == 'after1'
-
- def test_partial_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello World!'
-
- @expose()
- def causeerror(self):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- class OnRouteHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- app = TestApp(make_app(RootController(), hooks=[
- ErrorHook(), OnRouteHook()
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello World!')
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'inside'
-
- run_hook = []
- try:
- response = app.get('/causeerror')
- except Exception as e:
- assert isinstance(e, IndexError)
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'error'
-
- def test_on_error_response_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def causeerror(self):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- r = Response()
- r.text = u_('on_error')
-
- return r
-
- app = TestApp(make_app(RootController(), hooks=[
- ErrorHook()
- ]))
-
- response = app.get('/causeerror')
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'error'
- assert response.text == 'on_error'
-
- 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, priority=None):
- self.id = str(id)
- if priority:
- self.priority = priority
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- papp = make_app(RootController(), hooks=[
- SimpleHook(1, 3), SimpleHook(2, 2), SimpleHook(3, 1)
- ])
- app = TestApp(papp)
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route3'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route1'
- assert run_hook[3] == 'before3'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before1'
- assert run_hook[6] == 'inside'
- 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')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- class SubSubController(object):
- @expose()
- 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 == b_('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 == b_('Inside here!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'before'
- assert run_hook[1] == 'inside_sub'
- assert run_hook[2] == 'after'
-
- run_hook = []
- response = app.get('/sub/sub/')
- assert response.status_int == 200
- assert response.body == b_('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)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- 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 == b_('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 == b_('Inside here!')
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'before2'
- assert run_hook[2] == 'before1'
- assert run_hook[3] == 'inside_sub'
- assert run_hook[4] == 'after1'
- assert run_hook[5] == 'after2'
-
- def test_mixin_hooks(self):
- run_hook = []
-
- class HelperHook(PecanHook):
- priority = 2
-
- def before(self, state):
- run_hook.append('helper - before hook')
-
- # we'll use the same hook instance to avoid duplicate calls
- helper_hook = HelperHook()
-
- class LastHook(PecanHook):
- priority = 200
-
- def before(self, state):
- run_hook.append('last - before hook')
-
- class SimpleHook(PecanHook):
- priority = 1
-
- def before(self, state):
- run_hook.append('simple - before hook')
-
- class HelperMixin(object):
- __hooks__ = [helper_hook]
-
- class LastMixin(object):
- __hooks__ = [LastHook()]
-
- class SubController(HookController, HelperMixin):
- __hooks__ = [LastHook()]
-
- @expose()
- def index(self):
- return "This is sub controller!"
-
- class RootController(HookController, LastMixin):
- __hooks__ = [SimpleHook(), helper_hook]
-
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- papp = make_app(RootController())
- app = TestApp(papp)
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 4
- assert run_hook[0] == 'simple - before hook', run_hook[0]
- assert run_hook[1] == 'helper - before hook', run_hook[1]
- assert run_hook[2] == 'last - before hook', run_hook[2]
- assert run_hook[3] == 'inside', run_hook[3]
-
- run_hook = []
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('This is sub controller!')
-
- assert len(run_hook) == 4, run_hook
- assert run_hook[0] == 'simple - before hook', run_hook[0]
- assert run_hook[1] == 'helper - before hook', run_hook[1]
- assert run_hook[2] == 'last - before hook', run_hook[2]
- # LastHook is invoked once again -
- # for each different instance of the Hook in the two Controllers
- assert run_hook[3] == 'last - before hook', run_hook[3]
-
-
-class TestStateAccess(PecanTestCase):
-
- def setUp(self):
- super(TestStateAccess, self).setUp()
- self.args = None
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- @expose()
- def greet(self, name):
- return 'Hello, %s!' % name
-
- @expose()
- def greetmore(self, *args):
- return 'Hello, %s!' % args[0]
-
- @expose()
- def kwargs(self, **kw):
- return 'Hello, %s!' % kw['name']
-
- @expose()
- def mixed(self, first, second, *args):
- return 'Mixed'
-
- class SimpleHook(PecanHook):
- def before(inself, state):
- self.args = (state.controller, state.arguments)
-
- self.root = RootController()
- self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
-
- def test_no_args(self):
- self.app.get('/')
- assert self.args[0] == self.root.index
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_arg(self):
- self.app.get('/greet/joe')
- assert self.args[0] == self.root.greet
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['joe']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_vararg(self):
- self.app.get('/greetmore/joe')
- assert self.args[0] == self.root.greetmore
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == ['joe']
- assert kwargs(self.args[1]) == {}
-
- def test_single_kw(self):
- self.app.get('/kwargs/?name=joe')
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_single_kw_post(self):
- self.app.post('/kwargs/', params={'name': 'joe'})
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_mixed_args(self):
- self.app.get('/mixed/foo/bar/spam/eggs')
- assert self.args[0] == self.root.mixed
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['foo', 'bar']
- assert self.args[1].varargs == ['spam', 'eggs']
-
-
-class TestStateAccessWithoutThreadLocals(PecanTestCase):
-
- def setUp(self):
- super(TestStateAccessWithoutThreadLocals, self).setUp()
- self.args = None
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- return 'Hello, World!'
-
- @expose()
- def greet(self, req, resp, name):
- return 'Hello, %s!' % name
-
- @expose()
- def greetmore(self, req, resp, *args):
- return 'Hello, %s!' % args[0]
-
- @expose()
- def kwargs(self, req, resp, **kw):
- return 'Hello, %s!' % kw['name']
-
- @expose()
- def mixed(self, req, resp, first, second, *args):
- return 'Mixed'
-
- class SimpleHook(PecanHook):
- def before(inself, state):
- self.args = (state.controller, state.arguments)
-
- self.root = RootController()
- self.app = TestApp(make_app(
- self.root,
- hooks=[SimpleHook()],
- use_context_locals=False
- ))
-
- def test_no_args(self):
- self.app.get('/')
- assert self.args[0] == self.root.index
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_arg(self):
- self.app.get('/greet/joe')
- assert self.args[0] == self.root.greet
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 3
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].args[2] == 'joe'
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_single_vararg(self):
- self.app.get('/greetmore/joe')
- assert self.args[0] == self.root.greetmore
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == ['joe']
- assert kwargs(self.args[1]) == {}
-
- def test_single_kw(self):
- self.app.get('/kwargs/?name=joe')
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_single_kw_post(self):
- self.app.post('/kwargs/', params={'name': 'joe'})
- assert self.args[0] == self.root.kwargs
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 2
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'name': 'joe'}
-
- def test_mixed_args(self):
- self.app.get('/mixed/foo/bar/spam/eggs')
- assert self.args[0] == self.root.mixed
- assert isinstance(self.args[1], inspect.Arguments)
- assert len(self.args[1].args) == 4
- assert isinstance(self.args[1].args[0], Request)
- assert isinstance(self.args[1].args[1], Response)
- assert self.args[1].args[2:] == ['foo', 'bar']
- assert self.args[1].varargs == ['spam', 'eggs']
-
-
-class TestRestControllerStateAccess(PecanTestCase):
-
- def setUp(self):
- super(TestRestControllerStateAccess, self).setUp()
- self.args = None
-
- class RootController(rest.RestController):
-
- @expose()
- def _default(self, _id, *args, **kw):
- return 'Default'
-
- @expose()
- def get_all(self, **kw):
- return 'All'
-
- @expose()
- def get_one(self, _id, *args, **kw):
- return 'One'
-
- @expose()
- def post(self, *args, **kw):
- return 'POST'
-
- @expose()
- def put(self, _id, *args, **kw):
- return 'PUT'
-
- @expose()
- def delete(self, _id, *args, **kw):
- return 'DELETE'
-
- class SimpleHook(PecanHook):
- def before(inself, state):
- self.args = (state.controller, state.arguments)
-
- self.root = RootController()
- self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
-
- def test_get_all(self):
- self.app.get('/')
- assert self.args[0] == self.root.get_all
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_get_all_with_kwargs(self):
- self.app.get('/?foo=bar')
- assert self.args[0] == self.root.get_all
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_get_one(self):
- self.app.get('/1')
- assert self.args[0] == self.root.get_one
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_get_one_with_varargs(self):
- self.app.get('/1/2/3')
- assert self.args[0] == self.root.get_one
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == ['2', '3']
- assert kwargs(self.args[1]) == {}
-
- def test_get_one_with_kwargs(self):
- self.app.get('/1?foo=bar')
- assert self.args[0] == self.root.get_one
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_post(self):
- self.app.post('/')
- assert self.args[0] == self.root.post
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_post_with_varargs(self):
- self.app.post('/foo/bar')
- assert self.args[0] == self.root.post
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == ['foo', 'bar']
- assert kwargs(self.args[1]) == {}
-
- def test_post_with_kwargs(self):
- self.app.post('/', params={'foo': 'bar'})
- assert self.args[0] == self.root.post
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == []
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_put(self):
- self.app.put('/1')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_put_with_method_argument(self):
- self.app.post('/1?_method=put')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'_method': 'put'}
-
- def test_put_with_varargs(self):
- self.app.put('/1/2/3')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == ['2', '3']
- assert kwargs(self.args[1]) == {}
-
- def test_put_with_kwargs(self):
- self.app.put('/1?foo=bar')
- assert self.args[0] == self.root.put
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_delete(self):
- self.app.delete('/1')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {}
-
- def test_delete_with_method_argument(self):
- self.app.post('/1?_method=delete')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'_method': 'delete'}
-
- def test_delete_with_varargs(self):
- self.app.delete('/1/2/3')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == ['2', '3']
- assert kwargs(self.args[1]) == {}
-
- def test_delete_with_kwargs(self):
- self.app.delete('/1?foo=bar')
- assert self.args[0] == self.root.delete
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'foo': 'bar'}
-
- def test_post_with_invalid_method_kwarg(self):
- self.app.post('/1?_method=invalid')
- assert self.args[0] == self.root._default
- assert isinstance(self.args[1], inspect.Arguments)
- assert self.args[1].args == ['1']
- assert self.args[1].varargs == []
- assert kwargs(self.args[1]) == {'_method': 'invalid'}
-
-
-class TestTransactionHook(PecanTestCase):
- 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('/')
-
- @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')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('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 == b_('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'
-
- 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)
- return action_impl
-
- class RootController(object):
- @expose()
- @after_commit(action('action-one'))
- def index(self):
- run_hook.append('inside')
- return 'Index Method!'
-
- @expose()
- @transactional()
- @after_commit(action('action-two'))
- def decorated(self):
- run_hook.append('inside')
- return 'Decorated Method!'
-
- @expose()
- @after_rollback(action('action-three'))
- def rollback(self):
- abort(500)
-
- @expose()
- @transactional()
- @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')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Index Method!')
-
- 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 == b_('Index Method!')
-
- 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 == b_('Decorated Method!')
-
- assert len(run_hook) == 7
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'inside'
- assert run_hook[4] == 'commit'
- assert run_hook[5] == 'action-two'
- assert run_hook[6] == 'clear'
-
- run_hook = []
-
- response = app.get('/rollback', expect_errors=True)
- assert response.status_int == 500
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- run_hook = []
-
- response = app.post('/rollback', expect_errors=True)
- assert response.status_int == 500
-
- 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 run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'action-four'
- assert run_hook[5] == 'clear'
-
- run_hook = []
-
- response = app.get('/fourohfour', status=404)
- assert response.status_int == 404
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- def test_transaction_hook_with_transactional_decorator(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- @expose()
- def redirect(self):
- redirect('/')
-
- @expose()
- @transactional()
- def redirect_transactional(self):
- redirect('/')
-
- @expose()
- @transactional(False)
- def redirect_rollback(self):
- redirect('/')
-
- @expose()
- def error(self):
- return [][1]
-
- @expose()
- @transactional(False)
- def error_rollback(self):
- return [][1]
-
- @expose()
- @transactional()
- def error_transactional(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')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('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 = []
-
- # test hooks for /
-
- response = app.post('/')
- assert response.status_int == 200
- assert response.body == b_('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'
-
- #
- # 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[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[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 = []
-
- 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'
-
- run_hook = []
-
- try:
- response = app.get('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
-
- run_hook = []
-
- try:
- response = app.post('/error_transactional')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- try:
- response = app.get('/error_transactional')
- except IndexError:
- pass
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- run_hook = []
-
- try:
- response = app.post('/error_rollback')
- except IndexError:
- pass
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'start'
- assert run_hook[1] == 'rollback'
- assert run_hook[2] == 'clear'
-
- run_hook = []
-
- try:
- response = app.get('/error_rollback')
- except IndexError:
- pass
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- def test_transaction_hook_with_transactional_class_decorator(self):
- run_hook = []
-
- @transactional()
- class RootController(object):
- @expose()
- def index(self):
- run_hook.append('inside')
- return 'Hello, World!'
-
- @expose()
- def redirect(self):
- redirect('/')
-
- @expose()
- @transactional(False)
- def redirect_rollback(self):
- redirect('/')
-
- @expose()
- def error(self):
- return [][1]
-
- @expose(generic=True)
- def generic(self):
- pass
-
- @generic.when(method='GET')
- def generic_get(self):
- run_hook.append('inside')
- return 'generic get'
-
- @generic.when(method='POST')
- 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')
- )
- ]))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'inside'
- assert run_hook[4] == 'commit'
- assert run_hook[5] == 'clear'
-
- run_hook = []
-
- # test hooks for /
-
- response = app.post('/')
- assert response.status_int == 200
- assert response.body == b_('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 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[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[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 = []
-
- 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'
-
- run_hook = []
-
- try:
- response = app.get('/error')
- except IndexError:
- pass
-
- assert len(run_hook) == 5
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- assert run_hook[3] == 'rollback'
- assert run_hook[4] == 'clear'
-
- #
- # 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 == b_('generic get')
- assert len(run_hook) == 6
- assert run_hook[0] == 'start_ro'
- assert run_hook[1] == 'clear'
- assert run_hook[2] == 'start'
- 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 == b_('generic post')
- 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'
-
- def test_transaction_hook_with_broken_hook(self):
- """
- In a scenario where a preceding hook throws an exception,
- ensure that TransactionHook still rolls back properly.
- """
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- def gen(event):
- return lambda: run_hook.append(event)
-
- class MyCustomException(Exception):
- pass
-
- class MyHook(PecanHook):
-
- def on_route(self, state):
- raise MyCustomException('BROKEN!')
-
- app = TestApp(make_app(RootController(), hooks=[
- MyHook(),
- TransactionHook(
- start=gen('start'),
- start_ro=gen('start_ro'),
- commit=gen('commit'),
- rollback=gen('rollback'),
- clear=gen('clear')
- )
- ]))
-
- self.assertRaises(
- MyCustomException,
- app.get,
- '/'
- )
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'clear'
-
-
-class TestRequestViewerHook(PecanTestCase):
-
- 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=lambda: [
- RequestViewerHook(writer=_stdout)
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('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
-
- 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=lambda: [
- 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 "['RequestViewerHook']" 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=lambda: [
- RequestViewerHook(
- config={'items': ['path']}, writer=_stdout
- )
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('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=lambda: [
- RequestViewerHook(
- config={'blacklist': ['/']}, writer=_stdout
- )
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('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=lambda: [
- RequestViewerHook(
- config={'items': ['date']}, writer=_stdout
- )
- ]
- )
- )
- response = app.get('/')
-
- out = _stdout.getvalue()
-
- assert response.status_int == 200
- assert response.body == b_('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
-
- def test_hook_formatting(self):
- hooks = ['<pecan.hooks.RequestViewerHook object at 0x103a5f910>']
- 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']})
- viewer = RequestViewerHook(conf)
-
- assert viewer.items == ['url']
-
-
-class TestRestControllerWithHooks(PecanTestCase):
-
- def test_restcontroller_with_hooks(self):
-
- class SomeHook(PecanHook):
-
- def before(self, state):
- state.response.headers['X-Testing'] = 'XYZ'
-
- class BaseController(rest.RestController):
-
- @expose()
- def delete(self, _id):
- return 'Deleting %s' % _id
-
- class RootController(BaseController, HookController):
-
- __hooks__ = [SomeHook()]
-
- @expose()
- def get_all(self):
- return 'Hello, World!'
-
- @staticmethod
- def static(cls):
- return 'static'
-
- @property
- def foo(self):
- return 'bar'
-
- def testing123(self):
- return 'bar'
-
- unhashable = [1, 'two', 3]
-
- app = TestApp(
- make_app(
- RootController()
- )
- )
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
- assert response.headers['X-Testing'] == 'XYZ'
-
- response = app.delete('/100/')
- assert response.status_int == 200
- assert response.body == b_('Deleting 100')
- assert response.headers['X-Testing'] == 'XYZ'
diff --git a/pecan/tests/test_jsonify.py b/pecan/tests/test_jsonify.py
deleted file mode 100644
index 2dcd663..0000000
--- a/pecan/tests/test_jsonify.py
+++ /dev/null
@@ -1,233 +0,0 @@
-from datetime import datetime, date
-from decimal import Decimal
-try:
- from simplejson import loads
-except:
- from json import loads # noqa
-try:
- from sqlalchemy import orm, schema, types
- from sqlalchemy.engine import create_engine
-except ImportError:
- create_engine = None # noqa
-
-from webtest import TestApp
-from webob.multidict import MultiDict
-
-from pecan.jsonify import jsonify, encode, ResultProxy, RowProxy
-from pecan import Pecan, expose
-from pecan.tests import PecanTestCase
-
-
-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():
- 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'
- assert len(result) == 1
-
-
-class TestJsonify(PecanTestCase):
-
- 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.decode()) == {'name': 'Jonathan LaCour'}
-
-
-class TestJsonifyGenericEncoder(PecanTestCase):
- def test_json_callable(self):
- class JsonCallable(object):
- def __init__(self, arg):
- self.arg = arg
-
- def __json__(self):
- return {"arg": self.arg}
-
- result = encode(JsonCallable('foo'))
- assert loads(result) == {'arg': 'foo'}
-
- def test_datetime(self):
- today = date.today()
- now = datetime.now()
-
- result = encode(today)
- assert loads(result) == str(today)
-
- result = encode(now)
- assert loads(result) == str(now)
-
- def test_decimal(self):
- # XXX Testing for float match which is inexact
-
- d = Decimal('1.1')
- result = encode(d)
- assert loads(result) == float(d)
-
- def test_multidict(self):
- md = MultiDict()
- md.add('arg', 'foo')
- md.add('arg', 'bar')
- result = encode(md)
- assert loads(result) == {'arg': ['foo', 'bar']}
-
- def test_fallback_to_builtin_encoder(self):
- class Foo(object):
- pass
-
- self.assertRaises(TypeError, encode, Foo())
-
-
-class TestJsonifySQLAlchemyGenericEncoder(PecanTestCase):
-
- def setUp(self):
- super(TestJsonifySQLAlchemyGenericEncoder, self).setUp()
- 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):
- self._sa_class_manager = object()
- self._sa_instance_state = 'awesome'
- 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')
- ])
-
- def create_sa_proxies(self):
-
- # create the table and mapper
- metadata = schema.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': 'Jonathan', 'last_name': 'LaCour'},
- {'first_name': 'Yoann', 'last_name': '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'
- }
-
- 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'
- }
diff --git a/pecan/tests/test_no_thread_locals.py b/pecan/tests/test_no_thread_locals.py
deleted file mode 100644
index 1ca418e..0000000
--- a/pecan/tests/test_no_thread_locals.py
+++ /dev/null
@@ -1,1440 +0,0 @@
-import time
-from json import dumps, loads
-import warnings
-
-from webtest import TestApp
-from six import b as b_
-from six import u as u_
-import webob
-import mock
-
-from pecan import Pecan, expose, abort, Request, Response
-from pecan.rest import RestController
-from pecan.hooks import PecanHook, HookController
-from pecan.tests import PecanTestCase
-
-
-class TestThreadingLocalUsage(PecanTestCase):
-
- @property
- def root(self):
- class RootController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return 'Hello, World!'
-
- @expose()
- def warning(self):
- return ("This should be unroutable because (req, resp) are not"
- " arguments. It should raise a TypeError.")
-
- @expose(generic=True)
- def generic(self):
- return ("This should be unroutable because (req, resp) are not"
- " arguments. It should raise a TypeError.")
-
- @generic.when(method='PUT')
- def generic_put(self, _id):
- return ("This should be unroutable because (req, resp) are not"
- " arguments. It should raise a TypeError.")
-
- return RootController
-
- def test_locals_are_not_used(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- self.assertRaises(AssertionError, Pecan, self.root)
-
- def test_threadlocal_argument_warning(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- self.assertRaises(
- TypeError,
- app.get,
- '/warning/'
- )
-
- def test_threadlocal_argument_warning_on_generic(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- self.assertRaises(
- TypeError,
- app.get,
- '/generic/'
- )
-
- def test_threadlocal_argument_warning_on_generic_delegate(self):
- with mock.patch('threading.local', side_effect=AssertionError()):
-
- app = TestApp(Pecan(self.root(), use_context_locals=False))
- self.assertRaises(
- TypeError,
- app.put,
- '/generic/'
- )
-
-
-class TestIndexRouting(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return 'Hello, World!'
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_empty_root(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index(self):
- r = self.app_.get('/index')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
- def test_index_html(self):
- r = self.app_.get('/index.html')
- assert r.status_int == 200
- assert r.body == b_('Hello, World!')
-
-
-class TestManualResponse(PecanTestCase):
-
- def test_manual_response(self):
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- resp = webob.Response(resp.environ)
- resp.body = b_('Hello, World!')
- return resp
-
- app = TestApp(Pecan(RootController(), use_context_locals=False))
- r = app.get('/')
- assert r.body == b_('Hello, World!'), r.body
-
-
-class TestDispatch(PecanTestCase):
-
- @property
- def app_(self):
- class SubSubController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/sub/'
-
- @expose()
- def deeper(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/sub/deeper'
-
- class SubController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/'
-
- @expose()
- def deeper(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/sub/deeper'
-
- sub = SubSubController()
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/'
-
- @expose()
- def deeper(self, req, resp):
- assert isinstance(req, webob.BaseRequest)
- assert isinstance(resp, webob.Response)
- return '/deeper'
-
- sub = SubController()
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_one_level(self):
- r = self.app_.get('/deeper')
- assert r.status_int == 200
- assert r.body == b_('/deeper')
-
- def test_one_level_with_trailing(self):
- r = self.app_.get('/sub/')
- assert r.status_int == 200
- assert r.body == b_('/sub/')
-
- def test_two_levels(self):
- r = self.app_.get('/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/deeper')
-
- def test_two_levels_with_trailing(self):
- r = self.app_.get('/sub/sub/')
- assert r.status_int == 200
-
- def test_three_levels(self):
- r = self.app_.get('/sub/sub/deeper')
- assert r.status_int == 200
- assert r.body == b_('/sub/sub/deeper')
-
-
-class TestLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self, req, resp):
- return '/%s' % self.someID
-
- @expose()
- def name(self, req, resp):
- return '/%s/name' % self.someID
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- return '/'
-
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_index(self):
- r = self.app_.get('/')
- assert r.status_int == 200
- assert r.body == b_('/')
-
- def test_lookup(self):
- r = self.app_.get('/100/')
- assert r.status_int == 200
- assert r.body == b_('/100')
-
- def test_lookup_with_method(self):
- r = self.app_.get('/100/name')
- assert r.status_int == 200
- assert r.body == b_('/100/name')
-
- def test_lookup_with_wrong_argspec(self):
- class RootController(object):
- @expose()
- def _lookup(self, someID):
- return 'Bad arg spec' # pragma: nocover
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- app = TestApp(Pecan(RootController(), use_context_locals=False))
- r = app.get('/foo/bar', expect_errors=True)
- assert r.status_int == 404
-
-
-class TestCanonicalLookups(PecanTestCase):
-
- @property
- def app_(self):
- class LookupController(object):
- def __init__(self, someID):
- self.someID = someID
-
- @expose()
- def index(self, req, resp):
- return self.someID
-
- class UserController(object):
- @expose()
- def _lookup(self, someID, *remainder):
- return LookupController(someID), remainder
-
- class RootController(object):
- users = UserController()
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_canonical_lookup(self):
- assert self.app_.get('/users', expect_errors=404).status_int == 404
- assert self.app_.get('/users/', expect_errors=404).status_int == 404
- assert self.app_.get('/users/100').status_int == 302
- assert self.app_.get('/users/100/').body == b_('100')
-
-
-class TestControllerArguments(PecanTestCase):
-
- @property
- def app_(self):
- class RootController(object):
- @expose()
- def index(self, req, resp, id):
- return 'index: %s' % id
-
- @expose()
- def multiple(self, req, resp, one, two):
- return 'multiple: %s, %s' % (one, two)
-
- @expose()
- def optional(self, req, resp, id=None):
- return 'optional: %s' % str(id)
-
- @expose()
- def multiple_optional(self, req, resp, one=None, two=None,
- three=None):
- return 'multiple_optional: %s, %s, %s' % (one, two, three)
-
- @expose()
- def variable_args(self, req, resp, *args):
- return 'variable_args: %s' % ', '.join(args)
-
- @expose()
- def variable_kwargs(self, req, resp, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'variable_kwargs: %s' % ', '.join(data)
-
- @expose()
- def variable_all(self, req, resp, *args, **kwargs):
- data = [
- '%s=%s' % (key, kwargs[key])
- for key in sorted(kwargs.keys())
- ]
- return 'variable_all: %s' % ', '.join(list(args) + data)
-
- @expose()
- def eater(self, req, resp, 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)
- )
-
- @expose()
- def _route(self, args, request):
- if hasattr(self, args[0]):
- return getattr(self, args[0]), args[1:]
- else:
- return self.index, args
-
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_required_argument(self):
- try:
- r = self.app_.get('/')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "index() takes exactly 4 arguments (3 given)",
- "index() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_single_argument(self):
- r = self.app_.get('/1')
- assert r.status_int == 200
- assert r.body == b_('index: 1')
-
- def test_single_argument_with_encoded_url(self):
- r = self.app_.get('/This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_two_arguments(self):
- r = self.app_.get('/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_keyword_argument(self):
- r = self.app_.get('/?id=2')
- assert r.status_int == 200
- assert r.body == b_('index: 2')
-
- def test_keyword_argument_with_encoded_url(self):
- r = self.app_.get('/?id=This%20is%20a%20test%21')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_argument_and_keyword_argument(self):
- r = self.app_.get('/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: 3')
-
- def test_encoded_argument_and_keyword_argument(self):
- r = self.app_.get('/This%20is%20a%20test%21?id=three')
- assert r.status_int == 200
- assert r.body == b_('index: This is a test!')
-
- def test_explicit_kwargs(self):
- r = self.app_.post('/', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_path_with_explicit_kwargs(self):
- r = self.app_.post('/4', {'id': 'four'})
- assert r.status_int == 200
- assert r.body == b_('index: 4')
-
- def test_multiple_kwargs(self):
- r = self.app_.get('/?id=5&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('index: 5')
-
- def test_kwargs_from_root(self):
- r = self.app_.post('/', {'id': '6', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('index: 6')
-
- # multiple args
-
- def test_multiple_positional_arguments(self):
- r = self.app_.get('/multiple/one/two')
- assert r.status_int == 200
- assert r.body == b_('multiple: one, two')
-
- def test_multiple_positional_arguments_with_url_encode(self):
- r = self.app_.get('/multiple/One%20/Two%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: One , Two!')
-
- def test_multiple_positional_arguments_with_kwargs(self):
- r = self.app_.get('/multiple?one=three&two=four')
- assert r.status_int == 200
- assert r.body == b_('multiple: three, four')
-
- def test_multiple_positional_arguments_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple?one=Three%20&two=Four%20%21')
- assert r.status_int == 200
- assert r.body == b_('multiple: Three , Four !')
-
- def test_positional_args_with_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'five', 'two': 'six'})
- assert r.status_int == 200
- assert r.body == b_('multiple: five, six')
-
- def test_positional_args_with_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/multiple', {'one': 'Five%20', 'two': 'Six%20%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple: Five%20, Six%20%21')
-
- # optional arg
- def test_optional_arg(self):
- r = self.app_.get('/optional')
- assert r.status_int == 200
- assert r.body == b_('optional: None')
-
- def test_multiple_optional(self):
- r = self.app_.get('/optional/1')
- assert r.status_int == 200
- assert r.body == b_('optional: 1')
-
- def test_multiple_optional_url_encoded(self):
- r = self.app_.get('/optional/Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_optional_missing(self):
- r = self.app_.get('/optional/2/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_with_kwargs(self):
- r = self.app_.get('/optional?id=2')
- assert r.status_int == 200
- assert r.body == b_('optional: 2')
-
- def test_multiple_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_multiple_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/optional/3?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: 3')
-
- def test_url_encoded_positional_args(self):
- r = self.app_.get('/optional/Some%20Number?id=three')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_kwargs(self):
- r = self.app_.post('/optional', {'id': '4'})
- assert r.status_int == 200
- assert r.body == b_('optional: 4')
-
- def test_optional_arg_with_url_encoded_kwargs(self):
- r = self.app_.post('/optional', {'id': 'Some%20Number'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- def test_multiple_positional_arguments_with_dictionary_kwargs(self):
- r = self.app_.post('/optional/5', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: 5')
-
- def test_multiple_positional_url_encoded_arguments_with_kwargs(self):
- r = self.app_.post('/optional/Some%20Number', {'id': 'five'})
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_kwargs(self):
- r = self.app_.get('/optional?id=6&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: 6')
-
- def test_optional_arg_with_multiple_url_encoded_kwargs(self):
- r = self.app_.get('/optional?id=Some%20Number&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('optional: Some Number')
-
- def test_optional_arg_with_multiple_dictionary_kwargs(self):
- r = self.app_.post('/optional', {'id': '7', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('optional: 7')
-
- def test_optional_arg_with_multiple_url_encoded_dictionary_kwargs(self):
- r = self.app_.post('/optional', {
- 'id': 'Some%20Number',
- 'dummy': 'dummy'
- })
- assert r.status_int == 200
- assert r.body == b_('optional: Some%20Number')
-
- # multiple optional args
-
- def test_multiple_optional_positional_args(self):
- r = self.app_.get('/multiple_optional')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, None')
-
- def test_multiple_optional_positional_args_one_arg(self):
- r = self.app_.get('/multiple_optional/1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_one_url_encoded_arg(self):
- r = self.app_.get('/multiple_optional/One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_all_args(self):
- r = self.app_.get('/multiple_optional/1/2/3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_positional_args_all_url_encoded_args(self):
- r = self.app_.get('/multiple_optional/One%21/Two%21/Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_positional_args_too_many_args(self):
- r = self.app_.get('/multiple_optional/1/2/3/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_optional_positional_args_with_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_url_encoded_kwargs(self):
- r = self.app_.get('/multiple_optional?one=One%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_string_kwargs(self):
- r = self.app_.get('/multiple_optional/1?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_str_kwargs(self):
- r = self.app_.get('/multiple_optional/One%21?one=one')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_positional_args_with_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': '1'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_positional_args_with_encoded_dict_kwargs(self):
- r = self.app_.post('/multiple_optional', {'one': 'One%21'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, None, None')
-
- def test_multiple_optional_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/1', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, None, None')
-
- def test_multiple_optional_encoded_positional_args_and_dict_kwargs(self):
- r = self.app_.post('/multiple_optional/One%21', {'one': 'one'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, None, None')
-
- def test_multiple_optional_args_with_multiple_kwargs(self):
- r = self.app_.get('/multiple_optional?one=1&two=2&three=3&four=4')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_kwargs(self):
- r = self.app_.get(
- '/multiple_optional?one=One%21&two=Two%21&three=Three%21&four=4'
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One!, Two!, Three!')
-
- def test_multiple_optional_args_with_multiple_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {'one': '1', 'two': '2', 'three': '3', 'four': '4'}
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: 1, 2, 3')
-
- def test_multiple_optional_args_with_multiple_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/multiple_optional',
- {
- 'one': 'One%21',
- 'two': 'Two%21',
- 'three': 'Three%21',
- 'four': '4'
- }
- )
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: One%21, Two%21, Three%21')
-
- def test_multiple_optional_args_with_last_kwarg(self):
- r = self.app_.get('/multiple_optional?three=3')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, 3')
-
- def test_multiple_optional_args_with_last_encoded_kwarg(self):
- r = self.app_.get('/multiple_optional?three=Three%21')
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, None, Three!')
-
- def test_multiple_optional_args_with_middle_arg(self):
- r = self.app_.get('/multiple_optional', {'two': '2'})
- assert r.status_int == 200
- assert r.body == b_('multiple_optional: None, 2, None')
-
- def test_variable_args(self):
- r = self.app_.get('/variable_args')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_multiple_variable_args(self):
- r = self.app_.get('/variable_args/1/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: 1, dummy')
-
- def test_multiple_encoded_variable_args(self):
- r = self.app_.get('/variable_args/Testing%20One%20Two/Three%21')
- assert r.status_int == 200
- assert r.body == b_('variable_args: Testing One Two, Three!')
-
- def test_variable_args_with_kwargs(self):
- r = self.app_.get('/variable_args?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_args_with_dict_kwargs(self):
- r = self.app_.post('/variable_args', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_args: ')
-
- def test_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: ')
-
- def test_multiple_variable_kwargs(self):
- r = self.app_.get('/variable_kwargs/1/dummy', status=404)
- assert r.status_int == 404
-
- def test_multiple_variable_kwargs_with_explicit_kwargs(self):
- r = self.app_.get('/variable_kwargs?id=2&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=2')
-
- def test_multiple_variable_kwargs_with_explicit_encoded_kwargs(self):
- r = self.app_.get(
- '/variable_kwargs?id=Two%21&dummy=This%20is%20a%20test'
- )
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=This is a test, id=Two!')
-
- def test_multiple_variable_kwargs_with_dict_kwargs(self):
- r = self.app_.post('/variable_kwargs', {'id': '3', 'dummy': 'dummy'})
- assert r.status_int == 200
- assert r.body == b_('variable_kwargs: dummy=dummy, id=3')
-
- def test_multiple_variable_kwargs_with_encoded_dict_kwargs(self):
- r = self.app_.post(
- '/variable_kwargs',
- {'id': 'Three%21', 'dummy': 'This%20is%20a%20test'}
- )
- assert r.status_int == 200
- result = 'variable_kwargs: dummy=This%20is%20a%20test, id=Three%21'
- assert r.body == b_(result)
-
- def test_variable_all(self):
- r = self.app_.get('/variable_all')
- assert r.status_int == 200
- assert r.body == b_('variable_all: ')
-
- def test_variable_all_with_one_extra(self):
- r = self.app_.get('/variable_all/1')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 1')
-
- def test_variable_all_with_two_extras(self):
- r = self.app_.get('/variable_all/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 2, dummy')
-
- def test_variable_mixed(self):
- r = self.app_.get('/variable_all/3?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 3, day=12, month=1')
-
- def test_variable_mixed_explicit(self):
- r = self.app_.get('/variable_all/4?id=four&month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 4, day=12, id=four, month=1')
-
- def test_variable_post(self):
- r = self.app_.post('/variable_all/5/dummy')
- assert r.status_int == 200
- assert r.body == b_('variable_all: 5, dummy')
-
- def test_variable_post_with_kwargs(self):
- r = self.app_.post('/variable_all/6', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('variable_all: 6, day=12, month=1')
-
- def test_variable_post_mixed(self):
- r = self.app_.post(
- '/variable_all/7',
- {'id': 'seven', 'month': '1', 'day': '12'}
- )
- assert r.status_int == 200
- assert r.body == b_('variable_all: 7, day=12, id=seven, month=1')
-
- def test_no_remainder(self):
- try:
- r = self.app_.get('/eater')
- assert r.status_int != 200 # pragma: nocover
- except Exception as ex:
- assert type(ex) == TypeError
- assert ex.args[0] in (
- "eater() takes at least 4 arguments (3 given)",
- "eater() missing 1 required positional argument: 'id'"
- ) # this messaging changed in Python 3.3
-
- def test_one_remainder(self):
- r = self.app_.get('/eater/1')
- assert r.status_int == 200
- assert r.body == b_('eater: 1, None, ')
-
- def test_two_remainders(self):
- r = self.app_.get('/eater/2/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 2, dummy, ')
-
- def test_many_remainders(self):
- r = self.app_.get('/eater/3/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 3, dummy, foo, bar')
-
- def test_remainder_with_kwargs(self):
- r = self.app_.get('/eater/4?month=1&day=12')
- assert r.status_int == 200
- assert r.body == b_('eater: 4, None, day=12, month=1')
-
- def test_remainder_with_many_kwargs(self):
- r = self.app_.get('/eater/5?id=five&month=1&day=12&dummy=dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 5, dummy, day=12, month=1')
-
- def test_post_remainder(self):
- r = self.app_.post('/eater/6')
- assert r.status_int == 200
- assert r.body == b_('eater: 6, None, ')
-
- def test_post_three_remainders(self):
- r = self.app_.post('/eater/7/dummy')
- assert r.status_int == 200
- assert r.body == b_('eater: 7, dummy, ')
-
- def test_post_many_remainders(self):
- r = self.app_.post('/eater/8/dummy/foo/bar')
- assert r.status_int == 200
- assert r.body == b_('eater: 8, dummy, foo, bar')
-
- def test_post_remainder_with_kwargs(self):
- r = self.app_.post('/eater/9', {'month': '1', 'day': '12'})
- assert r.status_int == 200
- assert r.body == b_('eater: 9, None, day=12, month=1')
-
- def test_post_many_remainders_with_many_kwargs(self):
- r = self.app_.post(
- '/eater/10',
- {'id': 'ten', 'month': '1', 'day': '12', 'dummy': 'dummy'}
- )
- assert r.status_int == 200
- assert r.body == b_('eater: 10, dummy, day=12, month=1')
-
-
-class TestRestController(PecanTestCase):
-
- @property
- def app_(self):
-
- class OthersController(object):
-
- @expose()
- def index(self, req, resp):
- return 'OTHERS'
-
- @expose()
- def echo(self, req, resp, 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, req, resp, id):
- return self.data[int(id)]
-
- @expose('json')
- def get_all(self, req, resp):
- return dict(items=self.data)
-
- @expose()
- def length(self, req, resp, id, value=None):
- length = len(self.data[int(id)])
- if value:
- length += len(value)
- return str(length)
-
- @expose()
- def post(self, req, resp, value):
- self.data.append(value)
- resp.status = 302
- return 'CREATED'
-
- @expose()
- def edit(self, req, resp, id):
- return 'EDIT %s' % self.data[int(id)]
-
- @expose()
- def put(self, req, resp, id, value):
- self.data[int(id)] = value
- return 'UPDATED'
-
- @expose()
- def get_delete(self, req, resp, id):
- return 'DELETE %s' % self.data[int(id)]
-
- @expose()
- def delete(self, req, resp, id):
- del self.data[int(id)]
- return 'DELETED'
-
- @expose()
- def reset(self, req, resp):
- return 'RESET'
-
- @expose()
- def post_options(self, req, resp):
- return 'OPTIONS'
-
- @expose()
- def options(self, req, resp):
- abort(500)
-
- @expose()
- def other(self, req, resp):
- abort(500)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- return TestApp(Pecan(RootController(), use_context_locals=False))
-
- def test_get_all(self):
- r = self.app_.get('/things')
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=['zero', 'one', 'two', 'three'])))
-
- def test_get_one(self):
- for i, value in enumerate(['zero', 'one', 'two', 'three']):
- r = self.app_.get('/things/%d' % i)
- assert r.status_int == 200
- assert r.body == b_(value)
-
- def test_post(self):
- r = self.app_.post('/things', {'value': 'four'})
- assert r.status_int == 302
- assert r.body == b_('CREATED')
-
- def test_custom_action(self):
- r = self.app_.get('/things/3/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT three')
-
- def test_put(self):
- r = self.app_.put('/things/3', {'value': 'THREE!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- def test_put_with_method_parameter_and_get(self):
- r = self.app_.get('/things/3?_method=put', {'value': 'X'}, status=405)
- assert r.status_int == 405
-
- def test_put_with_method_parameter_and_post(self):
- r = self.app_.post('/things/3?_method=put', {'value': 'THREE!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- def test_get_delete(self):
- r = self.app_.get('/things/3/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE three')
-
- def test_delete_method(self):
- r = self.app_.delete('/things/3')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- def test_delete_with_method_parameter(self):
- r = self.app_.get('/things/3?_method=DELETE', status=405)
- assert r.status_int == 405
-
- def test_delete_with_method_parameter_and_post(self):
- r = self.app_.post('/things/3?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- def test_custom_method_type(self):
- r = self.app_.request('/things', method='RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- def test_custom_method_type_with_method_parameter(self):
- r = self.app_.get('/things?_method=RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- def test_options(self):
- r = self.app_.request('/things', method='OPTIONS')
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- def test_options_with_method_parameter(self):
- r = self.app_.post('/things', {'_method': 'OPTIONS'})
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- def test_other_custom_action(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.request('/things/other', method='MISC', status=405)
- assert r.status_int == 405
-
- def test_other_custom_action_with_method_parameter(self):
- r = self.app_.post('/things/other', {'_method': 'MISC'}, status=405)
- assert r.status_int == 405
-
- def test_nested_controller_with_trailing_slash(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.request('/things/others/', method='MISC')
- assert r.status_int == 200
- assert r.body == b_('OTHERS')
-
- def test_nested_controller_without_trailing_slash(self):
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = self.app_.request('/things/others', method='MISC', status=302)
- assert r.status_int == 302
-
- def test_invalid_custom_action(self):
- r = self.app_.get('/things?_method=BAD', status=405)
- assert r.status_int == 405
-
- def test_named_action(self):
- # test custom "GET" request "length"
- r = self.app_.get('/things/1/length')
- assert r.status_int == 200
- assert r.body == b_(str(len('one')))
-
- def test_named_nested_action(self):
- # test custom "GET" request through subcontroller
- r = self.app_.get('/things/others/echo?value=test')
- assert r.status_int == 200
- assert r.body == b_('test')
-
- def test_nested_post(self):
- # test custom "POST" request through subcontroller
- r = self.app_.post('/things/others/echo', {'value': 'test'})
- assert r.status_int == 200
- assert r.body == b_('test')
-
-
-class TestHooks(PecanTestCase):
-
- def test_basic_single_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- 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')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- app = TestApp(Pecan(
- RootController(),
- hooks=[SimpleHook()],
- use_context_locals=False
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('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, req, resp):
- 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)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- app = TestApp(Pecan(RootController(), hooks=[
- SimpleHook(1), SimpleHook(2), SimpleHook(3)
- ], use_context_locals=False))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route3'
- assert run_hook[3] == 'before1'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before3'
- assert run_hook[6] == 'inside'
- assert run_hook[7] == 'after3'
- assert run_hook[8] == 'after2'
- assert run_hook[9] == 'after1'
-
- def test_partial_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello World!'
-
- @expose()
- def causeerror(self, req, resp):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- class OnRouteHook(PecanHook):
- def on_route(self, state):
- run_hook.append('on_route')
-
- app = TestApp(Pecan(RootController(), hooks=[
- ErrorHook(), OnRouteHook()
- ], use_context_locals=False))
-
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello World!')
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'inside'
-
- run_hook = []
- try:
- response = app.get('/causeerror')
- except Exception as e:
- assert isinstance(e, IndexError)
-
- assert len(run_hook) == 2
- assert run_hook[0] == 'on_route'
- assert run_hook[1] == 'error'
-
- def test_on_error_response_hook(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def causeerror(self, req, resp):
- return [][1]
-
- class ErrorHook(PecanHook):
- def on_error(self, state, e):
- run_hook.append('error')
-
- r = webob.Response()
- r.text = u_('on_error')
-
- return r
-
- app = TestApp(Pecan(RootController(), hooks=[
- ErrorHook()
- ], use_context_locals=False))
-
- response = app.get('/causeerror')
-
- assert len(run_hook) == 1
- assert run_hook[0] == 'error'
- assert response.text == 'on_error'
-
- def test_prioritized_hooks(self):
- run_hook = []
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- class SimpleHook(PecanHook):
- def __init__(self, id, priority=None):
- self.id = str(id)
- if priority:
- self.priority = priority
-
- def on_route(self, state):
- run_hook.append('on_route' + self.id)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- papp = Pecan(RootController(), hooks=[
- SimpleHook(1, 3), SimpleHook(2, 2), SimpleHook(3, 1)
- ], use_context_locals=False)
- app = TestApp(papp)
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- assert len(run_hook) == 10
- assert run_hook[0] == 'on_route3'
- assert run_hook[1] == 'on_route2'
- assert run_hook[2] == 'on_route1'
- assert run_hook[3] == 'before3'
- assert run_hook[4] == 'before2'
- assert run_hook[5] == 'before1'
- assert run_hook[6] == 'inside'
- 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')
-
- def after(self, state):
- run_hook.append('after')
-
- def on_error(self, state, e):
- run_hook.append('error')
-
- class SubSubController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside_sub_sub')
- return 'Deep inside here!'
-
- class SubController(HookController):
- __hooks__ = [SimpleHook()]
-
- @expose()
- def index(self, req, resp):
- run_hook.append('inside_sub')
- return 'Inside here!'
-
- sub = SubSubController()
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- app = TestApp(Pecan(RootController(), use_context_locals=False))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('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 == b_('Inside here!')
-
- assert len(run_hook) == 3
- assert run_hook[0] == 'before'
- assert run_hook[1] == 'inside_sub'
- assert run_hook[2] == 'after'
-
- run_hook = []
- response = app.get('/sub/sub/')
- assert response.status_int == 200
- assert response.body == b_('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)
-
- def before(self, state):
- run_hook.append('before' + self.id)
-
- def after(self, state):
- run_hook.append('after' + self.id)
-
- def on_error(self, state, e):
- run_hook.append('error' + self.id)
-
- class SubController(HookController):
- __hooks__ = [SimpleHook(2)]
-
- @expose()
- def index(self, req, resp):
- run_hook.append('inside_sub')
- return 'Inside here!'
-
- class RootController(object):
- @expose()
- def index(self, req, resp):
- run_hook.append('inside')
- return 'Hello, World!'
-
- sub = SubController()
-
- app = TestApp(Pecan(
- RootController(),
- hooks=[SimpleHook(1)],
- use_context_locals=False
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('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 == b_('Inside here!')
-
- assert len(run_hook) == 6
- assert run_hook[0] == 'on_route1'
- assert run_hook[1] == 'before2'
- assert run_hook[2] == 'before1'
- assert run_hook[3] == 'inside_sub'
- assert run_hook[4] == 'after1'
- assert run_hook[5] == 'after2'
-
-
-class TestGeneric(PecanTestCase):
-
- @property
- def root(self):
- class RootController(object):
-
- def __init__(self, unique):
- self.unique = unique
-
- @expose(generic=True, template='json')
- def index(self, req, resp):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return {'hello': 'world'}
-
- @index.when(method='POST', template='json')
- def index_post(self, req, resp):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return req.json
-
- @expose(template='json')
- def echo(self, req, resp):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return req.json
-
- @expose(template='json')
- def extra(self, req, resp, first, second):
- assert self.__class__.__name__ == 'RootController'
- assert isinstance(req, Request)
- assert isinstance(resp, Response)
- assert self.unique == req.headers.get('X-Unique')
- return {'first': first, 'second': second}
-
- return RootController
-
- def test_generics_with_im_self_default(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.get('/', headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['hello'] == 'world'
-
- def test_generics_with_im_self_with_method(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.post_json('/', {'foo': 'bar'}, headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['foo'] == 'bar'
-
- def test_generics_with_im_self_with_path(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.post_json('/echo/', {'foo': 'bar'},
- headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['foo'] == 'bar'
-
- def test_generics_with_im_self_with_extra_args(self):
- uniq = str(time.time())
- with mock.patch('threading.local', side_effect=AssertionError()):
- app = TestApp(Pecan(self.root(uniq), use_context_locals=False))
- r = app.get('/extra/123/456', headers={'X-Unique': uniq})
- assert r.status_int == 200
- json_resp = loads(r.body.decode())
- assert json_resp['first'] == '123'
- assert json_resp['second'] == '456'
diff --git a/pecan/tests/test_rest.py b/pecan/tests/test_rest.py
deleted file mode 100644
index ba4d141..0000000
--- a/pecan/tests/test_rest.py
+++ /dev/null
@@ -1,1640 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import struct
-import sys
-import warnings
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest # pragma: nocover
-else:
- import unittest # pragma: nocover
-
-try:
- from simplejson import dumps, loads
-except:
- from json import dumps, loads # noqa
-
-from six import b as b_, PY3
-from webtest import TestApp
-
-from pecan import abort, expose, make_app, response, redirect
-from pecan.rest import RestController
-from pecan.tests import PecanTestCase
-
-
-class TestRestController(PecanTestCase):
-
- 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 == b_(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 == b_(value)
-
- # test post
- r = app.post('/things', {'value': 'four'})
- assert r.status_int == 302
- assert r.body == b_('CREATED')
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('four')
-
- # test edit
- r = app.get('/things/3/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT three')
-
- # test put
- r = app.put('/things/4', {'value': 'FOUR'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('FOUR')
-
- # test put with _method parameter and GET
- 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 == b_('FOUR')
-
- # test put with _method parameter and POST
- r = app.post('/things/4?_method=put', {'value': 'FOUR!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/things/4')
- assert r.status_int == 200
- assert r.body == b_('FOUR!')
-
- # test get delete
- r = app.get('/things/4/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE FOUR!')
-
- # test delete
- r = app.delete('/things/4')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/things')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['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.decode())['items']) == 4
-
- # test delete with _method parameter and POST
- r = app.post('/things/3?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/things')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 3
-
- # test "RESET" custom action
- r = app.request('/things', method='RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- # test "RESET" custom action with _method parameter
- r = app.get('/things?_method=RESET')
- assert r.status_int == 200
- assert r.body == b_('RESET')
-
- # test the "OPTIONS" custom action
- r = app.request('/things', method='OPTIONS')
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- # test the "OPTIONS" custom action with the _method parameter
- r = app.post('/things', {'_method': 'OPTIONS'})
- assert r.status_int == 200
- assert r.body == b_('OPTIONS')
-
- # test the "other" custom action
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- 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
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.request('/things/others/', method='MISC')
- assert r.status_int == 200
- assert r.body == b_('OTHERS')
-
- # test the "others" custom action missing trailing slash
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- 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 == b_('OTHERS')
-
- # test an invalid custom action
- r = app.get('/things?_method=BAD', status=405)
- assert r.status_int == 405
-
- # test custom "GET" request "count"
- r = app.get('/things/count')
- assert r.status_int == 200
- assert r.body == b_('3')
-
- # test custom "GET" request "length"
- r = app.get('/things/1/length')
- assert r.status_int == 200
- assert r.body == b_(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 == b_('test')
-
- # test custom "POST" request "length"
- r = app.post('/things/1/length', {'value': 'test'})
- assert r.status_int == 200
- assert r.body == b_(str(len('onetest')))
-
- # test custom "POST" request through subcontroller
- r = app.post('/things/others/echo', {'value': 'test'})
- assert r.status_int == 200
- assert r.body == b_('test')
-
- def test_getall_with_trailing_slash(self):
-
- class ThingsController(RestController):
-
- data = ['zero', 'one', 'two', 'three']
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- 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 == b_(dumps(dict(items=ThingsController.data)))
-
- def test_405_with_lookup(self):
-
- class LookupController(RestController):
-
- def __init__(self, _id):
- self._id = _id
-
- @expose()
- def get_all(self):
- return 'ID: %s' % self._id
-
- class ThingsController(RestController):
-
- @expose()
- def _lookup(self, _id, *remainder):
- return LookupController(_id), remainder
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # these should 405
- for path in ('/things', '/things/'):
- r = app.get(path, expect_errors=True)
- assert r.status_int == 405
-
- r = app.get('/things/foo')
- assert r.status_int == 200
- assert r.body == b_('ID: foo')
-
- def test_getall_with_lookup(self):
-
- class LookupController(RestController):
-
- def __init__(self, _id):
- self._id = _id
-
- @expose()
- def get_all(self):
- return 'ID: %s' % self._id
-
- class ThingsController(RestController):
-
- data = ['zero', 'one', 'two', 'three']
-
- @expose()
- def _lookup(self, _id, *remainder):
- return LookupController(_id), remainder
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- class RootController(object):
- things = ThingsController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- for path in ('/things', '/things/'):
- r = app.get(path)
- assert r.status_int == 200
- assert r.body == b_(dumps(dict(items=ThingsController.data)))
-
- r = app.get('/things/foo')
- assert r.status_int == 200
- assert r.body == b_('ID: foo')
-
- def test_simple_nested_rest(self):
-
- class BarController(RestController):
-
- @expose()
- def post(self):
- return "BAR-POST"
-
- @expose()
- def delete(self, id_):
- return "BAR-%s" % id_
-
- class FooController(RestController):
-
- bar = BarController()
-
- @expose()
- def post(self):
- return "FOO-POST"
-
- @expose()
- def delete(self, id_):
- return "FOO-%s" % id_
-
- class RootController(object):
- foo = FooController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.post('/foo')
- assert r.status_int == 200
- assert r.body == b_("FOO-POST")
-
- r = app.delete('/foo/1')
- assert r.status_int == 200
- assert r.body == b_("FOO-1")
-
- r = app.post('/foo/bar')
- assert r.status_int == 200
- assert r.body == b_("BAR-POST")
-
- r = app.delete('/foo/bar/2')
- assert r.status_int == 200
- assert r.body == b_("BAR-2")
-
- def test_complicated_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)
- if len(self.data) < foo_id + 1:
- self.data.extend([[]] * (foo_id - len(self.data) + 1))
- 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)]
-
- @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)
- response.status = 302
- return 'CREATED'
-
- @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'
-
- 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 == b_(dumps(dict(items=FoosController.data)))
-
- # test nested get_all
- r = app.get('/foos/1/bars')
- assert r.status_int == 200
- assert r.body == b_(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 == b_(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 == b_(value)
-
- # test post
- r = app.post('/foos', {'value': 'two'})
- assert r.status_int == 302
- assert r.body == b_('CREATED')
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('two')
-
- # test nested post
- r = app.post('/foos/2/bars', {'value': 'two-zero'})
- assert r.status_int == 302
- assert r.body == b_('CREATED FOR 2')
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('two-zero')
-
- # test edit
- r = app.get('/foos/1/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT one')
-
- # test nested edit
- r = app.get('/foos/1/bars/1/edit')
- assert r.status_int == 200
- assert r.body == b_('EDIT one-one')
-
- # test put
- r = app.put('/foos/2', {'value': 'TWO'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('TWO')
-
- # test nested put
- r = app.put('/foos/2/bars/0', {'value': 'TWO-ZERO'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('TWO-ZERO')
-
- # test put with _method parameter and GET
- 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 == b_('TWO')
-
- # test nested put with _method parameter and GET
- 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 == b_('TWO-ZERO')
-
- # test put with _method parameter and POST
- r = app.post('/foos/2?_method=put', {'value': 'TWO!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('TWO!')
-
- # test nested put with _method parameter and POST
- r = app.post('/foos/2/bars/0?_method=put', {'value': 'TWO-ZERO!'})
- assert r.status_int == 200
- assert r.body == b_('UPDATED')
-
- # make sure it works
- r = app.get('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('TWO-ZERO!')
-
- # test get delete
- r = app.get('/foos/2/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE TWO!')
-
- # test nested get delete
- r = app.get('/foos/2/bars/0/delete')
- assert r.status_int == 200
- assert r.body == b_('DELETE TWO-ZERO!')
-
- # test nested delete
- r = app.delete('/foos/2/bars/0')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos/2/bars')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 0
-
- # test delete
- r = app.delete('/foos/2')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['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.decode())['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.decode())['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 == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos/1/bars')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 1
-
- # test delete with _method parameter and POST
- r = app.post('/foos/1?_method=DELETE')
- assert r.status_int == 200
- assert r.body == b_('DELETED')
-
- # make sure it works
- r = app.get('/foos')
- assert r.status_int == 200
- assert len(loads(r.body.decode())['items']) == 1
-
- def test_nested_get_all(self):
-
- class BarsController(RestController):
-
- @expose()
- def get_one(self, foo_id, id):
- return '4'
-
- @expose()
- def get_all(self, foo_id):
- return '3'
-
- class FoosController(RestController):
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return '2'
-
- @expose()
- def get_all(self):
- return '1'
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foos/')
- assert r.status_int == 200
- assert r.body == b_('1')
-
- r = app.get('/foos/1/')
- assert r.status_int == 200
- assert r.body == b_('2')
-
- r = app.get('/foos/1/bars/')
- assert r.status_int == 200
- assert r.body == b_('3')
-
- r = app.get('/foos/1/bars/2/')
- assert r.status_int == 200
- assert r.body == b_('4')
-
- r = app.get('/foos/bars/', status=404)
- assert r.status_int == 404
-
- r = app.get('/foos/bars/1', status=404)
- assert r.status_int == 404
-
- def test_nested_get_all_with_lookup(self):
-
- class BarsController(RestController):
-
- @expose()
- def get_one(self, foo_id, id):
- return '4'
-
- @expose()
- def get_all(self, foo_id):
- return '3'
-
- @expose('json')
- def _lookup(self, id, *remainder):
- redirect('/lookup-hit/')
-
- class FoosController(RestController):
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return '2'
-
- @expose()
- def get_all(self):
- return '1'
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foos/')
- assert r.status_int == 200
- assert r.body == b_('1')
-
- r = app.get('/foos/1/')
- assert r.status_int == 200
- assert r.body == b_('2')
-
- r = app.get('/foos/1/bars/')
- assert r.status_int == 200
- assert r.body == b_('3')
-
- r = app.get('/foos/1/bars/2/')
- assert r.status_int == 200
- assert r.body == b_('4')
-
- r = app.get('/foos/bars/')
- assert r.status_int == 302
- assert r.headers['Location'].endswith('/lookup-hit/')
-
- r = app.get('/foos/bars/1')
- assert r.status_int == 302
- assert r.headers['Location'].endswith('/lookup-hit/')
-
- 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=405)
- assert r.status_int == 405
-
- # test get_one
- r = app.get('/things/1', status=405)
- assert r.status_int == 405
-
- # test post
- r = app.post('/things', {'value': 'one'}, status=405)
- assert r.status_int == 405
-
- # test edit
- r = app.get('/things/1/edit', status=405)
- assert r.status_int == 405
-
- # test put
- r = app.put('/things/1', {'value': 'ONE'}, status=405)
-
- # test put with _method parameter and GET
- 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=405)
- assert r.status_int == 405
-
- # test get delete
- r = app.get('/things/1/delete', status=405)
- assert r.status_int == 405
-
- # test delete
- r = app.delete('/things/1', status=405)
- assert r.status_int == 405
-
- # 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=405)
- assert r.status_int == 405
-
- # test "RESET" custom action
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- r = app.request('/things', method='RESET', status=405)
- assert r.status_int == 405
-
- def test_nested_rest_with_missing_intermediate_id(self):
-
- class BarsController(RestController):
-
- data = [['zero-zero', 'zero-one'], ['one-zero', 'one-one']]
-
- @expose('json')
- def get_all(self, foo_id):
- return dict(items=self.data[int(foo_id)])
-
- class FoosController(RestController):
-
- data = ['zero', 'one']
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- @expose('json')
- def get_all(self):
- return dict(items=self.data)
-
- class RootController(object):
- foos = FoosController()
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- # test get_all
- r = app.get('/foos')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_(dumps(dict(items=FoosController.data))))
-
- # test nested get_all
- r = app.get('/foos/1/bars')
- self.assertEqual(r.status_int, 200)
- self.assertEqual(r.body, b_(dumps(dict(items=BarsController.data[1]))))
-
- r = app.get('/foos/bars', expect_errors=True)
- self.assertEqual(r.status_int, 404)
-
- def test_custom_with_trailing_slash(self):
-
- class CustomController(RestController):
-
- _custom_actions = {
- 'detail': ['GET'],
- 'create': ['POST'],
- 'update': ['PUT'],
- 'remove': ['DELETE'],
- }
-
- @expose()
- def detail(self):
- return 'DETAIL'
-
- @expose()
- def create(self):
- return 'CREATE'
-
- @expose()
- def update(self, id):
- return id
-
- @expose()
- def remove(self, id):
- return id
-
- app = TestApp(make_app(CustomController()))
-
- r = app.get('/detail')
- assert r.status_int == 200
- assert r.body == b_('DETAIL')
-
- r = app.get('/detail/')
- assert r.status_int == 200
- assert r.body == b_('DETAIL')
-
- r = app.post('/create')
- assert r.status_int == 200
- assert r.body == b_('CREATE')
-
- r = app.post('/create/')
- assert r.status_int == 200
- assert r.body == b_('CREATE')
-
- r = app.put('/update/123')
- assert r.status_int == 200
- assert r.body == b_('123')
-
- r = app.put('/update/123/')
- assert r.status_int == 200
- assert r.body == b_('123')
-
- r = app.delete('/remove/456')
- assert r.status_int == 200
- assert r.body == b_('456')
-
- r = app.delete('/remove/456/')
- assert r.status_int == 200
- assert r.body == b_('456')
-
- 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)
- assert r.status_int == 405
-
- # test custom delete without ID
- r = app.delete('/things/others/')
- assert r.status_int == 200
- assert r.body == b_('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'})
- assert r.status_int == 200
- assert r.body == b_('DELETE')
-
- # test custom delete with ID
- r = app.delete('/things/others/reset/1')
- assert r.status_int == 200
- assert r.body == b_('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'})
- assert r.status_int == 200
- assert r.body == b_('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 == b_('one, two, three')
-
- # test nested get request
- r = app.get('/things/one/two/three/others/')
- assert r.status_int == 200
- assert r.body == b_('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
- assert r.body == b_('zero-zero-zero')
-
- def test_sub_nested_rest_with_overwrites(self):
-
- class FinalController(object):
-
- @expose()
- def index(self):
- return 'FINAL'
-
- @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)]
-
- @expose()
- def post(self):
- return 'POST-GRAND-CHILD'
-
- @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)]
-
- @expose()
- def post(self):
- return 'POST-CHILD'
-
- @expose()
- def put(self, id):
- return 'PUT-CHILD'
-
- class FoosController(RestController):
-
- data = ['zero']
-
- bars = BarsController()
-
- @expose()
- def get_one(self, id):
- return self.data[int(id)]
-
- @expose()
- def post(self):
- return 'POST'
-
- @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 == b_('POST')
-
- r = app.put('/foos/0')
- assert r.status_int == 200
- assert r.body == b_('PUT')
-
- r = app.post('/foos/bars')
- assert r.status_int == 200
- assert r.body == b_('POST-CHILD')
-
- r = app.put('/foos/bars/0')
- assert r.status_int == 200
- assert r.body == b_('PUT-CHILD')
-
- r = app.post('/foos/bars/bazs')
- assert r.status_int == 200
- assert r.body == b_('POST-GRAND-CHILD')
-
- r = app.put('/foos/bars/bazs/0')
- assert r.status_int == 200
- assert r.body == b_('PUT-GRAND-CHILD')
-
- r = app.get('/foos/bars/bazs/final/')
- assert r.status_int == 200
- assert r.body == b_('FINAL')
-
- r = app.get('/foos/bars/bazs/final/named')
- assert r.status_int == 200
- assert r.body == b_('NAMED')
-
- def test_post_with_kwargs_only(self):
-
- class RootController(RestController):
-
- @expose()
- def get_all(self):
- return 'INDEX'
-
- @expose('json')
- def post(self, **kw):
- return kw
-
- # create the app
- app = TestApp(make_app(RootController()))
-
- r = app.get('/')
- assert r.status_int == 200
- assert r.body == b_('INDEX')
-
- kwargs = {'foo': 'bar', 'spam': 'eggs'}
- r = app.post('/', kwargs)
- assert r.status_int == 200
- assert r.namespace['foo'] == 'bar'
- assert r.namespace['spam'] == 'eggs'
-
- def test_nested_rest_with_lookup(self):
-
- class SubController(RestController):
-
- @expose()
- def get_all(self):
- return "SUB"
-
- class FinalController(RestController):
-
- def __init__(self, id_):
- self.id_ = id_
-
- @expose()
- def get_all(self):
- return "FINAL-%s" % self.id_
-
- @expose()
- def post(self):
- return "POST-%s" % self.id_
-
- class LookupController(RestController):
-
- sub = SubController()
-
- def __init__(self, id_):
- self.id_ = id_
-
- @expose()
- def _lookup(self, id_, *remainder):
- return FinalController(id_), remainder
-
- @expose()
- def get_all(self):
- raise AssertionError("Never Reached")
-
- @expose()
- def post(self):
- return "POST-LOOKUP-%s" % self.id_
-
- @expose()
- def put(self, id_):
- return "PUT-LOOKUP-%s-%s" % (self.id_, id_)
-
- @expose()
- def delete(self, id_):
- return "DELETE-LOOKUP-%s-%s" % (self.id_, id_)
-
- class FooController(RestController):
-
- @expose()
- def _lookup(self, id_, *remainder):
- return LookupController(id_), remainder
-
- @expose()
- def get_one(self, id_):
- return "GET ONE"
-
- @expose()
- def get_all(self):
- return "INDEX"
-
- @expose()
- def post(self):
- return "POST"
-
- @expose()
- def put(self, id_):
- return "PUT-%s" % id_
-
- @expose()
- def delete(self, id_):
- return "DELETE-%s" % id_
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo')
- assert r.status_int == 200
- assert r.body == b_('INDEX')
-
- r = app.post('/foo')
- assert r.status_int == 200
- assert r.body == b_('POST')
-
- r = app.get('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('GET ONE')
-
- r = app.post('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('POST-LOOKUP-1')
-
- r = app.put('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('PUT-1')
-
- r = app.delete('/foo/1')
- assert r.status_int == 200
- assert r.body == b_('DELETE-1')
-
- r = app.put('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('PUT-LOOKUP-1-2')
-
- r = app.delete('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('DELETE-LOOKUP-1-2')
-
- r = app.get('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('FINAL-2')
-
- r = app.post('/foo/1/2')
- assert r.status_int == 200
- assert r.body == b_('POST-2')
-
- def test_nested_rest_with_default(self):
-
- class FooController(RestController):
-
- @expose()
- def _default(self, *remainder):
- return "DEFAULT %s" % remainder
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo/missing')
- assert r.status_int == 200
- assert r.body == b_("DEFAULT missing")
-
- def test_rest_with_non_utf_8_body(self):
- if PY3:
- # webob+PY3 doesn't suffer from this bug; the POST parsing in PY3
- # seems to more gracefully detect the bytestring
- return
-
- class FooController(RestController):
-
- @expose()
- def post(self):
- return "POST"
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- data = struct.pack('255h', *range(0, 255))
- r = app.post('/foo/', data, expect_errors=True)
- assert r.status_int == 400
-
- def test_dynamic_rest_lookup(self):
- class BarController(RestController):
- @expose()
- def get_all(self):
- return "BAR"
-
- @expose()
- def put(self):
- return "PUT_BAR"
-
- @expose()
- def delete(self):
- return "DELETE_BAR"
-
- class BarsController(RestController):
- @expose()
- def _lookup(self, id_, *remainder):
- return BarController(), remainder
-
- @expose()
- def get_all(self):
- return "BARS"
-
- @expose()
- def post(self):
- return "POST_BARS"
-
- class FooController(RestController):
- bars = BarsController()
-
- @expose()
- def get_all(self):
- return "FOO"
-
- @expose()
- def put(self):
- return "PUT_FOO"
-
- @expose()
- def delete(self):
- return "DELETE_FOO"
-
- class FoosController(RestController):
- @expose()
- def _lookup(self, id_, *remainder):
- return FooController(), remainder
-
- @expose()
- def get_all(self):
- return "FOOS"
-
- @expose()
- def post(self):
- return "POST_FOOS"
-
- class RootController(RestController):
- foos = FoosController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foos')
- assert r.status_int == 200
- assert r.body == b_('FOOS')
-
- r = app.post('/foos')
- assert r.status_int == 200
- assert r.body == b_('POST_FOOS')
-
- r = app.get('/foos/foo')
- assert r.status_int == 200
- assert r.body == b_('FOO')
-
- r = app.put('/foos/foo')
- assert r.status_int == 200
- assert r.body == b_('PUT_FOO')
-
- r = app.delete('/foos/foo')
- assert r.status_int == 200
- assert r.body == b_('DELETE_FOO')
-
- r = app.get('/foos/foo/bars')
- assert r.status_int == 200
- assert r.body == b_('BARS')
-
- r = app.post('/foos/foo/bars')
- assert r.status_int == 200
- assert r.body == b_('POST_BARS')
-
- r = app.get('/foos/foo/bars/bar')
- assert r.status_int == 200
- assert r.body == b_('BAR')
-
- r = app.put('/foos/foo/bars/bar')
- assert r.status_int == 200
- assert r.body == b_('PUT_BAR')
-
- r = app.delete('/foos/foo/bars/bar')
- assert r.status_int == 200
- assert r.body == b_('DELETE_BAR')
-
- def test_method_not_allowed_get(self):
- class ThingsController(RestController):
-
- @expose()
- def put(self, id_, value):
- response.status = 200
-
- @expose()
- def delete(self, id_):
- response.status = 200
-
- app = TestApp(make_app(ThingsController()))
- r = app.get('/', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'DELETE, PUT'
-
- def test_method_not_allowed_post(self):
- class ThingsController(RestController):
-
- @expose()
- def get_one(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.post('/', {'foo': 'bar'}, status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_method_not_allowed_put(self):
- class ThingsController(RestController):
-
- @expose()
- def get_one(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.put('/123', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_method_not_allowed_delete(self):
- class ThingsController(RestController):
-
- @expose()
- def get_one(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.delete('/123', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_proper_allow_header_multiple_gets(self):
- class ThingsController(RestController):
-
- @expose()
- def get_all(self):
- return dict()
-
- @expose()
- def get(self):
- return dict()
-
- app = TestApp(make_app(ThingsController()))
- r = app.put('/123', status=405)
- assert r.status_int == 405
- assert r.headers['Allow'] == 'GET'
-
- def test_rest_with_utf8_uri(self):
-
- class FooController(RestController):
- key = chr(0x1F330) if PY3 else unichr(0x1F330)
- data = {key: 'Success!'}
-
- @expose()
- def get_one(self, id_):
- return self.data[id_]
-
- @expose()
- def get_all(self):
- return "Hello, World!"
-
- @expose()
- def put(self, id_, value):
- return self.data[id_]
-
- @expose()
- def delete(self, id_):
- return self.data[id_]
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo/%F0%9F%8C%B0')
- assert r.status_int == 200
- assert r.body == b'Success!'
-
- r = app.put('/foo/%F0%9F%8C%B0', {'value': 'pecans'})
- assert r.status_int == 200
- assert r.body == b'Success!'
-
- r = app.delete('/foo/%F0%9F%8C%B0')
- assert r.status_int == 200
- assert r.body == b'Success!'
-
- r = app.get('/foo/')
- assert r.status_int == 200
- assert r.body == b'Hello, World!'
-
- @unittest.skipIf(not PY3, "test is Python3 specific")
- def test_rest_with_utf8_endpoint(self):
- class ChildController(object):
- @expose()
- def index(self):
- return 'Hello, World!'
-
- class FooController(RestController):
- pass
-
- # okay, so it's technically a chestnut, but close enough...
- setattr(FooController, '🌰', ChildController())
-
- class RootController(RestController):
- foo = FooController()
-
- app = TestApp(make_app(RootController()))
-
- r = app.get('/foo/%F0%9F%8C%B0/')
- assert r.status_int == 200
- assert r.body == b'Hello, World!'
-
-
-class TestExplicitRoute(PecanTestCase):
-
- def test_alternate_route(self):
-
- class RootController(RestController):
-
- @expose(route='some-path')
- def get_all(self):
- return "Hello, World!"
-
- self.assertRaises(
- ValueError,
- RootController
- )
diff --git a/pecan/tests/test_scaffolds.py b/pecan/tests/test_scaffolds.py
deleted file mode 100644
index 669aa7d..0000000
--- a/pecan/tests/test_scaffolds.py
+++ /dev/null
@@ -1,160 +0,0 @@
-import os
-import sys
-import tempfile
-import shutil
-
-from six.moves import cStringIO as StringIO
-
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest
-else:
- import unittest # noqa
-
-
-class TestPecanScaffold(PecanTestCase):
-
- def test_normalize_pkg_name(self):
- from pecan.scaffolds import PecanScaffold
- s = PecanScaffold()
- assert s.normalize_pkg_name('sam') == 'sam'
- assert s.normalize_pkg_name('sam1') == 'sam1'
- assert s.normalize_pkg_name('sam_') == 'sam_'
- assert s.normalize_pkg_name('Sam') == 'sam'
- assert s.normalize_pkg_name('SAM') == 'sam'
- assert s.normalize_pkg_name('sam ') == 'sam'
- assert s.normalize_pkg_name(' sam') == 'sam'
- assert s.normalize_pkg_name('sam$') == 'sam'
- assert s.normalize_pkg_name('sam-sam') == 'samsam'
-
-
-class TestScaffoldUtils(PecanTestCase):
-
- def setUp(self):
- super(TestScaffoldUtils, self).setUp()
- self.scaffold_destination = tempfile.mkdtemp()
- self.out = sys.stdout
-
- sys.stdout = StringIO()
-
- def tearDown(self):
- shutil.rmtree(self.scaffold_destination)
- sys.stdout = self.out
-
- def test_copy_dir(self):
- from pecan.scaffolds import PecanScaffold
-
- class SimpleScaffold(PecanScaffold):
- _scaffold_dir = ('pecan', os.path.join(
- 'tests', 'scaffold_fixtures', 'simple'
- ))
-
- SimpleScaffold().copy_to(os.path.join(
- self.scaffold_destination,
- 'someapp'
- ), out_=StringIO())
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ))
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'bar', 'spam.txt'
- ))
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ), 'r').read().strip() == 'YAR'
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ), 'r').read().strip() == 'YAR'
-
- def test_destination_directory_levels_deep(self):
- from pecan.scaffolds import copy_dir
- f = StringIO()
- copy_dir(
- (
- 'pecan', os.path.join('tests', 'scaffold_fixtures', 'simple')
- ),
- os.path.join(self.scaffold_destination, 'some', 'app'),
- {},
- out_=f
- )
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'some', 'app', 'foo')
- )
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'some', 'app', 'bar', 'spam.txt')
- )
- assert open(os.path.join(
- self.scaffold_destination, 'some', 'app', 'foo'
- ), 'r').read().strip() == 'YAR'
- assert open(os.path.join(
- self.scaffold_destination, 'some', 'app', 'bar', 'spam.txt'
- ), 'r').read().strip() == 'Pecan'
-
- def test_destination_directory_already_exists(self):
- from pecan.scaffolds import copy_dir
- f = StringIO()
- copy_dir(
- (
- 'pecan', os.path.join('tests', 'scaffold_fixtures', 'simple')
- ),
- os.path.join(self.scaffold_destination),
- {},
- out_=f
- )
- assert 'already exists' in f.getvalue()
-
- def test_copy_dir_with_filename_substitution(self):
- from pecan.scaffolds import copy_dir
- copy_dir(
- (
- 'pecan', os.path.join('tests', 'scaffold_fixtures', 'file_sub')
- ),
- os.path.join(
- self.scaffold_destination, 'someapp'
- ),
- {'package': 'thingy'},
- out_=StringIO()
- )
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'foo_thingy')
- )
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'bar_thingy', 'spam.txt')
- )
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo_thingy'
- ), 'r').read().strip() == 'YAR'
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'bar_thingy', 'spam.txt'
- ), 'r').read().strip() == 'Pecan'
-
- def test_copy_dir_with_file_content_substitution(self):
- from pecan.scaffolds import copy_dir
- copy_dir(
- (
- 'pecan',
- os.path.join('tests', 'scaffold_fixtures', 'content_sub'),
- ),
- os.path.join(
- self.scaffold_destination, 'someapp'
- ),
- {'package': 'thingy'},
- out_=StringIO()
- )
-
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'foo')
- )
- assert os.path.isfile(os.path.join(
- self.scaffold_destination, 'someapp', 'bar', 'spam.txt')
- )
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'foo'
- ), 'r').read().strip() == 'YAR thingy'
- assert open(os.path.join(
- self.scaffold_destination, 'someapp', 'bar', 'spam.txt'
- ), 'r').read().strip() == 'Pecan thingy'
diff --git a/pecan/tests/test_secure.py b/pecan/tests/test_secure.py
deleted file mode 100644
index 1d30ea3..0000000
--- a/pecan/tests/test_secure.py
+++ /dev/null
@@ -1,563 +0,0 @@
-import sys
-
-from six import b as b_
-from webtest import TestApp
-
-from pecan import expose, make_app
-from pecan.secure import secure, unlocked, SecureController
-from pecan.tests import PecanTestCase
-
-if sys.version_info < (2, 7):
- import unittest2 as unittest
-else:
- import unittest # noqa
-
-try:
- set()
-except:
- from sets import Set as set
-
-
-class TestSecure(PecanTestCase):
- def test_simple_secure(self):
- authorized = False
-
- class SecretController(SecureController):
- @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(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- response = app.get('/unlocked')
- assert response.status_int == 200
- assert response.body == b_('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 == b_('Allowed!')
-
- def test_unlocked_attribute(self):
- class AuthorizedSubController(object):
- @expose()
- def index(self):
- return 'Index'
-
- @expose()
- def allowed(self):
- return 'Allowed!'
-
- class SecretController(SecureController):
- @expose()
- def index(self):
- return 'Index'
-
- @expose()
- @unlocked
- def allowed(self):
- return 'Allowed!'
-
- authorized = unlocked(AuthorizedSubController())
-
- 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(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello, World!')
-
- response = app.get('/unlocked')
- assert response.status_int == 200
- assert response.body == b_('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 == b_('Allowed!')
-
- response = app.get('/secret/authorized/')
- assert response.status_int == 200
- assert response.body == b_('Index')
-
- response = app.get('/secret/authorized/allowed')
- assert response.status_int == 200
- assert response.body == b_('Allowed!')
-
- def test_secure_attribute(self):
- authorized = False
-
- class SubController(object):
- @expose()
- def index(self):
- return 'Hello from sub!'
-
- class RootController(object):
- @expose()
- def index(self):
- return 'Hello from root!'
-
- sub = secure(SubController(), lambda: authorized)
-
- app = TestApp(make_app(RootController()))
- response = app.get('/')
- assert response.status_int == 200
- assert response.body == b_('Hello from root!')
-
- response = app.get('/sub/', expect_errors=True)
- assert response.status_int == 401
-
- authorized = True
- response = app.get('/sub/')
- assert response.status_int == 200
- assert response.body == b_('Hello from sub!')
-
- def test_secured_generic_controller(self):
- authorized = False
-
- class RootController(object):
-
- @classmethod
- def check_permissions(cls):
- return authorized
-
- @expose(generic=True)
- def index(self):
- return 'Index'
-
- @secure('check_permissions')
- @index.when(method='POST')
- def index_post(self):
- return 'I should not be allowed'
-
- @secure('check_permissions')
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- response = app.post('/', expect_errors=True)
- assert response.status_int == 401
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_secured_generic_controller_lambda(self):
- authorized = False
-
- class RootController(object):
-
- @expose(generic=True)
- def index(self):
- return 'Index'
-
- @secure(lambda: authorized)
- @index.when(method='POST')
- def index_post(self):
- return 'I should not be allowed'
-
- @secure(lambda: authorized)
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- response = app.post('/', expect_errors=True)
- assert response.status_int == 401
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_secured_generic_controller_secure_attribute(self):
- authorized = False
-
- class SecureController(object):
-
- @expose(generic=True)
- def index(self):
- return 'I should not be allowed'
-
- @index.when(method='POST')
- def index_post(self):
- return 'I should not be allowed'
-
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- class RootController(object):
- sub = secure(SecureController(), lambda: authorized)
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/sub/', expect_errors=True)
- assert response.status_int == 401
- response = app.post('/sub/', expect_errors=True)
- assert response.status_int == 401
- response = app.get('/sub/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_secured_generic_controller_secure_attribute_with_unlocked(self):
-
- class RootController(SecureController):
-
- @unlocked
- @expose(generic=True)
- def index(self):
- return 'Unlocked!'
-
- @unlocked
- @index.when(method='POST')
- def index_post(self):
- return 'Unlocked!'
-
- @expose(generic=True)
- def secret(self):
- return 'I should not be allowed'
-
- app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
- response = app.get('/')
- assert response.status_int == 200
- response = app.post('/')
- assert response.status_int == 200
- response = app.get('/secret/', expect_errors=True)
- assert response.status_int == 401
-
- def test_state_attribute(self):
- from pecan.secure import Any, Protected
- assert repr(Any) == '<SecureState Any>'
- assert bool(Any) is False
-
- assert repr(Protected) == '<SecureState Protected>'
- assert bool(Protected) is True
-
- def test_secure_obj_only_failure(self):
- class Foo(object):
- pass
-
- try:
- secure(Foo())
- except Exception as e:
- assert isinstance(e, TypeError)
-
-
-class TestObjectPathSecurity(PecanTestCase):
-
- def setUp(self):
- super(TestObjectPathSecurity, self).setUp()
- 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'
-
- @classmethod
- def check_permissions(cls):
- permissions_checked.add('deepsecret')
- return cls.authorized
-
- class SubController(object):
- def __init__(self, myID):
- self.myID = myID
-
- @expose()
- def index(self):
- return 'Index %s' % self.myID
-
- deepsecret = DeepSecretController()
-
- class SecretController(SecureController):
- authorized = False
- independent_authorization = False
-
- @expose()
- def _lookup(self, someID, *remainder):
- if someID == 'notfound':
- return None
- elif someID == 'lookup_wrapped':
- return self.wrapped, remainder
- return SubController(someID), remainder
-
- @secure('independent_check_permissions')
- @expose()
- def independent(self):
- return 'Independent Security'
-
- wrapped = secure(
- SubController('wrapped'), 'independent_check_permissions'
- )
-
- @classmethod
- def check_permissions(cls):
- permissions_checked.add('secretcontroller')
- return cls.authorized
-
- @classmethod
- def independent_check_permissions(cls):
- permissions_checked.add('independent')
- return cls.independent_authorization
-
- class NotSecretController(object):
- @expose()
- def _lookup(self, someID, *remainder):
- if someID == 'notfound':
- return None
- return SubController(someID), remainder
-
- unlocked = unlocked(SubController('unlocked'))
-
- class RootController(object):
- secret = SecretController()
- notsecret = NotSecretController()
-
- self.deepsecret_cls = DeepSecretController
- self.secret_cls = SecretController
-
- self.permissions_checked = permissions_checked
- self.app = TestApp(make_app(
- RootController(),
- debug=True,
- static_root='tests/static'
- ))
-
- def tearDown(self):
- self.permissions_checked.clear()
- self.secret_cls.authorized = False
- self.deepsecret_cls.authorized = False
-
- def test_sub_of_both_not_secret(self):
- response = self.app.get('/notsecret/hi/')
- assert response.status_int == 200
- assert response.body == b_('Index hi')
-
- def test_protected_lookup(self):
- response = self.app.get('/secret/hi/', expect_errors=True)
- assert response.status_int == 401
-
- self.secret_cls.authorized = True
- response = self.app.get('/secret/hi/')
- assert response.status_int == 200
- assert response.body == b_('Index hi')
- assert 'secretcontroller' in self.permissions_checked
-
- def test_secured_notfound_lookup(self):
- response = self.app.get('/secret/notfound/', expect_errors=True)
- assert response.status_int == 404
-
- def test_secret_through_lookup(self):
- response = self.app.get(
- '/notsecret/hi/deepsecret/', expect_errors=True
- )
- assert response.status_int == 401
-
- def test_layered_protection(self):
- response = self.app.get('/secret/hi/deepsecret/', expect_errors=True)
- assert response.status_int == 401
- assert 'secretcontroller' in self.permissions_checked
-
- self.secret_cls.authorized = True
- response = self.app.get('/secret/hi/deepsecret/', expect_errors=True)
- assert response.status_int == 401
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' in self.permissions_checked
-
- self.deepsecret_cls.authorized = True
- response = self.app.get('/secret/hi/deepsecret/')
- assert response.status_int == 200
- assert response.body == b_('Deep Secret')
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' in self.permissions_checked
-
- def test_cyclical_protection(self):
- self.secret_cls.authorized = True
- self.deepsecret_cls.authorized = True
- response = self.app.get('/secret/1/deepsecret/2/deepsecret/')
- assert response.status_int == 200
- assert response.body == b_('Deep Secret')
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' in self.permissions_checked
-
- def test_unlocked_lookup(self):
- response = self.app.get('/notsecret/1/deepsecret/2/')
- assert response.status_int == 200
- assert response.body == b_('Index 2')
- assert 'deepsecret' not in self.permissions_checked
-
- 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
- )
- assert response.status_int == 404
- assert 'secretcontroller' in self.permissions_checked
- assert 'deepsecret' not in self.permissions_checked
-
- def test_independent_check_failure(self):
- response = self.app.get('/secret/independent/', expect_errors=True)
- assert response.status_int == 401
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_independent_check_success(self):
- self.secret_cls.independent_authorization = True
- response = self.app.get('/secret/independent')
- assert response.status_int == 200
- assert response.body == b_('Independent Security')
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_wrapped_attribute_failure(self):
- self.secret_cls.independent_authorization = False
- response = self.app.get('/secret/wrapped/', expect_errors=True)
- assert response.status_int == 401
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_wrapped_attribute_success(self):
- self.secret_cls.independent_authorization = True
- response = self.app.get('/secret/wrapped/')
- assert response.status_int == 200
- assert response.body == b_('Index wrapped')
- assert len(self.permissions_checked) == 1
- assert 'independent' in self.permissions_checked
-
- def test_lookup_to_wrapped_attribute_on_self(self):
- self.secret_cls.authorized = True
- self.secret_cls.independent_authorization = True
- response = self.app.get('/secret/lookup_wrapped/')
- assert response.status_int == 200
- assert response.body == b_('Index wrapped')
- assert len(self.permissions_checked) == 2
- assert 'independent' in self.permissions_checked
- assert 'secretcontroller' in self.permissions_checked
-
- def test_unlocked_attribute_in_insecure(self):
- response = self.app.get('/notsecret/unlocked/')
- assert response.status_int == 200
- assert response.body == b_('Index unlocked')
-
-
-class SecureControllerSharedPermissionsRegression(PecanTestCase):
- """Regression tests for https://github.com/dreamhost/pecan/issues/131"""
-
- def setUp(self):
- super(SecureControllerSharedPermissionsRegression, self).setUp()
-
- class Parent(object):
- @expose()
- def index(self):
- return 'hello'
-
- class UnsecuredChild(Parent):
- pass
-
- class SecureChild(Parent, SecureController):
- @classmethod
- def check_permissions(cls):
- return False
-
- class RootController(object):
-
- secured = SecureChild()
- unsecured = UnsecuredChild()
-
- self.app = TestApp(make_app(RootController()))
-
- def test_inherited_security(self):
- assert self.app.get('/secured/', status=401).status_int == 401
- assert self.app.get('/unsecured/').status_int == 200
diff --git a/pecan/tests/test_templating.py b/pecan/tests/test_templating.py
deleted file mode 100644
index 5fffb0c..0000000
--- a/pecan/tests/test_templating.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import tempfile
-
-from six import b as b_
-
-from pecan.templating import RendererFactory, format_line_context
-from pecan.tests import PecanTestCase
-
-
-class TestTemplate(PecanTestCase):
- def setUp(self):
- super(TestTemplate, self).setUp()
- self.rf = RendererFactory()
-
- def test_available(self):
- self.assertTrue(self.rf.available('json'))
- self.assertFalse(self.rf.available('badrenderer'))
-
- def test_create_bad(self):
- self.assertEqual(self.rf.get('doesnotexist', '/'), None)
-
- def test_extra_vars(self):
- extra_vars = self.rf.extra_vars
- self.assertEqual(extra_vars.make_ns({}), {})
-
- extra_vars.update({'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})
-
-
-class TestTemplateLineFormat(PecanTestCase):
-
- def setUp(self):
- super(TestTemplateLineFormat, self).setUp()
- self.f = tempfile.NamedTemporaryFile()
-
- def tearDown(self):
- del self.f
-
- def test_format_line_context(self):
- for i in range(11):
- self.f.write(b_('Testing Line %d\n' % i))
- self.f.flush()
-
- assert format_line_context(self.f.name, 0).count('Testing Line') == 10
diff --git a/pecan/tests/test_util.py b/pecan/tests/test_util.py
deleted file mode 100644
index 00e81f5..0000000
--- a/pecan/tests/test_util.py
+++ /dev/null
@@ -1,95 +0,0 @@
-import functools
-import inspect
-import unittest
-
-from pecan import expose
-from pecan import util
-
-
-class TestArgSpec(unittest.TestCase):
-
- @property
- def controller(self):
-
- class RootController(object):
-
- @expose()
- def index(self, a, b, c=1, *args, **kwargs):
- return 'Hello, World!'
-
- @staticmethod
- @expose()
- def static_index(a, b, c=1, *args, **kwargs):
- return 'Hello, World!'
-
- return RootController()
-
- def test_no_decorator(self):
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(self.controller.index.__func__)
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(self.controller.static_index)
- assert expected == actual
-
- def test_simple_decorator(self):
- def dec(f):
- return f
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(self.controller.index.__func__))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(self.controller.static_index))
- assert expected == actual
-
- def test_simple_wrapper(self):
- def dec(f):
- @functools.wraps(f)
- def wrapped(*a, **kw):
- return f(*a, **kw)
- return wrapped
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(self.controller.index.__func__))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(self.controller.static_index))
- assert expected == actual
-
- def test_multiple_decorators(self):
- def dec(f):
- @functools.wraps(f)
- def wrapped(*a, **kw):
- return f(*a, **kw)
- return wrapped
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(dec(dec(self.controller.index.__func__))))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(dec(dec(
- self.controller.static_index))))
- assert expected == actual
-
- def test_decorator_with_args(self):
- def dec(flag):
- def inner(f):
- @functools.wraps(f)
- def wrapped(*a, **kw):
- return f(*a, **kw)
- return wrapped
- return inner
-
- expected = inspect.getargspec(self.controller.index.__func__)
- actual = util.getargspec(dec(True)(self.controller.index.__func__))
- assert expected == actual
-
- expected = inspect.getargspec(self.controller.static_index)
- actual = util.getargspec(dec(True)(
- self.controller.static_index))
- assert expected == actual
diff --git a/pecan/util.py b/pecan/util.py
deleted file mode 100644
index bdb5d2b..0000000
--- a/pecan/util.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import inspect
-import sys
-
-import six
-
-
-def iscontroller(obj):
- return getattr(obj, 'exposed', False)
-
-
-def getargspec(method):
- """
- Drill through layers of decorators attempting to locate the actual argspec
- for a method.
- """
-
- argspec = inspect.getargspec(method)
- args = argspec[0]
- if args and args[0] == 'self':
- return argspec
- if hasattr(method, '__func__'):
- method = method.__func__
-
- func_closure = six.get_function_closure(method)
-
- # NOTE(sileht): if the closure is None we cannot look deeper,
- # so return actual argspec, this occurs when the method
- # is static for example.
- if func_closure is None:
- return argspec
-
- closure = next(
- (
- c for c in func_closure if six.callable(c.cell_contents)
- ),
- None
- )
- method = closure.cell_contents
- return getargspec(method)
-
-
-def _cfg(f):
- if not hasattr(f, '_pecan'):
- f._pecan = {}
- return f._pecan
-
-
-if sys.version_info >= (2, 6, 5):
- def encode_if_needed(s):
- return s
-else:
- def encode_if_needed(s): # noqa
- return s.encode('utf-8')