Flask先後端分離項目案例

簡介

學習慕課課程,Flask先後端分離API後臺接口的實現demo,前端能夠接入小程序,暫時已經完成後臺API基礎架構,使用postman調試. githtml

重構部分:前端

  1. token校驗模塊
  2. auths認證模塊
  3. scope權限模塊,增長全局掃描器(參考flask HTTPExceptions模塊)

收穫

  1. 咱們能夠接受定義時的複雜,但不能接受調用時的複雜
  2. 若是你以爲寫代碼厭倦,無聊,那你只是停留在功能的實現上,功能的實現很簡單,你要追求的是更好的寫法,抽象的藝術,不是機械的勞動而是要創造,要有本身的思考
  3. Sqlalchemy中對類的建立都是用元類的方式,因此調用的時候都不用實例化,當咱們重寫__init__方法是須要調用orm.reconstrcut裝飾器,纔會執行實例化對象的構造函數
  4. 權限等級模塊的設計(api訪問權限),如超級管理員,管理員,普通用戶,訪客,這四者之間的關係,有包含的關係,因此能夠考慮合併也能夠考慮排除的方式來構建權限控制模塊. 參考本項目中的app.libs.scope
  5. 學的是解決問題的方法,首先要有深度,在去考慮廣度,還要懂得遷移應用,造成本身的思惟模型。

推薦閱讀:
工做中如何作好技術積累
沒有技術深度的苦惱python

知識點覆盤

初始化flask應用程序

app = Flask(__name__, static_folder='views/statics', static_url_path='/static', template_folder="templates")

建立Flask應用程序實例對象, 若是模塊存在,會根據模塊所在的目錄去尋找靜態文件和模塊文件, 若是模塊不存在,會默認使用app對象所在的項目目錄git

  • __name__ 表示以此模塊所在的目錄做爲工做目錄,就是靜態文等從這個目錄下去找
  • static_folder 指定靜態文件存放相對路徑 flask默認會用/進行分割而後取最後一個做爲訪問url 相似Django中的STATICFILES_DIRS
  • static_url_path 指定訪問靜態文件的url地址前綴, 相似Django 中的 STATIC_URL
  • template_folder 指定模板文件的目錄
@property
    def static_url_path(self):
        """The URL prefix that the static route will be accessible from.

        If it was not configured during init, it is derived from
        :attr:`static_folder`.
        """
        if self._static_url_path is not None:
            return self._static_url_path

        if self.static_folder is not None:
            basename = os.path.basename(self.static_folder)
            return ("/" + basename).rstrip("/")

    @static_url_path.setter
    def static_url_path(self, value):
        if value is not None:
            value = value.rstrip("/")

        self._static_url_path = value

Flask 中url相關底層類

  • BaseConverter子類:保存提取url參數匹配規則
  • Rule類:記錄一個url和一個視圖函數的對應關係
  • Map類:記錄全部url地址和試圖函數對應的關係 Map(Rule, Rule, ....)
  • MapAdapter類:執行url匹配的過程,其中有一個match方法,Rule.match(path, method)

自定義路由管理器

from flask import Flask

app = Flask(__name__)

from werkzeug.routing import BaseConverter

class RegexUrl(BaseConverter):
    # 指定匹配參數時的正則表達式
    # 如: # regex = '\d{6}'
    def __init__(self, url_map, regex):
        """
        :param url_map: flask會自動傳遞該參數
        :param regex: 自定義的匹配規則
        """
        super(RegexUrl, self).__init__(url_map)
        self.regex = regex
    
    # 在對應的試圖函數以前調用
    # 從url中提取出參數以後,會先調用to_python
    # 會把提取出的值做爲參數傳遞給to_pthon在返回給對應的試圖
    def to_python(self, value):
        """能夠在這裏作一些參數的類型轉換"""
        return value
    
    # 調用url_for時會被調用, 用來處理url反向解析時url參數處理
	# 返回值用來拼接url
    def to_url(self, value):
        """對接收到參數作一些過濾等"""
        return value
        
# 將自定義路由轉換器類添加到轉換器字典中
app.url_map.converters['re'] = RegexUrl


# 案例
@app.route('/user/<re("[a-z]{3}"):id>')
def hello(id):
    return f'hello {id}'


if __name__ == '__main__':
    app.run(debug=True)

全局異常捕獲

AOP編程思想,面向切面編程,把事件統一在一個地方處理,在一個統一的出口作處理github

errorhandler 在flask 1.0版本以前只支持填寫對應的錯誤碼,好比 @app.errorhandler(404)正則表達式

在flask1.0版本以後就支持全局的異常捕獲了@app.errorhandler(code_or_exception),有了這個以後,就能夠在全局作一個異常捕獲了,不用每一個視圖函數都作異常捕獲。編程

@app.errorhandler(Exception)
def framework_error(e):
    if isinstance(e, APIException):
        return e
    elif isinstance(e, HTTPException):
        code = e.code
        msg = e.description
        error_code = 1007
        return APIException(msg, code, error_code)

    else:
        if not current_app.config['DEBUG']:
            return ServerError()
        else:
            raise e

異常類型

  • 可預知的異常(已知異常)
  • 徹底沒有意識的異常(未知異常)

abort函數

  • abort(狀態碼) 是一個默認的拋出異常的方法
  • 調用abort函數能夠拋出一個指定狀態碼對應的異常信息
  • abort函數會當即終止當前視圖函數的運行**

模型對象的序列化

場景:咱們有時候可能須要返回模型對象中的某些字段,或者所有字段,平時的作法就是將對象中的各個字段轉爲字典在返回jsonnify(data), 可是這樣的寫法可能在每一個須要返回數據的試圖函數中都寫一個對應的字典。。對象轉字典在返回。json默認是不能序列化對象的,通常咱們的作法是 json.dumps(obj, default=lambda o: o.__dict__)可是 __dict__中只保存實例屬性,咱們的模型類基本定義的類屬性。解決這個問題就要看jsonify中是如何作序列化的,而後怎麼重寫。json

  1. 重寫JSONEncoder
from datetime import date
from flask import Flask as _Flask
from flask.json import JSONEncoder as _JSONEncoder

class JSONEncoder(_JSONEncoder):
    """
    重寫json序列化,使得模型類的可序列化
    """
    def default(self, o):
        if hasattr(o, 'keys') and hasattr(o, '__getitem__'):
            return dict(o)
        if isinstance(o, date):
            return o.strftime('%Y-%m-%d')
        
   		super(JSONEncoder, self).default(o)
        

# 須要將重寫的類綁定到應用程序中
class Flask(_Flask):
    json_encoder = JSONEncoder
  1. 模型類的定義
class User(Base):
    id = Column(Integer, primary_key=True)
    email = Column(String(24), unique=True, nullable=False)
    nickname = Column(String(24), unique=True)
    auth = Column(SmallInteger, default=1)
    _password = Column('password', String(100))
    
    def keys(self):
        return ['id', 'email', 'nickname', 'auth']
    
    def __getitem__(self, item):
        return getattr(self, item)

注意: 修改了json_encode方法後,只要調用到flask.json 模塊的都會走這個方法flask

爲何要寫keys__getitem__方法小程序

當咱們使用dict(object) 操做一個對象的時候,dict首先會到實例中找keys的方法,將其返回列表的值做爲key, 而後會根據object[key] 獲取對應的值,因此實例要實現__getitem__方法纔可使用中括號的方式調用屬性

進階寫法 - 控制返回的字段

場景:當咱們有一個Book的模型類,咱們的api接口可能須要返回book的詳情頁因此就要返回全部字典,但另一個接口可能只須要返回某幾個字段。

class Book(Base):
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=False)
    author = Column(String(30), default='未名')
    binding = Column(String(20))
    publisher = Column(String(50))
    price = Column(String(20))
    pages = Column(Integer)
    pubdate = Column(String(20))
    isbn = Column(String(15), nullable=False, unique=True)
    summary = Column(String(1000))
    image = Column(String(50))
	
    # orm實例化對象, 字段須要寫在構造函數中,這樣每一個實例對象都會有本身的一份,刪除增長都不會互相影響
    @orm.reconstructor
    def __init__(self):
        self.fields = ['id', 'title', 'author', 'binding',
                       'publisher', 'price', 'pages', 'pubdate',
                       'isbn', 'summary', 'image']
        
   	def keys(self):
        return self.fields if hasattr(self, 'fields') else []
    
    def hide(self, *keys):
        for key in keys:
            self.fields.remove(key)
        return self
    
    def append(self, *keys):
        for key in keys:
            self.fields.append(key)
        return self


@api.route('/search')
def search():
    books = Book.query.filter().all()  # 根據某些條件搜索的
   	books = [book.hide('summary') for book in books]
    return jsonify(books)
    
    
@api,route('/<isbn>/detail')
def detail(isbn):
    book = Book.query.filter_by(isbn=isbn).first_or_404()
    return jsonify(book)

請求鉤子函數

  • before_first_request:在處理第一個請求前運行。
  • before_request:在每次請求前運行。
  • after_request:若是沒有未處理的異常拋出,在每次請求後運行。
  • teardown_request:在每次請求後運行,即便有未處理的異常拋出。

全局掃描器

模仿flask exceptions 預加載各個異常類的方式,將用戶組自動加載進內存中,這樣獲取的話就更方便

str2obj = {}
level2str = {}


def iteritems(d, *args, **kwargs):
    return iter(d.items(*args, **kwargs))


def _find_scope_group():
    for _name, obj in iteritems(globals()):
        try:
            is_scope_obj = issubclass(obj, BaseScope)
        except TypeError:
            is_scope_obj = False
        if not is_scope_obj or obj.level < 1:
            continue

        old_obj = str2obj.get(_name, None)
        if old_obj is not None and issubclass(obj, old_obj):
            continue
        str2obj[_name] = obj
        level2str[obj.level] = _name


# 模仿flask exceptions 預加載各個異常類的方式,將用戶組自動加載進內存
_find_scope_group()
del _find_scope_group

常見bug

  1. form正則校驗注意事項

r'[1]{6, 25}$'

帶空格和不帶空格是兩碼事, 正則裏面{,} 連續不帶空格

r'[2]{6,25}$'

參考

Python Flask高級編程之RESTFul API先後端分離精講
七月老師的課程挺好的,不是純寫代碼,而是從問題入手,怎麼把複雜問題簡單化,從0到1。


  1. A-Za-z0-9_ ↩︎

  2. A-Za-z0-9_ ↩︎

相關文章
相關標籤/搜索