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):
            view_kwargs = _convert_models(view_kwargs, decorator_kwargs)
            view_kwargs = _convert_query_params(view_kwargs, decorator_kwargs)
            return fn(*view_args, **view_kwargs)
        return decorated

    if decorator_args and callable(decorator_args[0]):
        return wrapped(decorator_args[0])
    return wrapped
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')