
Welcome to pre-request’s document. This framework is designed to validate params for Flask request params, this framework can validate complex struct and field, including Cross Field, Cross Struct.
User’s Guide¶
This part of the documentation will instruct how to use pre-request in your flask project。
Installation¶
Python Version¶
We recommend using the latest version of python 3. pre-request supports Python 3.5 and newer and PyPy
Dependencies¶
This framework is designed for Flask, therefore Flask will be installed automatically when installing pre-request
Install pre-request with pip¶
You can use the following command to install pre-request:
pip install pre-request
Quickstart¶
Eager to get started? This page gives a good example to use pre-request. It assumes you already have pre-request installed If you do not, head over to the Installation section.
Minimal Example¶
A minimal example looks something like this:
from flask import Flask
from pre_request import pre
from pre_request import Rule
app = Flask(__name__)
req_params = {
"userId": Rule(type=int, required=True)
}
@app.route("/")
@pre.catch(req_params)
def hello_world(params):
return str(params)
what happened in this code ?
Use pre-request library to import a global object pre
Define request params rule, userId must be type of int and required
Use @pre.catch(req_params) to filter input value
Use ~flask.g or def hello_world(params) to get formatted input value。
pre singleton¶
pre-request support global singleton object, we can use this object to update runtime params
pre.fuzzy pre-request will fuzzy error message to avoid expose sensitive information
pre.sore_key use another params to store formatted request info
pre.content_type pre-request will response html or json error message, use application/json or text/html
pre.skip_filter pre-request will ignore all of the check filter, but dest is still valid.
Flask Extension¶
pre-request support flask extension configuration to load params.
app = Flask(__name__)
app.config["PRE_FUZZY"] = True
app.config["PRE_STORE_KEY"] = "pp"
app.config["PRE_CONTENT_TYPE"] = "application/json"
app.config["PRE_SKIP_FILTER"] = False
pre.init_app(app=app)
Decorator¶
pre-request use decorator pre.catch to validate request params with special kind of method
@app.route("/get", methods=['get'])
@pre.catch(get=req_params)
def get_handler(params):
return str(params)
@app.route("/post", methods=['post'])
@pre.catch(post=req_params)
def get_handler(params):
return str(params)
we can also support validate different rule for different request method.
@app.route("/all", methods=['get', 'post'])
@pre.catch(get=get_field, post=post_field)
def all_handler(params):
return str(params)
you can validate params for all of the request methods with no key.
@app.route("/all", methods=['get', 'post'])
@pre.catch(rules)
def all_handler(params):
return str(params)
Use parse¶
We can use function pre.parse instead of decorator @pre.catch(). At this mode, you must catch ParamsValueError by yourself.
args = {
"params": Rule(email=True)
}
@app.errorhandler(ParamsValueError)
def params_value_error(e):
return pre.fmt_resp(e)
@app.route("/index")
def example_handler():
rst = pre.parse(args)
return str(rst)
Validate Rules¶
Control:
Rule |
Desc |
---|---|
type |
Direct type |
required |
Param is required |
default |
Default value if param is empty |
dest |
Direct key for result |
required_with |
Required with other key |
location |
Which location to read value for request |
skip |
Skip all of the filters |
Other:
Rule |
Desc |
---|---|
json |
Json deserialize value |
callback |
Custom callback function |
Fields:
Rule |
Desc |
---|---|
eq_key |
Field equal to another field |
neq_key |
Field not equal to another field |
gt_key |
Field greater than another field |
gte_key |
Field greater than or equal to another field |
lt_key |
Field less than another field |
lte_key |
Field less than or equal to another field |
Network:
Rule |
Desc |
---|---|
ipv4 |
Internet protocol address IPv4 |
ipv6 |
Internet protocol address IPv6 |
mac |
Media access control address MAC |
url_encode |
Url encode with urllib.parse.quote |
url_decode |
Url decode with urllib.parse.unquote |
Strings:
Rule |
Desc |
---|---|
len |
Content length for string or array |
trim |
Trim space characters |
reg |
Regex expression |
contains |
Contains |
contains_any |
Contains any items |
excludes |
Excludes |
startswith |
Starts with |
not_startswith |
Not start with |
not_endswith |
Not end with |
endswith |
Ends with |
lower |
Lowercase |
upper |
Uppercase |
Split |
Split string with special character |
Format:
Rule |
Desc |
---|---|
fmt |
date or datetime format |
latitude |
Latitude |
longitude |
Longitude |
structure |
Describe substructure for array or dict |
multi |
Value is array |
deep |
Find value from substructure |
enum |
Enum value |
alpha |
Alpha only |
alphanum |
Alpha or numeric |
numeric |
Numeric |
number |
Number only |
Email address for RFC5322 |
Comparison:
Rule |
Desc |
---|---|
eq |
Equals |
neq |
Not equal |
gt |
Greater than |
gte |
Greater than or equal |
lt |
Less than |
lte |
Less than or equal |
location¶
By default, pre-request try to parse values form flask.Request.values and flask.Request.json. Use location to specify location to get values. current support [“args”, “form”, “values”, “headers”, “cookies”, “json”]
params = {
"Access-Token": Rule(location="headers"),
"userId": Rule(location=["cookies", "headers", "args"])
}
deep¶
By default, pre-request can parse value from complex structure. we can use deep=False to turn off this feature, pre-request will parse values from top level.
params = {
"userInfo": {
"userId": Rule(type=int, required=False),
"socialInfo": {
"gender": Rule(type=int, enum=[1, 2], default=1),
"age": Rule(type=int, gte=18, lt=80),
"country": Rule(required=True, deep=False)
}
}
}
type¶
pre-request try to convert value to special type.
params = {
"userId": Rule(type=int)
}
skip¶
pre-request will skip validate value at this field. we will put origin value in the result structure.
params = {
"userName": Rule(skip=True)
}
multi¶
if you set multi=True, we will check every items in array. otherwise it will be regarded as a whole。
params = {
"userIds": Rule(type=int, multi=True)
}
structure¶
You can use structure field to define sub structure in array. This field will be only valid in multi=True.
- params = {
- “friends”: Rule(multi=True, structure={
“userId”: Rule(type=int, required=True), “userName”: Rule(type=str, required=True)
})
}
required¶
pre-request validate the value is not None or user do not input this value. Specially, if user don’t input this value and skip=True, pre-request will fill it with missing type.
params = {
"profile": Rule(required=False)
}
required_with¶
The field under validation must be present and not empty only if any of the other specified fields are present.
params = {
"nickName": Rule(required=False),
"profile": Rule(required=False, required_with="nickName")
}
default¶
pre-request will fill the default value into the field only if the field is not required and current value is None
params = {
"nickName": Rule(required=False, default="张三")
}
split¶
pre-request will split origin string value with special char and the check rule will filter to every value in the result array。
params = {
"userId": Rule(int, split=",")
}
trim¶
pre-request will try to remove the space characters at the beginning and end of the string.
params = {
"nickName": Rule(trim=True)
}
enum¶
Ensure that the parameters entered by the user are within the specified specific value range.
params = {
"gender": Rule(direct_type=int, enum=[1, 2])
}
reg¶
Use regular expressions to verity that the user input string meets the requirements.
params = {
"tradeDate": Rule(reg=r"^[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$")
}
contains¶
Ensure that the field entered by the user contain all of the special value.
params = {
"content": Rule(contains=["你好", "再见"])
}
contains_any¶
Ensure that the field entered by the user contain any of the special value.
params = {
"content": Rule(contains_any=["你好", "再见"])
}
excludes¶
Ensure that the field entered by the user can not contain any of special value.
params = {
"content": Rule(excludes=["张三", "李四"])
}
startswith¶
Ensure that the input string value must be start with special substring
params = {
"nickName": Rule(startswith="CN")
}
not_endswith¶
Ensure that the input string value must be not start with special substring
params = {
"nickName": Rule(not_startswith="USA")
}
endswith¶
Ensure that the input string value must be end with special substring
params = {
"email": Rule(endswith="@eastwu.cn")
}
not_endswith¶
Ensure that the input string value must be not end with special substring
params = {
"email": Rule(not_endswith="@eastwu.cn")
}
lower¶
pre-request will convert all characters in the string to lowercase style.
params = {
"nickName": Rule(lower=True)
}
upper¶
pre-request will convert all characters in the string to uppercase style.
params = {
"country": Rule(upper=True)
}
ipv4/ipv6¶
Ensure that the field entered by the user conform to the ipv4/6 format.
params = {
"ip4": Rule(ipv4=True)
"ip6": Rule(ipv6=True)
}
mac¶
Ensure that the field entered by the user conform to the MAC address format.
params = {
"macAddress": Rule(mac=True)
}
url_encode¶
Encode url by function urllib.parse.quote. This rule is only valid for parameters of type str. You can select the encoding type through the encoding parameter.
params = {
"url": Rule(type=str, url_encode=True, encoding="GB2312")
}
url_decode¶
Decode url by function urllib.parse.unquote. This rule is only valid for parameters of type str. You can select the encoding type through the encoding parameter.
params = {
"url": Rule(type=str, url_decode=True, encoding="GB2312")
}
alpha¶
Check that a string can oly consist of letters.
params = {
"p": Rule(type=str, alpha=True)
}
alphanum¶
Check that a string can oly consist of letters or numeric.
params = {
"p": Rule(type=str, alphanum=True)
}
numeric¶
Check that a string can oly consist of numeric.
params = {
"p": Rule(type=str, numeric=True)
}
number¶
Check that a string can oly consist of number.
params = {
"p": Rule(type=str, number=True)
}
email¶
Check that a string is valid email address.
params = {
"p": Rule(type=str, email=True)
}
fmt¶
Provides the style when the string is converted to datetime or date type. This is valid only on type=datetime.datetime
params = {
"birthday": Rule(type=datetime.datetime, fmt="%Y-%m-%d"),
"otherDate": Rule(type=datetime.date, fmt="%Y-%m-%d")
}
latitude / longitude¶
Ensure that the field entered by the user conform to the latitude/longitude format.
params = {
"latitude": Rule(latitude=True),
"longitude": Rule(longitude=True)
}
eq / eq_key¶
Used to check whether the user input parameter is equal to another value or another parameter.
params = {
"userId": Rule(eq=10086),
"userId2": Rule(eq_key="userId")
}
neq / neq_key¶
Used to check whether the user input parameter is not equal to another value or another parameter.
params = {
"userId": Rule(neq=0),
"forbidUserId": Rule(neq_key="userId")
}
gt / gt_key¶
Used to check whether the user input parameter is great than another value or another parameter.
params = {
"kidAge": Rule(type=int, gt=0),
"fatherAge": Rule(type=int, gt_key="kidAge")
}
gte / gte_key¶
Used to check whether the user input parameter is great than or equal to another value or another parameter.
params = {
"kidAge": Rule(type=int, gte=0),
"brotherAge": Rule(type=int, gte_key="kidAge")
}
lt / lt_key¶
Used to check whether the user input parameter is less than another value or another parameter.
params = {
"fatherAge": Rule(type=int, lt=100),
"kidAge": Rule(type=int, lt_key="fatherAge")
}
lte / lte_key¶
Used to check whether the user input parameter is less than or equal to another value or another parameter.
params = {
"fatherAge": Rule(type=int, lte=100),
"kidAge": Rule(type=int, lte_key="fatherAge")
}
dest¶
We will convert the key of the parameter to another value specified.
params = {
"userId": Rule(type=int, dest="user_id")
}
json¶
We will try to use the json.loads method to parse the value of the parameter to convert it into a list or dict type.
callback¶
If the filters we provide cannot meet your needs, you can pass in the parse function you defied through the callback method.
def hand(value):
if value <= 10:
raise ParamsValueError("'userId' must be greater than 10")
return value + 100
params = {
"userId": Rule(type=int, callback=hand)
}
Customize¶
Response¶
Normally, when pre-request finds that the user input parameter does not meet the requirements, it will directly interrupt the processing and return the discovered problem to the requester. The default JSON type of response format provided by pre-request is as follows:
{
"respCode": 400,
"respMsg": "Error Message",
"result": {}
}
In some scenarios, we need different response formats. Pre-request provides the ability to customize the response.
You need to implement a class that inherits from BaseResponse
to implement your own data response
processing.
from flask import make_response
from pre_request import BaseResponse
class CustomResponse(BaseResponse):
def make_response(
cls,
error: "ParamsValueError",
fuzzy: bool = False,
formatter: t.Optional[t.Callable] = None
):
result = {
"code": 900,
"rst": {}
}
from flask import make_response # pylint: disable=import-outside-toplevel
response = make_response(json.dumps(result))
response.headers["Content-Type"] = "application/json; charset=utf-8"
return response
from pre_request import pre
pre.add_response(CusResponse)
Formatter¶
If you feel that the custom response class is too complicated, we also provide the function of a custom formatting function. The pre-request will give priority to calling your custom function to generate a response string.
def custom_formatter(error: ParamsValueError):
""" 自定义结果格式化函数
"""
return {
"code": 411,
"msg": "hello",
"sss": "tt",
}
from pre_request import pre
pre.add_formatter(custom_formatter)
Filter¶
pre-request 提供了丰富的过滤器插件。但是面对各式各样的业务需求,您可能也觉得pre-request无法满足您。因此pre-request 提供了自定义过滤器功能,让您可以更加自身的业务需求去扩展pre-request框架。
通常情况下,自定义的过滤器需要继承自 BaseFilter
类。
from pre_request import BaseFilter
class CustomFilter(BaseFilter):
def filter_required(self):
""" 检查当前过滤式,是否必须要执行
"""
return True
def __call__(self, *args, **kwargs):
""" 自定义过滤器时需要实现的主要功能
"""
super().__call__()
if self.rule.direct_type == int and self.key == "number" and self.value != 10086:
raise ParamsValueError(message="any error messages you want")
return self.value + 1
如上所示,您至少需要实现 filter_required 和 __call__ 方法。在运行您的过滤器之前,先调用 filter_required 方法判断当前过滤器是否需要被执行,然后再调用 __call__ 方法运行过滤器。
最后,您需要在项目初始化时将自定义过滤器安装到pre-request中
from pre_request import pre
pre.add_filter(CustomFilter)
Store Key¶
By default, pre-request stores formatted input parameters in ~flask.g.params and the params parameter of the current function。 You can set the store_key parameter of the pre-request to change the storage key of the parameter.
from pre_request import pre
pre.store_key = "pre_params"