Kaydet (Commit) 29d1bc08 authored tarafından R David Murray's avatar R David Murray

#24277: The new email API is no longer provisional.

This is a wholesale reorganization and editing of the email documentation to
make the new API the standard one, and the old API the 'legacy' one.  The
default is still the compat32 policy, for backward compatibility.  We will
change that eventually.
üst 23e86337
#!/usr/bin/env python3
import smtplib
from email.message import EmailMessage
from email.headerregistry import Address
from email.utils import make_msgid
# Create the base text message.
msg = EmailMessage()
msg['Subject'] = "Ayons asperges pour le déjeuner"
msg['From'] = Address("Pepé Le Pew", "pepe", "example.com")
msg['To'] = (Address("Penelope Pussycat", "penelope", "example.com"),
Address("Fabrette Pussycat", "fabrette", "example.com"))
msg.set_content("""\
Salut!
Cela ressemble à un excellent recipie[1] déjeuner.
[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718
--Pepé
""")
# Add the html version. This converts the message into a multipart/alternative
# container, with the original text message as the first part and the new html
# message as the second part.
asparagus_cid = make_msgid()
msg.add_alternative("""\
<html>
<head></head>
<body>
<p>Salut!<\p>
<p>Cela ressemble à un excellent
<a href="http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718>
recipie
</a> déjeuner.
</p>
<img src="cid:{asparagus_cid}" \>
</body>
</html>
""".format(asparagus_cid=asparagus_cid[1:-1]), subtype='html')
# note that we needed to peel the <> off the msgid for use in the html.
# Now add the related image to the html part.
with open("roasted-asparagus.jpg", 'rb') as img:
msg.get_payload()[1].add_related(img.read(), 'image', 'jpeg',
cid=asparagus_cid)
# Make a local copy of what we are going to send.
with open('outgoing.msg', 'wb') as f:
f.write(bytes(msg))
# Send the message via local SMTP server.
with smtplib.SMTP('localhost') as s:
s.send_message(msg)
...@@ -2,47 +2,55 @@ ...@@ -2,47 +2,55 @@
import smtplib import smtplib
from email.mime.multipart import MIMEMultipart from email.message import EmailMessage
from email.mime.text import MIMEText from email.headerregistry import Address
from email.utils import make_msgid
# me == my email address
# you == recipient's email address # Create the base text message.
me = "my@email.com" msg = EmailMessage()
you = "your@email.com" msg['Subject'] = "Ayons asperges pour le déjeuner"
msg['From'] = Address("Pepé Le Pew", "pepe", "example.com")
# Create message container - the correct MIME type is multipart/alternative. msg['To'] = (Address("Penelope Pussycat", "penelope", "example.com"),
msg = MIMEMultipart('alternative') Address("Fabrette Pussycat", "fabrette", "example.com"))
msg['Subject'] = "Link" msg.set_content("""\
msg['From'] = me Salut!
msg['To'] = you
Cela ressemble à un excellent recipie[1] déjeuner.
# Create the body of the message (a plain-text and an HTML version).
text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttps://www.python.org" [1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718
html = """\
--Pepé
""")
# Add the html version. This converts the message into a multipart/alternative
# container, with the original text message as the first part and the new html
# message as the second part.
asparagus_cid = make_msgid()
msg.add_alternative("""\
<html> <html>
<head></head> <head></head>
<body> <body>
<p>Hi!<br> <p>Salut!<\p>
How are you?<br> <p>Cela ressemble à un excellent
Here is the <a href="https://www.python.org">link</a> you wanted. <a href="http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718>
recipie
</a> déjeuner.
</p> </p>
<img src="cid:{asparagus_cid}" \>
</body> </body>
</html> </html>
""" """.format(asparagus_cid=asparagus_cid[1:-1]), subtype='html')
# note that we needed to peel the <> off the msgid for use in the html.
# Record the MIME types of both parts - text/plain and text/html. # Now add the related image to the html part.
part1 = MIMEText(text, 'plain') with open("roasted-asparagus.jpg", 'rb') as img:
part2 = MIMEText(html, 'html') msg.get_payload()[1].add_related(img.read(), 'image', 'jpeg',
cid=asparagus_cid)
# Attach parts into message container. # Make a local copy of what we are going to send.
# According to RFC 2046, the last part of a multipart message, in this case with open('outgoing.msg', 'wb') as f:
# the HTML message, is best and preferred. f.write(bytes(msg))
msg.attach(part1)
msg.attach(part2)
# Send the message via local SMTP server. # Send the message via local SMTP server.
s = smtplib.SMTP('localhost') with smtplib.SMTP('localhost') as s:
# sendmail function takes 3 arguments: sender's address, recipient's address s.send_message(msg)
# and message to send - here it is sent as one string.
s.sendmail(me, you, msg.as_string())
s.quit()
...@@ -3,22 +3,14 @@ ...@@ -3,22 +3,14 @@
"""Send the contents of a directory as a MIME message.""" """Send the contents of a directory as a MIME message."""
import os import os
import sys
import smtplib import smtplib
# For guessing MIME type based on file name extension # For guessing MIME type based on file name extension
import mimetypes import mimetypes
from argparse import ArgumentParser from argparse import ArgumentParser
from email import encoders from email.message import EmailMessage
from email.message import Message from email.policy import SMTP
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
COMMASPACE = ', '
def main(): def main():
...@@ -47,12 +39,12 @@ must be running an SMTP server. ...@@ -47,12 +39,12 @@ must be running an SMTP server.
directory = args.directory directory = args.directory
if not directory: if not directory:
directory = '.' directory = '.'
# Create the enclosing (outer) message # Create the message
outer = MIMEMultipart() msg = EmailMessage()
outer['Subject'] = 'Contents of directory %s' % os.path.abspath(directory) msg['Subject'] = 'Contents of directory %s' % os.path.abspath(directory)
outer['To'] = COMMASPACE.join(args.recipients) msg['To'] = ', '.join(args.recipients)
outer['From'] = args.sender msg['From'] = args.sender
outer.preamble = 'You will not see this in a MIME-aware mail reader.\n' msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
for filename in os.listdir(directory): for filename in os.listdir(directory):
path = os.path.join(directory, filename) path = os.path.join(directory, filename)
...@@ -67,33 +59,18 @@ must be running an SMTP server. ...@@ -67,33 +59,18 @@ must be running an SMTP server.
# use a generic bag-of-bits type. # use a generic bag-of-bits type.
ctype = 'application/octet-stream' ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1) maintype, subtype = ctype.split('/', 1)
if maintype == 'text': with open(path, 'rb') as fp:
with open(path) as fp: msg.add_attachment(fp.read(),
# Note: we should handle calculating the charset maintype=maintype,
msg = MIMEText(fp.read(), _subtype=subtype) subtype=subtype,
elif maintype == 'image': filename=filename)
with open(path, 'rb') as fp:
msg = MIMEImage(fp.read(), _subtype=subtype)
elif maintype == 'audio':
with open(path, 'rb') as fp:
msg = MIMEAudio(fp.read(), _subtype=subtype)
else:
with open(path, 'rb') as fp:
msg = MIMEBase(maintype, subtype)
msg.set_payload(fp.read())
# Encode the payload using Base64
encoders.encode_base64(msg)
# Set the filename parameter
msg.add_header('Content-Disposition', 'attachment', filename=filename)
outer.attach(msg)
# Now send or store the message # Now send or store the message
composed = outer.as_string()
if args.output: if args.output:
with open(args.output, 'w') as fp: with open(args.output, 'wb') as fp:
fp.write(composed) fp.write(msg.as_bytes(policy=SMTP))
else: else:
with smtplib.SMTP('localhost') as s: with smtplib.SMTP('localhost') as s:
s.sendmail(args.sender, args.recipients, composed) s.send_message(msg)
if __name__ == '__main__': if __name__ == '__main__':
......
# Import the email modules we'll need # Import the email modules we'll need
from email.parser import Parser from email.parser import BytesParser, Parser
from email.policy import default
# If the e-mail headers are in a file, uncomment these two lines: # If the e-mail headers are in a file, uncomment these two lines:
# with open(messagefile) as fp: # with open(messagefile, 'rb') as fp:
# headers = Parser().parse(fp) # headers = BytesParser(policy=default).parse(fp)
# Or for parsing headers in a string, use: # Or for parsing headers in a string (this is an uncommon operation), use:
headers = Parser().parsestr('From: <user@example.com>\n' headers = Parser(policy=default).parsestr(
'From: Foo Bar <user@example.com>\n'
'To: <someone_else@example.com>\n' 'To: <someone_else@example.com>\n'
'Subject: Test message\n' 'Subject: Test message\n'
'\n' '\n'
'Body would go here\n') 'Body would go here\n')
# Now the header items can be accessed as a dictionary: # Now the header items can be accessed as a dictionary:
print('To: %s' % headers['to']) print('To: {}'.format(headers['to']))
print('From: %s' % headers['from']) print('From: {}'.format(headers['from']))
print('Subject: %s' % headers['subject']) print('Subject: {}'.format(headers['subject']))
# You can also access the parts of the addresses:
print('Recipient username: {}'.format(headers['to'].addresses[0].username))
print('Sender name: {}'.format(headers['from'].addresses[0].display_name))
# Import smtplib for the actual sending function # Import smtplib for the actual sending function
import smtplib import smtplib
# Here are the email package modules we'll need # And imghdr to find the types of our images
from email.mime.image import MIMEImage import imghdr
from email.mime.multipart import MIMEMultipart
COMMASPACE = ', ' # Here are the email package modules we'll need
from email.message import EmailMessage
# Create the container (outer) email message. # Create the container email message.
msg = MIMEMultipart() msg = EmailMessage()
msg['Subject'] = 'Our family reunion' msg['Subject'] = 'Our family reunion'
# me == the sender's email address # me == the sender's email address
# family = the list of all recipients' email addresses # family = the list of all recipients' email addresses
msg['From'] = me msg['From'] = me
msg['To'] = COMMASPACE.join(family) msg['To'] = ', '.join(family)
msg.preamble = 'Our family reunion' msg.preamble = 'Our family reunion'
# Assume we know that the image files are all in PNG format # Open the files in binary mode. Use imghdr to figure out the
# MIME subtype for each specific image.
for file in pngfiles: for file in pngfiles:
# Open the files in binary mode. Let the MIMEImage class automatically
# guess the specific image type.
with open(file, 'rb') as fp: with open(file, 'rb') as fp:
img = MIMEImage(fp.read()) img_data = fp.read()
msg.attach(img) msg.add_attachment(img_data, maintype='image',
subtype=imghdr.what(None, img_data))
# Send the email via our own SMTP server. # Send the email via our own SMTP server.
s = smtplib.SMTP('localhost') with smtplib.SMTP('localhost') as s:
s.send_message(msg) s.send_message(msg)
s.quit()
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
import smtplib import smtplib
# Import the email modules we'll need # Import the email modules we'll need
from email.mime.text import MIMEText from email.message import EmailMessage
# Open a plain text file for reading. For this example, assume that # Open the plain text file whose name is in textfile for reading.
# the text file contains only ASCII characters.
with open(textfile) as fp: with open(textfile) as fp:
# Create a text/plain message # Create a text/plain message
msg = MIMEText(fp.read()) msg = EmailMessage()
msg.set_content(fp.read())
# me == the sender's email address # me == the sender's email address
# you == the recipient's email address # you == the recipient's email address
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
"""Unpack a MIME message into a directory of files.""" """Unpack a MIME message into a directory of files."""
import os import os
import sys
import email import email
import errno
import mimetypes import mimetypes
from email.policy import default
from argparse import ArgumentParser from argparse import ArgumentParser
...@@ -22,8 +22,8 @@ Unpack a MIME message into a directory of files. ...@@ -22,8 +22,8 @@ Unpack a MIME message into a directory of files.
parser.add_argument('msgfile') parser.add_argument('msgfile')
args = parser.parse_args() args = parser.parse_args()
with open(args.msgfile) as fp: with open(args.msgfile, 'rb') as fp:
msg = email.message_from_file(fp) msg = email.message_from_binary_file(fp, policy=default)
try: try:
os.mkdir(args.directory) os.mkdir(args.directory)
......
...@@ -8,6 +8,11 @@ ...@@ -8,6 +8,11 @@
-------------- --------------
This module is part of the legacy (``Compat32``) email API. In the new
API only the aliases table is used.
The remaining text in this section is the original documentation of the module.
This module provides a class :class:`Charset` for representing character sets This module provides a class :class:`Charset` for representing character sets
and character set conversions in email messages, as well as a character set and character set conversions in email messages, as well as a character set
registry and several convenience methods for manipulating this registry. registry and several convenience methods for manipulating this registry.
......
This diff is collapsed.
...@@ -8,6 +8,12 @@ ...@@ -8,6 +8,12 @@
-------------- --------------
This module is part of the legacy (``Compat32``) email API. In the
new API the functionality is provided by the *cte* parameter of
the :meth:`~email.message.EmailMessage.set_content` method.
The remaining text in this section is the original documentation of the module.
When creating :class:`~email.message.Message` objects from scratch, you often When creating :class:`~email.message.Message` objects from scratch, you often
need to encode the payloads for transport through compliant mail servers. This need to encode the payloads for transport through compliant mail servers. This
is especially true for :mimetype:`image/\*` and :mimetype:`text/\*` type messages is especially true for :mimetype:`image/\*` and :mimetype:`text/\*` type messages
......
...@@ -20,33 +20,27 @@ The following exception classes are defined in the :mod:`email.errors` module: ...@@ -20,33 +20,27 @@ The following exception classes are defined in the :mod:`email.errors` module:
.. exception:: MessageParseError() .. exception:: MessageParseError()
This is the base class for exceptions raised by the :class:`~email.parser.Parser` This is the base class for exceptions raised by the
class. It is derived from :exc:`MessageError`. :class:`~email.parser.Parser` class. It is derived from
:exc:`MessageError`. This class is also used internally by the parser used
by :mod:`~email.headerregistry`.
.. exception:: HeaderParseError() .. exception:: HeaderParseError()
Raised under some error conditions when parsing the :rfc:`2822` headers of a Raised under some error conditions when parsing the :rfc:`5322` headers of a
message, this class is derived from :exc:`MessageParseError`. It can be raised message, this class is derived from :exc:`MessageParseError`. The
from the :meth:`Parser.parse <email.parser.Parser.parse>` or :meth:`~email.message.EmailMessage.set_boundary` method will raise this
:meth:`Parser.parsestr <email.parser.Parser.parsestr>` methods. error if the content type is unknown when the method is called.
:class:`~email.header.Header` may raise this error for certain base64
Situations where it can be raised include finding an envelope header after the decoding errors, and when an attempt is made to create a header that appears
first :rfc:`2822` header of the message, finding a continuation line before the to contain an embedded header (that is, there is what is supposed to be a
first :rfc:`2822` header is found, or finding a line in the headers which is continuation line that has no leading whitespace and looks like a header).
neither a header or a continuation line.
.. exception:: BoundaryError() .. exception:: BoundaryError()
Raised under some error conditions when parsing the :rfc:`2822` headers of a Deprecated and no longer used.
message, this class is derived from :exc:`MessageParseError`. It can be raised
from the :meth:`Parser.parse <email.parser.Parser.parse>` or
:meth:`Parser.parsestr <email.parser.Parser.parsestr>` methods.
Situations where it can be raised include not being able to find the starting or
terminating boundary in a :mimetype:`multipart/\*` message when strict parsing
is used.
.. exception:: MultipartConversionError() .. exception:: MultipartConversionError()
...@@ -64,14 +58,14 @@ The following exception classes are defined in the :mod:`email.errors` module: ...@@ -64,14 +58,14 @@ The following exception classes are defined in the :mod:`email.errors` module:
:class:`~email.mime.nonmultipart.MIMENonMultipart` (e.g. :class:`~email.mime.nonmultipart.MIMENonMultipart` (e.g.
:class:`~email.mime.image.MIMEImage`). :class:`~email.mime.image.MIMEImage`).
Here's the list of the defects that the :class:`~email.parser.FeedParser`
Here is the list of the defects that the :class:`~email.parser.FeedParser`
can find while parsing messages. Note that the defects are added to the message can find while parsing messages. Note that the defects are added to the message
where the problem was found, so for example, if a message nested inside a where the problem was found, so for example, if a message nested inside a
:mimetype:`multipart/alternative` had a malformed header, that nested message :mimetype:`multipart/alternative` had a malformed header, that nested message
object would have a defect, but the containing messages would not. object would have a defect, but the containing messages would not.
All defect classes are subclassed from :class:`email.errors.MessageDefect`, but All defect classes are subclassed from :class:`email.errors.MessageDefect`.
this class is *not* an exception!
* :class:`NoBoundaryInMultipartDefect` -- A message claimed to be a multipart, * :class:`NoBoundaryInMultipartDefect` -- A message claimed to be a multipart,
but had no :mimetype:`boundary` parameter. but had no :mimetype:`boundary` parameter.
......
...@@ -6,13 +6,14 @@ ...@@ -6,13 +6,14 @@
Here are a few examples of how to use the :mod:`email` package to read, write, Here are a few examples of how to use the :mod:`email` package to read, write,
and send simple email messages, as well as more complex MIME messages. and send simple email messages, as well as more complex MIME messages.
First, let's see how to create and send a simple text message: First, let's see how to create and send a simple text message (both the
text content and the addresses may contain unicode characters):
.. literalinclude:: ../includes/email-simple.py .. literalinclude:: ../includes/email-simple.py
And parsing RFC822 headers can easily be done by the parse(filename) or Parsing RFC822 headers can easily be done by the using the classes
parsestr(message_as_string) methods of the Parser() class: from the :mod:`~email.parser` module:
.. literalinclude:: ../includes/email-headers.py .. literalinclude:: ../includes/email-headers.py
...@@ -34,30 +35,19 @@ above, into a directory of files: ...@@ -34,30 +35,19 @@ above, into a directory of files:
.. literalinclude:: ../includes/email-unpack.py .. literalinclude:: ../includes/email-unpack.py
Here's an example of how to create an HTML message with an alternative plain Here's an example of how to create an HTML message with an alternative plain
text version: [2]_ text version. To make things a bit more interesting, we include a related
image in the html part, and we save a copy of what we are going to send to
disk, as well as sending it.
.. literalinclude:: ../includes/email-alternative.py .. literalinclude:: ../includes/email-alternative.py
.. _email-contentmanager-api-examples: If we were sent the message from the last example, here is one way we could
process it:
Examples using the Provisional API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is a reworking of the last example using the provisional API. To make
things a bit more interesting, we include a related image in the html part, and
we save a copy of what we are going to send to disk, as well as sending it.
This example also shows how easy it is to include non-ASCII, and simplifies the
sending of the message using the :meth:`.send_message` method of the
:mod:`smtplib` module.
.. literalinclude:: ../includes/email-alternative-new-api.py
If we were instead sent the message from the last example, here is one
way we could process it:
.. literalinclude:: ../includes/email-read-alternative-new-api.py .. literalinclude:: ../includes/email-read-alternative.py
Up to the prompt, the output from the above is: Up to the prompt, the output from the above is:
...@@ -75,4 +65,3 @@ Up to the prompt, the output from the above is: ...@@ -75,4 +65,3 @@ Up to the prompt, the output from the above is:
.. rubric:: Footnotes .. rubric:: Footnotes
.. [1] Thanks to Matthew Dixon Cowles for the original inspiration and examples. .. [1] Thanks to Matthew Dixon Cowles for the original inspiration and examples.
.. [2] Contributed by Martin Matejek.
This diff is collapsed.
...@@ -8,6 +8,14 @@ ...@@ -8,6 +8,14 @@
-------------- --------------
This module is part of the legacy (``Compat32``) email API. In the current API
encoding and decoding of headers is handled transparently by the
dictionary-like API of the :class:`~email.message.EmailMessage` class. In
addition to uses in legacy code, this module can be useful in applications that
need to completely control the character sets used when encoding headers.
The remaining text in this section is the original documentation of the module.
:rfc:`2822` is the base standard that describes the format of email messages. :rfc:`2822` is the base standard that describes the format of email messages.
It derives from the older :rfc:`822` standard which came into widespread use at It derives from the older :rfc:`822` standard which came into widespread use at
a time when most email was composed of ASCII characters only. :rfc:`2822` is a a time when most email was composed of ASCII characters only. :rfc:`2822` is a
......
...@@ -7,19 +7,13 @@ ...@@ -7,19 +7,13 @@
.. moduleauthor:: R. David Murray <rdmurray@bitdance.com> .. moduleauthor:: R. David Murray <rdmurray@bitdance.com>
.. sectionauthor:: R. David Murray <rdmurray@bitdance.com> .. sectionauthor:: R. David Murray <rdmurray@bitdance.com>
.. versionadded:: 3.3
as a :term:`provisional module <provisional package>`.
**Source code:** :source:`Lib/email/headerregistry.py` **Source code:** :source:`Lib/email/headerregistry.py`
.. note:: --------------
The headerregistry module has been included in the standard library on a .. versionadded:: 3.3 as a :term:`provisional module <provisional package>`.
:term:`provisional basis <provisional package>`. Backwards incompatible
changes (up to and including removal of the module) may occur if deemed
necessary by the core developers.
-------------- .. versionchanged:: 3.6 provisonal status removed.
Headers are represented by customized subclasses of :class:`str`. The Headers are represented by customized subclasses of :class:`str`. The
particular class used to represent a given header is determined by the particular class used to represent a given header is determined by the
...@@ -86,10 +80,11 @@ headers. ...@@ -86,10 +80,11 @@ headers.
.. method:: fold(*, policy) .. method:: fold(*, policy)
Return a string containing :attr:`~email.policy.Policy.linesep` Return a string containing :attr:`~email.policy.Policy.linesep`
characters as required to correctly fold the header according characters as required to correctly fold the header according to
to *policy*. A :attr:`~email.policy.Policy.cte_type` of *policy*. A :attr:`~email.policy.Policy.cte_type` of ``8bit`` will be
``8bit`` will be treated as if it were ``7bit``, since strings treated as if it were ``7bit``, since headers may not contain arbitrary
may not contain binary data. binary data. If :attr:`~email.policy.EmailPolicy.utf8` is ``False``,
non-ASCII data will be :rfc:`2047` encoded.
``BaseHeader`` by itself cannot be used to create a header object. It ``BaseHeader`` by itself cannot be used to create a header object. It
...@@ -106,7 +101,7 @@ headers. ...@@ -106,7 +101,7 @@ headers.
values for at least the keys ``decoded`` and ``defects``. ``decoded`` values for at least the keys ``decoded`` and ``defects``. ``decoded``
should be the string value for the header (that is, the header value fully should be the string value for the header (that is, the header value fully
decoded to unicode). The parse method should assume that *string* may decoded to unicode). The parse method should assume that *string* may
contain transport encoded parts, but should correctly handle all valid contain content-transfer-encoded parts, but should correctly handle all valid
unicode characters as well so that it can parse un-encoded header values. unicode characters as well so that it can parse un-encoded header values.
``BaseHeader``'s ``__new__`` then creates the header instance, and calls its ``BaseHeader``'s ``__new__`` then creates the header instance, and calls its
...@@ -135,11 +130,10 @@ headers. ...@@ -135,11 +130,10 @@ headers.
mechanism for encoding non-ASCII text as ASCII characters within a header mechanism for encoding non-ASCII text as ASCII characters within a header
value. When a *value* containing encoded words is passed to the value. When a *value* containing encoded words is passed to the
constructor, the ``UnstructuredHeader`` parser converts such encoded words constructor, the ``UnstructuredHeader`` parser converts such encoded words
back in to the original unicode, following the :rfc:`2047` rules for into unicode, following the :rfc:`2047` rules for unstructured text. The
unstructured text. The parser uses heuristics to attempt to decode certain parser uses heuristics to attempt to decode certain non-compliant encoded
non-compliant encoded words. Defects are registered in such cases, as well words. Defects are registered in such cases, as well as defects for issues
as defects for issues such as invalid characters within the encoded words or such as invalid characters within the encoded words or the non-encoded text.
the non-encoded text.
This header type provides no additional attributes. This header type provides no additional attributes.
...@@ -213,15 +207,16 @@ headers. ...@@ -213,15 +207,16 @@ headers.
the list of addresses is "flattened" into a one dimensional list). the list of addresses is "flattened" into a one dimensional list).
The ``decoded`` value of the header will have all encoded words decoded to The ``decoded`` value of the header will have all encoded words decoded to
unicode. :class:`~encodings.idna` encoded domain names are also decoded to unicode. The unicode. :class:`~encodings.idna` encoded domain names are also decoded to
``decoded`` value is set by :attr:`~str.join`\ ing the :class:`str` value of unicode. The ``decoded`` value is set by :attr:`~str.join`\ ing the
the elements of the ``groups`` attribute with ``', '``. :class:`str` value of the elements of the ``groups`` attribute with ``',
'``.
A list of :class:`.Address` and :class:`.Group` objects in any combination A list of :class:`.Address` and :class:`.Group` objects in any combination
may be used to set the value of an address header. ``Group`` objects whose may be used to set the value of an address header. ``Group`` objects whose
``display_name`` is ``None`` will be interpreted as single addresses, which ``display_name`` is ``None`` will be interpreted as single addresses, which
allows an address list to be copied with groups intact by using the list allows an address list to be copied with groups intact by using the list
obtained ``groups`` attribute of the source header. obtained from the ``groups`` attribute of the source header.
.. class:: SingleAddressHeader .. class:: SingleAddressHeader
...@@ -267,7 +262,7 @@ variant, :attr:`~.BaseHeader.max_count` is set to 1. ...@@ -267,7 +262,7 @@ variant, :attr:`~.BaseHeader.max_count` is set to 1.
.. class:: ParameterizedMIMEHeader .. class:: ParameterizedMIMEHeader
MOME headers all start with the prefix 'Content-'. Each specific header has MIME headers all start with the prefix 'Content-'. Each specific header has
a certain value, described under the class for that header. Some can a certain value, described under the class for that header. Some can
also take a list of supplemental parameters, which have a common format. also take a list of supplemental parameters, which have a common format.
This class serves as a base for all the MIME headers that take parameters. This class serves as a base for all the MIME headers that take parameters.
......
This diff is collapsed.
...@@ -8,6 +8,11 @@ ...@@ -8,6 +8,11 @@
-------------- --------------
This module is part of the legacy (``Compat32``) email API. Its functionality
is partially replaced by the :mod:`~email.contentmanager` in the new API, but
in certain applications these classes may still be useful, even in non-legacy
code.
Ordinarily, you get a message object structure by passing a file or some text to Ordinarily, you get a message object structure by passing a file or some text to
a parser, which parses the text and returns the root message object. However a parser, which parses the text and returns the root message object. However
you can also build a complete message structure from scratch, or even individual you can also build a complete message structure from scratch, or even individual
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -8,7 +8,43 @@ ...@@ -8,7 +8,43 @@
-------------- --------------
There are several useful utilities provided in the :mod:`email.utils` module: There are a couple of useful utilities provided in the :mod:`email.utils`
module:
.. function:: localtime(dt=None)
Return local time as an aware datetime object. If called without
arguments, return current time. Otherwise *dt* argument should be a
:class:`~datetime.datetime` instance, and it is converted to the local time
zone according to the system time zone database. If *dt* is naive (that
is, ``dt.tzinfo`` is ``None``), it is assumed to be in local time. In this
case, a positive or zero value for *isdst* causes ``localtime`` to presume
initially that summer time (for example, Daylight Saving Time) is or is not
(respectively) in effect for the specified time. A negative value for
*isdst* causes the ``localtime`` to attempt to divine whether summer time
is in effect for the specified time.
.. versionadded:: 3.3
.. function:: make_msgid(idstring=None, domain=None)
Returns a string suitable for an :rfc:`2822`\ -compliant
:mailheader:`Message-ID` header. Optional *idstring* if given, is a string
used to strengthen the uniqueness of the message id. Optional *domain* if
given provides the portion of the msgid after the '@'. The default is the
local hostname. It is not normally necessary to override this default, but
may be useful certain cases, such as a constructing distributed system that
uses a consistent domain name across multiple hosts.
.. versionchanged:: 3.2
Added the *domain* keyword.
The remaining functions are part of the legacy (``Compat32``) email API. There
is no need to directly use these with the new API, since the parsing and
formatting they provide is done automatically by the header parsing machinery
of the new API.
.. function:: quote(str) .. function:: quote(str)
...@@ -141,36 +177,6 @@ There are several useful utilities provided in the :mod:`email.utils` module: ...@@ -141,36 +177,6 @@ There are several useful utilities provided in the :mod:`email.utils` module:
.. versionadded:: 3.3 .. versionadded:: 3.3
.. function:: localtime(dt=None)
Return local time as an aware datetime object. If called without
arguments, return current time. Otherwise *dt* argument should be a
:class:`~datetime.datetime` instance, and it is converted to the local time
zone according to the system time zone database. If *dt* is naive (that
is, ``dt.tzinfo`` is ``None``), it is assumed to be in local time. In this
case, a positive or zero value for *isdst* causes ``localtime`` to presume
initially that summer time (for example, Daylight Saving Time) is or is not
(respectively) in effect for the specified time. A negative value for
*isdst* causes the ``localtime`` to attempt to divine whether summer time
is in effect for the specified time.
.. versionadded:: 3.3
.. function:: make_msgid(idstring=None, domain=None)
Returns a string suitable for an :rfc:`2822`\ -compliant
:mailheader:`Message-ID` header. Optional *idstring* if given, is a string
used to strengthen the uniqueness of the message id. Optional *domain* if
given provides the portion of the msgid after the '@'. The default is the
local hostname. It is not normally necessary to override this default, but
may be useful certain cases, such as a constructing distributed system that
uses a consistent domain name across multiple hosts.
.. versionchanged:: 3.2
Added the *domain* keyword.
.. function:: decode_rfc2231(s) .. function:: decode_rfc2231(s)
Decode the string *s* according to :rfc:`2231`. Decode the string *s* according to :rfc:`2231`.
......
...@@ -951,6 +951,26 @@ class MIMEPart(Message): ...@@ -951,6 +951,26 @@ class MIMEPart(Message):
policy = default policy = default
Message.__init__(self, policy) Message.__init__(self, policy)
def as_string(self, unixfrom=False, maxheaderlen=None, policy=None):
"""Return the entire formatted message as a string.
Optional 'unixfrom', when true, means include the Unix From_ envelope
header. maxheaderlen is retained for backward compatibility with the
base Message class, but defaults to None, meaning that the policy value
for max_line_length controls the header maximum length. 'policy' is
passed to the Generator instance used to serialize the mesasge; if it
is not specified the policy associated with the message instance is
used.
"""
policy = self.policy if policy is None else policy
if maxheaderlen is None:
maxheaderlen = policy.max_line_length
return super().as_string(maxheaderlen=maxheaderlen, policy=policy)
def __str__(self):
return self.as_string(policy=self.policy.clone(utf8=True))
def is_attachment(self): def is_attachment(self):
c_d = self.get('content-disposition') c_d = self.get('content-disposition')
return False if c_d is None else c_d.content_disposition == 'attachment' return False if c_d is None else c_d.content_disposition == 'attachment'
......
...@@ -764,6 +764,26 @@ class TestEmailMessage(TestEmailMessageBase, TestEmailBase): ...@@ -764,6 +764,26 @@ class TestEmailMessage(TestEmailMessageBase, TestEmailBase):
m.set_content(content_manager=cm) m.set_content(content_manager=cm)
self.assertEqual(m['MIME-Version'], '1.0') self.assertEqual(m['MIME-Version'], '1.0')
def test_as_string_uses_max_header_length_by_default(self):
m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n')
self.assertEqual(len(m.as_string().strip().splitlines()), 3)
def test_as_string_allows_maxheaderlen(self):
m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n')
self.assertEqual(len(m.as_string(maxheaderlen=0).strip().splitlines()),
1)
self.assertEqual(len(m.as_string(maxheaderlen=34).strip().splitlines()),
6)
def test_str_defaults_to_policy_max_line_length(self):
m = self._str_msg('Subject: long line' + ' ab'*50 + '\n\n')
self.assertEqual(len(str(m).strip().splitlines()), 3)
def test_str_defaults_to_utf8(self):
m = EmailMessage()
m['Subject'] = 'unicöde'
self.assertEqual(str(m), 'Subject: unicöde\n\n')
class TestMIMEPart(TestEmailMessageBase, TestEmailBase): class TestMIMEPart(TestEmailMessageBase, TestEmailBase):
# Doing the full test run here may seem a bit redundant, since the two # Doing the full test run here may seem a bit redundant, since the two
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment