Skip to content

Commit 2a88a73

Browse files
committed
version 3.8.0.0 update
集成Flask-Pydantic,现在可以使用type hints对API及SQL的参数类型进行校验
1 parent a348e7f commit 2a88a73

File tree

10 files changed

+37
-20
lines changed

10 files changed

+37
-20
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ COPY . /opt
66
VOLUME ["/opt/logs"]
77
EXPOSE 5000
88
RUN pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r /opt/requirements.txt
9-
RUN pip3 install Flask-SSM==3.7.3.4
9+
RUN pip3 install Flask-SSM==3.8.0.0
1010
# 启动
1111
CMD python3 ./app.py

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ APP_PROCESS = 1 # 进程个数
5151
<pre>from flask_ssm.springframework.stereotype import Controller</pre>
5252
</li>
5353
<li>
54-
如<code><a href="./test/demo/controller/base_controller.py" target="_blank">test.demo.controller.base_controller</a></code>所示,对需要注册的接口直接加上<code>&commat;RequestMapping&lpar;value=**&comma;&nbsp;method=**&rpar;</code>或<code>&commat;GetMapping&lpar;value=**&rpar;</code>或<code>&commat;PostMapping&lpar;value=**&rpar;</code>
54+
如<code><a href="./test/demo/controller/base_controller.py" target="_blank">test.demo.controller.base_controller</a></code>所示,对需要注册的接口直接加上<code>&commat;RequestMapping&lpar;value=**&comma;&nbsp;method=**&rpar;</code>或<code>&commat;GetMapping&lpar;value=**&rpar;</code>或<code>&commat;PostMapping&lpar;value=**&rpar;</code>。如果需要对参数的类型进行校验,可以使用type&nbsp;hints。
5555
</li>
5656
<li>
5757
如<code><a href="./test/demo/controller/customize_controller.py" target="_blank">test.demo.controller.customize_controller</a></code>所示,如果需要json格式的响应体,在<code>&commat;RequestMapping</code>后面加上<code>&commat;ResponseBody</code>,把函数返回值映射为json格式写入响应体中。
@@ -104,7 +104,7 @@ if __name__ == "__main__":
104104
<pre>from flask_ssm.springframework.stereotype import Repository</pre>
105105
</li>
106106
<li>
107-
如<code><a href="./test/demo/dao/tablename_dao.py" target="_blank">test.demo.dao.tablename_dao</a></code>所示,对于查询函数,使用<code>&commat;Mapper&lpar;result_type=**&rpar;</code>修饰,并使用<code>result_type</code>指定返回类型。函数的参数为传入SQL语句的参数,返回值为SQL语句,SQL语句的参数部分同MyBatis的用法,即可实现返回对象的自动封装。
107+
如<code><a href="./test/demo/dao/tablename_dao.py" target="_blank">test.demo.dao.tablename_dao</a></code>所示,对于查询函数,使用<code>&commat;Mapper&lpar;result_type=**&rpar;</code>修饰,并使用<code>result_type</code>指定返回类型。函数的参数为传入SQL语句的参数,返回值为SQL语句,SQL语句的参数部分同MyBatis的用法,即可实现返回对象的自动封装。如果需要对SQL语句传参类型进行校验,可以使用type&nbsp;hints。
108108
<pre>
109109
@Mapper(result_type=str)
110110
def query_one(param):
@@ -414,4 +414,7 @@ class Pojo:
414414
<tr>
415415
<td>3.7.3.4</td><td>fix some bugs</td><td>2024年7月27日</td>
416416
</tr>
417+
<tr>
418+
<td>3.8.0.0</td><td>集成Flask-Pydantic,现在可以使用type&nbsp;hints对API及SQL的参数类型进行校验</td><td>2024年7月31日</td>
419+
</tr>
417420
</table>

build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
docker build -t zongxr/flask-ssm-example:3.7.3.4 .
2+
docker build -t zongxr/flask-ssm-example:3.8.0.0 .
33
python3 setup.py sdist bdist_wheel
44
python3 -m twine upload --repository testpypi dist/*
55
python3 -m twine upload dist/*

flask_ssm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# -*- coding: utf-8 -*-
22

33

4-
__version__ = "3.7.3.4"
4+
__version__ = "3.8.0.0"

flask_ssm/pybatis/annotation.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
from sqlalchemy.engine.cursor import CursorResult
1515
from sqlalchemy.engine.result import MappingResult
1616
from sqlalchemy.engine.row import Row
17+
from pydantic import validate_call
1718
from flask_ssm.springframework.stereotype import Repository
1819
from flask_ssm.utils.module_utils import try_to_import
19-
from flask_ssm.utils.type_utils import __get_origin__, pojo_private_properties
20+
from flask_ssm.utils.type_utils import __get_origin__, pojo_private_properties, validate_single_value
2021

2122

2223
if sys.version_info >= (3, 9):
@@ -50,6 +51,8 @@ def __call__(self, func):
5051
:param func: 原函数
5152
:return:
5253
"""
54+
func = validate_call(func)
55+
5356
@wraps(func)
5457
def wrapper(*params, **kwparams):
5558
_module_ = inspect.getmodule(func)
@@ -118,9 +121,7 @@ def wrapper(*params, **kwparams):
118121
keys = list(result.mappings().keys())
119122
if len(keys) > 1:
120123
current_app.logger.warning("found %d columns, only pick columns[0]: %s" % (len(keys), keys[0]))
121-
_res_ = [x[0] for x in result]
122-
if len(_res_) > 0 and type(_res_[0]) is not _class_:
123-
current_app.logger.warning("type of T is %s, but required result_type is %s" % (type(_res_[0]), _class_))
124+
_res_ = [validate_single_value(_class_, x[0]) for x in result]
124125
return _res_
125126
elif __get_origin__(self.result_type) is tuple:
126127
if self.result_type in (Tuple, tuple): # Tuple or tuple
@@ -148,9 +149,7 @@ def wrapper(*params, **kwparams):
148149
keys = list(result.mappings().keys())
149150
if len(keys) > 1:
150151
current_app.logger.warning("found %d columns, only pick columns[0]: %s" % (len(keys), keys[0]))
151-
_res_ = tuple(x[0] for x in result)
152-
if len(_res_) > 0 and type(_res_[0]) is not _class_:
153-
current_app.logger.warning("type of T is %s, but required result_type is %s" % (type(_res_[0]), _class_))
152+
_res_ = tuple(validate_single_value(_class_, x[0]) for x in result)
154153
return _res_
155154
elif issubclass(__get_origin__(self.result_type), Generator):
156155
if self.result_type in (Generator, typing.Generator): # Generator
@@ -178,7 +177,7 @@ def wrapper(*params, **kwparams):
178177
keys = list(result.mappings().keys())
179178
if len(keys) > 1:
180179
current_app.logger.warning("found %d columns, only pick columns[0]: %s" % (len(keys), keys[0]))
181-
return (x[0] for x in result)
180+
return (validate_single_value(_class_, x[0]) for x in result)
182181
elif __get_origin__(self.result_type) is dict:
183182
if self.result_type is Dict or self.result_type is dict: # Dict or dict
184183
result: CursorResult = db.session.execute(sql, kwparams)
@@ -194,7 +193,7 @@ def wrapper(*params, **kwparams):
194193
values = list(zip(*result.fetchall()))
195194
keys = list(result.mappings().keys())
196195
return dict(zip(keys, values)) if len(values) > 0 else {key: tuple() for key in keys}
197-
else: # Dict[str, T]
196+
else: # Dict[str, Any]
198197
result: CursorResult = db.session.execute(sql, kwparams)
199198
values = result.fetchone()
200199
keys = list(result.mappings().keys())
@@ -210,9 +209,7 @@ def wrapper(*params, **kwparams):
210209
current_app.logger.warning("found %d fields, only pick fields[0]: %s" % (len(keys), keys[0]))
211210
fetch_result = result.fetchone()
212211
_res_ = None if fetch_result is None else fetch_result[0]
213-
if _res_ is not None and type(_res_) is not _class_:
214-
current_app.logger.warning("type of T is %s, but required result_type is %s" % (type(_res_), _class_))
215-
return _res_
212+
return validate_single_value(_class_, _res_)
216213
return wrapper
217214

218215

flask_ssm/springframework/web/bind/annotation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import inspect
66
from urllib.parse import unquote
77
from flask import request, Response
8+
from flask_pydantic import validate
89
from flask_ssm.utils.module_utils import blueprint_from_module
910
from flask_ssm.utils.type_utils import to_json
1011

@@ -65,6 +66,7 @@ def result(*args, **kwargs):
6566
else:
6667
return _inner_result_
6768
bp = blueprint_from_module(func)
69+
func = validate()(func)
6870
return bp.route(self.rule, methods=self.methods)(result)
6971

7072

flask_ssm/utils/type_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# -*- coding: utf-8 -*-
22
import sys
3+
import logging
34
from typing import Type
45
from flask import Response, jsonify
6+
from pydantic import create_model, ValidationError
57

68

79
if sys.version_info >= (3, 8):
@@ -50,3 +52,13 @@ def to_json(obj) -> Response:
5052
return jsonify(dict(obj))
5153
else:
5254
return jsonify(obj.__dict__)
55+
56+
57+
def validate_single_value(value_type, value):
58+
model = create_model('DynamicModel', value=(value_type, ...))
59+
try:
60+
validated_value = model(value=value)
61+
return validated_value.value
62+
except ValidationError as e:
63+
logging.warning(e)
64+
return value

requirements.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ Flask-APScheduler~=1.12.3,>1.12.2
44
Flask-Cors~=3.0.10
55
Flask-HTTPAuth~=4.5.0
66
Flask-SQLAlchemy~=2.5.1
7+
Flask-Pydantic~=0.12.0
78
SQLAlchemy~=1.4.18
9+
pydantic~=2.0
810
typing-inspect~=0.8.0;python_version=="3.7"
911
APScheduler==3.10.4
10-
Werkzeug>=2.1.2,<=2.3.8
12+
Werkzeug>=2.1.2,<=2.3.8
13+
setuptools~=65.5.0

run_with_docker.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/bin/bash
22
mkdir -p /opt/flask-ssm-example/logs
3-
docker run -d -p 5000:5000 -v /opt/flask-ssm-example/logs:/opt/logs zongxr/flask-ssm-example:3.7.3.4
3+
docker run -d -p 5000:5000 -v /opt/flask-ssm-example/logs:/opt/logs zongxr/flask-ssm-example:3.8.0.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
long_description = long_description.replace("./app.py", base_url + "/tree/main/app.py")
1111
packages = list(filter(lambda x: not x.startswith("test"), setuptools.find_packages()))
1212
requires_list = open('./requirements.txt', 'r', encoding='utf8').readlines()
13-
requires_list = [x.strip() for x in requires_list if not x.startswith("PyMySQL")]
13+
requires_list = [x.strip() for x in requires_list if (not x.startswith("PyMySQL")) and (not x.startswith("setuptools"))]
1414

1515

1616
setuptools.setup(

0 commit comments

Comments
 (0)