【Python】Flask-Mail发送邮件报错解决方案(UnicodeEncodeError 'ascii' codec can't encode characters in position 52-55 ordinal not in range(128))

发布时间 2023-03-27 15:35:32作者: 双份浓缩馥芮白

✨报错提示

完全相同的代码在macOS上运行正常

在Windows上运行报错

报错提示如下:

Traceback (most recent call last):
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask\app.py", line 2528, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask\app.py", line 1825, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask\app.py", line 1823, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "E:\Code\Study\Flask_Study\Flask05_Q&A_Platform\blueprints\auth.py", line 94, in get_email_captcha
    mail.send(message)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask_mail.py", line 492, in send
    message.send(connection)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask_mail.py", line 427, in send
    connection.send(self)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask_mail.py", line 190, in send
    message.as_bytes() if PY3 else message.as_string(),
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\site-packages\flask_mail.py", line 385, in as_bytes
    return self._message().as_bytes()
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\email\message.py", line 178, in as_bytes
    g.flatten(self, unixfrom=unixfrom)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\email\generator.py", line 116, in flatten
    self._write(msg)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\email\generator.py", line 199, in _write
    self._write_headers(msg)
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\email\generator.py", line 422, in _write_headers
    self._fp.write(self.policy.fold_binary(h, v))
  File "C:\Users\Doubl\miniconda3\envs\Temp\lib\email\policy.py", line 202, in fold_binary
    return folded.encode(charset, 'surrogateescape')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 53-56: ordinal not in range(128)

✨错误排查

根据报错提示 我们定位到环境外部库的

${conda_dir}\miniconda3\envs\${env_name}\lib\email\policy.py

阅读EmailPolicy源码可知

@_extend_docstrings
class EmailPolicy(Policy):

    """+
    PROVISIONAL

    The API extensions enabled by this policy are currently provisional.
    Refer to the documentation for details.

    This policy adds new header parsing and folding algorithms.  Instead of
    simple strings, headers are custom objects with custom attributes
    depending on the type of the field.  The folding algorithm fully
    implements RFCs 2047 and 5322.

    In addition to the settable attributes listed above that apply to
    all Policies, this policy adds the following additional attributes:

    utf8                -- if False (the default) message headers will be
                           serialized as ASCII, using encoded words to encode
                           any non-ASCII characters in the source strings.  If
                           True, the message headers will be serialized using
                           utf8 and will not contain encoded words (see RFC
                           6532 for more on this serialization format).

    refold_source       -- if the value for a header in the Message object
                           came from the parsing of some source, this attribute
                           indicates whether or not a generator should refold
                           that value when transforming the message back into
                           stream form.  The possible values are:

                           none  -- all source values use original folding
                           long  -- source values that have any line that is
                                    longer than max_line_length will be
                                    refolded
                           all  -- all values are refolded.

                           The default is 'long'.

    header_factory      -- a callable that takes two arguments, 'name' and
                           'value', where 'name' is a header field name and
                           'value' is an unfolded header field value, and
                           returns a string-like object that represents that
                           header.  A default header_factory is provided that
                           understands some of the RFC5322 header field types.
                           (Currently address fields and date fields have
                           special treatment, while all other fields are
                           treated as unstructured.  This list will be
                           completed before the extension is marked stable.)

    content_manager     -- an object with at least two methods: get_content
                           and set_content.  When the get_content or
                           set_content method of a Message object is called,
                           it calls the corresponding method of this object,
                           passing it the message object as its first argument,
                           and any arguments or keywords that were passed to
                           it as additional arguments.  The default
                           content_manager is
                           :data:`~email.contentmanager.raw_data_manager`.

    """

同时我们阅读导致报错的fold_binary方法

def fold_binary(self, name, value):
    """+
    The same as fold if cte_type is 7bit, except that the returned value is
    bytes.

    If cte_type is 8bit, non-ASCII binary data is converted back into
    bytes.  Headers with binary data are not refolded, regardless of the
    refold_header setting, since there is no way to know whether the binary
    data consists of single byte characters or multibyte characters.

    If utf8 is true, headers are encoded to utf8, otherwise to ascii with
    non-ASCII unicode rendered as encoded words.

    """
    folded = self._fold(name, value, refold_binary=self.cte_type=='7bit')
    charset = 'utf8' if self.utf8 else 'ascii'
    return folded.encode(charset, 'surrogateescape')

尝试在charset = 'utf8' if self.utf8 else 'ascii'后添加print(charset)

重新启动项目

!一定要重新启动项目 Flask的debug=True对于外部库文件变化不会自动重新启动项目

输出结果

127.0.0.1 - - [27/Mar/2023 15:10:37] "GET /auth/mail/test?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 304 -
charset: ascii
charset: ascii
charset: ascii
charset: ascii
charset: ascii
charset: ascii
charset: ascii
charset: ascii
127.0.0.1 - - [27/Mar/2023 15:12:07] "GET /auth/mail/test HTTP/1.1" 500 -

因此我们重新回到policy.py

删除添加的print(charset)

可以确定问题是charset = 'utf8' if self.utf8 else 'ascii'这行代码导致


✨解决方案

反复阅读外部库源码

没有找到可以传入charset参数的地方

本解决方案涉及到修改外部库源代码

每次更换环境都需要修改


建议使用其他module开发邮件发送模块

建议使用其他module开发邮件发送模块

建议使用其他module开发邮件发送模块


定位到${conda_dir}\miniconda3\envs\${env_name}\lib\email\policy.py第88行

刚好在构造函数前面

message_factory = EmailMessage
utf8 = False
refold_source = 'long'
header_factory = HeaderRegistry()
content_manager = raw_data_manager

def __init__(self, **kw):
    # Ensure that each new instance gets a unique header factory
    # (as opposed to clones, which share the factory).
    if 'header_factory' not in kw:
        object.__setattr__(self, 'header_factory', HeaderRegistry())
    super().__init__(**kw)

修改utf8 = Falseutf8 = True


⭐转载请注明出处

本文作者:双份浓缩馥芮白

原文链接:https://www.cnblogs.com/Flat-White/p/17261697.html

版权所有,如需转载请注明出处。