cpython: 6d12285e250b (original) (raw)

new file mode 100644 --- /dev/null +++ b/Doc/library/email.contentmanager.rst @@ -0,0 +1,427 @@ +:mod:email.contentmanager: Managing MIME Content +-------------------------------------------------- + +.. module:: email.contentmanager

+.. moduleauthor:: R. David Murray rdmurray@bitdance.com +.. sectionauthor:: R. David Murray rdmurray@bitdance.com + + +.. note:: +

+.. versionadded:: 3.4

+The :mod:~email.message module provides a class that can represent an +arbitrary email message. That basic message model has a useful and flexible +API, but it provides only a lower-level API for interacting with the generic +parts of a message (the headers, generic header parameters, and the payload, +which may be a list of sub-parts). This module provides classes and tools +that provide an enhanced and extensible API for dealing with various specific +types of content, including the ability to retrieve the content of the message +as a specialized object type rather than as a simple bytes object. The module +automatically takes care of the RFC-specified MIME details (required headers +and parameters, etc.) for the certain common content types content properties, +and support for additional types can be added by an application using the +extension mechanisms. + +This module defines the eponymous "Content Manager" classes. The base +:class:.ContentManager class defines an API for registering content +management functions which extract data from Message objects or insert data +and headers into Message objects, thus providing a way of converting +between Message objects containing data and other representations of that +data (Python data types, specialized Python objects, external files, etc). The +module also defines one concrete content manager: :data:raw_data_manager +converts between MIME content types and str or bytes data. It also +provides a convenient API for managing the MIME parameters when inserting +content into Message\ s. It also handles inserting and extracting +Message objects when dealing with the message/rfc822 content type. + +Another part of the enhanced interface is subclasses of +:class:~email.message.Message that provide new convenience API functions, +including convenience methods for calling the Content Managers derived from +this module. + +.. note:: +

+ +.. class:: EmailMessage(policy=default) +

+

+ +

+

+

+

+

+

+

+

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ + +.. class:: ContentManager() +

+

+

+

+

+ +

+

+

+

+

+ +

+ +

+ + +.. class:: MIMEPart(policy=default) +

+ + +Content Manager Instances +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently the email package provides only one concrete content manager, +:data:raw_data_manager, although more may be added in the future. +:data:raw_data_manager is the +:attr:~email.policy.EmailPolicy.content_manager provided by +:attr:~email.policy.EmailPolicy and its derivatives. + + +.. data:: raw_data_manager +

+

+

+

+

+

+

+

+

+

+

+

+

--- a/Doc/library/email.message.rst +++ b/Doc/library/email.message.rst @@ -33,10 +33,11 @@ Here are the methods of the :class:`Mess .. class:: Message(policy=compat32)

Set a parameter in the :mailheader:Content-Type header. If the parameter already exists in the header, its value will be replaced with @@ -482,6 +484,12 @@ Here are the methods of the :class:`Mess language, defaulting to the empty string. Both charset and language should be strings.

+

+ .. method:: del_param(param, header='content-type', requote=True)

--- a/Doc/library/email.policy.rst +++ b/Doc/library/email.policy.rst @@ -371,7 +371,7 @@ added matters. To illustrate:: to) :rfc:5322, :rfc:2047, and the current MIME RFCs. This policy adds new header parsing and folding algorithms. Instead of

@@ -408,6 +408,20 @@ added matters. To illustrate:: fields are treated as unstructured. This list will be completed before the extension is marked stable.)

+

+ + The class provides the following concrete implementations of the abstract methods of :class:Policy: @@ -427,7 +441,7 @@ added matters. To illustrate:: The name is returned unchanged. If the input value has a name attribute and it matches name ignoring case, the value is returned unchanged. Otherwise the name and value are passed to

@@ -435,7 +449,7 @@ added matters. To illustrate:: If the value has a name attribute, it is returned to unmodified. Otherwise the name, and the value with any CR or LF characters

@@ -445,9 +459,9 @@ added matters. To illustrate:: A value is considered to be a 'source value' if and only if it does not have a name attribute (having a name attribute means it is a header object of some sort). If a source value needs to be refolded

Source values are split into lines using :meth:~str.splitlines. If @@ -502,23 +516,23 @@ With all of these :class:EmailPolicies [](#l3.63) the email package is changed from the Python 3.2 API in the following ways:[](#l3.64) [](#l3.65) * Setting a header on a :class:~email.message.Message` results in that

* Fetching a header value from a :class:~email.message.Message results

From the application view, this means that any header obtained through the -:class:~email.message.Message is a custom header object with custom +:class:~email.message.Message is a header object with extra attributes, whose string value is the fully decoded unicode value of the header. Likewise, a header may be assigned a new value, or a new header created, using a unicode string, and the policy will take care of converting the unicode string into the correct RFC encoded form. -The custom header objects and their attributes are described in +The header objects and their attributes are described in :mod:~email.headerregistry.

--- a/Doc/library/email.rst +++ b/Doc/library/email.rst @@ -53,6 +53,7 @@ Contents of the :mod:email package doc email.generator.rst email.policy.rst email.headerregistry.rst

--- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -280,6 +280,21 @@ result: a bytes object containing the f (Contributed by R. David Murray in :issue:18600.) +A pair of new subclasses of :class:~email.message.Message have been added, +along with a new sub-module, :mod:~email.contentmanager. All documentation +is currently in the new module, which is being added as part of the new +:term:provisional <provosional package> email API. These classes provide a +number of new methods that make extracting content from and inserting content +into email messages much easier. See the :mod:~email.contentmanager +documentation for details. + +These API additions complete the bulk of the work that was planned as part of +the email6 project. The currently provisional API is scheduled to become final +in Python 3.5 (possibly with a few minor additions in the area of error +handling). + +(Contributed by R. David Murray in :issue:18891.) + functools ---------

new file mode 100644 --- /dev/null +++ b/Lib/email/contentmanager.py @@ -0,0 +1,249 @@ +import binascii +import email.charset +import email.message +import email.errors +from email import quoprimime + +class ContentManager: +

+

+

+

+

+

+ + +raw_data_manager = ContentManager() + + +def get_text_content(msg, errors='replace'):

+raw_data_manager.add_get_handler('text', get_text_content) + + +def get_non_text_content(msg):

+for maintype in 'audio image video application'.split():

+ + +def get_message_content(msg):

+for subtype in 'rfc822 external-body'.split():

+ + +def get_and_fixup_unknown_message_content(msg):

+raw_data_manager.add_get_handler('message',

+ + +def _prepare_set(msg, maintype, subtype, headers):

+ + +def _finalize_set(msg, disposition, filename, cid, params):

+ + +# XXX: This is a cleaned-up version of base64mime.body_encode. It would +# be nice to drop both this and quoprimime.body_encode in favor of +# enhanced binascii routines that accepted a max_line_length parameter. +def _encode_base64(data, max_line_length):

+ + +def _encode_text(string, charset, cte, policy):

+ + +def set_text_content(msg, string, subtype="plain", charset='utf-8', cte=None,

+raw_data_manager.add_set_handler(str, set_text_content) + + +def set_message_content(msg, message, subtype="rfc822", cte=None,

+raw_data_manager.add_set_handler(email.message.Message, set_message_content) + + +def set_bytes_content(msg, data, maintype, subtype, cte='base64',

+for typ in (bytes, bytearray, memoryview):

--- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -8,8 +8,6 @@ import re import uu -import base64 -import binascii from io import BytesIO, StringIO

Intrapackage imports

@@ -679,7 +677,7 @@ class Message: return failobj def set_param(self, param, value, header='Content-Type', requote=True,

If the parameter already exists in the header, its value will be @@ -723,8 +721,11 @@ class Message: else: ctype = SEMISPACE.join([ctype, append_param]) if ctype != self.get(header):

def del_param(self, param, header='content-type', requote=True): """Remove the given parameter completely from the Content-Type header. @@ -905,3 +906,208 @@ class Message: # I.e. def walk(self): ... from email.iterators import walk + + +class MIMEPart(Message): +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+ + +class EmailMessage(MIMEPart): +

--- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -5,6 +5,7 @@ code that adds all the email6 features. from email._policybase import Policy, Compat32, compat32, _extend_docstrings from email.utils import _has_surrogates from email.headerregistry import HeaderRegistry as HeaderRegistry +from email.contentmanager import raw_data_manager all = [ 'Compat32', @@ -58,10 +59,22 @@ class EmailPolicy(Policy): special treatment, while all other fields are treated as unstructured. This list will be completed before the extension is marked stable.) +

+ """ refold_source = 'long' header_factory = HeaderRegistry()

def init(self, **kw): # Ensure that each new instance gets a unique header factory

--- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -68,9 +68,13 @@ def _has_surrogates(s):

How to deal with a string containing bytes before handing it to the

application through the 'normal' interface.

def _sanitize(string):

+

Helpers

--- a/Lib/test/test_email/init.py +++ b/Lib/test/test_email/init.py @@ -2,6 +2,7 @@ import os import sys import unittest import test.support +import collections import email from email.message import Message from email._policybase import compat32 @@ -42,6 +43,8 @@ class TestEmailBase(unittest.TestCase): # here we make minimal changes in the test_email tests compared to their # pre-3.3 state. policy = compat32

def init(self, *args, **kw): super().init(*args, **kw) @@ -54,11 +57,23 @@ class TestEmailBase(unittest.TestCase): with openfile(filename) as fp: return email.message_from_file(fp, policy=self.policy)

+

+ def _bytes_repr(self, b): return [repr(x) for x in b.splitlines(keepends=True)] @@ -123,6 +138,7 @@ def parameterize(cls): """ paramdicts = {}

@@ -134,7 +150,15 @@ def parameterize(cls): d[n] = x attr = d paramdicts[name[:-7] + 'as'] = attr

new file mode 100644 --- /dev/null +++ b/Lib/test/test_email/test_contentmanager.py @@ -0,0 +1,796 @@ +import unittest +from test.test_email import TestEmailBase, parameterize +import textwrap +from email import policy +from email.message import EmailMessage +from email.contentmanager import ContentManager, raw_data_manager + + +@parameterize +class TestContentManager(TestEmailBase): +

+

+

+

+

+

+

+

+

+

+

+

+ + +@parameterize +class TestRawDataManager(TestEmailBase):

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+ + +if name == 'main':

--- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -661,7 +661,7 @@ class TestContentTypeHeader(TestHeaderBa 'text/plain; name="ascii_is_the_default"'), 'rfc2231_bad_character_in_charset_parameter_value': (

@@ -669,6 +669,18 @@ class TestContentTypeHeader(TestHeaderBa [errors.UndecodableBytesDefect], 'text/plain; charset="utf-8\uFFFD\uFFFD\uFFFD"'),

+ 'rfc2231_encoded_then_unencoded_segments': ( ('application/x-foo;' '\tname0="us-ascii'en-us'My";'

--- a/Lib/test/test_email/test_message.py +++ b/Lib/test/test_email/test_message.py @@ -1,6 +1,13 @@ import unittest +import textwrap from email import policy -from test.test_email import TestEmailBase +from email.message import EmailMessage, MIMEPart +from test.test_email import TestEmailBase, parameterize + + +# Helper. +def first(iterable):

class Test(TestEmailBase): @@ -14,5 +21,738 @@ class Test(TestEmailBase): m['To'] = 'xyz@abc' +@parameterize +class TestEmailMessageBase: +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+ +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+ + + +class TestEmailMessage(TestEmailMessageBase, TestEmailBase):

+

+

+

+ + +class TestMIMEPart(TestEmailMessageBase, TestEmailBase):

+

+ + if name == 'main': unittest.main()

--- a/Lib/test/test_email/test_policy.py +++ b/Lib/test/test_email/test_policy.py @@ -30,6 +30,7 @@ class PolicyAPITests(unittest.TestCase): 'raise_on_defect': False, 'header_factory': email.policy.EmailPolicy.header_factory, 'refold_source': 'long',

# For each policy under test, we give here what we expect the defaults to

--- a/Misc/NEWS +++ b/Misc/NEWS @@ -42,6 +42,9 @@ Core and Builtins Library ------- +- Issue #18891: Completed the new email package (provisional) API additions