subclasses for all binary data types. This custom function is coded in the upcoming
mailtools package of this chapter (Example 13-23). Because it is used by email to en-
code from bytes to text at initialization time, it is able to decode to ASCII text per
Unicode as an extra step, after running the original call to perform Base64 encoding
and arrange content-encoding headers. The fact that email does not do this extra Uni-
code decoding step itself is a genuine bug in that package (albeit, one introduced by
changes elsewhere in Python standard libraries), but the workaround does its job:
# in mailtools.mailSender module ahead in this chapter...
def fix_encode_base64(msgobj):
from email.encoders import encode_base64
encode_base64(msgobj) # what email does normally: leaves bytes
bytes = msgobj.get_payload() # bytes fails in email pkg on text gen
text = bytes.decode('ascii') # decode to unicode str so text gen works
...line splitting logic omitted...
msgobj.set_payload('\n'.join(lines))
>>> from email.mime.image import MIMEImage
>>> from mailtools.mailSender import fix_encode_base64 # use custom workaround
>>> bytes = open('monkeys.jpg', 'rb').read()
>>> m = MIMEImage(bytes, _encoder=fix_encode_base64) # convert to ascii str
>>> print(m.as_string()[:500])
Content-Type: image/jpeg
MIME-Version: 1.0
Content-Transfer-Encoding: base64
/9j/4AAQSkZJRgABAQEAeAB4AAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcG
BwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwM
DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAHoAvQDASIA
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc
>>> print(m) # to print the entire message: very long
Another possible workaround involves defining a custom MIMEImage class that is like
the original but does not attempt to perform Base64 ending on creation; that way, we
could encode and translate to str before message object creation, but still make use of
the original class’s header-generation logic. If you take this route, though, you’ll find
that it requires repeating (really, cutting and pasting) far too much of the original logic
to be reasonable—this repeated code would have to mirror any future email changes:
>>> from email.mime.nonmultipart import MIMENonMultipart
>>> class MyImage(MIMENonMultipart):
... def __init__(self, imagedata, subtype):
... MIMENonMultipart.__init__(self, 'image', subtype)
... self.set_payload(_imagedata)
...repeat all the base64 logic here, with an extra ASCII Unicode decode...
>>> m = MyImage(text_from_bytes)
Interestingly, this regression in email actually reflects an unrelated change in Python’s
base64 module made in 2007, which was completely benign until the Python 3.X bytes/
940 | Chapter 13: Client-Side Scripting