說一下Python項目中的驗參

在積累了必定的工做與項目實戰經驗後,愈來愈意識到驗參的重要性。 前幾天又重讀總結了一下【程序員修煉之道】,書中提到,卓有成效的程序員從不相信任何人,包括本身。 關於這句話的一個很重要的實踐便是在本身編寫的程序中,作好驗參工做,使對字段的限制與文檔一致。這點能夠顯著得加強系統的穩定性,保護系統的健壯與數據的一致。html

在實際工做中,發現驗參環節並非那麼容易作好,微服務系統有多層結構,每一個服務內代碼的組織又是分層的, 在哪些場景與環節進行驗參比較實用是本身須要考慮清楚的點。 這篇總結一下工做中見到的各Python項目對於驗參的各類處理,以及經常使用的驗參的庫。前端

須要驗參的幾個場景

假設項目爲一個簡單的兩層結構,gateway接受瀏覽器http請求,經過thrift rpc協議調下層service,service將數據寫到DB中。java

這樣一個簡單的調用鏈能夠抽出幾處須要驗參的地方:python

  • 收到http請求時,對前端傳來的參數進行驗證,確保前端傳來的參數與前端文檔中約定的一致;
  • service服務代碼分層爲handler, service, model三層,handler接受到thrift請求,將thrift request對象序列化爲dict後,調用service層代碼前,須要進行參數驗證(包括一些業務上的驗參),確保client端傳來的參數與idl中定義的參數規則一致;
  • service服務的service層往DB寫數據前(包括新增與更新時)須要驗參,確保寫到數據庫中的數據與model文件(orm定義)中定義的規則一致。

驗參如何作

if else直接幹

以前在寫一個Java項目的時候,問到組內的一個較有經驗的Java開發在Java中驗參一般怎麼作,怎樣是比較地道的寫法。 他開玩笑說,if else不就行了嘛。(固然,以後仍是告訴了我能夠用javax.validation中的註解很天然輕易地完成這個任務)git

誠然,if else能夠擼出一切。並且的確我也在一些項目中包括剛參加工做時的小公司的代碼裏見到過這樣的作法,不用引入第三方庫,直接進行判斷,某字段是否存在,某字段是否爲None,字段長度是否超長等。 這的確能夠完成工做,但真的不夠clean,每次在函數的前部分都要處理這些東西,代碼寫出來很醜。若要把這種if抽出來,粒度又太細,同時又要使用好比裝飾器這種技術將它和函數本體編織起來,並且不一樣函數要驗證的條件每每又是不同的,以前寫的驗證方法可能還要再改以達到通用,這就又要再改引用了這個驗證方法的方法,等等狀況會出現不少問題,因此並非一個長久的組織方法。程序員

下面介紹幾個我見過的用於驗參的第三方包,可接避免上面那樣的重複造輪子。github

marshmallow

marshmallow自己是Python中的一個出色的用於作序列化的庫,同時也提供了在驗參功能。 它容許開發者定義一些schema,schema中能夠以各類方式(allow_none, required, lambda)來表述一個字段的規則,同時在反序列化(將外部參數轉化爲領域對象)時,會自動進行驗參工做,並將不符合規則的參數統一組織起來,並且容許開發者本身提供個性化的相應報錯信息。web

上面說到反序列化,那這位又問了,序列化時它不作驗參嗎?的確如此,marshmallow的做者認爲,序列化是指將本身系統內的數據給提供出去(至關於to_json()),對於它們的質量與來源是咱們能夠保證的,故不須要在這個時候進行反序列化。 但實際狀況也並不每每如此,好比我最近接觸到的項目,因爲以前沒有作好驗參工做,且表結構較複雜,會有一些存在庫中的歷史數據實際上是少字段的或者是不符合規則的,在這種狀況下,能夠先將數據取出來,進行序列化生成dict對象,再用dict對象來調用schema.validate(dict)來專門進行驗證,從而蒐集信息,修補數據。數據庫

它的schema能夠按照以下方式定義:json

from marshmallow import Schema, fields, validate, validates

class UserSchema(Schema):
    name = fields.Str(required=True, validate=lambda n: n)
    age = fields.Decimal(required=True, validate=lambda n: n > 18)
    location = fields.Str(required=True)

 @validates('location')
    def validate_location(self, value):
        valid_locations = [u'SHANGHAI', u'TOKYO', u'NEWYORK']
        if value not in valid_locations:
            raise ValidationError('Unknown location.')
複製代碼

使用marshmallow進行驗參

此外,marshmallow還提供了一些常見規則的驗證,好比Email,URL來驗證字段是否符合規則,不用再去硬寫一些讓人頭疼的正則。它們都繼承於Validator類,你也能夠繼承它來編寫本身的驗證規則類來擴展marshmallow的能力,使得一切都很地道、好用。

webargs

在gateway層面,面對http請求,可使用webargs包來進行參數提取與校驗。 咱們知道,http請求傳參有多種可能,好比在url中的?key=value&key2=value2這種格式,此外,post方法傳參的可能就更多了,有plan text,application/json等。

而webargs包即是用來簡化這一拿參驗參的過程,它讓開發者能夠經過定義一個schema或是dict的結構來表示本身指望從http中獲得哪些參數,以及使用哪些規則。查一下源碼的話,很容易發現,它內部也是使用了marshmallow,調用了marshmallow的load函數,最後返回一個dict。 這種方式使用起來對開發仍是很友好的,舉例以下(抄自項目readme):

from flask import Flask
from webargs import fields
from webargs.flaskparser import use_args

app = Flask(__name__)

hello_args = {"name": fields.Str(required=True)}
# or use schema
# HelloArgs(Schema):
# name = fields.Str(required=True)


@app.route("/")
@use_args(hello_args)
# 對應上面
# @use_args(HelloArgs(strict=True))
def index(args):
    return "Hello " + args["name"]


if __name__ == "__main__":
    app.run()

# curl http://localhost:5000/\?name\='World'
# Hello World
複製代碼

schema

說了上面,哪位又問了,marshmallow與webargs所作的驗參都不夠專注,前者是爲序列化服務,後者更多地是爲取參同時進行,有些狀況下只有兩個參數,不想去定義那些schema,感受好麻煩,並且只想用單一的驗參功能,要怎麼作呢?

那麼schema就是你想要的。Schema validation just got Pythonic 它的用法與api比較pythonic,很語義化並且夠函數式,寫起來仍是比較好玩的,不過要注意正確性。 在工做中我見過一些同事拿它在service中的handler代碼層驗參。 抄襲項目readme示例代碼以下,諸位能夠感覺下,仍是蠻有意思的:

schema示例代碼


必定不要小看驗參這件事,在大型項目的開發中,可能有不少歷史遺留問題,兼容性問題,甚至是來自腳本的惡意請求等。你根本沒法肯定你編寫的代碼會被怎樣調用,若是這些環節失去了這些保障,線上的複雜狀況,會讓你的代碼在一些匪夷所思的地方報出經典的NoneType error,甚至有些數據庫的字段會被莫名其妙地被寫爲空,卻根本不知道它是在何處發生的。這些錯誤的發生會讓開發人員不知所措,措手不及,由於明明在測試時根本沒出現過,極度難以調試。 等到時候再要加校驗,已經很困難了。

在經歷了一些莫明其妙的問題後,我不得不開始重視驗參環節,畢竟沒吃過虧仍是不知道疼。 我認爲驗參環節是一種運行時的assert技術,marshmallow與webargs提供的序列化、取參數的同時進行驗參我認爲是比較好的方案,它不會讓開發者在代碼中多寫一行專門去調驗參函數又能把這件事給作得很棒。

作好參數驗證,是一次請求,一個函數運行,一次持久化成功的第一道保障,client環境太複雜,咱們這些寫service的仍是要保護好本身啊!

相關文章
相關標籤/搜索