def put_loader(*args, serializer): """ Decorator to automatically load and update a model from json request data :param serializer: The ModelSerializer to use to load data from the request """ def wrapped(fn): @wraps(fn) def decorated(*args, **kwargs): try: result = serializer.load(request.get_json(), instance=kwargs.pop('instance')) except ValidationError as v: errors = v.messages result = v.valid_data else: errors = None # TODO: When to abort the request? #abort(HTTPStatus.NOT_FOUND) return fn(result, errors) return decorated if was_decorated_without_parenthesis(args): return wrapped(args[0]) return wrapped
def serializer(self, *args, many=False): """Decorator to wrap a :class:`~backend.api.ModelSerializer` class, registering the wrapped serializer as the specific one to use for the serializer's model. For example:: from backend.extensions.api import api from backend.api import ModelSerializer from models import Foo @api.serializer # @api.serializer() works too class FooSerializer(ModelSerializer): class Meta: model = Foo @api.serializer(many=True) class FooListSerializer(ModelSerializer): class Meta: model = Foo """ def decorator(serializer_class): model_name = serializer_class.Meta.model.__name__ self.deferred_serializers.append( (model_name, serializer_class, many)) return serializer_class if was_decorated_without_parenthesis(args): return decorator(args[0]) return decorator
def attach_events(*args): """Class decorator for SQLAlchemy models to attach listeners on class methods decorated with :func:`.on` Usage:: @attach_events class User(Model): email = Column(String(50)) @on('email', 'set') def lowercase_email(self, new_value, old_value, initiating_event): self.email = new_value.lower() """ def wrapper(cls): for name, fn in cls.__dict__.items(): if not name.startswith('__') and hasattr(fn, _SQLAlchemyEvent.ATTR): e = getattr(fn, _SQLAlchemyEvent.ATTR) if e.field_name: event.listen(getattr(cls, e.field_name), e.event_name, fn, **e.listen_kwargs) else: event.listen(cls, e.event_name, fn, **e.listen_kwargs) return cls if was_decorated_without_parenthesis(args): return wrapper(args[0]) return wrapper
def auth_required(*args, **kwargs): """Decorator for requiring an authenticated user, optionally with roles Roles are passed as keyword arguments, like so: @auth_required(role='REQUIRE_THIS_ONE_ROLE') @auth_required(roles=['REQUIRE', 'ALL', 'OF', 'THESE', 'ROLES']) @auth_required(one_of=['EITHER_THIS_ROLE', 'OR_THIS_ONE']) One of role or roles kwargs can also be combined with one_of: @auth_required(role='REQUIRED', one_of=['THIS', 'OR_THIS']) # equivalent, but more clearly describing the resultant behavior: @auth_required(role='REQUIRED', and_one_of=['THIS', 'OR_THIS']) Aborts with HTTP 401: Unauthorized if no user is logged in, or HTTP 403: Forbidden if any of the specified role checks fail """ required_roles = [] one_of_roles = [] if not was_decorated_without_parenthesis(args): if 'role' in kwargs and 'roles' in kwargs: raise RuntimeError( 'can only pass one of `role` or `roles` kwargs to auth_required' ) elif 'role' in kwargs: required_roles = [kwargs['role']] elif 'roles' in kwargs: required_roles = kwargs['roles'] if 'one_of' in kwargs and 'and_one_of' in kwargs: raise RuntimeError( 'can only pass one of `one_of` or `and_one_of` kwargs to auth_required' ) elif 'one_of' in kwargs: one_of_roles = kwargs['one_of'] elif 'and_one_of' in kwargs: one_of_roles = kwargs['and_one_of'] def wrapper(fn): @wraps(fn) @flask_security_auth_required('session', 'token') @roles_required(*required_roles) @roles_accepted(*one_of_roles) def decorated(*args, **kwargs): return fn(*args, **kwargs) return decorated if was_decorated_without_parenthesis(args): return wrapper(args[0]) return wrapper
def param_converter(*decorator_args, **decorator_kwargs): """ Call with the url parameter names as keyword argument keys, their values being the model to convert to. Models will be looked up by the url param names. If a url param name is prefixed with the snake-cased model name, the prefix will be stripped. If a model isn't found, abort with a 404. The action's argument names must match the snake-cased model names. For example:: @bp.route('/users/<int:user_id>/posts/<int:id>') @param_converter(user_id=User, id=Post) def show_post(user, post): # the param converter does the database lookups: # user = User.query.filter_by(id=user_id).first() # post = Post.query.filter_by(id=id).first() # and calls the decorated action: show_post(user, post) # or to customize the argument names passed to the action: @bp.route('/users/<int:user_id>/posts/<int:post_id>') @param_converter(user_id={'user_arg_name': User}, post_id={'post_arg_name': Post}) def show_post(user_arg_name, post_arg_name): # do stuff ... Also supports parsing arguments from the query string. For query string keyword arguments, use a lookup (dict, Enum) or callable:: @bp.route('/users/<int:id>') @param_converter(id=User, foo=str, optional=int) def show_user(user, foo, optional=10): # GET /users/1?foo=bar # calls show_user(user=User.get(1), foo='bar') """ def wrapped(fn): @wraps(fn) def decorated(*view_args, **view_kwargs): try: view_kwargs = _convert_models(view_kwargs, decorator_kwargs) view_kwargs = _convert_query_params( view_kwargs, decorator_kwargs) return fn(*view_args, **view_kwargs) except HTTPException as e: raise e except Exception: if apm.is_initialized: apm.capture_exception() traceback.print_exc() abort(HTTPStatus.INTERNAL_SERVER_ERROR) return decorated if was_decorated_without_parenthesis(decorator_args): return wrapped(decorator_args[0]) return wrapped
def auth_required_same_user(*args, **kwargs): """Decorator for requiring an authenticated user to be the same as the user in the URL parameters. By default the user url parameter name to lookup is 'id', but this can be customized by passing an argument: @auth_require_same_user('user_id') @bp.route('/users/<int:user_id>/foo/<int:id>') def get(user_id, id): # do stuff Any keyword arguments are passed along to the @auth_required decorator, so roles can also be specified in the same was as it, eg: @auth_required_same_user('user_id', role='ROLE_ADMIN') Aborts with HTTP 403: Forbidden if the user-check fails """ auth_kwargs = {} user_id_parameter_name = 'id' if not was_decorated_without_parenthesis(args): auth_kwargs = kwargs if args and isinstance(args[0], str): user_id_parameter_name = args[0] def wrapper(fn): @wraps(fn) @auth_required(**auth_kwargs) def decorated(*args, **kwargs): try: user_id = request.view_args[user_id_parameter_name] except KeyError: raise KeyError('Unable to find the user lookup parameter ' f'{user_id_parameter_name} in the url args') if not Permission(UserNeed(user_id)).can(): abort(HTTPStatus.FORBIDDEN) return fn(*args, **kwargs) return decorated if was_decorated_without_parenthesis(args): return wrapper(args[0]) return wrapper
def post_loader(*args, serializer): """ Decorator to automatically instantiate a model from json request data :param serializer: The ModelSerializer to use to load data from the request """ def wrapped(fn): @wraps(fn) def decorated(*args, **kwargs): return fn(*serializer.load(request.get_json())) return decorated if was_decorated_without_parenthesis(args): return wrapped(args[0]) return wrapped
def list_loader(*args, model): """ Decorator to automatically query the database for all records of a model. :param model: The model class to query """ def wrapped(fn): @wraps(fn) def decorated(*args, **kwargs): return fn(model.query.all()) return decorated if was_decorated_without_parenthesis(args): return wrapped(args[0]) return wrapped
def anonymous_user_required(*args, **kwargs): """Decorator requiring no user be logged in Aborts with HTTP 403: Forbidden if there is an authenticated user """ def wrapper(fn): @wraps(fn) def decorated(*args, **kwargs): if current_user.is_authenticated: abort(HTTPStatus.FORBIDDEN) return fn(*args, **kwargs) return decorated if was_decorated_without_parenthesis(args): return wrapper(args[0]) return wrapper
def put_loader(*args, serializer): """ Decorator to automatically load and update a model from json request data :param serializer: The ModelSerializer to use to load data from the request """ def wrapped(fn): @wraps(fn) def decorated(*args, **kwargs): result = serializer.load(request.get_json(), instance=kwargs.pop('instance')) if not result.errors and not result.data.id: abort(HTTPStatus.NOT_FOUND) return fn(*result) return decorated if was_decorated_without_parenthesis(args): return wrapped(args[0]) return wrapped
def post_loader(*args, serializer): """ Decorator to automatically instantiate a model from json request data :param serializer: The ModelSerializer to use to load data from the request """ def wrapped(fn): @wraps(fn) def decorated(*args, **kwargs): try: result = serializer.load(request.get_json()) except ValidationError as v: errors = v.messages result = v.valid_data else: errors = None return fn(result, errors) return decorated if was_decorated_without_parenthesis(args): return wrapped(args[0]) return wrapped