2012-02-22 7 views
7

Deseo enviar mensajes de correo electrónico que tengan cuerpos Unicode arbitrarios en un programa Python 3.2. Pero, en realidad, estos mensajes consistirán en gran parte de texto ASCII de 7 bits. Entonces me gustaría que los mensajes codificados en utf-8 usen citado-imprimible. Hasta el momento, he encontrado que esto funciona, pero parece mal:¿Cómo uso el módulo de correo electrónico Python 3.2 para enviar mensajes Unicode codificados en utf-8 con imprimible entre comillas?

c = email.charset.Charset('utf-8') 
c.body_encoding = email.charset.QP 
m = email.message.Message() 
m.set_payload("My message with an '\u05d0' in it.".encode('utf-8').decode('iso8859-1'), c) 

Esto se traduce en un mensaje de correo electrónico con exactamente el contenido correcto:

To: [email protected] 
From: [email protected] 
Subject: This is a subjective subject. 
MIME-Version: 1.0 
Content-Type: text/plain; charset="utf-8" 
Content-Transfer-Encoding: quoted-printable 

My message with an '=D7=90' in it. 

En particular b'\xd7\x90'.decode('utf-8') resultados en el carácter Unicode originales . Por lo tanto, la codificación quoted-printable representa correctamente el utf-8. Soy muy consciente de que este es un hack increíblemente feo. Pero funciona.

Esto es Python 3. Se espera que las cadenas de texto siempre sean unicode. No debería tener que decodificarlo para utf-8. Y luego convertirlo de bytes de nuevo en str por .decode('iso8859-1') es un truco horrible, y no debería tener que hacer eso tampoco.

¿El módulo email acaba de romperse con respecto a las codificaciones? ¿No estoy recibiendo algo?

He intentado simplemente establecerlo antiguo, sin juego de caracteres. Eso me deja con un mensaje de correo electrónico Unicode, y eso no está bien. También intenté dejar los pasos encode y decode. Si los dejo a ambos, se queja de que el \u05d0 está fuera de rango al tratar de decidir si ese carácter necesita ser citado en la codificación imprimible entre comillas. Si salgo solo en el paso encode, se queja amargamente de cómo estoy pasando en un bytes y quiere un str.

+0

Si ' "Mi mensaje con un '\ u05d0' en ella."' Es el Unicode que desea, entonces no se puede usar '" Mi mensaje con un '\ u05d0' en it. ". encode ('utf-8'). decode ('iso8859-1')' ya que este es un unicode diferente. (Habrá alterado el mensaje.) – unutbu

+0

@unutbu: Felicidades por descubrir por qué el código es muy feo. Pero funciona. Alcanza el resultado deseado. Ver mi actualización – Omnifarious

Respuesta

8

Ese paquete de correo electrónico no se confunde sobre cuál es cuál (codificación de datos binarios codificados unicode versus content-transfer), pero la documentación no lo deja muy claro, ya que gran parte de la documentación data de una época en la que "codificación "significa content-transfer-encoding. Estamos trabajando en una mejor API que hará todo esto más fácil de asimilar (y mejores documentos).

En realidad, hay una manera de hacer que el paquete de correo electrónico use QP para cuerpos utf-8, pero no está muy bien documentado. Usted lo hace así:

>>> charset.add_charset('utf-8', charset.QP, charset.QP) 
>>> m = MIMEText("This is utf-8 text: á", _charset='utf-8') 
>>> str(m) 
'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nThis is utf-8 text: =E1' 
+0

¡Gracias! Esto responde perfectamente a mi pregunta y me da una forma de hacer lo que quiero que no sea un hack perturbador. :-) – Omnifarious

+1

Eso maneja bien a tu personaje. Pero no maneja el personaje \ u05d0. De hecho, no codifica tu personaje como utf-8, lo codifica como iso8859-1. : -/ – Omnifarious

+0

También vea Python [issue1525919] (http://bugs.python.org/issue1525919#msg29229). – mmoya

1

Correr

import email 
import email.charset 
import email.message 

c = email.charset.Charset('utf-8') 
c.body_encoding = email.charset.QP 
m = email.message.Message() 
m.set_payload("My message with an '\u05d0' in it.", c) 
print(m.as_string()) 

Rendimientos este mensaje de rastreo:

File "/usr/lib/python3.2/email/quoprimime.py", line 81, in body_check 
    return chr(octet) != _QUOPRI_BODY_MAP[octet] 
KeyError: 1488 

Desde

In [11]: int('5d0',16) 
Out[11]: 1488 

está claro que el Unicode '\u05d0' es el personaje problema. _QUOPRI_BODY_MAP se define en quoprimime.py por

_QUOPRI_HEADER_MAP = dict((c, '=%02X' % c) for c in range(256)) 
_QUOPRI_BODY_MAP = _QUOPRI_HEADER_MAP.copy() 

Este dict sólo contiene las claves de range(256). Entonces creo que tienes razón; quoprimime.py no se puede utilizar para codificar unicode arbitrario.

Como solución alternativa, se puede usar (por defecto) base64 omitiendo

c.body_encoding = email.charset.QP 

Tenga en cuenta que la latest version de quoprimime.py no utiliza _QUOPRI_BODY_MAP en absoluto, por lo que el uso de la última Python podría solucionar el problema.

+2

Sospecho que no. El problema parece ser que no está convirtiendo correctamente a utf-8 bytes antes de aplicar la codificación imprimible entre comillas. Los métodos 'as_string' y' __str__' de 'email.message.Message' deben estar en desuso en favor de los métodos que devuelven bytes. Supongo que todo el paquete de correo electrónico está un poco confundido acerca de la diferencia entre la codificación binaria realizada en un mensaje de correo electrónico y la codificación implícita al usar un sistema de codificación de caracteres en particular. Esos dos son conceptos separados a pesar de que ambos usan el término 'codificación'. – Omnifarious

Cuestiones relacionadas