django,flask的forms组件对比+地表最强pydantic

django

相关模块

from django import forms
from django.core.exceptions import ValidationError

Form类

与models层有关联就继承forms.ModelForm,否则用forms.Form

钩子函数

  1. 局部钩子 def clean_field(self),校验通过返回self.data,否则raise ValidationError;
  2. 全局钩子 def clean(self),校验通过返回字段值,否则raise ValidationError;
  3. 原则:校验单个字段用局部钩子,多个用全局钩子;

常用字段约束

title = forms.CharField(max_length=20,min_length=5,
		error_messages={
		'required': '标题不能为空',
        'min_length': '标题最少为5个字符',
       	'max_length': '标题最多为20个字符'
       	},
        widget=forms.TextInput(
        	attrs={
        		'class': "form-control",
        		'placeholder': '标题5-20个字符'
        		}
      		)
     	)
     	
from django.core.validators import RegexValidator     	
phone = forms.CharField(label='手机号', max_length=11, validators=[
        RegexValidator(r'^1[1|3|4|5|6|7|8|9]\d{9}$', '手机号格式错误')
    ])

由于字段已经在model层定义了一次,无需再定义,可以直接继承ModelForm更加方便!

class AdminModelForm(forms.ModelForm): 
    class Meta:
        model = models.Admin
        #fields = '__all__'
        #exclude = ('id',)
        fields = ('username', 'email')
          
        widgets = {
            'email' : forms.PasswordInput(attrs=		
            {'class':"alex"} )
        }

常用方法或属性

  1. self.cleaned_data取出经过字段初步清洗的数据,可以get()取值。
  2. is_valid()检验数据
  3. errors不通过的字段,{ ‘field’:[ ‘required!’,‘maxlength!’] }
  4. data传入数据,创建
  5. instance本实例的对象
  6. 若data和instance都传入则为修改

flask

相关模块

from wtforms.fields import simple, core, html5
from wtforms import widgets
from wtforms import Form, validators

Form类

flask基本裸奔,没有model层,所以Form组件还需要再定义一遍字段

from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__, template_folder='templates')
app.debug = True



class RegisterForm(Form):
    name = simple.StringField(
        label='用户名',
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={'class': 'form-control'},
        default='alex'
    )

    pwd = simple.PasswordField(
        label='密码',
        validators=[
            validators.DataRequired(message='密码不能为空.')
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

    pwd_confirm = simple.PasswordField(
        label='重复密码',
        validators=[
            validators.DataRequired(message='重复密码不能为空.'),
            validators.EqualTo('pwd', message="两次密码输入不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

    email = html5.EmailField(
        label='邮箱',
        validators=[
            validators.DataRequired(message='邮箱不能为空.'),
            validators.Email(message='邮箱格式错误')
        ],
        widget=widgets.TextInput(input_type='email'),
        render_kw={'class': 'form-control'}
    )

    gender = core.RadioField(
        label='性别',
        choices=(
            (1, '男'),
            (2, '女'),
        ),
        coerce=int # “1” “2”
     )
    city = core.SelectField(
        label='城市',
        choices=(
            ('bj', '北京'),
            ('sh', '上海'),
        )
    )

    hobby = core.SelectMultipleField(
        label='爱好',
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        coerce=int
    )

    favor = core.SelectMultipleField(
        label='喜好',
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        widget=widgets.ListWidget(prefix_label=False),
        option_widget=widgets.CheckboxInput(),
        coerce=int,
        default=[1, 2]
    )

    def __init__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))

    def validate_pwd_confirm(self, field):
        """
        自定义pwd_confirm字段规则,例:与pwd字段是否一致
        :param field:
        :return:
        """
        # 最开始初始化时,self.data中已经有所有的值

        if field.data != self.data['pwd']:
            # raise validators.ValidationError("密码不一致") # 继续后续验证
            raise validators.StopValidation("密码不一致")  # 不再继续后续验证


@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        form = RegisterForm(data={'gender': 2,'hobby':[1,]}) # initial
        return render_template('register.html', form=form)
    else:
        form = RegisterForm(formdata=request.form)
        if form.validate():
            print('用户提交数据通过格式验证,提交的值为:', form.data)
        else:
            print(form.errors)
        return render_template('register.html', form=form)



if __name__ == '__main__':
    app.run()
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0  50px">
    {% for field in form %}
    <p>{{field.label}}: {{field}} {{field.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>

钩子函数

  1. 局部钩子 def clean_field(self,field):field.data取到field的值,self.data取到全部值,校验不通过raise Error,否则什么都不需要做

传参和方法

Form类实例化参数:

  1. formdata:需要被验证的form表单数据。

  2. obj:当formdata参数为提供时候,可以使用对象,也就是会是有obj.字段的值进行验证或设置默认值。

  3. prefix: 字段前缀匹配,当传入该参数时,所有验证字段必须以这个开头(无太大意义)。

  4. data: 当formdata参数和obj参数都有时候,可以使用该参数传入字典格式的待验证数据或者生成html的默认值,列如:{‘usernam’:'admin’}。

  5. meta:用于覆盖当前已经定义的form类的meta配置,参数格式为字典。

  6. form = LoginForm()

  7. form = LoginForm(formdata=)

  8. form .validate()

字段介绍

wtforms中的Field类主要用于数据验证和字段渲染(生成html),以下是比较常见的字段:

  1. StringField 字符串字段,生成input要求字符串
  2. PasswordField  密码字段,自动将输入转化为小黑点
  3. DateField  日期字段,格式要求为datetime.date一样
  4. IntergerField  整型字段,格式要求是整数
  5. FloatField  文本字段,值是浮点数
  6. BooleanField  复选框,值为True或者False
  7. RadioField  一组单选框
  8. SelectField  下拉列表,需要注意一下的是choices参数确定了下拉选项,但是和HTML中的 标签一样。
  9. MultipleSelectField  多选字段,可选多个值的下拉列表

字段参数:

  1. label:字段别名,在页面中可以通过字段.label展示;
  2. validators:验证规则列表;
  3. filters:过滤器列表,用于对提交数据进行过滤;
  4. description:描述信息,通常用于生成帮助信息;
  5. id:表示在form类定义时候字段的位置,通常你不需要定义它,默认会按照定义的先后顺序排序。
  6. default:默认值
  7. widget:html插件,通过该插件可以覆盖默认的插件,更多通过用户自定义;
  8. render_kw:自定义html属性;
  9. choices:复选类型的选项 ;
from flask import Flask,render_template,redirect,request
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets

app = Flask(__name__,template_folder="templates")
app.debug = True

=======================simple===========================
class RegisterForm(Form):
    name = simple.StringField(
        label="用户名",
        validators=[
            validators.DataRequired()
        ],
        widget=widgets.TextInput(),
        render_kw={"class":"form-control"},
        default="wd"
    )
    pwd = simple.PasswordField(
        label="密码",
        validators=[
            validators.DataRequired(message="密码不能为空")
        ]
    )
    pwd_confim = simple.PasswordField(
        label="重复密码",
        validators=[
            validators.DataRequired(message='重复密码不能为空.'),
            validators.EqualTo('pwd',message="两次密码不一致")
        ],
        widget=widgets.PasswordInput(),
        render_kw={'class': 'form-control'}
    )

  ========================html5============================
    email = html5.EmailField(  #注意这里用的是html5.EmailField
        label='邮箱',
        validators=[
            validators.DataRequired(message='邮箱不能为空.'),
            validators.Email(message='邮箱格式错误')
        ],
        widget=widgets.TextInput(input_type='email'),
        render_kw={'class': 'form-control'}
    )

  ===================以下是用core来调用的=======================
    gender = core.RadioField(
        label="性别",
        choices=(
            (1,"男"),
            (1,"女"),
        ),
        coerce=int  #限制是int类型的
    )
    city = core.SelectField(
        label="城市",
        choices=(
            ("bj","北京"),
            ("sh","上海"),
        )
    )
    hobby = core.SelectMultipleField(
        label='爱好',
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        coerce=int
    )
    favor = core.SelectMultipleField(
        label="喜好",
        choices=(
            (1, '篮球'),
            (2, '足球'),
        ),
        widget = widgets.ListWidget(prefix_label=False),
        option_widget = widgets.CheckboxInput(),
        coerce = int,
        default = [1, 2]
    )

    def __init__(self,*args,**kwargs):  #这里的self是一个RegisterForm对象
        '''重写__init__方法'''
        super(RegisterForm,self).__init__(*args, **kwargs)  #继承父类的init方法
        self.favor.choices =((1, '篮球'), (2, '足球'), (3, '羽毛球'))  #把RegisterForm这个类里面的favor重新赋值,实现动态改变复选框中的选项

    def validate_pwd_confim(self,field,):
        '''
        自定义pwd_config字段规则,例:与pwd字段是否一致
        :param field:
        :return:
        '''
        # 最开始初始化时,self.data中已经有所有的值
        if field.data != self.data['pwd']:
            # raise validators.ValidationError("密码不一致") # 继续后续验证
            raise validators.StopValidation("密码不一致")  # 不再继续后续验证

@app.route('/register',methods=["GET","POST"])
def register():
    if request.method=="GET":
        form = RegisterForm(data={'gender': 1})  #默认是1,
        return render_template("register.html",form=form)
    else:
        form = RegisterForm(formdata=request.form)
        if form.validate():  #判断是否验证成功
            print('用户提交数据通过格式验证,提交的值为:', form.data)  #所有的正确信息
        else:
            print(form.errors)  #所有的错误信息
        return render_template('register.html', form=form)

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

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0  50px">
    {% for item in form %}
    <p>{{item.label}}: {{item}} {{item.errors[0] }}</p>
    {% endfor %}
    <input type="submit" value="提交">
</form>
</body>
</html>

Meta

Meta主要用于自定义wtforms的功能,大多都是配置选项,以下是配置参数:

csrf = True   # 是否自动生成CSRF标签
csrf_field_name = 'csrf_token'   # 生成CSRF标签name
csrf_secret = 'adwadada'     # 自动生成标签的值,加密用的csrf_secret
csrf_context = lambda x: request.url  # 自动生成标签的值,加密用的csrf_context
csrf_class = MyCSRF         # 生成和比较csrf标签     
locales = False      # 是否支持翻译
locales = ('zh', 'en')  # 设置默认语言环境
cache_translations = True  # 是否对本地化进行缓存
translations_cache = {}       # 保存本地化缓存信息的字段

示例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template, request, redirect, session
from wtforms import Form
from wtforms.csrf.core import CSRF
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
from hashlib import md5

app = Flask(__name__, template_folder='templates')
app.debug = True


class MyCSRF(CSRF):
    """
    Generate a CSRF token based on the user's IP. I am probably not very
    secure, so don't use me.
    """

    def setup_form(self, form):
        self.csrf_context = form.meta.csrf_context()
        self.csrf_secret = form.meta.csrf_secret
        return super(MyCSRF, self).setup_form(form)

    def generate_csrf_token(self, csrf_token):
        gid = self.csrf_secret + self.csrf_context
        token = md5(gid.encode('utf-8')).hexdigest()
        return token

    def validate_csrf_token(self, form, field):
        print(field.data, field.current_token)
        if field.data != field.current_token:
            raise ValueError('Invalid CSRF')


class TestForm(Form):
    name = html5.EmailField(label='用户名')
    pwd = simple.StringField(label='密码')

    class Meta:
        # -- CSRF
        # 是否自动生成CSRF标签
        csrf = True
        # 生成CSRF标签name
        csrf_field_name = 'csrf_token'

        # 自动生成标签的值,加密用的csrf_secret
        csrf_secret = 'xxxxxx'
        # 自动生成标签的值,加密用的csrf_context
        csrf_context = lambda x: request.url
        # 生成和比较csrf标签
        csrf_class = MyCSRF

        # -- i18n
        # 是否支持本地化
        # locales = False
        locales = ('zh', 'en')
        # 是否对本地化进行缓存
        cache_translations = True
        # 保存本地化缓存信息的字段
        translations_cache = {}


@app.route('/index/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        form = TestForm()
    else:
        form = TestForm(formdata=request.form)
        if form.validate():
            print(form)
    return render_template('index.html', form=form)


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

实现原理

wtforms实现原理主要从三个方面进行说明:form类创建过程、实例化过程、验证过程。

从整体看其实现原理实则就是将每个类别的功能(如Filed、validate、meta等)通过form进行组织、封装,在form类中调用每个类别对象的方法实现数据的验证和html的渲染。这里先总结下验证流程:

  1. for循环每个字段;
  2. 执行该字段的pre_validate钩子函数;
  3. 执行该字段参数的validators中的验证方法和validate_字段名钩子函数(如果有);
  4. 执行该字段的post_validate钩子函数;
  5. 完成当前字段的验证,循环下一个字段,接着走该字段的2、3、4流程,直到所有字段验证完成;

从form类的validate方法作为入口:

def validate(self):
    """
    Validates the form by calling `validate` on each field, passing any
    extra `Form.validate_<fieldname>` validators to the field validator.
    """
    extra = {}
    for name in self._fields: # 循环每个field 
        #寻找当前类中以validate_’字段名匹配的方法’,例如pwd字段就寻找validate_pwd,也就是钩子函数
        inline = getattr(self.__class__, 'validate_%s' % name, None) 
        if inline is not None:
            extra[name] = [inline] #把钩子函数放到extra字典中
    return super(Form, self).validate(extra) #接着调用父类的validate方法

验证时候先获取所有每个字段定义的validate_+'字段名’匹配的方法,并保存在extra字典中,在执行父类的validate方法:

def validate(self, extra_validators=None):
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):  # 循环字段的名称和对象
            if extra_validators is not None and name in extra_validators: # 判断该字段是否有钩子函数
                extra = extra_validators[name] # 获取到钩子函数
            else:
                extra = tuple()
            if not field.validate(self, extra): # 执行字段的validate方法
                success = False
        return success

执行每个字段的validate方法:

def validate(self, form, extra_validators=tuple()):
        self.errors = list(self.process_errors)
        stop_validation = False

        # Call pre_validate
        try:
            self.pre_validate(form)      # 先执行字段字段中的pre_validate方法,这是一个自定义钩子函数
        except StopValidation as e:
            if e.args and e.args[0]:
                self.errors.append(e.args[0])
            stop_validation = True
        except ValueError as e:
            self.errors.append(e.args[0])

        # Run validators
        if not stop_validation:     
            chain = itertools.chain(self.validators, extra_validators)     # 拼接字段中的validator和validate_+'字段名'验证
            stop_validation = self._run_validation_chain(form, chain)  # 执行每一个验证规则,self.validators先执行

        # Call post_validate
        try:
            self.post_validate(form, stop_validation)
        except ValueError as e:
            self.errors.append(e.args[0])

        return len(self.errors) == 0

在该方法中,先会执行内部预留给用户自定义的字段的pre_validate方法,在将字段中的验证规则(validator也就是我们定义的validators=[validators.DataRequired()],)和钩子函数(validate_+‘字段名’)拼接在一起执行,注意这里的validator先执行而字段的钩子函数后执行:

def _run_validation_chain(self, form, validators):
       
        for validator in validators:  # 循环每个验证规则
            try:
                validator(form, self)   # 传入提交数据并执行,如果是对象执行__call__,如果是函数直接调用
            except StopValidation as e:
                if e.args and e.args[0]:    
                    self.errors.append(e.args[0])   # 如果有错误,追加到整体错误中
                return True
            except ValueError as e:
                self.errors.append(e.args[0])

        return False        

很明显就是循环每一个验证规则,并执行,有错误追加到整体错误中,回到validate方法中,接着又会执行post_validate,这也是内置钩子函数,允许用户自己定义,最后这个字段的数据验证完成了,而在开始的for循环,循环结束意味着整个验证过程结束。

小结

  1. wtform并没有django的forms组件强大;
  2. 前后端分离后 forms组件逐渐派不上用场,尤其是wtforms,带有严重的前后端不分离倾向。
  3. drf 的序列化器写法和django forms相似(不再赘述)

Pydantic — 强大的数据校验工具

Pydantic 比 Django-rest-framework 还快了12.3倍:
在这里插入图片描述

安装pydantic

Pydantic 是一个使用Python类型注解进行数据验证和管理的模块。安装方法非常简单,打开终端输入:

pip install pydantic

快速入门

Pydantic 基于Python3.7以上的类型注解特性,实现了可以对任何类做数据校验的功能:

# Pydantic 数据校验功能
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: Optional[datetime] = None
    friends: List[int] = []


external_data = {
    'id': '123',
    'signup_ts': '2019-06-01 12:22',
    'friends': [1, 2, '3'],
}
user = User(**external_data)
print(user.id)
print(type(user.id))
#> 123
#> <class 'int'>
print(repr(user.signup_ts))
#> datetime.datetime(2019, 6, 1, 12, 22)
print(user.friends)
#> [1, 2, 3]
print(user.dict())
"""
{
    'id': 123,
    'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
    'friends': [1, 2, 3],
    'name': 'John Doe',
}
"""

从上面的基本使用可以看到,它甚至能自动帮你做数据类型的转换,比如代码中的 user.id, 在字典中是字符串,但经过Pydantic校验器后,它自动变成了int型,因为User类里的注解就是int型。

当我们的数据和定义的注解类型不一致时会报这样的Error:

from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: Optional[datetime] = None
    friends: List[int] = []


external_data = {
    'id': '123',
    'signup_ts': '2019-06-01 12:222',
    'friends': [1, 2, '3'],
}
user = User(**external_data)
"""
Traceback (most recent call last):
  File "1.py", line 18, in <module>
    user = User(**external_data)
  File "pydantic\main.py", line 331, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for User
signup_ts
  invalid datetime format (type=value_error.datetime)
"""

Pydantic 模型数据导出

通过Pydantic模型中自带的 json 属性方法,能让经过校验后的数据一行命令直接转成 json 字符串,如前文中的user对象:

print(user.dict())  # 转为字典
"""
{
    'id': 123,
    'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
    'friends': [1, 2, 3],
    'name': 'John Doe',
}
"""
print(user.json())  # 转为json
"""
{"id": 123, "signup_ts": "2019-06-01T12:22:00", "friends": [1, 2, 3], "name": "John Doe"}
"""

它还支持将整个数据结构导出为 schema json,它能完整地描述整个对象的数据结构类型:

print(user.schema_json(indent=2))
"""
{
  "title": "User",
  "type": "object",
  "properties": {
    "id": {
      "title": "Id",
      "type": "integer"
    },
    "signup_ts": {
      "title": "Signup Ts",
      "type": "string",
      "format": "date-time"
    },
    "friends": {
      "title": "Friends",
      "default": [],
      "type": "array",
      "items": {
        "type": "integer"
      }
    },
    "name": {
      "title": "Name",
      "default": "John Doe",
      "type": "string"
    }
  },
  "required": [
    "id"
  ]
}
"""

数据导入

除了直接定义数据校验模型,它还能通过ORM、字符串、文件导入到数据校验模型:

比如字符串(raw):

from datetime import datetime
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime = None
      
m = User.parse_raw('{"id": 123, "name": "James"}')
print(m)
#> id=123 signup_ts=None name='James'

此外,它能直接将ORM的对象输入,转为Pydantic的对象,比如从Sqlalchemy ORM:

from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr

Base = declarative_base()


class CompanyOrm(Base):
    __tablename__ = 'companies'
    id = Column(Integer, primary_key=True, nullable=False)
    public_key = Column(String(20), index=True, nullable=False, unique=True)
    name = Column(String(63), unique=True)
    domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
    id: int
    public_key: constr(max_length=20)
    name: constr(max_length=63)
    domains: List[constr(max_length=255)]

    class Config:
        orm_mode = True


co_orm = CompanyOrm(
    id=123,
    public_key='foobar',
    name='Testing',
    domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <models_orm_mode.CompanyOrm object at 0x7f0bdac44850>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']

从Json文件导入:

from datetime import datetime
from pathlib import Path
from pydantic import BaseModel
​
​
class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime = None
      
path = Path('data.json')
path.write_text('{"id": 123, "name": "James"}')
m = User.parse_file(path)
print(m)

从pickle导入:

import pickle
from datetime import datetime
from pydantic import BaseModel
​
pickle_data = pickle.dumps({
    'id': 123,
    'name': 'James',
    'signup_ts': datetime(2017, 7, 14)
})
m = User.parse_raw(
    pickle_data, content_type='application/pickle', allow_pickle=True
)
print(m)
#> id=123 signup_ts=datetime.datetime(2017, 7, 14, 0, 0) name='James'

自定义数据校验

还能给它增加 validator 装饰器,增加需要的校验逻辑:

from pydantic import BaseModel, ValidationError, validator


class UserModel(BaseModel):
    name: str
    username: str
    password1: str
    password2: str

    @validator('name')
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

    @validator('password2')
    def passwords_match(cls, v, values, **kwargs):
        if 'password1' in values and v != values['password1']:
            raise ValueError('passwords do not match')
        return v

    @validator('username')
    def username_alphanumeric(cls, v):
        assert v.isalnum(), 'must be alphanumeric'
        return v

Pydantic V2

Pydantic V2 is compatible with Python 3.7 and above.

官方明确表明,v2版本的包中依旧会支持v1版本的接口:

Pydantic V1 is still available when you need it, though we recommend migrating to Pydantic V2 for its improvements and new features.

If you need to use latest Pydantic V1, you can install it with:

pip install "pydantic==1.*"

The Pydantic V2 package also continues to provide access to the Pydantic V1 API by importing through pydantic.v1. For example, you can use the BaseModel class from Pydantic V1 instead of the Pydantic V2 pydantic.BaseModel class:

from pydantic.v1 import BaseModel

You can also import functions that have been removed from Pydantic V2, such as lenient_isinstance:

from pydantic.v1.utils import lenient_isinstance

Pydantic V1 documentation is available at https://docs.pydantic.dev/1.10/.

关于Changes to pydantic.BaseModel,详细参考:https://docs.pydantic.dev/2.0/migration/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值