跳转到主要内容

为zope.schema字段提供RFC822打包

项目描述

简介

本软件包提供将由 zope.schema 字段描述的内容对象转换为 RFC (2)822 风格消息的原始方法。它使用 Python 标准库中的 email 模块。

它包括

  • 一个标记接口 IPrimaryField,可用于指示模式的主体字段。主体字段将用作消息体。如果有多个字段标记为主体,则体将转换为 MIME 多部分消息。

  • 一个接口 IFieldMarshaler,描述了将字符串转换为适合编码为 RFC 2822 风格消息的转换器。这些是多适配器,基于 (context, field)context 是内容对象,field 是模式字段实例。

  • zope.schema 包中标准字段的 IFieldMarshaler 的默认实现。

  • 辅助方法,用于从一个或多个模式或字段列表构造消息,以及解析消息并根据需要更新上下文对象。

辅助方法由 plone.rfc822.interfaces.IMessageAPI 描述。它们可以直接从 plone.rfc822 包导入

def constructMessageFromSchema(context, schema, charset='utf-8'):
    """Convenience method which calls ``constructMessage()`` with all the
    fields, in order, of the given schema interface
    """

def constructMessageFromSchemata(context, schemata, charset='utf-8'):
    """Convenience method which calls ``constructMessage()`` with all the
    fields, in order, of all the given schemata (a sequence of schema
    interfaces).
    """

def constructMessage(context, fields, charset='utf-8'):
    """Helper method to construct a message.

    ``context`` is a content object.

    ``fields`` is a sequence of (name, field) pairs for the fields which make
    up the message. This can be obtained from zope.schema.getFieldsInOrder,
    for example.

    ``charset`` is the message charset.

    The message body will be constructed from the primary field, i.e. the
    field which is marked with ``IPrimaryField``. If no such field exists,
    the message will have no body. If multiple fields exist, the message will
    be a multipart message. Otherwise, it will contain a scalar string
    payload.

    A field will be ignored if ``(context, field)`` cannot be multi-adapted
    to ``IFieldMarshaler``, or if the ``marshal()`` method returns None.
    """

def renderMessage(message, mangleFromHeader=False):
    """Render a message to a string
    """

def initializeObjectFromSchema(context, schema, message, defaultCharset='utf-8'):
    """Convenience method which calls ``initializeObject()`` with all the
    fields, in order, of the given schema interface
    """

def initializeObjectFromSchemata(context, schemata, message, defaultCharset='utf-8'):
    """Convenience method which calls ``initializeObject()`` with all the
    fields in order, of all the given schemata (a sequence of schema
    interfaces).
    """

def initializeObject(context, fields, message, defaultCharset='utf-8'):
    """Initialise an object from a message.

    ``context`` is the content object to initialise.

    ``fields`` is a sequence of (name, field) pairs for the fields which make
    up the message. This can be obtained from zope.schema.getFieldsInOrder,
    for example.

    ``message`` is a ``Message`` object.

    ``defaultCharset`` is the default character set to use.

    If the message is a multipart message, the primary fields will be read
    in order.
    """

使用的消息格式遵循以下规则

  • 所有非主体字段均表示为标题。标题名称取自字段名称。值是适当的 IFieldMarshal 多适配器的 marshal() 方法返回的编码字符串。

  • 如果找不到 IFieldMarshaler 适配器,则忽略标题。

  • 类似地,在解析消息时,如果没有找到给定标题的字段,则忽略该标题。

  • 如果有一个单一的主体字段,则消息有一个字符串有效载荷,它是主体字段的打包值。在这种情况下,消息的 Content-Type 标题将来自主体字段的打包器。

  • 如果有多个主体字段,则每个字段都编码为其自己的消息,每个消息都有自己的 Content-Type 标题。外部消息将具有 multipart/mixed 内容类型和其他字段的标题。

  • 如果正在解析的消息比主体字段更多或少于部分,则引发 ValueError 错误。

  • 允许重复的字段名称,并将作为重复标题进行编码。在解析消息时,每个标题需要有一个字段。也就是说,如果消息包含两个名为“foo”的标题,则传递给 initializeObject() 方法的字段名称/实例对列表应包含两个带有名称“foo”的对。第一个字段将用于第一个标题值,第二个字段将用于第二个标题值。如果出现第三个“foo”标题,则将其忽略。

  • 由于消息标题始终为小写,因此在解析消息时将不区分大小写匹配字段名称。

Supermodel 处理程序

如果安装了 plone.supermodel,则此软件包将为 marshal 命名空间注册一个命名空间处理程序,URI 为 http://namespaces.plone.org/supermodel/marshal。这可以用于标记字段为主体字段

<model xmlns="http://namespaces.plone.org/supermodel/schema"
       xmlns:marshal="http://namespaces.plone.org/supermodel/marshal">
    <schema>
        <field type="zope.schema.Text" name="test" marshal:primary="true">
            <title>Test field</title>
        </field>
    </schema>
</model>

plone.supermodel 可以作为额外依赖项使用 [supermodel] 安装,但这可能仅适用于运行测试。如果没有安装包,则处理程序不会忽略。

许可证说明

此软件包在 BSD 许可下发布。贡献者,请勿添加对 GPL 代码的依赖。

问题跟踪器

请通过Plone 问题跟踪器报告问题。

支持

Dexterity 使用问题可以通过Plone 的支持渠道获得解答。

贡献

源代码位于Github 上的 Plone 代码仓库

贡献者请阅读Plone 内核开发流程文档

变更日志

3.0.1 (2024-01-22)

内部

  • 更新配置文件。[plone 开发者] (237ff4c8, 6e36bcc4)

3.0.0 (2023-04-26)

重大变更

  • 移除长期弃用的 renderMessage 函数。[jensens] (1-1)

  • 删除对 Python 2.7 的兼容性。[gforcada] (#1)

内部

  • 更新配置文件。[plone 开发者] (a864b30f)

2.0.2 (2020-04-22)

错误修复

  • 较小的打包更新。[#1]

2.0.1 (2019-05-21)

错误修复

  • 在负载解析器中使用更好的类型检查。[Rotonen] (#7)

2.0.0 (2018-11-04)

重大变更

  • 弃用 renderMessage(message),改用 stdlibs 中的 message.as_string()email.message.Message 类中获取。[jensens]

  • MIME-headers 中的换行处理:现在明确转义了 \n。这遵循了 RFC2822 3.2.2 节。[jensens]

  • 删除对 Python 2.6 的支持。[jensens]

新特性

  • constructMessage 现在会自动为所有 marshallers 处理 base64 编码,当 marshaler.asciiFalsemarshaler.getContentTypeNone 时。[jensens]

  • 支持 Python 3+,还包括大代码重构。[jensens]

1.1.4 (2018-06-20)

新特性

  • 开始基本 Python 3 支持。[pbauer, dhavlik]

1.1.3 (2016-08-09)

修复

  • 代码清理:pep8, isort, utf8 headers 等。[jensens]

  • 使用 zope.interface 装饰器。[gforcada]

1.1.2 (2016-02-21)

修复

  • 修复测试隔离问题。[thet]

  • 将弃用的 zope.testing.doctest 导入替换为 stdlib 中的 doctest 模块。[thet]

1.1.1 (2015-03-21)

  • 更新测试以反映模型命名空间表示形式的变更,通过添加 18n xml 命名空间。[sneridagh]

  • 确保测试不会因消息包含尾部空白行而失败。这修复了在 Ubuntu 14.04 上的测试失败。[timo]

1.1 (2013-08-14)

  • 为 Plone 4.2/4.3 兼容性更改创建分支。[esteele]

1.0.2 (2013-07-28)

  • 尽可能将集合作为 ASCII 串进行打包。[davisagli]

  • 添加对打包十进制字段的支持。[davisagli]

1.0.1 (2013-01-01)

1.0 (2011-05-20)

1.0b2 (2011-02-11)

  • 添加 IPrimaryFieldInfo 以在内容项上查找主字段信息。

1.0b1 (2009-10-08)

  • 首次发布

消息构建和解析

此包包含从一系列模式字段构建 RFC 2822 样式消息的辅助方法,以及解析消息并基于其头信息和体负载初始化对象的辅助方法。

在我们开始之前,让我们加载默认字段 marshallers 并配置注解,我们将在本测试的后面使用它们

>>> configuration = u"""\
... <configure
...      xmlns="http://namespaces.zope.org/zope"
...      i18n_domain="plone.rfc822.tests">
...
...     <include package="zope.component" file="meta.zcml" />
...     <include package="zope.annotation" />
...
...     <include package="plone.rfc822" />
...
... </configure>
... """
>>> from io import StringIO
>>> from zope.configuration import xmlconfig
>>> xmlconfig.xmlconfig(StringIO(configuration))

主字段

消息体假定来自“主”字段,这通过一个标记接口来指示。

为了说明模式,考虑以下模式接口

>>> from zope.interface import Interface, alsoProvides
>>> from plone.rfc822.interfaces import IPrimaryField
>>> from zope import schema

>>> class ITestContent(Interface):
...
...     title = schema.TextLine(title=u"Title")
...     description = schema.Text(title=u"Description")
...     body = schema.Text(title=u"Body text")
...     emptyfield = schema.TextLine(title=u"Empty field", missing_value=u'missing')

主字段实例标记如下

>>> alsoProvides(ITestContent['body'], IPrimaryField)

构建消息

现在假设我们有一个提供此接口的实例,我们希望将其打包到消息中

>>> from zope.interface import implementer
>>> @implementer(ITestContent)
... class TestContent(object):
...     title = ""
...     description = ""
...     body = ""
...     emptyfield = None

>>> content = TestContent()
>>> content.title = "Test title"
>>> content.description = """Täst description
... with a newline"""
>>> content.body = "<p>Test body</p>"

我们可以这样从实例和模式创建消息

>>> from plone.rfc822 import constructMessageFromSchema
>>> msg = constructMessageFromSchema(content, ITestContent)

输出如下

>>> print(msg.as_string())
title: Test title
description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=
emptyfield:
Content-Type: text/plain; charset="utf-8"
<BLANKLINE>
<p>Test body</p>

请注意,非 ASCII 的头值被 UTF-8 编码。编码算法足够智能,仅在必要时才编码值,否则保留更易读的字段值。

这里的正文是默认消息类型

>>> msg.get_default_type()
'text/plain'

这是因为默认字段类型中没有管理内容类型的。

正文也使用 utf-8 编码,因为主字段指定了这种编码。

如果我们想使用不同的内容类型,可以明确设置它

>>> msg.set_type('text/html')
>>> print(msg.as_string())
title: Test title
description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=
emptyfield:
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<BLANKLINE>
<p>Test body</p>

或者,如果我们知道对象上提供 ITestContent 接口的所有 IText 字段总是存储 HTML,可以注册一个自定义的 IFieldMarshaler 适配器,该适配器会向消息构造函数指示这一点。现在让我们看看。

自定义序列化程序

默认序列化程序可以通过将内容对象和字段实例多适配到 IFieldMarshaler 获取

>>> from zope.component import getMultiAdapter
>>> from plone.rfc822.interfaces import IFieldMarshaler
>>> getMultiAdapter((content, ITestContent['body'],), IFieldMarshaler)
<plone.rfc822.defaultfields.UnicodeValueFieldMarshaler object at ...>

现在让我们通过扩展这个类并重写 getContentType() 来创建我们自己的序列化程序

>>> from plone.rfc822.defaultfields import UnicodeValueFieldMarshaler
>>> from zope.schema.interfaces import IText
>>> from zope.component import adapter
>>> @adapter(ITestContent, IText)
... class TestBodyMarshaler(UnicodeValueFieldMarshaler):
...     def getContentType(self):
...         return 'text/html'

通常,我们会通过 ZCML 注册它。为了测试目的,我们将使用 zope.component API 来注册它。

>>> from zope.component import provideAdapter
>>> provideAdapter(TestBodyMarshaler)

提示:如果模式包含多个文本字段,此适配器将适用于所有这些字段。为了避免这种情况,我们可以给字段标记一个自定义标记接口(类似于我们上面标记 IPrimaryField 字段的方式),或者让序列化程序检查字段名称。

现在让我们再次尝试

>>> msg = constructMessageFromSchema(content, ITestContent)
>>> print(msg.as_string())
title: Test title
description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=
emptyfield:
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<BLANKLINE>
<p>Test body</p>

注意内容类型是如何改变的。

消费消息

消息可以用来初始化一个对象。首先必须构造该对象

>>> newContent = TestContent()

然后我们需要获取一个 Message 对象。这个 email 模块包含用于此目的的辅助函数。

>>> messageBody = """\
... title: Test title
... description: =?utf-8?q?Test_description=0D=0Awith_a_newline?=
... Content-Type: text/html
...
... <p>Test body</p>"""
>>> from email import message_from_string
>>> msg = message_from_string(messageBody)

现在可以使用消息根据给定的架构初始化对象。这应该是构造消息时使用的相同架构。

>>> from plone.rfc822 import initializeObjectFromSchema
>>> initializeObjectFromSchema(newContent, ITestContent, msg)
>>> newContent.title
'Test title'
>>> print(newContent.description)
Test description
with a newline
>>> newContent.body
'<p>Test body</p>'

我们还可以使用转移编码和字符集消费消息

>>> messageBody = """\
... title: =?utf-8?q?Test_title?=
... description: =?utf-8?q?Test_description=0D=0Awith_a_newline?=
... emptyfield:
... Content-Transfer-Encoding: base64
... Content-Type: text/html; charset="utf-8"
... <BLANKLINE>
... PHA+VGVzdCBib2R5PC9wPg==
... <BLANKLINE>"""
>>> msg = message_from_string(messageBody)
>>> msg.get_content_type()
'text/html'
>>> msg.get_content_charset()
'utf-8'
>>> initializeObjectFromSchema(newContent, ITestContent, msg)
>>> newContent.title
'Test title'
>>> print(newContent.description)
Test description
with a newline
>>> newContent.body
'<p>Test body</p>'

注意:空字段将使用字段的 missing_value

>>> newContent.emptyfield
'missing'

处理多个主字段和重复字段名称

我们的类型可能有多个主字段,甚至可能有重复的字段名称。

例如,考虑以下架构接口,打算用于注释适配器

>>> class IPersonalDetails(Interface):
...     description = schema.Text(title=u"Personal description")
...     currentAge = schema.Int(title=u"Age", min=0)
...     personalProfile = schema.Text(title=u"Profile")
>>> alsoProvides(IPersonalDetails['personalProfile'], IPrimaryField)

注释存储看起来是这样的

>>> from persistent import Persistent
>>> @implementer(IPersonalDetails)
... @adapter(ITestContent)
... class PersonalDetailsAnnotation(Persistent):
...
...     def __init__(self):
...         self.description = None
...         self.currentAge = None
...         self.personalProfile = None
>>> from zope.annotation.factory import factory
>>> provideAdapter(factory(PersonalDetailsAnnotation))

现在我们应该能够将内容实例适配到 IPersonalDetails,前提是它是可注释的。

>>> from zope.annotation.interfaces import IAttributeAnnotatable
>>> alsoProvides(content, IAttributeAnnotatable)
>>> personalDetails = IPersonalDetails(content)
>>> personalDetails.description = u"<p>My description</p>"
>>> personalDetails.currentAge = 21
>>> personalDetails.personalProfile = u"<p>My profile</p>"

默认序列化程序将尝试在获取或设置值之前将上下文适配到给定字段的架构。如果我们向消息构造函数传递多个架构(或字段的组合序列),它将处理重复的字段名称(作为重复的头),以及多个主字段(作为多部分消息附件)。

以下是它将看到的字段

>>> from zope.schema import getFieldsInOrder
>>> allFields = getFieldsInOrder(ITestContent) + \
...             getFieldsInOrder(IPersonalDetails)
>>> [f[0] for f in allFields]
['title', 'description', 'body', 'emptyfield', 'description', 'currentAge', 'personalProfile']
>>> [f[0] for f in allFields if IPrimaryField.providedBy(f[1])]
['body', 'personalProfile']

现在让我们构造一个消息。因为我们现在有两个名为 description 的字段,我们将通过该名称获取两个头。因为我们有两个主字段,我们将得到一个包含两个附件的多部分消息

>>> from plone.rfc822 import constructMessageFromSchemata
>>> msg = constructMessageFromSchemata(content, (ITestContent, IPersonalDetails,))
>>> msgString = msg.as_string()
>>> print(msgString)
title: Test title
description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=
emptyfield:
description: <p>My description</p>
currentAge: 21
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============...=="
<BLANKLINE>
--===============...==
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<BLANKLINE>
<p>Test body</p>
--===============...==
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<BLANKLINE>
<p>My profile</p>
--===============...==--
<BLANKLINE>

(注意,我们在这里使用了省略号,以便 doctest 能够与生成的边界字符串一起工作)。

注意,两个消息都有一个 MIME 类型为 ‘text/html’ 并且没有字符集。这是因为我们之前注册的用于 (ITestContent, IText) 的自定义适配器。

显然,我们也可以读取这个消息。请注意,在这种情况下,传递给 initializeObject() 的字段的顺序很重要,这既决定了哪个字段获得哪个 description 头,也匹配两个附件到两个主字段。

>>> newContent = TestContent()
>>> alsoProvides(newContent, IAttributeAnnotatable)
>>> from plone.rfc822 import initializeObjectFromSchemata
>>> msg = message_from_string(msgString)
>>> initializeObjectFromSchemata(newContent, [ITestContent, IPersonalDetails], msg)
>>> newContent.title
'Test title'
>>> newContent.marker = True
>>> newContent.description
'T\xe4st description\nwith a newline'
>>> newContent.body
'<p>Test body</p>'
>>> newPersonalDetails = IPersonalDetails(newContent)
>>> newPersonalDetails.description
'<p>My description</p>'
>>> newPersonalDetails.currentAge
21
>>> newPersonalDetails.personalProfile
'<p>My profile</p>'

处理多个架构的替代方法

在上面的例子中,我们创建了一个包含对应于我们两个模式中字段头部的单个封装消息,并且只有主要字段被分离到不同的附加有效载荷中。

另一种方法是将每个模式单独分离成自己的多部分消息。要做到这一点,我们只需多次使用 constructMessage() 函数。

>>> mainMessage = constructMessageFromSchema(content, ITestContent)
>>> personalDetailsMessage = constructMessageFromSchema(content, IPersonalDetails)
>>> from email.mime.multipart import MIMEMultipart
>>> envelope = MIMEMultipart()
>>> envelope.attach(mainMessage)
>>> envelope.attach(personalDetailsMessage)
>>> envelopeString = envelope.as_string()
>>> print(envelopeString)
Content-Type: multipart/mixed; boundary="===============...=="
MIME-Version: 1.0
<BLANKLINE>
--===============...==
title: Test title
description: =?utf-8?q?T=C3=A4st_description=5Cnwith_a_newline?=
emptyfield:
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<BLANKLINE>
<p>Test body</p>
--===============...==
description: <p>My description</p>
currentAge: 21
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
<BLANKLINE>
<p>My profile</p>
--===============...==--...

哪种方法更有效将主要取决于消息的预期接收者。

编码有效载荷和处理文件名

最后,让我们考虑一个更复杂的例子,该例子受到 plone.namedfile 中的字段序列化器的启发。

假设我们有一个值类型,旨在表示一个带有文件名和内容类型的二进制文件

>>> from zope.interface import Interface, implementer
>>> from zope import schema
>>> class IFileValue(Interface):
...     data = schema.Bytes(title=u"Raw data")
...     contentType = schema.ASCIILine(title=u"MIME type")
...     filename = schema.ASCIILine(title=u"Filename")
>>> @implementer(IFileValue)
... class FileValue(object):
...
...     def __init__(self, data, contentType, filename):
...         self.data = data
...         self.contentType = contentType
...         self.filename = filename

假设我们有一个自定义字段类型来表示这一点

>>> from zope.schema.interfaces import IObject
>>> class IFileField(IObject):
...     pass
>>> @implementer(IFileField)
... class FileField(schema.Object):
...     schema = IFileValue
...     def __init__(self, **kw):
...         if 'schema' in kw:
...             self.schema = kw.pop('schema')
...         super(FileField, self).__init__(schema=self.schema, **kw)

我们可以为该字段注册一个字段序列化器,它将执行以下操作

  • 坚持该字段只能用作主要字段,因为在标题中编码二进制文件几乎没有意义。

  • 在 Content-Disposition 标题中保存文件名。

  • 能够从该标题中再次读取文件名。

  • 使用 base64 编码有效载荷

    >>> from plone.rfc822.interfaces import IFieldMarshaler
    >>> from email.encoders import encode_base64
    
    >>> from zope.component import adapter
    >>> from plone.rfc822.defaultfields import BaseFieldMarshaler
    
    >>> @adapter(Interface, IFileField)
    ... class FileFieldMarshaler(BaseFieldMarshaler):
    ...
    ...     ascii = False
    ...
    ...     def encode(self, value, charset='utf-8', primary=False):
    ...         if not primary:
    ...             raise ValueError("File field cannot be marshaled as a non-primary field")
    ...         if value is None:
    ...             return None
    ...         return value.data
    ...
    ...     def decode(self, value, message=None, charset='utf-8', contentType=None, primary=False):
    ...         filename = None
    ...         # get the filename from the Content-Disposition header if possible
    ...         if primary and message is not None:
    ...             filename = message.get_filename(None)
    ...         return FileValue(value, contentType, filename)
    ...
    ...     def getContentType(self):
    ...         value = self._query()
    ...         if value is None:
    ...             return None
    ...         return value.contentType
    ...
    ...     def getCharset(self, default='utf-8'):
    ...         return None # this is not text data!
    ...
    ...     def postProcessMessage(self, message):
    ...         value = self._query()
    ...         if value is not None:
    ...             filename = value.filename
    ...             if filename:
    ...                 # Add a new header storing the filename if we have one
    ...                 message.add_header('Content-Disposition', 'attachment', filename=filename)
    
    >>> from zope.component import provideAdapter
    >>> provideAdapter(FileFieldMarshaler)
    

为了说明序列化,让我们创建一个包含两个文件字段的内容对象。

>>> class IFileContent(Interface):
...     file1 = FileField()
...     file2 = FileField()
>>> @implementer(IFileContent)
... class FileContent(object):
...     file1 = None
...     file2 = None
>>> fileContent = FileContent()
>>> fileContent.file1 = FileValue('dummy file', 'text/plain', 'dummy1.txt')
>>> fileContent.file2 = FileValue('<html><body>test</body></html>', 'text/html', 'dummy2.html')

到目前为止,这两个字段都没有标记为主要字段。让我们看看当我们尝试从这个模式中构建一个消息时会发生什么。

>>> from plone.rfc822 import constructMessageFromSchema
>>> message = constructMessageFromSchema(fileContent, IFileContent)
>>> print(message.as_string())
<BLANKLINE>
<BLANKLINE>

不出所料,我们没有获得任何消息头部和消息主体。现在让我们将其中一个字段标记为主要字段

>>> from plone.rfc822.interfaces import IPrimaryField
>>> from zope.interface import alsoProvides
>>> alsoProvides(IFileContent['file1'], IPrimaryField)
>>> message = constructMessageFromSchema(fileContent, IFileContent)
>>> messageBody = message.as_string()
>>> print(messageBody)
MIME-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="dummy1.txt"
<BLANKLINE>
ZHVtbXkgZmlsZQ==

在这里,我们有一个 base64 编码的有效载荷、一个 Content-Disposition 头部和一个根据主要字段的内容类型头部。

我们也可以从这个消息中重建对象。

>>> from plone.rfc822 import initializeObjectFromSchema
>>> from email import message_from_string
>>> inputMessage = message_from_string(messageBody)
>>> newFileContent = FileContent()
>>> initializeObjectFromSchema(newFileContent, IFileContent, inputMessage)
>>> newFileContent.file1.data
b'dummy file'
>>> newFileContent.file1.contentType
'text/plain'
>>> newFileContent.file1.filename
'dummy1.txt'
>>> newFileContent.file2 is None
True

现在让我们展示如果我们编码消息中的两个文件会发生什么。在这种情况下,我们应该得到一个包含两个有效载荷的多部分文档。

>>> alsoProvides(IFileContent['file2'], IPrimaryField)
>>> message = constructMessageFromSchema(fileContent, IFileContent)
>>> messageBody = message.as_string()
>>> print(messageBody) # doctest: +ELLIPSIS
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============...=="
<BLANKLINE>
--===============...==
MIME-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="dummy1.txt"
<BLANKLINE>
ZHVtbXkgZmlsZQ==
--===============...==
MIME-Version: 1.0
Content-Type: text/html
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="dummy2.html"
<BLANKLINE>
PGh0bWw+PGJvZHk+dGVzdDwvYm9keT48L2h0bWw+
--===============...==--...

同样,我们也可以重建对象,这次包含两个字段

>>> inputMessage = message_from_string(messageBody)
>>> newFileContent = FileContent()
>>> initializeObjectFromSchema(newFileContent, IFileContent, inputMessage)
>>> newFileContent.file1.data
b'dummy file'
>>> newFileContent.file1.contentType
'text/plain'
>>> newFileContent.file1.filename
'dummy1.txt'
>>> newFileContent.file2.data
b'<html><body>test</body></html>'
>>> newFileContent.file2.contentType
'text/html'
>>> newFileContent.file2.filename
'dummy2.html'

Py2 和 Py3 之间的特殊性

测试 Python 2 和 3 stdlib 之间不同的特殊行为:非 utf8 字符串中的换行符处理。

Python 2.7 的 email.header 保持带有转义值的行,而 Python 3.6 将其转换为 RFC2047 编码的头部,请参阅 https://tools.ietf.org/html/rfc2047.html 技术上两者都是可以的。

>>> content.description = "Test content\nwith newline difference"
>>> msg = constructMessageFromSchema(content, ITestContent)
>>> effective_output = msg.as_string()
>>> effective_output.split('\n')[1]
'description: =?utf-8?q?Test_content=5Cnwith_newline_difference?='

项目详情


下载文件

为您的平台下载文件。如果您不确定选择哪一个,请了解更多关于 安装包 的信息。

源分布

plone.rfc822-3.0.1.tar.gz (38.4 kB 查看哈希)

上传

构建分布

plone.rfc822-3.0.1-py3-none-any.whl (29.2 kB 查看哈希值)

上传时间 Python 3

支持者