Документация Python неофициальный перевод

email.examples.md

394 строк · 17.0 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.11/library/email.examples.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [`email`](https://python-all.ru/3.11/library/email.html#module-email): Примеры89Вот несколько примеров использования пакета [`email`](https://python-all.ru/3.11/library/email.html#module-email) для чтения, записи, и отправки простых email-сообщений, а также более сложных MIME-сообщений.1011Сначала посмотрим, как создать и отправить простое текстовое сообщение (как текст, так и адреса могут содержать символы Unicode):1213```python14# Импорт smtplib для функции отправки15import smtplib1617# Импортируются необходимые модули email.18from email.message import EmailMessage1920# Открыть текстовый файл, имя которого находится в textfile, для чтения.21with open(textfile) as fp:22    # Создать сообщение text/plain23    msg = EmailMessage()24    msg.set_content(fp.read())2526# me == адрес электронной почты отправителя27# you == адрес электронной почты получателя28msg['Subject'] = f'The contents of {textfile}'29msg['From'] = me30msg['To'] = you3132# Отправить сообщение через собственный SMTP-сервер.33s = smtplib.SMTP('localhost')34s.send_message(msg)35s.quit()36```3738Разбор заголовков [**RFC 822**](https://python-all.ru/3.11/library/email.examples.html) легко выполняется с помощью классов из модуля [`parser`](https://python-all.ru/3.11/library/email.parser.html#module-email.parser):3940```python41# Импортируются необходимые модули email.42from email.parser import BytesParser, Parser43from email.policy import default4445# Если заголовки e-mail находятся в файле, раскомментируйте эти две строки:46# with open(messagefile, 'rb') as fp:47#     headers = BytesParser(policy=default).parse(fp)4849#  Или для разбора заголовков из строки (это нечастая операция), используйте:50headers = Parser(policy=default).parsestr(51        'From: Foo Bar <user@example.com>\n'52        'To: <someone_else@example.com>\n'53        'Subject: Test message\n'54        '\n'55        'Body would go here\n')5657#  Теперь к элементам заголовков можно обращаться как к словарю:58print('To: {}'.format(headers['to']))59print('From: {}'.format(headers['from']))60print('Subject: {}'.format(headers['subject']))6162# Также можно обращаться к частям адресов:63print('Recipient username: {}'.format(headers['to'].addresses[0].username))64print('Sender name: {}'.format(headers['from'].addresses[0].display_name))65```6667Вот пример отправки MIME-сообщения, содержащего много семейных фотографий, которые могут находиться в каталоге:6869```python70# Импортируем smtplib для непосредственной отправки.71import smtplib7273# Вот модули пакета email, которые нам понадобятся.74from email.message import EmailMessage7576# Создаём контейнерное email-сообщение.77msg = EmailMessage()78msg['Subject'] = 'Our family reunion'79# me == адрес электронной почты отправителя80# family = список адресов электронной почты всех получателей81msg['From'] = me82msg['To'] = ', '.join(family)83msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'8485# Откройте файлы в бинарном режиме. Можно также опустить подтип86# если хотите, чтобы MIMEImage угадал его.87for file in pngfiles:88    with open(file, 'rb') as fp:89        img_data = fp.read()90    msg.add_attachment(img_data, maintype='image',91                                 subtype='png')9293# Отправляем email через собственный SMTP-сервер.94with smtplib.SMTP('localhost') as s:95    s.send_message(msg)96```9798Вот пример отправки всего содержимого каталога в виде email- сообщения: [\[1\]](https://python-all.ru/3.11/library/email.examples.html#id3)99100```python101#!/usr/bin/env python3102103"""Отправляем содержимое каталога как MIME-сообщение."""104105import os106import smtplib107# Для определения типа MIME по расширению имени файла108import mimetypes109110from argparse import ArgumentParser111112from email.message import EmailMessage113from email.policy import SMTP114115def main():116    parser = ArgumentParser(description="""\117Send the contents of a directory as a MIME message.118Unless the -o option is given, the email is sent by forwarding to your local119SMTP server, which then does the normal delivery process.  Your local machine120must be running an SMTP server.121""")122    parser.add_argument('-d', '--directory',123                        help="""Mail the contents of the specified directory,124                        otherwise use the current directory.  Only the regular125                        files in the directory are sent, and we don't recurse to126                        subdirectories.""")127    parser.add_argument('-o', '--output',128                        metavar='FILE',129                        help="""Print the composed message to FILE instead of130                        sending the message to the SMTP server.""")131    parser.add_argument('-s', '--sender', required=True,132                        help='The value of the From: header (required)')133    parser.add_argument('-r', '--recipient', required=True,134                        action='append', metavar='RECIPIENT',135                        default=[], dest='recipients',136                        help='A To: header value (at least one required)')137    args = parser.parse_args()138    directory = args.directory139    if not directory:140        directory = '.'141    # Создаём сообщение142    msg = EmailMessage()143    msg['Subject'] = f'Contents of directory {os.path.abspath(directory)}'144    msg['To'] = ', '.join(args.recipients)145    msg['From'] = args.sender146    msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'147148    for filename in os.listdir(directory):149        path = os.path.join(directory, filename)150        if not os.path.isfile(path):151            continue152        # Определяем тип содержимого по расширению файла. Кодировка153        # будет проигнорирована, хотя стоит проверять простые вещи, такие как154        # gzip-файлы или сжатые файлы.155        ctype, encoding = mimetypes.guess_type(path)156        if ctype is None or encoding is not None:157            # Не удалось определить тип, или файл закодирован (сжат), поэтому158            # используем общий тип 'набор битов'.159            ctype = 'application/octet-stream'160        maintype, subtype = ctype.split('/', 1)161        with open(path, 'rb') as fp:162            msg.add_attachment(fp.read(),163                               maintype=maintype,164                               subtype=subtype,165                               filename=filename)166    # Теперь отправляем или сохраняем сообщение167    if args.output:168        with open(args.output, 'wb') as fp:169            fp.write(msg.as_bytes(policy=SMTP))170    else:171        with smtplib.SMTP('localhost') as s:172            s.send_message(msg)173174if __name__ == '__main__':175    main()176```177178Вот пример распаковки MIME-сообщения вроде показанного выше в каталог с файлами:179180```python181#!/usr/bin/env python3182183"""Распаковываем MIME-сообщение в каталог файлов."""184185import os186import email187import mimetypes188189from email.policy import default190191from argparse import ArgumentParser192193def main():194    parser = ArgumentParser(description="""\195Unpack a MIME message into a directory of files.196""")197    parser.add_argument('-d', '--directory', required=True,198                        help="""Unpack the MIME message into the named199                        directory, which will be created if it doesn't already200                        exist.""")201    parser.add_argument('msgfile')202    args = parser.parse_args()203204    with open(args.msgfile, 'rb') as fp:205        msg = email.message_from_binary_file(fp, policy=default)206207    try:208        os.mkdir(args.directory)209    except FileExistsError:210        pass211212    counter = 1213    for part in msg.walk():214        # multipart/* – это просто контейнеры215        if part.get_content_maintype() == 'multipart':216            continue217        # Приложениям следует тщательно проверять имя файла, чтобы218        # email-сообщение не могло перезаписать важные файлы219        filename = part.get_filename()220        if not filename:221            ext = mimetypes.guess_extension(part.get_content_type())222            if not ext:223                # Использовать универсальное расширение для набора битов.224                ext = '.bin'225            filename = f'part-{counter:03d}{ext}'226        counter += 1227        with open(os.path.join(args.directory, filename), 'wb') as fp:228            fp.write(part.get_payload(decode=True))229230if __name__ == '__main__':231    main()232```233234Вот пример создания HTML-сообщения с альтернативной текстовой версией. Чтобы было интереснее, мы добавляем связанное изображение в HTML-часть и сохраняем копию того, что собираемся отправить на диск, а также отправляем его.235236```python237#!/usr/bin/env python3238239import smtplib240241from email.message import EmailMessage242from email.headerregistry import Address243from email.utils import make_msgid244245# Создаём базовое текстовое сообщение.246msg = EmailMessage()247msg['Subject'] = "Ayons asperges pour le déjeuner"248msg['From'] = Address("Pepé Le Pew", "pepe", "example.com")249msg['To'] = (Address("Penelope Pussycat", "penelope", "example.com"),250             Address("Fabrette Pussycat", "fabrette", "example.com"))251msg.set_content("""\252Salut!253254Cela ressemble à un excellent recipie[1] déjeuner.255256[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718257258--Pepé259""")260261# Добавляем HTML-версию. Это преобразует сообщение в multipart/alternative.262# контейнер, где исходное текстовое сообщение – первая часть, а новое html263# сообщение – вторая часть.264asparagus_cid = make_msgid()265msg.add_alternative("""\266<html>267  <head></head>268  <body>269    <p>Salut!</p>270    <p>Cela ressemble à un excellent271        <a href="http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718">272            recipie273        </a> déjeuner.274    </p>275    <img src="cid:{asparagus_cid}" />276  </body>277</html>278""".format(asparagus_cid=asparagus_cid[1:-1]), subtype='html')279# Обратите внимание: для использования в html потребовалось снять угловые скобки с msgid.280281# Теперь добавьте связанное изображение в html-часть.282with open("roasted-asparagus.jpg", 'rb') as img:283    msg.get_payload()[1].add_related(img.read(), 'image', 'jpeg',284                                     cid=asparagus_cid)285286# Создается локальная копия отправляемых данных.287with open('outgoing.msg', 'wb') as f:288    f.write(bytes(msg))289290# Отправка сообщения через локальный SMTP-сервер.291with smtplib.SMTP('localhost') as s:292    s.send_message(msg)293```294295Если бы нам отправили сообщение из предыдущего примера, вот один из способов, как его можно обработать:296297```python298import os299import sys300import tempfile301import mimetypes302import webbrowser303304# Импортируются необходимые модули email.305from email import policy306from email.parser import BytesParser307308def magic_html_parser(html_text, partfiles):309    """Возвращает безопасно очищенный html, связанный с файлами частей.310311    Атрибуты href="cid:...." перезаписываются, чтобы указывать на имена файлов из partfiles.312    Хотя это и нетривиально, возможно реализовать с помощью html.parser.313    """314    raise NotImplementedError("Add the magic needed")315316# В реальной программе имя файла получается из аргументов.317with open('outgoing.msg', 'rb') as fp:318    msg = BytesParser(policy=policy.default).parse(fp)319320# Теперь к элементам заголовка можно обращаться как к словарю, и любые не-ASCII символы будут321# преобразованы в Unicode:322print('To:', msg['to'])323print('From:', msg['from'])324print('Subject:', msg['subject'])325326# Если нужно вывести предварительный просмотр содержимого сообщения, можно извлечь327# наименее отформатированную полезную нагрузку и вывести первые три строки. Конечно,328# если сообщение не содержит части в обычном тексте, вывод первых трех строк html329# вероятно, бесполезен, но это всего лишь концептуальный пример.330simplest = msg.get_body(preferencelist=('plain', 'html'))331print()332print(''.join(simplest.get_content().splitlines(keepends=True)[:3]))333334ans = input("View full message?")335if ans.lower()[0] == 'n':336    sys.exit()337338# Можно извлечь наиболее полную альтернативу для отображения:339richest = msg.get_body()340partfiles = {}341if richest['content-type'].maintype == 'text':342    if richest['content-type'].subtype == 'plain':343        for line in richest.get_content().splitlines():344            print(line)345        sys.exit()346    elif richest['content-type'].subtype == 'html':347        body = richest348    else:349        print("Don't know how to display {}".format(richest.get_content_type()))350        sys.exit()351elif richest['content-type'].content_type == 'multipart/related':352    body = richest.get_body(preferencelist=('html'))353    for part in richest.iter_attachments():354        fn = part.get_filename()355        if fn:356            extension = os.path.splitext(part.get_filename())[1]357        else:358            extension = mimetypes.guess_extension(part.get_content_type())359        with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as f:360            f.write(part.get_content())361            # снова удалите угловые скобки, чтобы перейти от формы cid в email к форме в html.362            partfiles[part['content-id'][1:-1]] = f.name363else:364    print("Don't know how to display {}".format(richest.get_content_type()))365    sys.exit()366with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:367    f.write(magic_html_parser(body.get_content(), partfiles))368webbrowser.open(f.name)369os.remove(f.name)370for fn in partfiles.values():371    os.remove(fn)372373# Конечно, есть много email-сообщений, которые могут нарушить эту простую374# программу, но она справится с наиболее распространенными.375```376377До приглашения вывод из примера выше:378379```text380To: Penelope Pussycat <penelope@example.com>, Fabrette Pussycat <fabrette@example.com>381From: Pepé Le Pew <pepe@example.com>382Subject: Ayons asperges pour le déjeuner383384Salut!385386Cela ressemble à un excellent recipie[1] déjeuner.387```388389Сноски390391\[[1](https://python-all.ru/3.11/library/email.examples.html#id2)\]392393Спасибо Matthew Dixon Cowles за оригинальную идею и примеры.394