2017-10-19 102 views
2

我目前正在重構一個我兩年前寫過的Flask應用程序,並且我懷疑已經完成了一些並不像使用庫時那樣優雅和乾淨的事情。因此,我會問有關如何在現狀提高了一些建議:Flask中的自定義身份驗證和參數檢查

  1. 該應用程序通過形式/<category>/<endpoint>其中<category>是10個不同類別之一的路線提供了一些API,每個端點的可到達。
  2. 對於每個<category>我創建一個獨特的Python模塊<category>.py,把它放在一個api/子目錄和不同flask.Blueprint(實際上是一個子類物,見下文中的),並註冊與所述主app = Flask(__name__)
  3. 每個模塊<category>.py包含多個函數,用作相應類別的端點,並且每個函數都使用路由裝飾器進行修飾。

到目前爲止好。

每個端點函數都接受許多參數,這些參數可以作爲GET請求的參數傳遞,也可以作爲POST請求的JSON有效內容中的parameter字段的一部分傳遞。因此,在調用相應的端點功能之前,檢查是否提供了正確名稱的正確數量的參數。

此外,應用程序需要檢查是否允許客戶端調用某個功能。爲此,讀取由Web服務器(在我的情況下爲FCGI上的lighttpd)設置的環境變量SSL_CLIENT_CERT,並將其指紋與某些內部權限文件進行比較。

因爲我不知道在哪裏把邏輯做到上面我分類flask.Blueprint和寫我自己的(修改)route修飾器(custom_route)。這個裝飾器現在返回一個自定義的錯誤響應(flask.Response對象)或一個自定義的成功響應(從而使用從客戶端傳遞的參數調用端點函數)。

所以一個模塊category_xy.py看起來是這樣的:

category_xy = CustomBlueprint('category_xy', __name__)  

@category_xy.custom_route('/category_xy/some_endpoint', 
          auth_required=True, 
          methods=['GET', 'POST']) 
def some_endpoint(foo, bar): 
    # do stuff 
    return '123' 

在一個單獨的文件中定義爲(部分pseudoish代碼)CustomBlueprint

from flask import Blueprint, Response, g, request 
from functools import wraps 


class CustomBlueprint(Blueprint): 
    def custom_route(self, rule, **options): 
     def decorator(f): 

      # don't pass the custom parameter 'auth_required' on to 
      # self.route 
      modified_options = dict(options) 
      if 'auth_required' in modified_options: 
       del modified_options['auth_required'] 

      @self.route(rule, **modified_options) 
      @wraps(f) 
      def wrapper(): 
       # do some authentication checks... 
       if not authenticiated(): 
        return Response(...) 

       # check if correct paramters have been passed 
       if not correct_paramters(): 
        return Response(...) 

       # extract parameter values from either GET or POST request 
       arg_values = get_arg_values(request) 

       # call the decorated function with these parameters 
       result = f(*arg_values) 

       return Response(result, ...) 
      return wrapper 
     return decorator 

這工作,但給人的感覺並不乾淨我認爲應該有一個更好更乾淨的方式來做到這一點。把所有這些邏輯放在一個自定義的裝飾器中感覺是錯誤的。

有人對Flask有更多的經驗可以提供一些想法和/或最佳實踐嗎?

+0

我想你的類別有一些共同之處:類似的端點名稱,授權機制,驗證函數。那就對了? –

+0

是的!每個端點功能都會觸發服務器上的某些操作。一個類別內的所有操作都有些相關,並且通常也有類似的端點名稱。 –

回答

0

作爲開始,你可以切換到Pluggable Views - API,可以讓你寫你的視圖功能類:

from flask import Blueprint, Response, render_template 
from flask.views import View 

from models import db, CategoryXY # Let`s have here a database model 

category_blueprint = Blueprint('category_xy', __name__) 

class CategoryXYView(View): 
    def get_list(self): 
     items = db.session.query(CategoryXY).all() 
     return render_template('category_xy_list.html', items=items) 

    def get_one(self, item_id): 
     item = db.session.query(CategoryXY).first() 
     if item is None: 
      return Response(status=404) 
     return render_template('category_xy_edit.html', item=item) 

    def dispatch_request(self, item_id): 
     """Required method which handles incoming requests""" 
     if item_id is None: 
      return self.get_list() 
     return self.get_one(item_id) 

category_xy_view = CategoryXYView.as_view('category_xy') 
category_blueprint.add_url_rule(
    'category_xy', 
    view_func=category_xy_view, 
    defaults={'item_id': None} 
) 
category_blueprint.add_url_rule(
    'category_xy/<item_id>', 
    view_func=category_xy_view 
) 

,當你有基本視圖與通用方法類(數據庫對象的處理變得非常有幫助,驗證,授權檢查)和「類別」類都是從它繼承:

class BaseView(View): 
    model = None 
    edit_template = None 
    list_template = None 

    def get_one(self, item_id): 
     item = db.session.query(self.model).filter_by(id=item_id).first() 
     if item is None: 
      return Response(status=404) 
     return render_template(self.edit_template, item=item) 

    def get_list(self): 
     items = db.session.query(self.model).all() 
     return render_template(self.list_template, items=items) 

    def dispatch_request(self, item_id): 
     if item_id is None: 
      return self.get_list() 
     return self.get_one(item_id) 

class CategoryXYView(BaseView): 
    model = CategoryXY 
    edit_template = 'category_xy_edit.html' 
    list_template = 'category_xy_list.html' 

category_xy_view = CategoryXYView.as_view('category_xy') 
category_blueprint.add_url_rule(
    'category_xy', 
    view_func=category_xy_view, 
    defaults={'item_id': None} 
) 
category_blueprint.add_url_rule(
    'category_xy/<item_id>', 
    view_func=category_xy_view 
) 

如果一個類別已擴展功能,你可以重寫的常用方法之一:

class CategoryXXView(BaseView): 
    model = CategoryXX 
    edit_template = 'category_xx_edit.html' 
    list_template = 'category_xx_list.html' 

    def get_one(self, item_id): 
     item = db.session.query(self.model).first() 
     if item is None: 
      return Response(status=404) 
     xy_items = db.session.query(CategoryXY).all() 
     return render_template(self.edit_template, item=item, xy_items=xy_items) 

爲了使路由生成自動化,您可以編寫一個BaseView的元類。每個新類的路由將在類定義後生成:

class ViewMeta(type): 
    def __new__(mcs, name, bases, attributes): 
     cls = type(name, bases, attributes) 

     if cls.route is not None: 
      view_function = cls.as_view(cls.route) 
      category_blueprint.add_url_rule(
       cls.route, 
       view_func=view_function, 
       defaults={'item_id': None} 
      ) 
      category_blueprint.add_url_rule(
       '{}/<item_id>'.format(cls.route), 
       view_func=view_function 
      ) 
     return cls 

class BaseView(View): 
    __metaclass__ = ViewMeta 
    route = None 
    model = None 
    # Other common attributes 

class CategoryXYView(BaseView): 
    route = 'category_xy' 
    model = CategoryXY