diff options
Diffstat (limited to 'pecan')
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 Binary files differdeleted file mode 100644 index a8f403e..0000000 --- a/pecan/scaffolds/base/public/images/logo.png +++ /dev/null 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 Binary files differdeleted file mode 100644 index 9b30321..0000000 --- a/pecan/tests/middleware/static_fixtures/self.png +++ /dev/null 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') |