原代码:

def generate_confirmation_token(self, expiration=3600):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'confirm': self.id}).decode('utf-8')
        
    def confirm(self, token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token.encode('utf-8'))
        except:
            return False
        if data.get('confirm') != self.id: 
            return False
        self.confirmed = True
        db.session.add(self)
        return True

对应界面,点击这里跳转报错

bytes文件怎么设置_flask

报错(只截取了部分):

File "E:\program\Python\Web\MySecondFlaskApp\app\auth\views.py", line 99, in resend_confirmation
    token = current_user.generate_confirmation_token()
  File "E:\program\Python\Web\MySecondFlaskApp\app\models.py", line 44, in generate_confirmation_token
    return s.dumps({'confirm': self.id}).decode('utf-8')
  File "E:\program\Python\Web\MySecondFlaskApp\venv\lib\site-packages\itsdangerous\serializer.py", line 208, in dumps
    rv = self.make_signer(salt).sign(payload)
  File "E:\program\Python\Web\MySecondFlaskApp\venv\lib\site-packages\itsdangerous\timed.py", line 55, in sign
    return value + sep + self.get_signature(value)
  File "E:\program\Python\Web\MySecondFlaskApp\venv\lib\site-packages\itsdangerous\signer.py", line 209, in get_signature
    key = self.derive_key()
  File "E:\program\Python\Web\MySecondFlaskApp\venv\lib\site-packages\itsdangerous\signer.py", line 195, in derive_key
    bytes, self.digest_method(self.salt + b"signer" + secret_key).digest()
    TypeError: unsupported operand type(s) for +: 'int' and 'bytes'
  • 尝试1:
    看报错的倒数第二行,self.digest_method(self.salt + b"signer" + secret_key).digest() 三个参数相加,以为是其中有参数类型变成int类型了,salt,signer都不是我指定的,那问题就出在secret_key。于是跑到配置文件把SECRET_KEY = 'hard to guess string’改成b ‘hard to guess string’ ,果不其然,失败
  • 尝试2:
    看s.dumps的源码,并且结合这两篇文章浅浅理解了一下源码
    文章1 和 文章2 大致猜想是参数编码的问题,编码导致数据类型出错。查资料得知:

str通过encode()方法可以编码为指定的bytes。反过来,当从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法。反之,则使用encode()方法即可!

  • 怪不得代码里对token先decode 再encode的,那我把编码删掉,或者调换一下,先encode再decode… 果不其然,都失败

忽然想起来,前面跟着狗书导的包
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer 报错

ImportError: cannot import name 'TimedJSONWebSignatureSerializer' from 'itsdangerous

一查原来是我安装的 itsdangerous的版本(2.1.2)已经摒弃了这个类,在Stack Overflow上查到的解决办法是,from itsdangerous.url_safe import URLSafeTimedSerializer as Serializer。其他代码没变。

是不是这个包有问题,毕竟换了一个包,代码可能需要稍微修改。
打开看看这个包的源码,从url_safe.py 中找到了父类Serializer,进入父类包serializer.py 找到,看看各个参数的类型。等等,我肯定是哪里传参传错了,才会导致int和bytes类型相加。但是是哪里呢?不知道


耗费了一上午的时间,有点想走捷径:降低itsdangerous的版本,完全跟着书上走算了
但是好像答案就在眼前了…

走,去Stack Overflow上看看,虽然不知道怎么描述问题…把最后一行的报错信息粘上去试试吧

这个好像跟我的问题一样!Unsupported operand type(s) when using URLSafeTimedSerializer.dumps from itsdangerous 不应该给传入Serializer类的第二个参数传入int类型expiration=3600,而报错确实是int类型和bytes类型相加…刚才太关注bytes了,忽略了int是从哪里来的
参考答案是把 expiration 这个参数去掉了

但是没有有效期怎么行呢?该在什么地方设置token的有效期呢?

(参考答案还让把 .decode(‘utf-8’)去掉,因为s.dump已经是str了,不需要再解码。如果需要bytes类型,可以相应地执行编码 .encode(‘utf-8’))

继续搜索:the “dumps” method of itsdangerous throws a TypeError expiration根本就不是Serializer的参数,而设置有效期,是在s.loads函数里面,叫max_age
更改代码为:

def generate_confirmation_token(self):
        s = Serializer(current_app.config['SECRET_KEY'], 'confirmation')
        return s.dumps({'confirm': self.id})

    def confirm(self, token, max_age=3600):
        s = Serializer(current_app.config['SECRET_KEY'], 'confirmation')
        try:
            data = s.loads(token.encode('utf-8'),max_age=max_age)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True

记得相应调整调用这俩函数的地方,尤其比如单元测试里面的:

  • test_expired_confirmation_token()函数中:
    token = u.generate_confirmation_token(1),把括号中的1去掉。另外self.assertFalse(u.confirm(token))改成 self.assertFalse(u.confirm(token,max_age=1))

最后修改model后得迁移一下数据库,再运行,成功啦!:

bytes文件怎么设置_学习_02

总结: Stack Overflow真好用 …(×
想解决问题,一个重要因素是怎样准确描述问题。有时候问题不是没有办法解决,而是问错方向了才找不到答案。
但是刚开始,你可能不熟悉这个问题,问不到点子上,会搜寻一些不相关的问题答案,但是在搜寻答案和查看源码的过程中不断修正问的问题,使之更接近bug的本质。最终问出恰当的问题,搜到正确地答案。这个过程是非常重要且不可避免的。我相信很少有人每次遇到bug,都能马上找到答案