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

email.examples.md

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

1> **Источник:** https://python-all.ru/3.12/library/email.examples.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [`email`](https://python-all.ru/3.12/library/email.html#module-email): Примеры89Вот несколько примеров использования пакета [`email`](https://python-all.ru/3.12/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.12/library/email.examples.html) легко выполняется с помощью классов из модуля [`parser`](https://python-all.ru/3.12/library/email.parser.html#module-email.parser):3940```python41# Импортируются необходимые модули email.42#from email.parser import BytesParser43from email.parser import Parser44from email.policy import default4546# Если заголовки e-mail находятся в файле, раскомментируйте эти две строки:47# with open(messagefile, 'rb') as fp:48#     headers = BytesParser(policy=default).parse(fp)4950#  Или для разбора заголовков из строки (это нечастая операция), используйте:51headers = Parser(policy=default).parsestr(52        'From: Foo Bar <user@example.com>\n'53        'To: <someone_else@example.com>\n'54        'Subject: Test message\n'55        '\n'56        'Body would go here\n')5758#  Теперь к элементам заголовков можно обращаться как к словарю:59print('To: {}'.format(headers['to']))60print('From: {}'.format(headers['from']))61print('Subject: {}'.format(headers['subject']))6263# Также можно обращаться к частям адресов:64print('Recipient username: {}'.format(headers['to'].addresses[0].username))65print('Sender name: {}'.format(headers['from'].addresses[0].display_name))66```6768Вот пример отправки MIME-сообщения, содержащего много семейных фотографий, которые могут находиться в каталоге:6970```python71# Импортируем smtplib для непосредственной отправки.72import smtplib7374# Вот модули пакета email, которые нам понадобятся.75from email.message import EmailMessage7677# Создаём контейнерное email-сообщение.78msg = EmailMessage()79msg['Subject'] = 'Our family reunion'80# me == адрес электронной почты отправителя81# family = список адресов электронной почты всех получателей82msg['From'] = me83msg['To'] = ', '.join(family)84msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'8586# Откройте файлы в бинарном режиме. Можно также опустить подтип87# если хотите, чтобы MIMEImage угадал его.88for file in pngfiles:89    with open(file, 'rb') as fp:90        img_data = fp.read()91    msg.add_attachment(img_data, maintype='image',92                                 subtype='png')9394# Отправляем email через собственный SMTP-сервер.95with smtplib.SMTP('localhost') as s:96    s.send_message(msg)97```9899Вот пример отправки всего содержимого каталога в виде email- сообщения: [\[1\]](https://python-all.ru/3.12/library/email.examples.html#id3)100101```python102#!/usr/bin/env python3103104"""Отправляем содержимое каталога как MIME-сообщение."""105106import os107import smtplib108# Для определения типа MIME по расширению имени файла109import mimetypes110111from argparse import ArgumentParser112113from email.message import EmailMessage114from email.policy import SMTP115116def main():117    parser = ArgumentParser(description="""\118Send the contents of a directory as a MIME message.119Unless the -o option is given, the email is sent by forwarding to your local120SMTP server, which then does the normal delivery process.  Your local machine121must be running an SMTP server.122""")123    parser.add_argument('-d', '--directory',124                        help="""Mail the contents of the specified directory,125                        otherwise use the current directory.  Only the regular126                        files in the directory are sent, and we don't recurse to127                        subdirectories.""")128    parser.add_argument('-o', '--output',129                        metavar='FILE',130                        help="""Print the composed message to FILE instead of131                        sending the message to the SMTP server.""")132    parser.add_argument('-s', '--sender', required=True,133                        help='The value of the From: header (required)')134    parser.add_argument('-r', '--recipient', required=True,135                        action='append', metavar='RECIPIENT',136                        default=[], dest='recipients',137                        help='A To: header value (at least one required)')138    args = parser.parse_args()139    directory = args.directory140    if not directory:141        directory = '.'142    # Создаём сообщение143    msg = EmailMessage()144    msg['Subject'] = f'Contents of directory {os.path.abspath(directory)}'145    msg['To'] = ', '.join(args.recipients)146    msg['From'] = args.sender147    msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'148149    for filename in os.listdir(directory):150        path = os.path.join(directory, filename)151        if not os.path.isfile(path):152            continue153        # Определяем тип содержимого по расширению файла. Кодировка154        # будет проигнорирована, хотя стоит проверять простые вещи, такие как155        # gzip-файлы или сжатые файлы.156        ctype, encoding = mimetypes.guess_type(path)157        if ctype is None or encoding is not None:158            # Не удалось определить тип, или файл закодирован (сжат), поэтому159            # используем общий тип 'набор битов'.160            ctype = 'application/octet-stream'161        maintype, subtype = ctype.split('/', 1)162        with open(path, 'rb') as fp:163            msg.add_attachment(fp.read(),164                               maintype=maintype,165                               subtype=subtype,166                               filename=filename)167    # Теперь отправляем или сохраняем сообщение168    if args.output:169        with open(args.output, 'wb') as fp:170            fp.write(msg.as_bytes(policy=SMTP))171    else:172        with smtplib.SMTP('localhost') as s:173            s.send_message(msg)174175if __name__ == '__main__':176    main()177```178179Вот пример распаковки MIME-сообщения вроде показанного выше в каталог с файлами:180181```python182#!/usr/bin/env python3183184"""Распаковываем MIME-сообщение в каталог файлов."""185186import os187import email188import mimetypes189190from email.policy import default191192from argparse import ArgumentParser193194def main():195    parser = ArgumentParser(description="""\196Unpack a MIME message into a directory of files.197""")198    parser.add_argument('-d', '--directory', required=True,199                        help="""Unpack the MIME message into the named200                        directory, which will be created if it doesn't already201                        exist.""")202    parser.add_argument('msgfile')203    args = parser.parse_args()204205    with open(args.msgfile, 'rb') as fp:206        msg = email.message_from_binary_file(fp, policy=default)207208    try:209        os.mkdir(args.directory)210    except FileExistsError:211        pass212213    counter = 1214    for part in msg.walk():215        # multipart/* – это просто контейнеры216        if part.get_content_maintype() == 'multipart':217            continue218        # Приложениям следует тщательно проверять имя файла, чтобы219        # email-сообщение не могло перезаписать важные файлы220        filename = part.get_filename()221        if not filename:222            ext = mimetypes.guess_extension(part.get_content_type())223            if not ext:224                # Использовать универсальное расширение для набора битов.225                ext = '.bin'226            filename = f'part-{counter:03d}{ext}'227        counter += 1228        with open(os.path.join(args.directory, filename), 'wb') as fp:229            fp.write(part.get_payload(decode=True))230231if __name__ == '__main__':232    main()233```234235Вот пример создания HTML-сообщения с альтернативной текстовой версией. Чтобы было интереснее, мы добавляем связанное изображение в HTML-часть и сохраняем копию того, что собираемся отправить на диск, а также отправляем его.236237```python238#!/usr/bin/env python3239240import smtplib241242from email.message import EmailMessage243from email.headerregistry import Address244from email.utils import make_msgid245246# Создаём базовое текстовое сообщение.247msg = EmailMessage()248msg['Subject'] = "Ayons asperges pour le déjeuner"249msg['From'] = Address("Pepé Le Pew", "pepe", "example.com")250msg['To'] = (Address("Penelope Pussycat", "penelope", "example.com"),251             Address("Fabrette Pussycat", "fabrette", "example.com"))252msg.set_content("""\253Salut!254255Cela ressemble à un excellent recipie[1] déjeuner.256257[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718258259--Pepé260""")261262# Добавляем HTML-версию. Это преобразует сообщение в multipart/alternative.263# контейнер, где исходное текстовое сообщение – первая часть, а новое html264# сообщение – вторая часть.265asparagus_cid = make_msgid()266msg.add_alternative("""\267<html>268  <head></head>269  <body>270    <p>Salut!</p>271    <p>Cela ressemble à un excellent272        <a href="http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718">273            recipie274        </a> déjeuner.275    </p>276    <img src="cid:{asparagus_cid}">277  </body>278</html>279""".format(asparagus_cid=asparagus_cid[1:-1]), subtype='html')280# Обратите внимание: для использования в html потребовалось снять угловые скобки с msgid.281282# Теперь добавьте связанное изображение в html-часть.283with open("roasted-asparagus.jpg", 'rb') as img:284    msg.get_payload()[1].add_related(img.read(), 'image', 'jpeg',285                                     cid=asparagus_cid)286287# Создается локальная копия отправляемых данных.288with open('outgoing.msg', 'wb') as f:289    f.write(bytes(msg))290291# Отправка сообщения через локальный SMTP-сервер.292with smtplib.SMTP('localhost') as s:293    s.send_message(msg)294```295296Если бы нам отправили сообщение из предыдущего примера, вот один из способов, как его можно обработать:297298```python299import os300import sys301import tempfile302import mimetypes303import webbrowser304305# Импортируются необходимые модули email.306from email import policy307from email.parser import BytesParser308309def magic_html_parser(html_text, partfiles):310    """Возвращает безопасно очищенный html, связанный с файлами частей.311312    Атрибуты href="cid:...." перезаписываются, чтобы указывать на имена файлов из partfiles.313    Хотя это и нетривиально, возможно реализовать с помощью html.parser.314    """315    raise NotImplementedError("Add the magic needed")316317# В реальной программе имя файла получается из аргументов.318with open('outgoing.msg', 'rb') as fp:319    msg = BytesParser(policy=policy.default).parse(fp)320321# Теперь к элементам заголовка можно обращаться как к словарю, и любые не-ASCII символы будут322# преобразованы в Unicode:323print('To:', msg['to'])324print('From:', msg['from'])325print('Subject:', msg['subject'])326327# Если нужно вывести предварительный просмотр содержимого сообщения, можно извлечь328# наименее отформатированную полезную нагрузку и вывести первые три строки. Конечно,329# если сообщение не содержит части в обычном тексте, вывод первых трех строк html330# вероятно, бесполезен, но это всего лишь концептуальный пример.331simplest = msg.get_body(preferencelist=('plain', 'html'))332print()333print(''.join(simplest.get_content().splitlines(keepends=True)[:3]))334335ans = input("View full message?")336if ans.lower()[0] == 'n':337    sys.exit()338339# Можно извлечь наиболее полную альтернативу для отображения:340richest = msg.get_body()341partfiles = {}342if richest['content-type'].maintype == 'text':343    if richest['content-type'].subtype == 'plain':344        for line in richest.get_content().splitlines():345            print(line)346        sys.exit()347    elif richest['content-type'].subtype == 'html':348        body = richest349    else:350        print("Don't know how to display {}".format(richest.get_content_type()))351        sys.exit()352elif richest['content-type'].content_type == 'multipart/related':353    body = richest.get_body(preferencelist=('html'))354    for part in richest.iter_attachments():355        fn = part.get_filename()356        if fn:357            extension = os.path.splitext(part.get_filename())[1]358        else:359            extension = mimetypes.guess_extension(part.get_content_type())360        with tempfile.NamedTemporaryFile(suffix=extension, delete=False) as f:361            f.write(part.get_content())362            # снова удалите угловые скобки, чтобы перейти от формы cid в email к форме в html.363            partfiles[part['content-id'][1:-1]] = f.name364else:365    print("Don't know how to display {}".format(richest.get_content_type()))366    sys.exit()367with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:368    f.write(magic_html_parser(body.get_content(), partfiles))369webbrowser.open(f.name)370os.remove(f.name)371for fn in partfiles.values():372    os.remove(fn)373374# Конечно, есть много email-сообщений, которые могут нарушить эту простую375# программу, но она справится с наиболее распространенными.376```377378До приглашения вывод из примера выше:379380```text381To: Penelope Pussycat <penelope@example.com>, Fabrette Pussycat <fabrette@example.com>382From: Pepé Le Pew <pepe@example.com>383Subject: Ayons asperges pour le déjeuner384385Salut!386387Cela ressemble à un excellent recipie[1] déjeuner.388```389390Сноски391392\[[1](https://python-all.ru/3.12/library/email.examples.html#id2)\]393394Спасибо Matthew Dixon Cowles за оригинальную идею и примеры.395