我想使用 Pydantic 使用特定的哈希函数将明文字符串密码转换为分配时字节类型的哈希值。
这是一个最小的例子,展示了我当前(不起作用)的方法。不过我对 Pydantic 还没有很深入的了解。
import bcrypt
from pydantic import BaseModel, field_validator
def hash_password(password: str) -> bytes:
return bcrypt.hashpw(password.encode('utf-8'), salt = bcrypt.gensalt())
class Password(BaseModel):
hash_value: bytes
@field_validator("hash_value")
@classmethod
def set_password(cls, plain_password: str) -> bytes:
return hash_password(plain_password)
class Settings:
DEFAULT_PASSWORD = "my_plain_password"
settings = Settings()
password_doc = Password(
hash_value = settings.DEFAULT_PASSWORD
)
起初我不小心将 hash_values 声明为 str,没有意识到
hashpw
的返回值是 bytes 类型。这不知何故起作用了,hash_password
函数在分配时被调用。但是,发生的所有隐式类型转换都使我的哈希密码无效。
现在的问题是 Pydantic 在赋值时需要一个字节值,并在将字符串
settings.DEFAULT_PASSWORD
传递给 set_password
方法之前隐式地将其转换为字节值,即使这个方法需要字符串类型。
我的错误信息:
Traceback (most recent call last):
File "xxx", line 20, in <module>
password_doc = Password(
^^^^^^^^^
File "xxx", line 176, in __init__
self.__pydantic_validator__.validate_python(data, self_instance=self)
File "xxx", line 13, in set_password
return hash_password(plain_password)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "xxx", line 5, in hash_password
return bcrypt.hashpw(password.encode('utf-8'), salt = bcrypt.gensalt())
^^^^^^^^^^^^^^^
AttributeError: 'bytes' object has no attribute 'encode'. Did you mean: 'decode'?
问题是,默认情况下,field_validator 使用“after”验证器。也就是说,它们运行 pydantic 自己的内部验证器。 Pydantic 知道您想要一个
bytes
并且它已经通过了 str
。它知道如何在 str
和 bytes
之间进行转换,因此它会在将结果传递给验证器之前自动进行编码。
如果您添加
mode='before'
,那么您的验证器将首先运行。但是,它必须能够接受任何输入,并且如果给定一个 int 或一个列表或其他任何内容,则不会崩溃。
例如。
@field_validator("hash_value", mode="before")
@classmethod
def set_password(cls, plain_password: str | bytes) -> bytes:
if isinstance(plain_password, bytes):
try:
plain_password = plain_password.decode("utf8")
except UnicodeDecodeError as ex:
# not strictly necessary as UnicodeDecodeError subclasses ValueError
# but shows that you must handle possible errors and raise ValueErrors
# when an input is invalid. And how to provide a supplementary
# error message.
raise ValueError("password is not a valid utf8 byte-string")
elif not isinstance(plain_password, str):
raise ValueError
return hash_password(plain_password)