# -*- coding: utf-8 -*-
"""CBV for bottle.py application instances.
Provides a base class for creating class-based web handlers for bottle.py
application instances with application routing mechanism.
"""
from __future__ import absolute_import
__author__ = "Papavassiliou Vassilis"
__date__ = "2015-11-29"
__all__ = ['BaseHandler', 'HandlerMeta', 'route_method', 'plugin_method',
'HandlerError', 'HandlerHTTPMethodError', 'HandlerPluginError',
'BaseHandlerPlugin']
from bottle_neck import __version__
import bottle
import functools
import inspect
import six
import re
version = tuple(map(int, __version__.split('.')))
DEFAULT_ROUTES = ("get", "put", "post", "delete", "patch", "options")
CORS_ROUTES = ("put", "post", "delete", "patch")
PLUGIN_SCOPE = (
(False, 'plugins'),
(True, 'global_plugins')
)
class HandlerError(Exception):
"""Base module Exception class.
"""
pass
class HandlerHTTPMethodError(HandlerError):
"""Raises for invalid HTTP method declaration.
"""
pass
class HandlerPluginError(HandlerError):
"""Raises when a handler plugin error occurs.
"""
pass
class ClassPropertyDescriptor(object):
"""ClassProperty Descriptor class.
Straight up stolen from stack overflow Implements class level property
non-data descriptor.
"""
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, cls=None):
if cls is None: # pragma: no cover
cls = type(obj)
return self.fget.__get__(obj, cls)()
def classproperty(func):
"""classproperty decorator.
Using this decorator a class can have a property. Necessary for properties
that don't need instance initialization. Works exactly the same as a
normal property.
Examples:
>>> class MyClass(object):
... @classproperty
... def my_prop(self):
... return self.__name__ + ' class'
...
>>> MyClass.my_prop
'MyClass class'
"""
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
def cached_classproperty(fun):
"""A memorization decorator for class properties.
It implements the above `classproperty` decorator, with
the difference that the function result is computed and attached
to class as direct attribute. (Lazy loading and caching.)
"""
@functools.wraps(fun)
def get(cls):
try:
return cls.__cache[fun]
except AttributeError:
cls.__cache = {}
except KeyError: # pragma: no cover
pass
ret = cls.__cache[fun] = fun(cls)
return ret
return classproperty(get)
def plugin_method(*plugin_names):
"""Plugin Method decorator.
Signs a web handler function with the plugins to be applied as attributes.
Args:
plugin_names (list): A list of plugin callable names
Returns:
A wrapped handler callable.
Examples:
>>> @plugin_method('json', 'bill')
... def method():
... return "Hello!"
...
>>> print method.json
True
>>> print method.bill
True
"""
def wrapper(callable_obj):
for plugin_name in plugin_names:
if not hasattr(callable_obj, plugin_name):
setattr(callable_obj, plugin_name, True)
return callable_obj
return wrapper
def route_method(method_name, extra_part=False):
"""Custom handler routing decorator.
Signs a web handler callable with the http method as attribute.
Args:
method_name (str): HTTP method name (i.e GET, POST)
extra_part (bool): Indicates if wrapped callable name should be a part
of the actual endpoint.
Returns:
A wrapped handler callable.
examples:
>>> @route_method('GET')
... def method():
... return "Hello!"
...
>>> method.http_method
'GET'
>>> method.url_extra_part
None
"""
def wrapper(callable_obj):
if method_name.lower() not in DEFAULT_ROUTES:
raise HandlerHTTPMethodError(
'Invalid http method in method: {}'.format(method_name)
)
callable_obj.http_method = method_name.upper()
callable_obj.url_extra_part = callable_obj.__name__ if extra_part\
else None
return classmethod(callable_obj)
return wrapper
class BaseHandlerPlugin(object):
"""
"""
def __init__(self, callable_object, *args, **kwargs): # pragma: no cover
self._wrapped = callable_object
self._args = args
self._kwargs = kwargs
self.__doc__ = callable_object.__doc__
@cached_classproperty
def func_name(self):
cls_name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__name__)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', cls_name).lower()
def apply(self, *args, **kwargs): # pragma: no cover
raise NotImplementedError("Must Override `apply` method")
def __call__(self, *args, **kwargs): # pragma: no cover
return self.apply(*args, **kwargs)
class HandlerMeta(type):
"""BaseHandler metaclass
Provides meta functionality for ensuring that `http_method` don't require
an instance and test and that derived classes implements `Singleton`.
"""
_instances = {}
def __new__(mcs, name, bases, attrs):
http_methods = [attr for attr in attrs if
attr.lower() in DEFAULT_ROUTES]
for handler in http_methods:
attrs[handler] = classmethod(attrs[handler])
return super(HandlerMeta, mcs).__new__(mcs, name, bases, attrs)
def __call__(cls, *args):
"""Enforce Singleton class initiation
"""
if cls not in cls._instances:
cls._instances[cls] = super(
HandlerMeta, cls
).__call__(*args)
return cls._instances[cls]
@six.add_metaclass(HandlerMeta)
class BaseHandler(object):
"""Base Handler class for implementing Class-Based Web handler for bottle.
Subclass `BaseHandler` in order to its API for application routing, and
implement any of the known http method:
Class attributes:
base_endpoint (str): The handler endpoint prefix.
cors_enabled (bool): Indicates if CORS is enabled.
plugins (dict): A key/value mapping of available plugins.
global_plugins (dict): A key/value mapping default applying plugins.
"""
request = bottle.request
response = bottle.response
base_endpoint = '/'
cors_enabled = True
plugins = dict()
global_plugins = dict()
@classmethod
def add_plugin(cls, plugin_callables, global_scope=False):
"""Register a new plugin in handler.
Args:
plugin_callables (list): A list of plugin callables.
global_scope (bool): Indicates The scope of the plugin.
Returns:
Class instance.
"""
repo = getattr(cls, dict(PLUGIN_SCOPE)[global_scope])
for plugin_callable in plugin_callables:
if hasattr(plugin_callable, 'func_name'):
repo[plugin_callable.func_name] = plugin_callable
else: # pragma: no cover
repo[plugin_callable.__name__] = plugin_callable
return cls
@classmethod
def register_app(cls, application):
"""Register class view in bottle application.
Args:
application (instance): A bottle.Bottle() instance.
Returns:
Class instance.
"""
if cls is BaseHandler:
raise HandlerError("Cant register a `BaseHandler` class instance")
routes = cls._get_http_members()
router = getattr(application, "route")
for func_name, func_callable in routes:
method_args
PyPI 官网下载 | bottle-neck-1.14.tar.gz
版权申诉
50 浏览量
2022-01-09
15:21:09
上传
评论
收藏 10KB GZ 举报
挣扎的蓝藻
- 粉丝: 13w+
- 资源: 15万+