如何使 Flask App 变得更加 DRY(连接多个 oauth API)

32 阅读2分钟

如何使用 Flask-oauthlib 连接多个 API(例如 Twitter、GitHub 等)?目前,我将每一项服务作为单独的蓝图。在每个服务的视图文件中,有三个相同的视图:登录、授权和获取令牌。现在的代码并不是很 DRY,我难以理解如何集中处理这些视图(更多是概念上的)。

如何才能使代码更加 DRY?我希望更多地从概念上理解,而不是让我只写代码。

下面列出了一些可能有助于理解这个问题的信息。这是应用程序的结构:

  • App
    • Services
      • FourSquare BP
      • GitHub BP
      • Twitter BP
      • ...
    • Other BPs

通用 API 视图可能会放在 Services/api_views.py 中

以下是 API 蓝图视图文件之一(Twitter)的示例。

twitter = Blueprint('twitter', __name__, url_prefix='/twitter')
bp = twitter

bp.api = TwitterAPI()
bp.oauth = bp.api.oauth_app

# 下面是每个文件中确切相同的内容。

@bp.route('/')
@login_required
def login():
    if current_user.get(bp.name, None):
        return redirect(url_for('frontend.index'))
    return bp.oauth.authorize(callback=url_for('.authorized', _external=True))

@bp.route('/authorized')
@bp.oauth.authorized_handler
def authorized(resp):
    if resp is None:
        flash(u'You denied the request to sign in.')
        return redirect(url_for('frontend.index'))

    if bp.oauth_type == 'oauth2':
        resp['access_token'] = (resp['access_token'], '')

    current_user[bp.name] = resp
    current_user.save()

    flash('You were signed in to %s' % bp.name.capitalize())
    return redirect(url_for('frontend.index'))

@bp.oauth.tokengetter
def get_token(token=None):
    if bp.oauth_type == 'oauth2':
        return current_user.get(bp.name, None)['access_token']
    return current_user.get(bp.name, None)['oauth_token']

我尝试将它们一起放在一个类中,然后导入它们,但遇到各种装饰器的问题(oauth 装饰器出现问题最多)。

2、解决方案

我想出了一种看起来像上述问题的一个好解决方案。请告诉我这是否真的是一个好解决方案。

我决定使用 Flask 类视图。这几乎让我为上述每个主要 API 函数创建了一个类视图:

class APILoginView(View):
    decorators = [login_required]

    def __init__(self, blueprint):
        self.blueprint = blueprint

    def dispatch_request(self):
        if current_user.get(self.blueprint.name, None):
            return redirect(url_for('frontend.index'))
        return self.blueprint.oauth.authorize(callback=url_for('.authorized', _external=True))

class APIAuthorizedView(View):
    decorators = [login_required]

    def __init__(self, blueprint):
        self.blueprint = blueprint

    def dispatch_request(self, resp):
        if resp is None:
            flash(u'You denied the request to sign in.')
            return redirect(url_for('frontend.index'))

        if self.blueprint.api.oauth_type == 'oauth2':
            resp['access_token'] = (resp['access_token'], '') #need to make it a tuple for oauth2 requests

        current_user[self.blueprint.name] = resp
        current_user.save()

        flash('You were signed in to %s' % self.blueprint.name.capitalize())
        return redirect(url_for('frontend.index'))

这是简单的一步。最棘手的事情是弄清楚如何概括 Flask-OAuthLib 库所需的 tokengetter。以下是我的最终结果:

class APIToken():
    def __init__(self, blueprint):
        self.blueprint = blueprint

    def get_token(self, token=None):
        if self.blueprint.api.oauth_type == 'oauth2':
            return current_user.get(self.blueprint.name, None)['access_token']
        return current_user.get(self.blueprint.name, None)['oauth_token']

最后,创建一个函数在蓝图上注册这些视图:

def registerAPIViews(blueprint):
    login_view = APILoginView.as_view('login', blueprint=blueprint)
    auth_view = blueprint.oauth.authorized_handler(
                        APIAuthorizedView.as_view('authorized', blueprint=blueprint))

    blueprint.add_url_rule('/', view_func=login_view)
    blueprint.add_url_rule('/authorized', view_func=auth_view)

    apiToken = APIToken(blueprint)
    token_getter = blueprint.oauth.tokengetter(apiToken.get_token)

    return blueprint