Пример #1
0
def create_blueprint(name, import_name, **kwargs):
    ''' Provides basic blueprint creation for all API handlers.
    '''
    url_prefix = kwargs.pop('url_prefix', '')
    bp = Blueprint(name, import_name, url_prefix=url_prefix)

    # Set App level before-request middleware.
    bp.before_app_first_request(lambda: None)

    # Set global before-request
    bp.before_app_request(lambda: None)

    return bp
Пример #2
0
class RestApi(object):
    def __init__(self, name, import_name, **kw):
        self.bp = Blueprint(name, import_name, **kw)

    def route(self, url, **options):
        """ 通过装饰器注册路由
        以下是例子:
            api = RestApi('home', __name__)
            @api.route('/')
            @api.route('/home, endpoint='home')
            class HomeApi(RestView):
                def get(self):
                    return self.ok()
        也可以限制每个路由接受的方法
            @api.route('/', methods=['GET'])
            @api.route('/create, methods=['POST'])
            class EmployeeCreateApi(RestView):
                def get(self):
                    return self.ok()
                def post(self):
                    name = request.form.get('name')
                    ...
                    return self.ok()
        """
        def decorator(cls):
            endpoint = options.pop("endpoint", None) or cls.__name__
            methods = options.pop("methods", None) or cls.methods
            self.bp.add_url_rule(url,
                                 view_func=cls.as_view(endpoint),
                                 methods=methods)
            return cls

        return decorator

    def before_app_request(self, func):
        return self.bp.before_app_request(func)
Пример #3
0
        if error is None:
            session.clear()
            session['user_id'] = user.uid
            session['user_name'] = user.username

            res = {"user_id": user.uid, "name": user.username}

            return jsonify(Const.errcode('0', res=res))

        # flash(error)

    return abort(404)


'''
bp.before_app_request() 注册一个 在视图函数之前运行的函数,不论其 URL 是什么。 
load_logged_in_user 检查用户 id 是否已经储存在 session 中,并从数据库中获取用户数据,
然后储存在 g.user 中。 g.user 的持续时间比请求要长。 如果没有用户 id ,或者 id 不存在,
那么 g.user 将会是 None 。
'''


@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = odb.query_per(User, 'uid', user_id)
Пример #4
0
# -*- coding: UTF-8 -*-
__author__ = 'hunter'
from flask import Blueprint

from app.util.response_util import code_handle
from app.util.login_util import login

api = Blueprint('api', __name__)

api.after_app_request(code_handle)
api.before_app_request(login)

from . import views, errors
Пример #5
0
def create_blueprint(import_name, blogging_engine):

    blog_app = Blueprint("blogging", import_name, template_folder='templates')

    # register index
    index_func = cached_func(blogging_engine, index)
    blog_app.add_url_rule("/", defaults={"count": None, "page": 1},
                          view_func=index_func)
    blog_app.add_url_rule("/<int:count>/", defaults={"page": 1},
                          view_func=index_func)
    blog_app.add_url_rule("/<int:count>/<int:page>/", view_func=index_func)

    # register page_by_id
    page_by_id_func = cached_func(blogging_engine, page_by_id)
    blog_app.add_url_rule("/page/<int:post_id>/", defaults={"slug": ""},
                          view_func=page_by_id_func)
    blog_app.add_url_rule("/page/<int:post_id>/<slug>/",
                          view_func=page_by_id_func)

    # register posts_by_tag
    posts_by_tag_func = cached_func(blogging_engine, posts_by_tag)
    blog_app.add_url_rule("/tag/<tag>/", defaults=dict(count=None, page=1),
                          view_func=posts_by_tag_func)
    blog_app.add_url_rule("/tag/<tag>/<int:count>/", defaults=dict(page=1),
                          view_func=posts_by_tag_func)
    blog_app.add_url_rule("/tag/<tag>/<int:count>/<int:page>/",
                          view_func=posts_by_tag_func)

    # register posts_by_author
    posts_by_author_func = cached_func(blogging_engine, posts_by_author)
    blog_app.add_url_rule("/author/<user_id>/",
                          defaults=dict(count=None, page=1),
                          view_func=posts_by_author_func)
    blog_app.add_url_rule("/author/<user_id>/<int:count>/",
                          defaults=dict(page=1),
                          view_func=posts_by_author_func)
    blog_app.add_url_rule("/author/<user_id>/<int:count>/<int:page>/",
                          view_func=posts_by_author_func)

    # register editor
    editor_func = editor  # For now lets not cache this
    blog_app.add_url_rule('/editor/', methods=["GET", "POST"],
                          defaults={"post_id": None},
                          view_func=editor_func)
    blog_app.add_url_rule('/editor/<int:post_id>/', methods=["GET", "POST"],
                          view_func=editor_func)

    # register delete
    delete_func = delete  # For now lets not cache this
    blog_app.add_url_rule("/delete/<int:post_id>/", methods=["POST"],
                          view_func=delete_func)

    # register sitemap
    sitemap_func = cached_func(blogging_engine, sitemap)
    blog_app.add_url_rule("/sitemap.xml", view_func=sitemap_func)

    # register feed
    feed_func = cached_func(blogging_engine, feed)
    blog_app.add_url_rule('/feeds/all.atom.xml', view_func=feed_func)

    #register before app request
    # to set the language for sqlalchemy plugin
    blog_app.before_app_request(set_locale)

    return blog_app
Пример #6
0
            and the browser then sends it back with subsequent requests.
            
            Now that the user's id is stored in the session, it will be available
            on subsequent requests. At the beginning of each request, if a user is
            logged in, 
            their information should be loaded and made available to other views.
            """
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('main_index'))

        flash(error)
    return render_template('auth/login.html')


"""
bp.before_app_request() registers a function that runs before the view function,
no matter what URL is requested. load_logged_in_user checks if a user id is
stored in the session and gets that user's data from the database, storing it on
g.user, which lasts for the length of the request. If there is no user id, or if
the id doesn't exist, g.user will be None.
"""


@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
Пример #7
0
import flask_restplus
from flask import Blueprint
from .. import OrganizationsManager

orgs_blueprint = Blueprint('organizations', __name__)
api = flask_restplus.Api(app=orgs_blueprint, doc='/docs')


def get_orgs_config():
    return OrganizationsManager.instance.orgs_config


from . import environ
orgs_blueprint.before_app_request(environ.push_environ_to_g)


from . import rest
Пример #8
0
class Dockerflow(object):
    """
    The Dockerflow Flask extension. Set it up like this:

    .. code-block:: python
       :caption: ``myproject.py``

       from flask import Flask
       from dockerflow.flask import Dockerflow

       app = Flask(__name__)
       dockerflow = Dockerflow(app)

    Or if you use the Flask application factory pattern, in
    an own module set up Dockerflow first:

    .. code-block:: python
       :caption: ``myproject/deployment.py``

       from dockerflow.flask import Dockerflow

       dockerflow = Dockerflow()

    and then import and initialize it with the Flask application
    object when you create the application:

    .. code-block:: python
       :caption: ``myproject/app.py``

       def create_app(config_filename):
           app = Flask(__name__)
           app.config.from_pyfile(config_filename)

           from myproject.deployment import dockerflow
           dockerflow.init_app(app)

           from myproject.views.admin import admin
           from myproject.views.frontend import frontend
           app.register_blueprint(admin)
           app.register_blueprint(frontend)

            return app

    See the parameters for a more detailed list of optional features when
    initializing the extension.

    :param app: The Flask app that this Dockerflow extension should be
                initialized with.
    :type root: ~flask.Flask or None

    :param db: A Flask-SQLAlchemy extension instance to be used by the
               built-in Dockerflow check for the database connection.
    :param redis: A Redis connection to be used by the built-in Dockerflow
                  check for the Redis connection.
    :param migrate: A Flask-Migrate extension instance to be used by the
                    built-in Dockerflow check for Alembic migrations.
    :param silenced_checks: Dockerflow check IDs to ignore when running
                            through the list of configured checks.
    :type silenced_checks: list

    :param version_path: The filesystem path where the ``version.json`` can
                         be found. Defaults to the parent directory of the
                         Flask app's root path.
    """
    def __init__(self,
                 app=None,
                 db=None,
                 redis=None,
                 migrate=None,
                 silenced_checks=None,
                 version_path=None,
                 *args,
                 **kwargs):
        # The Flask blueprint to add the Dockerflow signal callbacks and views
        self._blueprint = Blueprint('dockerflow', 'dockerflow.flask.app')

        # The Dockerflow specific logger to be used by internals of this
        # extension.
        self.logger = logging.getLogger('dockerflow.flask')
        self.logger.addHandler(logging.NullHandler())
        self.logger.setLevel(logging.INFO)

        # The request summary logger to be used by this extension
        # without pre-configuration. See docs for how to set it up.
        self.summary_logger = logging.getLogger('request.summary')

        # An ordered dictionary for storing custom Dockerflow checks in.
        self.checks = OrderedDict()

        # A list of IDs of custom Dockerflow checks to ignore in case they
        # show up.
        self.silenced_checks = silenced_checks or []

        # The path where to find the version JSON file. Defaults to the
        # parent directory of the app root path.
        self.version_path = version_path
        self._version_callback = version.get_version

        # Initialize the app if given.
        if app:
            self.init_app(app)
        # Initialize the built-in checks.
        if db:
            self.init_check(checks.check_database_connected, db)
        if redis:
            self.init_check(checks.check_redis_connected, redis)
        if migrate:
            self.init_check(checks.check_migrations_applied, migrate)

    def init_check(self, check, obj):
        """
        Adds a given check callback with the provided object to the list
        of checks. Useful for built-ins but also advanced custom checks.
        """
        self.logger.info('Adding extension check %s' % check.__name__)
        check = functools.wraps(check)(functools.partial(check, obj))
        self.check(func=check)

    def init_app(self, app):
        """
        Initializes the extension with the given app, registers the
        built-in views with an own blueprint and hooks up our signal
        callbacks.
        """
        # If no version path was provided in the init of the Dockerflow
        # class we'll use the parent directory of the app root path.
        if self.version_path is None:
            self.version_path = os.path.dirname(app.root_path)

        for view in (
            ('/__version__', 'version', self._version_view),
            ('/__heartbeat__', 'heartbeat', self._heartbeat_view),
            ('/__lbheartbeat__', 'lbheartbeat', self._lbheartbeat_view),
        ):
            self._blueprint.add_url_rule(*view)
        self._blueprint.before_app_request(self._before_request)
        self._blueprint.after_app_request(self._after_request)
        self._blueprint.app_errorhandler(HeartbeatFailure)(
            self._heartbeat_exception_handler)
        app.register_blueprint(self._blueprint)
        got_request_exception.connect(self._got_request_exception, sender=app)

        if not hasattr(app, 'extensions'):  # pragma: nocover
            app.extensions = {}
        app.extensions['dockerflow'] = self

    def _heartbeat_exception_handler(self, error):
        """
        An exception handler to act as a middleman to return
        a heartbeat view response with a 500 error code.
        """
        return error.get_response()

    def _before_request(self):
        """
        The before_request callback.
        """
        g._request_id = str(uuid.uuid4())
        g._start_timestamp = time.time()

    def _after_request(self, response):
        """
        The signal handler for the request_finished signal.
        """
        if not getattr(g, '_has_exception', False):
            extra = self.summary_extra()
            self.summary_logger.info('', extra=extra)
        return response

    def _got_request_exception(self, sender, exception, **extra):
        """
        The signal handler for the got_request_exception signal.
        """
        extra = self.summary_extra()
        extra['errno'] = 500
        self.summary_logger.error(str(exception), extra=extra)
        g._has_exception = True

    def user_id(self):
        """
        Return the ID of the current request's user
        """
        # This needs flask-login to be installed
        if not has_flask_login:
            return

        # and the actual login manager installed
        if not hasattr(current_app, 'login_manager'):
            return

        # fail if no current_user was attached to the request context
        try:
            is_authenticated = current_user.is_authenticated
        except AttributeError:
            return

        # because is_authenticated could be a callable, call it
        if callable(is_authenticated):
            is_authenticated = is_authenticated()

        # and fail if the user isn't authenticated
        if not is_authenticated:
            return

        # finally return the user id
        try:
            return current_user.get_id()
        except UserLoadingError:
            # but don't fail if for some reason getting the user id
            # created an exception to not accidently make exception
            # handling worse. If sqlalchemy is used that catches
            # all SQLAlchemyError exceptions.
            pass

    def summary_extra(self):
        """
        Build the extra data for the summary logger.
        """
        out = {
            'errno': 0,
            'agent': request.headers.get('User-Agent', ''),
            'lang': request.headers.get('Accept-Language', ''),
            'method': request.method,
            'path': request.path,
        }

        # set the uid value to the current user ID
        user_id = self.user_id()
        if user_id is None:
            user_id = ''
        out['uid'] = user_id

        # the rid value to the current request ID
        request_id = g.get('_request_id', None)
        if request_id is not None:
            out['rid'] = request_id

        # and the t value to the time it took to render
        start_timestamp = g.get('_start_timestamp', None)
        if start_timestamp is not None:
            # Duration of request, in milliseconds.
            out['t'] = int(1000 * (time.time() - start_timestamp))

        return out

    def _version_view(self):
        """
        View that returns the contents of version.json or a 404.
        """
        version_json = self._version_callback(self.version_path)
        if version_json is None:
            return 'version.json not found', 404
        else:
            return jsonify(version_json)

    def _lbheartbeat_view(self):
        """
        Lets the load balancer know the application is running and available.
        Must return 200 (not 204) for ELB
        http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-healthchecks.html
        """
        return '', 200

    def _heartbeat_check_detail(self, check):
        errors = list(
            filter(lambda e: e.id not in self.silenced_checks, check()))
        level = max([0] + [e.level for e in errors])

        return {
            'status': checks.level_to_text(level),
            'level': level,
            'messages': {e.id: e.msg
                         for e in errors},
        }

    def _heartbeat_view(self):
        """
        Runs all the registered checks and returns a JSON response with either
        a status code of 200 or 500 depending on the results of the checks.

        Any check that returns a warning or worse (error, critical) will
        return a 500 response.
        """
        details = {}
        statuses = {}
        level = 0

        for name, check in self.checks.items():
            detail = self._heartbeat_check_detail(check)
            statuses[name] = detail['status']
            level = max(level, detail['level'])
            if detail['level'] > 0:
                details[name] = detail

        payload = {
            'status': checks.level_to_text(level),
            'checks': statuses,
            'details': details,
        }

        def render(status_code):
            return make_response(jsonify(payload), status_code)

        if level < checks.WARNING:
            status_code = 200
            heartbeat_passed.send(self, level=level)
            return render(status_code)
        else:
            status_code = 500
            heartbeat_failed.send(self, level=level)
            raise HeartbeatFailure(response=render(status_code))

    def version_callback(self, func):
        """
        A decorator to optionally register a new Dockerflow version callback
        and use that instead of the default of
        :func:`dockerflow.version.get_version`.

        The callback will be passed the value of the
        ``version_path`` parameter to the Dockerflow extension object,
        which defaults to the parent directory of the Flask app's root path.

        The callback should return a dictionary with the
        version information as defined in the Dockerflow spec,
        or None if no version information could be loaded.

        E.g.::

            app = Flask(__name__)
            dockerflow = Dockerflow(app)

            @dockerflow.version_callback
            def my_version(root):
                return json.loads(os.path.join(root, 'acme_version.json'))

        """
        self._version_callback = func

    def check(self, func=None, name=None):
        """
        A decorator to register a new Dockerflow check to be run
        when the /__heartbeat__ endpoint is called., e.g.::

            from dockerflow.flask import checks

            @dockerflow.check
            def storage_reachable():
                try:
                    acme.storage.ping()
                except SlowConnectionException as exc:
                    return [checks.Warning(exc.msg, id='acme.health.0002')]
                except StorageException as exc:
                    return [checks.Error(exc.msg, id='acme.health.0001')]

        or using a custom name::

            @dockerflow.check(name='acme-storage-check)
            def storage_reachable():
                # ...

        """
        if func is None:
            return functools.partial(self.check, name=name)

        if name is None:
            name = func.__name__

        self.logger.info('Registered Dockerflow check %s', name)

        @functools.wraps(func)
        def decorated_function(*args, **kwargs):
            self.logger.info('Called Dockerflow check %s', name)
            return func(*args, **kwargs)

        self.checks[name] = decorated_function
        return decorated_function
Пример #9
0
def create_blueprint(endpoints):
    """Create Invenio-Records-REST blueprint.

    :params endpoints: Dictionary representing the endpoints configuration.
    :returns: Configured blueprint.
    """
    endpoints = endpoints or {}

    blueprint = Blueprint(
        'reroils_record_editor',
        __name__,
        template_folder='templates',
        static_folder='static',
        url_prefix='/editor',
    )
    rec_types = endpoints.keys()
    blueprint.add_app_template_filter(can_edit)
    blueprint.add_app_template_filter(jsondumps)
    blueprint.register_error_handler(PermissionDenied, permission_denied_page)
    menu_func = partial(init_menu, endpoints=endpoints)
    menu_func.__module__ = init_menu.__module__
    menu_func.__name__ = init_menu.__name__
    blueprint.before_app_request(menu_func)
    for rec_type in rec_types:
        # search view
        if endpoints.get(rec_type, {}).get('api'):
            search_func = partial(search,
                                  record_type=rec_type,
                                  endpoints=endpoints)
            search_func.__module__ = search.__module__
            search_func.__name__ = search.__name__
            blueprint.add_url_rule('/search/%s' % rec_type,
                                   endpoint='search_%s' % rec_type,
                                   view_func=search_func)
        # create view
        create_func = partial(create,
                              record_type=rec_type,
                              endpoints=endpoints)
        create_func.__module__ = create.__module__
        create_func.__name__ = create.__name__
        blueprint.add_url_rule('/create/%s' % rec_type,
                               endpoint='create_%s' % rec_type,
                               view_func=create_func)
        # update view
        update_func = partial(update,
                              record_type=rec_type,
                              endpoints=endpoints)
        update_func.__module__ = update.__module__
        update_func.__name__ = update.__name__
        blueprint.add_url_rule('/update/%s/<int:pid>' % rec_type,
                               endpoint='update_%s' % rec_type,
                               view_func=update_func)
        # delete view
        delete_func = partial(delete,
                              record_type=rec_type,
                              endpoints=endpoints)
        delete_func.__module__ = delete.__module__
        delete_func.__name__ = delete.__name__
        blueprint.add_url_rule('/delete/%s/<int:pid>' % rec_type,
                               endpoint='delete_%s' % rec_type,
                               view_func=delete_func)
        # save api
        save_func = partial(save, record_type=rec_type, endpoints=endpoints)
        save_func.__module__ = save.__module__
        save_func.__name__ = save.__name__
        blueprint.add_url_rule('/save/%s' % rec_type,
                               endpoint='save_%s' % rec_type,
                               view_func=save_func,
                               methods=['POST'])
    return blueprint
Пример #10
0
from flask import Blueprint

from fibonacci.fib.log_request import log_request

fib = Blueprint('fib', __name__)
fib.before_app_request(log_request)

from . import views
Пример #11
0
from flask import Blueprint, g, session
from .article_api_bp import routes as article_routes
from .session_api_bp import routes as session_routes
from .user_api_bp import load_logged_in_user, routes as user_routes

bp = Blueprint('api', __name__)

routes = (article_routes + session_routes + user_routes)


def tear_down_function(e=None):
    # print('teardown!!!!!!')
    g.db.session.close()
    close_db()


for r in routes:
    bp.add_url_rule(r['rule'],
                    r.get('endpoint', None),
                    view_func=r['view_func'],
                    **r.get('options', {}))
bp.before_app_request(load_logged_in_user)
# bp.teardown_app_request(tear_down_function)
Пример #12
0
blueprint = Blueprint('middleware', __name__)


# So here we can do something before every request or after. Depending on our needs.
# E.g. we can authenticate every request here.
class Middleware:
    def __init__(self):
        self.logger = logging.getLogger(__name__)

    @staticmethod
    @blueprint.before_app_request
    def do_something_before_a_request():
        logger = logging.getLogger(__name__)
        auth = request.headers.get('Authorization')

        if auth == 'no_valid_user':
            logger.info('User not authorized')
            return Response(json.dumps({'message': 'Unauthorized'}),
                            status=401,
                            mimetype='application/json')

    def do_something_else_before_a_request(self):
        auth = request.headers.get('Authorization')

        self.logger.info(f'Got auth {auth}')


# Almost the same here as in the handlers.
middleware = Middleware()
blueprint.before_app_request(middleware.do_something_else_before_a_request)
Пример #13
0
main_static_folder = base_dir + '/app/static'
main_template_folder = base_dir + '/app/templates'
app_file_template_folder = base_dir + '/AppFiles'

main = Blueprint('main',
                 __name__,
                 static_folder=main_static_folder,
                 template_folder=main_template_folder)
auth = Blueprint('auth', __name__)
app_file = Blueprint('app_file',
                     __name__,
                     static_folder=app_file_template_folder)

# main.before_app_first_request(before_app_first_request)
main.before_app_request(before_app_request)
main.before_request(before_request)


@main.after_request
def after_request(response):
    print('*' * 20 + who_am_i() + '    start' + '*' * 20)
    print(request)
    print(response)
    print(response.status)
    print(response.headers)
    print(response.get_data())
    print('*' * 20 + who_am_i() + '    end' + '*' * 20)
    return response

Пример #14
0
# -*- coding:utf-8 -*-
# *************************************************************************
#   Copyright © 2016 Godinsec. All rights reserved.
#   File Name: urls.py
#   Author: Allan
#   Mail: [email protected]
#   Created Time: 16/9/21
# # *************************************************************************
from flask import Blueprint
from .views import get_captcha, login, logout, change_password, unconfirmed,\
    before_request, confirm, resend_confirmation, forbidden_page, active_request

auth = Blueprint('auth',
                 __name__,
                 static_folder='static',
                 template_folder='templates')

auth.before_app_request(before_request)

auth.add_url_rule('/captcha', view_func=get_captcha)
auth.add_url_rule('/login', view_func=login, methods=['GET', 'POST'])
auth.add_url_rule('/change_password',
                  view_func=change_password,
                  methods=['GET', 'POST'])
auth.add_url_rule('/logout', view_func=logout)
auth.add_url_rule('/confirm/<token>', view_func=confirm)
auth.add_url_rule('/confirm', view_func=resend_confirmation)
auth.add_url_rule('/unconfirmed', view_func=unconfirmed)
auth.add_url_rule('/forbidden/<int:req_active>', view_func=forbidden_page)
auth.add_url_rule('/active', view_func=active_request)
Пример #15
0
def create_blueprint(endpoints):
    """Create Invenio-Records-REST blueprint.

    :params endpoints: Dictionary representing the endpoints configuration.
    :returns: Configured blueprint.
    """
    endpoints = endpoints or {}

    blueprint = Blueprint(
        'reroils_record_editor',
        __name__,
        template_folder='templates',
        static_folder='static',
        url_prefix='/editor',
    )
    rec_types = endpoints.keys()
    blueprint.add_app_template_filter(can_edit)
    blueprint.add_app_template_filter(jsondumps)
    blueprint.register_error_handler(PermissionDenied,
                                     permission_denied_page)
    menu_func = partial(init_menu, endpoints=endpoints)
    menu_func.__module__ = init_menu.__module__
    menu_func.__name__ = init_menu.__name__
    blueprint.before_app_request(menu_func)
    for rec_type in rec_types:
        # search view
        if endpoints.get(rec_type, {}).get('api'):
            search_func = partial(search, record_type=rec_type,
                                  endpoints=endpoints)
            search_func.__module__ = search.__module__
            search_func.__name__ = search.__name__
            blueprint.add_url_rule('/search/%s' % rec_type,
                                   endpoint='search_%s' % rec_type,
                                   view_func=search_func)
        # create view
        create_func = partial(create, record_type=rec_type,
                              endpoints=endpoints)
        create_func.__module__ = create.__module__
        create_func.__name__ = create.__name__
        blueprint.add_url_rule('/create/%s' % rec_type,
                               endpoint='create_%s' % rec_type,
                               view_func=create_func)
        # update view
        update_func = partial(update, record_type=rec_type,
                              endpoints=endpoints)
        update_func.__module__ = update.__module__
        update_func.__name__ = update.__name__
        blueprint.add_url_rule('/update/%s/<int:pid>' % rec_type,
                               endpoint='update_%s' % rec_type,
                               view_func=update_func)
        # delete view
        delete_func = partial(delete, record_type=rec_type,
                              endpoints=endpoints)
        delete_func.__module__ = delete.__module__
        delete_func.__name__ = delete.__name__
        blueprint.add_url_rule('/delete/%s/<int:pid>' % rec_type,
                               endpoint='delete_%s' % rec_type,
                               view_func=delete_func)
        # save api
        save_func = partial(save, record_type=rec_type, endpoints=endpoints)
        save_func.__module__ = save.__module__
        save_func.__name__ = save.__name__
        blueprint.add_url_rule('/save/%s' % rec_type,
                               endpoint='save_%s' % rec_type,
                               view_func=save_func, methods=['POST'])
    return blueprint