Test body
\"\n\nWe could create a message from this instance and schema like this::\n\n >>> from plone.rfc822 import constructMessageFromSchema\n >>> msg = constructMessageFromSchema(content, ITestContent)\n\nThe output looks like this::\n\n >>> print(msg.as_string())\n title: Test title\n description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=\n emptyfield:\n Content-Type: text/plain; charset=\"utf-8\"\nTest body
\n\nNotice how the non-ASCII header values are UTF-8 encoded.\nThe encoding algorithm is clever enough to only encode the value if it is necessary,\nleaving more readable field values otherwise.\n\nThe body here is of the default message type::\n\n >>> msg.get_default_type()\n 'text/plain'\n\nThis is because none of the default field types manage a content type.\n\nThe body is also utf-8 encoded, because the primary field specified this\nencoding.\n\nIf we want to use a different content type, we could set it explicitly::\n\n >>> msg.set_type('text/html')\n >>> print(msg.as_string())\n title: Test title\n description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=\n emptyfield:\n MIME-Version: 1.0\n Content-Type: text/html; charset=\"utf-8\"\nTest body
\n\nAlternatively, if we know that any ``IText`` field on an object providing\nour ``ITestContent`` interface always stores HTML, could register a custom\n``IFieldMarshaler`` adapter which would indicate this to the message\nconstructor. Let's take a look at that now.\n\nCustom marshalers\n-----------------\n\nThe default marshaler can be obtained by multi-adapting the content object\nand the field instance to ``IFieldMarshaler``:\n\n >>> from zope.component import getMultiAdapter\n >>> from plone.rfc822.interfaces import IFieldMarshaler\n >>> getMultiAdapter((content, ITestContent['body'],), IFieldMarshaler)\nTest body
\n\nNotice how the Content-Type has changed.\n\nConsuming a message\n-------------------\n\nA message can be used to initialise an object. The object has to be\nconstructed first:\n\n >>> newContent = TestContent()\n\nWe then need to obtain a ``Message`` object. The ``email`` module contains\nhelper functions for this purpose.\n\n >>> messageBody = \"\"\"\\\n ... title: Test title\n ... description: =?utf-8?q?Test_description=0D=0Awith_a_newline?=\n ... Content-Type: text/html\n ...\n ...Test body
\"\"\"\n\n >>> from email import message_from_string\n >>> msg = message_from_string(messageBody)\n\nThe message can now be used to initialise the object according to the given\nschema. This should be the same schema as the one used to construct the\nmessage.\n\n >>> from plone.rfc822 import initializeObjectFromSchema\n >>> initializeObjectFromSchema(newContent, ITestContent, msg)\n\n >>> newContent.title\n 'Test title'\n >>> print(newContent.description)\n Test description\n with a newline\n\n >>> newContent.body\n 'Test body
'\n\nWe can also consume messages with a transfer encoding and a charset:\n\n >>> messageBody = \"\"\"\\\n ... title: =?utf-8?q?Test_title?=\n ... description: =?utf-8?q?Test_description=0D=0Awith_a_newline?=\n ... emptyfield:\n ... Content-Transfer-Encoding: base64\n ... Content-Type: text/html; charset=\"utf-8\"\n ...Test body
'\n\nNote: Empty fields will result in the field's ``missing_value`` being used:\n\n >>> newContent.emptyfield\n 'missing'\n\nHandling multiple primary fields and duplicate field names\n----------------------------------------------------------\n\nIt is possible that our type could have multiple primary fields or even\nduplicate field names.\n\nFor example, consider the following schema interface, intended to be used\nin an annotation adapter:\n\n >>> class IPersonalDetails(Interface):\n ... description = schema.Text(title=u\"Personal description\")\n ... currentAge = schema.Int(title=u\"Age\", min=0)\n ... personalProfile = schema.Text(title=u\"Profile\")\n\n >>> alsoProvides(IPersonalDetails['personalProfile'], IPrimaryField)\n\nThe annotation storage would look like this:\n\n >>> from persistent import Persistent\n >>> @implementer(IPersonalDetails)\n ... @adapter(ITestContent)\n ... class PersonalDetailsAnnotation(Persistent):\n ...\n ... def __init__(self):\n ... self.description = None\n ... self.currentAge = None\n ... self.personalProfile = None\n\n >>> from zope.annotation.factory import factory\n >>> provideAdapter(factory(PersonalDetailsAnnotation))\n\nWe should now be able to adapt a content instance to IPersonalDetails,\nprovided it is annotatable.\n\n >>> from zope.annotation.interfaces import IAttributeAnnotatable\n >>> alsoProvides(content, IAttributeAnnotatable)\n\n >>> personalDetails = IPersonalDetails(content)\n >>> personalDetails.description = u\"My description
\"\n >>> personalDetails.currentAge = 21\n >>> personalDetails.personalProfile = u\"My profile
\"\n\nThe default marshalers will attempt to adapt the context to the schema of\na given field before getting or setting a value. If we pass multiple schemata\n(or a combined sequence of fields) to the message constructor, it will\nhandle both duplicate field names (as duplicate headers) and multiple primary\nfields (as multipart message attachments).\n\nHere are the fields it will see:\n\n >>> from zope.schema import getFieldsInOrder\n >>> allFields = getFieldsInOrder(ITestContent) + \\\n ... getFieldsInOrder(IPersonalDetails)\n\n >>> [f[0] for f in allFields]\n ['title', 'description', 'body', 'emptyfield', 'description', 'currentAge', 'personalProfile']\n\n >>> [f[0] for f in allFields if IPrimaryField.providedBy(f[1])]\n ['body', 'personalProfile']\n\nLet's now construct a message. Since we now have two fields called\n``description``, we will get two headers by that name. Since we have two\nprimary fields, we will get a multipart message with two attachments::\n\n >>> from plone.rfc822 import constructMessageFromSchemata\n >>> msg = constructMessageFromSchemata(content, (ITestContent, IPersonalDetails,))\n >>> msgString = msg.as_string()\n >>> print(msgString)\n title: Test title\n description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=\n emptyfield:\n description:My description
\n currentAge: 21\n MIME-Version: 1.0\n Content-Type: multipart/mixed; boundary=\"===============...==\"\nTest body
\n --===============...==\n MIME-Version: 1.0\n Content-Type: text/html; charset=\"utf-8\"\nMy profile
\n --===============...==--\nTest body
'\n\n >>> newPersonalDetails = IPersonalDetails(newContent)\n >>> newPersonalDetails.description\n 'My description
'\n\n >>> newPersonalDetails.currentAge\n 21\n\n >>> newPersonalDetails.personalProfile\n 'My profile
'\n\nAlternative ways to deal with multiple schemata\n-----------------------------------------------\n\nIn the example above, we created a single enveloping message with headers\ncorresponding to the fields in both our schemata, and only the primary fields\nseparated out into different attached payloads.\n\nAn alternative approach would be to separate each schema out into its\nown multipart message. To do that, we would simply use the\n``constructMessage()`` function multiple times.\n\n >>> mainMessage = constructMessageFromSchema(content, ITestContent)\n >>> personalDetailsMessage = constructMessageFromSchema(content, IPersonalDetails)\n\n >>> from email.mime.multipart import MIMEMultipart\n >>> envelope = MIMEMultipart()\n >>> envelope.attach(mainMessage)\n >>> envelope.attach(personalDetailsMessage)\n\n >>> envelopeString = envelope.as_string()\n >>> print(envelopeString)\n Content-Type: multipart/mixed; boundary=\"===============...==\"\n MIME-Version: 1.0\nTest body
\n --===============...==\n description:My description
\n currentAge: 21\n MIME-Version: 1.0\n Content-Type: text/html; charset=\"utf-8\"\nMy profile
\n --===============...==--...\n\nWhich approach works best will depend largely on the intended recipient of\nthe message.\n\nEncoding the payload and handling filenames\n-------------------------------------------\n\nFinally, let's consider a more complex example, inspired by the field\nmarshaler in ``plone.namedfile``.\n\nLet's say we have a value type intended to represent a binary file with a\nfilename and content type:\n\n >>> from zope.interface import Interface, implementer\n >>> from zope import schema\n\n >>> class IFileValue(Interface):\n ... data = schema.Bytes(title=u\"Raw data\")\n ... contentType = schema.ASCIILine(title=u\"MIME type\")\n ... filename = schema.ASCIILine(title=u\"Filename\")\n\n >>> @implementer(IFileValue)\n ... class FileValue(object):\n ...\n ... def __init__(self, data, contentType, filename):\n ... self.data = data\n ... self.contentType = contentType\n ... self.filename = filename\n\nSuppose we had a custom field type to represent this:\n\n >>> from zope.schema.interfaces import IObject\n >>> class IFileField(IObject):\n ... pass\n\n >>> @implementer(IFileField)\n ... class FileField(schema.Object):\n ... schema = IFileValue\n ... def __init__(self, **kw):\n ... if 'schema' in kw:\n ... self.schema = kw.pop('schema')\n ... super(FileField, self).__init__(schema=self.schema, **kw)\n\nWe can register a field marshaler for this field which will do the following:\n\n* Insist that the field is only used as a primary field, since it makes\n little sense to encode a binary file in a header.\n* Save the filename in a Content-Disposition header.\n* Be capable of reading the filename again from this header.\n* Encode the payload using base64\n\n >>> from plone.rfc822.interfaces import IFieldMarshaler\n >>> from email.encoders import encode_base64\n\n >>> from zope.component import adapter\n >>> from plone.rfc822.defaultfields import BaseFieldMarshaler\n\n >>> @adapter(Interface, IFileField)\n ... class FileFieldMarshaler(BaseFieldMarshaler):\n ...\n ... ascii = False\n ...\n ... def encode(self, value, charset='utf-8', primary=False):\n ... if not primary:\n ... raise ValueError(\"File field cannot be marshaled as a non-primary field\")\n ... if value is None:\n ... return None\n ... return value.data\n ...\n ... def decode(self, value, message=None, charset='utf-8', contentType=None, primary=False):\n ... filename = None\n ... # get the filename from the Content-Disposition header if possible\n ... if primary and message is not None:\n ... filename = message.get_filename(None)\n ... return FileValue(value, contentType, filename)\n ...\n ... def getContentType(self):\n ... value = self._query()\n ... if value is None:\n ... return None\n ... return value.contentType\n ...\n ... def getCharset(self, default='utf-8'):\n ... return None # this is not text data!\n ...\n ... def postProcessMessage(self, message):\n ... value = self._query()\n ... if value is not None:\n ... filename = value.filename\n ... if filename:\n ... # Add a new header storing the filename if we have one\n ... message.add_header('Content-Disposition', 'attachment', filename=filename)\n\n >>> from zope.component import provideAdapter\n >>> provideAdapter(FileFieldMarshaler)\n\nTo illustrate marshaling, let's create a content object that contains two file\nfields.\n\n >>> class IFileContent(Interface):\n ... file1 = FileField()\n ... file2 = FileField()\n\n >>> @implementer(IFileContent)\n ... class FileContent(object):\n ... file1 = None\n ... file2 = None\n\n >>> fileContent = FileContent()\n >>> fileContent.file1 = FileValue('dummy file', 'text/plain', 'dummy1.txt')\n >>> fileContent.file2 = FileValue('test', 'text/html', 'dummy2.html')\n\nAt this point, neither of these fields is marked as a primary field. Let's see\nwhat happens when we attempt to construct a message from this schema.\n\n >>> from plone.rfc822 import constructMessageFromSchema\n >>> message = constructMessageFromSchema(fileContent, IFileContent)\n >>> print(message.as_string())\n