使用装饰器时,视图函数映射会覆盖现有的终结点函数

Vin*_*eph 0 python decorator flask python-decorators

我正在使用下面的装饰器来验证应用程序中的端点。

from google.appengine.api import users
from flask import redirect, render_template, request
from google.appengine.ext import ndb


def authenticate_admin(func):
    def authenticate_and_call(*args, **kwargs):
        user = users.get_current_user()
        if user is None:
            return redirect(users.create_login_url(request.url))
        else:
            email = user.email()
            register_user_if_required(email, user)
            if not users.is_current_user_admin():
                return redirect_to_unauthorized(email)
            return func(*args, **kwargs)

    def redirect_to_unauthorized(email):
        return render_template('xxxx/vvvv.html',
                               email=email,
                               users=users)

    return authenticate_and_call


def register_user_if_required(email, user):
Run Code Online (Sandbox Code Playgroud)

我有以下端点,该端点仅允许管理员访问它。

@admin_routes.route('/xxxx')
@authenticate_admin
def xxxxx():
    return render_template('xxxx/xxxxx.html',
                           user=user,
                           logout=users.create_logout_url('/'))
Run Code Online (Sandbox Code Playgroud)

从某种意义上讲,只有管理员才能访问上述端点。但是,当我尝试添加具有相同注释但具有不同花式URL的新端点时,出现错误。这是端点的代码。

@admin_routes.route('/xxxx/bbbbbb')
@authenticate_admin
def abc():
    .....
    return render_template('xxxx/xxxx/zzzzz.html',
                           user=user,
                           breadcrumb=breadcrumb)
Run Code Online (Sandbox Code Playgroud)

这是我运行应用程序时遇到的错误。

Traceback (most recent call last):
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 240, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 299, in _LoadHandler
    handler, path, err = LoadObject(self._handler)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 85, in LoadObject
    obj = __import__(path[0])
  File "/Users/vinay/App-Engine/xxxxx/main.py", line 61, in <module>
    app.register_blueprint(admin_routes)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
    return f(self, *args, **kwargs)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 889, in register_blueprint
    blueprint.register(self, options, first_registration)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 153, in register
    deferred(state)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 172, in <lambda>
    s.add_url_rule(rule, endpoint, view_func, **options))
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 76, in add_url_rule
    view_func, defaults=defaults, **options)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
    return f(self, *args, **kwargs)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 984, in add_url_rule
    'existing endpoint function: %s' % endpoint)
AssertionError: View function mapping is overwriting an existing endpoint function: admin_routes.authenticate_and_call
Traceback (most recent call last):
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 240, in emit
    record.exc_info)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2520, in RunInTransactionCustomRetries
    return RunInTransactionOptions(options, function, *args, **kwargs)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2630, in RunInTransactionOptions
    ok, result = _DoOneTry(function, args, kwargs)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2650, in _DoOneTry
    result = function(*args, **kwargs)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 270, in __EmitTx
    handler=self.__RelativePath(os.environ['PATH_TRANSLATED']))
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/request_environment.py", line 113, in __getitem__
    return self._request.environ[key]
KeyError: 'PATH_TRANSLATED'
Logged from file wsgi.py, line 263
INFO     2015-08-09 03:19:14,731 module.py:812] default: "GET / HTTP/1.1" 500 -
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 6

您需要确保装饰包装器的名称与包装的视图函数的名称相同,否则所有视图都看起来像相同的端点(authenticate_and_call)。

您可以使用@functool.wraps()实用程序执行此操作

from functools import wraps

def authenticate_admin(func):
    @wraps(func)
    def authenticate_and_call(*args, **kwargs):
        user = users.get_current_user()
        if user is None:
            return redirect(users.create_login_url(request.url))
        else:
            email = user.email()
            register_user_if_required(email, user)
            if not users.is_current_user_admin():
                return redirect_to_unauthorized(email)
            return func(*args, **kwargs)

    def redirect_to_unauthorized(email):
        return render_template('Admin/UnauthorizedAdmin.html',
                               email=email,
                               users=users)

    return authenticate_and_call
Run Code Online (Sandbox Code Playgroud)

这样可以确保将元数据(例如函数名)复制funcauthenticate_and_call包装器。从那里@Flask.route()可以选择该名称作为端点名称。