@@ -283,6 +283,45 @@ def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
283283_RAW_TO_EMAIL_MAPPING = {raw : email for email , raw in _EMAIL_TO_RAW_MAPPING .items ()}
284284
285285
286+ # This class is for writing RFC822 messages
287+ class RFC822Policy (email .policy .EmailPolicy ):
288+ """
289+ This is :class:`email.policy.EmailPolicy`, but with a simple ``header_store_parse``
290+ implementation that handles multi-line values, and some nice defaults.
291+ """
292+
293+ utf8 = True
294+ mangle_from_ = False
295+ max_line_length = 0
296+
297+ def header_store_parse (self , name : str , value : str ) -> tuple [str , str ]:
298+ size = len (name ) + 2
299+ value = value .replace ("\n " , "\n " + " " * size )
300+ return (name , value )
301+
302+
303+ # This class is for writing RFC822 messages
304+ class RFC822Message (email .message .EmailMessage ):
305+ """
306+ This is :class:`email.message.EmailMessage` with two small changes: it defaults to
307+ our `RFC822Policy`, and it correctly writes unicode when being called
308+ with `bytes()`.
309+ """
310+
311+ def __init__ (self ) -> None :
312+ super ().__init__ (policy = RFC822Policy ())
313+
314+ def as_bytes (
315+ self , unixfrom : bool = False , policy : email .policy .Policy | None = None
316+ ) -> bytes :
317+ """
318+ Return the bytes representation of the message.
319+
320+ This handles unicode encoding.
321+ """
322+ return self .as_string (unixfrom , policy = policy ).encode ("utf-8" )
323+
324+
286325def parse_email (data : bytes | str ) -> tuple [RawMetadata , dict [str , list [str ]]]:
287326 """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
288327
@@ -860,3 +899,35 @@ def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
860899 """``Provides`` (deprecated)"""
861900 obsoletes : _Validator [list [str ] | None ] = _Validator (added = "1.1" )
862901 """``Obsoletes`` (deprecated)"""
902+
903+ def as_rfc822 (self ) -> RFC822Message :
904+ """
905+ Return an RFC822 message with the metadata.
906+ """
907+ message = RFC822Message ()
908+ self ._write_metadata (message )
909+ return message
910+
911+ def _write_metadata (self , message : RFC822Message ) -> None :
912+ """
913+ Return an RFC822 message with the metadata.
914+ """
915+ for name , validator in self .__class__ .__dict__ .items ():
916+ if isinstance (validator , _Validator ) and name != "description" :
917+ value = getattr (self , name )
918+ email_name = _RAW_TO_EMAIL_MAPPING [name ]
919+ if value is not None :
920+ if email_name == "project-url" :
921+ for label , url in value .items ():
922+ message [email_name ] = f"{ label } , { url } "
923+ elif email_name == "keywords" :
924+ message [email_name ] = "," .join (value )
925+ elif isinstance (value , list ):
926+ for item in value :
927+ message [email_name ] = str (item )
928+ else :
929+ message [email_name ] = str (value )
930+
931+ # The description is a special case because it is in the body of the message.
932+ if self .description is not None :
933+ message .set_payload (self .description )
0 commit comments