Практикум Python: отправка файлов по электронной почте::Журнал СА 3.2005
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Дата-центры  

Дата-центры: есть ли опасность утечки данных?

Российские компании уже несколько лет испытывают дефицит вычислительных мощностей. Рост числа проектов,

 Читать далее...

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6241
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6947
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4233
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 3015
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3813
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3828
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6323
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3174
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3467
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7285
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10652
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12370
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 14008
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9132
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7084
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5394
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4623
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3433
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3165
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3406
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3031
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Практикум Python: отправка файлов по электронной почте

Архив номеров / 2005 / Выпуск №3 (28) / Практикум Python: отправка файлов по электронной почте

Рубрика: Программирование /  Анализ данных   | Дополнительные материалы

СЕРГЕЙ СУПРУНОВ

Практикум Python: отправка файлов по электронной почте

Как вы отправляете по электронной почте файл из Windows? Я, например, до недавнего времени делал так: находил нужный файл в дереве каталогов в FAR; набирал в командной строке «start .», чтобы открыть каталог в «Проводнике»; щелкал по файлу правой кнопкой мыши, выбирал «Add to archive…»; затем щелкал по полученному файлу архива и выбирал «Отправить –> Адресат»… В общем, долго и утомительно. Теперь я делаю это из FAR командной строкой такого вида:

C:Temp>send to me file Годовой отчет.doc” as year2004.zip

По заголовку статьи вы уже поняли, что такого счастья удалось достичь благодаря языку Python, и здесь достаточно подробно описываются предпринятые для этого шаги. Цель статьи, как обычно, не в описании готового решения для «copy – paste», а в том, чтобы показать пути решения подобных задач.

Итак, с чем нам предстоит столкнуться. Прежде всего познакомимся с использованием модуля smtplib, входящего в стандартную библиотеку Python, и нужного нам для формирования текста почтового сообщения и собственно для отправки. Вспомним, как работать с zip-архивами. Ну и попутно решим некоторые проблемы с кодировками, традиционно присущие Windows.

Начнем с главного сценария – send.py:

Листинг 1. Начало сценария send.py

# -*- coding: cp1251 -*-

#########################################################

#  Utility for send files by e-mail from command line

#--------------------------------------------------------

#  Usage:

#   send to <addr> files <file1[ file2...]> [(nonzipped | as <zip>)]

#########################################################

import sys, os, glob, myzip, pymaconf

from mystd import mystdin, mystdout

from pysender import pysender

В приведенном фрагменте подключаются нужные нам модули. Из них sys, os и glob входят в стандартную библиотеку Python, myzip – слегка модифицированный вариант модуля, который был разработан ранее для операций упаковки файлов (см. статью «Автоматизируем FTP с помощью Python», журнал «Системный администратор» №12, декабрь 2004 г.), а остальные будут рассмотрены в процессе работы.

Чтобы созданные наработки было удобнее использовать в дальнейшем, оформим функции нашего сценария как класс pyma. Этот класс я приведу полностью, снабжая комментариями наиболее интересные моменты. Ниже будут даны еще некоторые пояснения.

Листинг 2. Середина сценария send.py (класс pyma)

classpyma:

# функция инициализации – вводим переменные, которым нужно задать значения по умолчанию

    def __init__(self):

           self.FLG_NZ = False # будем ли упаковывать файлы

           self.ZIPNAME = ''   # имя zip-архива

# Функция разбора параметров, введенных в командной строке

    def parseParameters(self, *argv):

           STOP = 0

           # должно быть не менее 5 параметров

           iflen(argv) < 5:

                 self.Usage() # (имя программы + еще 4)

           # 1-й обязательно «to»

           if argv[1][:2] != 'to':

                 self.Usage()

           # 3-й обязательно «fi[les]»

           if argv[3][:2] != 'fi':

                 self.Usage()

           self.TO = self.getaddrbyalias(argv[2])

           # если последний параметр начинается

           ifargv[-1][:2] == 'no':

                 # с «no», не упаковывать

                 self.FLG_NZ = True

                 STOP = -1

           # если предпоследний – «as»

           elif argv[-2][:2] == 'as':

                 # то последний – имя архива

                 self.ZIPNAME = argv[-1]

                 STOP = -2

           # иначе имя архива – по 1-му файлу

           else:

                 self.ZIPNAME = zn = argv[4]

                 if zn.find('*') > -1 or zn.find('?') > -1:

                        self.ZIPNAME = 'archive.zip'

           self.ZIPNAME = Unicode(self.ZIPNAME,pymaconf.fsyscodepage).encode(pymaconf.fzipcodepage)

           if self.ZIPNAME[-4:] != '.zip':

                 self.ZIPNAME = self.ZIPNAME + '.zip'

           # все остальные параметры – шаблоны файлов

           ifSTOP:     

                 files = argv[4:STOP]

           else:

                 files = argv[4:]

           if not files:

                 self.Usage()

           # формируем список файлов по шаблонам

           self.FILELIST = []

           for pattern in files:

                 tmplist = glob.glob(pattern)

                 for file in tmplist:

                        if os.path.isfile(file):

                               self.FILELIST.append(file)

           if not self.FILELIST:

                 print '\nERROR: no files found.'

                 self.Usage()

           # если нужно – запрос темы

           if pymaconf.promptsubj:

                 tmp = raw_input('Subject: ')

                 if tmp:

                        pymaconf.defsubject = tmp

                 print

           # если нужно – запрос сообщения

           ifpymaconf.promptmess:

                 tmp = raw_input('Message: ')

                 if tmp:

                        pymaconf.defmessage = tmp

                 print

# Функция определения полного адреса по псевдониму

    def getaddrbyalias(self, addr):

           abf = os.path.dirname(__file__) + os.path.sep + 'addrbook.ab'

           ab = open(abf, 'r').readlines()

           aliases = {}

           for line in ab:

                 alias, fulladdr = line.split()

                 aliases[alias] = fulladdr

           try:

                 fulladdr = aliases[addr]

           except:

                 fulladdr= addr

           return fulladdr

# Функция формирования и отправки сообщения

    def sendmail(self):

           self.parseParameters(*sys.argv)

           ifself.FLG_NZ:

           # если не упаковывать – отправка файлов по списку

                 pysender().sendfiles(self.TO, self.FILELIST)

           else:

           # иначе упаковываем по списку и отправляем архив

                 myzip.writepattzip(self.ZIPNAME, self.FILELIST)

                 pysender().sendfiles(self.TO, (self.ZIPNAME,))

                 os.remove(self.ZIPNAME)

# Функция вывода сообщения о правильном синтаксисе

    def Usage(self):

           print '''

Utility for send files by e-mail from command line

Usage:

   send to <addr> files <file1[ file2...]> [(nonzipped | as <zip>)]'''

           sys.exit()

То есть этот модуль обеспечивает лишь разбор строки параметров, а сами функции отправки и упаковки размещены в других модулях. Обратите внимание на конструкции вида «argv[-2][:2]». Они используются для того, чтобы в параметрах командной строки значащими были только первые два символа. Операция [m:n], называемая срезом, позволяет извлечь из массива «подмассив» начиная с элемента m до элемента n, не включая последний. Если m или n пропущено, подразумевается «с начала массива» или «до конца массива» соответственно.

Текстовая строка может рассматриваться как массив одиночных символов. Благодаря этому ключ «files» можно записать и как «file», что более логично при отправке одного файла, и даже как «fi». Аналогично ключ «nonzipped» можно сокращать вплоть до «no».

Конечно, можно было бы использовать более традиционный синтаксис командной строки, но такая запись воспринимается и запоминается лучше. Сравните, например, две команды:

send–t amsand@rambler.ru –f report.txt –z rep2.zip

send to amsand@rambler.ru file report.txt as rep2.zip

Лично мне больше нравится вторая, хотя она и сложнее в разборе.

Последний отрывок файла send.py приведен ниже, но большую часть происходящего в нем я поясню немного позже. На данный момент достаточно знать, что он просто вызывает функцию отправки сообщения.

Листинг 3. Конец сценария send.py

if __name__ == '__main__':

    mo = mystdout()

    mi = mystdin()

    mo.setmystdout()

    mi.setmystdin()

    nc = pyma()

    nc.sendmail()

    mo.setorigin()

    mi.setorigin()

На этом send.py завершается. Должно быть, вы уже заметили, что нигде не видно ни адреса отправителя, ни параметров smtp-сервера, через который выполняется отправка. Конечно, все это присутствует, но вынесено в конфигурационный файл pymaconf.py, представленный ниже:

Листинг 4. Конфигурационный сценарий pymaconf.py

# -*- coding: cp1251 -*-

# Имя smtp-сервера

smtpserver       = 'my.server.ru'

# Параметры smtp-авторизации

authrequire      = 0

authlogin        = ''

authpassword     = ''

# Адрес отправителя

fromaddr         = 'pyma.test@my.server.ru'

# Запрашивать ли тему сообщения

promptsubj       = 1

# Тема сообщения по умолчанию

defsubject       = 'Отправка файла'

# Запрашивать ли текст письма

promptmess       = 1

# Текст письма по умолчанию

defmessage       = 'Отправка файла\n\n\t-= pyma v.0.1 =-'

# Отправлять ли копии писем на указанный адрес

backmail         = 1

backaddr         = 'pyma.back@my.server.ru'

# Параметры перекодировок

basecodepage     = 'cp1251'   # текст в исходниках

termcodepage     = 'cp866'    # терминал (ввод-вывод)

mailcodepage     = 'cp1251'   # кодировка писем

fsyscodepage     = 'cp1251'   # кодировка имен файлов в системе

fzipcodepage     = 'cp1251'   # кодировка имени zip-архива

Собственно, здесь нет ничего интересного – просто определяется ряд переменных, которые будут использоваться в дальнейшем. Если ваш smtp-сервер требует авторизацию, установите переменную authrequire в 1 и заполните значения authlogin и authpassword. В данной программе пароль хранится в открытом виде, поэтому если доступ к вашей машине достаточно «либеральный», следует позаботиться о защите этой информации. Переменные promptsubj и promptmess определяют, должен ли выдаваться запрос на ввод темы и тела письма или следует просто подставлять значения, определенные по умолчанию.

Функция sendmail() класса pyma вызывает разбор командной строки, выделяя адрес получателя, список файлов для отправки и ключи, определяющие параметры упаковки. Функция getaddrbyalias() возвращает полный адрес по указанному в командной строке псевдониму. Если соответствие не найдено, возвращается адрес, введенный в командной строке. Сам файл addrbook.ab выглядит примерно так:

buh ivanova@my.server.ru

me  amsand@rambler.ru

..   ..   ..

То есть если указать «to me», почта будет отправлена на адрес amsand@rambler.ru, как если бы было указано «to amsand@rambler.ru».

Обратите внимание на конструкцию, с помощью которой определяется полный путь к файлу адресной книги:

abf = os.path.dirname(__file__) + os.path.sep + "addrbook.ab"

Поскольку нужно, чтобы утилиту отправки можно было запускать из любого каталога, и именно там Python будет искать файлы, указанные без пути, то нам нужно «привязаться» к месту размещения файла send.py. Встроенная переменная __file__ возвращает полное имя модуля, из которого она вызывается. Функция dirname() модуля os.path вырезает из полного имени файла имя каталога. Ну и чтобы обеспечить переносимость между различными операционными системами вместо явного указания слеша для отделения каталога от имени файла используем переменную os.path.sep, которая возвращает прямой слеш «/» на системах UNIX и обратный «» в Windows.

Вернемся к функции sendmail(). Если в командной строке определен ключ «nonzipped», то в процессе ее разбора функцией parseParameters() переменная FLG_NZ получит истинное значение. При этом весь список файлов, помещенный в переменную FILELIST, отправляется непосредственно в функцию sendfiles() модуля pysender (который будет рассмотрен далее). Если же требуется упаковка, что является поведением по умолчанию, то список файлов отдается на обработку функции writepattzip() модуля myzip, который был разработан ранее для графической утилиты отправки файлов по FTP. Здесь мы не будем к нему возвращаться, код этого модуля, как и всех остальных, можно будет скачать с сайта журнала http://samag.ru, в разделе «Исходный код». После упаковки функция sendfiles() получает имя созданного архива. Как только отправка завершится, zip-файл будет удален.

Вся основная работа выполняется функцией sendfiles() модуля pysender. Ниже представлен этот модуль:

Листинг 5. Модуль pysender.py

# -*- coding: cp1251 -*-

import os, smtplib, pymaconf, pymalog

class pysender:

       # Функция инициализации

    def __init__(self):

    # Разбираем файл mime-типов.

           mtf = os.path.dirname(__file__) + os.path.sep + 'mime.types'

           mt = open(mtf, 'r').readlines()

           self.mimetypes = {}

           for line in mt:

                 mimetype, mimeext = line[:-1].split()

                 self.mimetypes[mimeext] = mimetype

    # Шаблон заголовка письма, включая тело письма

    messagepart = '''From: %(fromaddr)s

To: %(toaddr)s

Subject: %(subject)s

X-Mailer: PyMa 0.1

MIME-Version: 1.0

Content-Type: multipart/mixed; boundary="%(boundary)s"

This is a multi-part message in MIME format.

--%(boundary)s

Content-Type: text/plain; charset="%(codepage)s"

Content-Transfer-Encoding: Base64

%(message)s

'''

    # Шаблон вложения в письме

    attachpart = '''--%(boundary)s

Content-Type: %(mimetype)s; name="%(filename)s"

Content-Transfer-Encoding: base64

Content-Disposition: attachment; filename="%(filename)s"

%(mimed)s'''

# Функция возвращает mime-тип по расширению файла

# Используется словарь, сформированный во время инициализации класса

    def getmimetypebyext(self, ext):

           try:

                 mt = self.mimetypes[ext]

           except:

                 mt = 'application/octet-stream'

           return mt

# Функция формирует сообщение и отправляет его

    def sendfiles(self, toaddr, filelist):

    # заполняем словарь параметров, используемых в шаблоне

           param = {}

           param['boundary'] = '-=-=_BOUNDARY_adba_YRADNUOB_=-=-'

           param['fromaddr'] = pymaconf.fromaddr

           if pymaconf.backmail and pymaconf.backaddr:

                 param['toaddr'] = toaddr + '\nBcc: ' + pymaconf.backaddr

                 envtoaddr = [toaddr]

                 envtoaddr.append(pymaconf.backaddr)

           else:

                 param['toaddr'] = toaddr

                 envtoaddr = [toaddr]

       param['subject']  = pymaconf.defsubject

           param['message']  = pymaconf.defmessage

           param['codepage'] = pymaconf.mailcodepage

           param['message'] = smtplib.base64.encodestring(param['message'])

           msg= self.messagepart % param

    # для каждого файла из списка формируем часть сообщения, объявляющего вложение

           files = ''

           print 'pysender: MIME-преобразование вложений:'

           for filename in filelist:

                 files += filename + ','

                 param['filename']  = filename

                 base, ext = os.path.splitext(filename)

                 param['mimetype']  = self.getmimetypebyext(ext[1:])

                 print 'pysender:    + %s...' %  filename

                 ist = open(filename, 'rb').read()

                 param['mimed'] = smtplib.base64.encodestring(ist)

                 msg = msg + (self.attachpart % param)         

    # отправка сообщения

           print 'pysender: соединение с %s...' % pymaconf.smtpserver

           try:

                 session = smtplib.SMTP(pymaconf.smtpserver)

                 if pymaconf.authrequire:

                        session.login(pymaconf.authlogin,pymaconf.authpassword)

                 print 'pysender: выполняется отправка...'

                 session.sendmail(pymaconf.fromaddr, envtoaddr, msg)

                 print 'pysender: отправка завершена.'

                 session.quit()

                 pymalog.write('SENT:  %s to %s' % (files, toaddr))

           except:

                 print 'pysender: ОШИБКА ОТПРАВКИ.'

                 pymalog.write('ERROR: %s to %s' % (files, toaddr))

Центральной частью описанного здесь класса являются шаблоны сообщений messagepart и attachpart. В них широко применяется использование знакомест вида %(key)s, которые заполняются значениями, соответствующими ключам из словаря, указываемого после оператора «%». Например, строка param[‘message’] будет помещена в шаблон вместо конструкции %(message)s.

Шаблоны описывают вид сообщения в формате MIME. Для упрощения работы все, включая тело сообщения, мы кодируем в соответствии с Base64, для чего используется функция encodestring() класса smtp.base64. Каждое вложение также преобразовывается согласно Base64 и оформляется как mime-часть сообщения. Для определения mime-типа файла по его расширению используется файл соответствия mime.types, в качестве которого выступает слегка откорректированный одноименный файл, поставляемый вместе с Apache.

Главное, что нужно в нем сделать, – разбить строки, в которых одному mime-типу ставятся в соответствие несколько расширений, таким образом, чтобы в каждой строке фигурировало только одно расширение. Комментарии и строки без расширений удаляются. Файл, который использую я, можно будет найти на сайте журнала в разделе «Исходный код». Если определить mime-тип не удалось, используется значение «application/octet-stream».

После того как будет сформировано сообщение нужного формата, оно отправляется адресату с помощью функции sendmail() стандартного модуля smtplib. Помимо текста сообщения ей передаются адреса отправителя и получателя для формирования конверта. Если в конфигурационном файле включен режим отправки копий всех писем на указанный адрес (что может быть полезно как для контроля, так и в качестве плохонькой, но все же замены папки «Отправленные»), то к адресу получателя добавляется и указанный в pymaconf.py. Функция write() модуля pymalog заносит в лог-файл сообщение о результате отправки письма, чтобы в дальнейшем можно было отследить, что, когда и куда отправлялось. Сам модуль pymalog.py:

Листинг 6. Модуль pymalog.py

# -*- coding: cp1251 -*-

import time, os

name = os.path.dirname(__file__) + os.path.sep + 'pyma.log'

def write(str):

    dt = time.strftime('%d.%m.%Y %H:%I:%S')

    log = open(name, 'a+')

    log.write('%s: %s\n' % (dt, str))

    log.close()

if __name__ == '__main__':

    write('qwerty')

Все. Можно вводить заветную командную строку и смотреть, что получилось. А получились две неприятности, с которыми нам предстоит побороться. Первая – наш код записан в cp1251, а FAR выводит строки в cp866. В итоге весь вывод, который происходит во время работы утилиты, имеет нечитаемый вид. Если переписать код в cp866, то текст сообщений будет выглядеть нормально, но нечитаемыми станут имена файлов, имеющие символы кириллицы (Windows использует для именования файлов cp1251). Наверняка указанную проблему можно было бы обойти проще, чем это сделал я, но использованное решение может быть применено в других случаях, а потому представляет определенную учебную ценность. Мы переопределим стандартные потоки ввода-вывода. Для этого напишем еще один модуль, mystd.py:

Листинг 7. Модуль mystd.py

# -*- coding: cp1251 -*-

import sys

from pymaconf import termcodepage, basecodepage

# Класс переопределения потока ввода

classmystdin:

    def readline(self):

           str = self.origin.readline()

           return unicode(str, termcodepage).encode(basecodepage)

    def setmystdin(self):

           self.origin, sys.stdin = sys.stdin, self

    def setorigin(self):

           sys.stdin = self.origin

# Класс переопределения потока вывода

classmystdout:

    def write(self, str):

           self.setorigin()

           print unicode(str, basecodepage).encode(termcodepage),

           self.setmystdout()

    def setmystdout(self):

           self.origin, sys.stdout = sys.stdout, self

    def setorigin(self):

           sys.stdout = self.origin

Итак, что тут происходит? Класс mystdout имеет три функции – setmystdout(), назначает в качестве стандартного потока вывода sys.stdout сам этот класс, setorigin() возвращает прежнее значение, заблаговременно сохраненное в self.origin. А функция write() необходима, чтобы этот класс действительно мог играть роль потока вывода. В ней-то и происходит все самое интересное – мы возвращаем на место поток stdout, выводим в него переданную как параметр и перекодированную строку, и вновь назначаем потоком вывода класс mystdout. В итоге выводимые строки по-прежнему попадают на экран, но предварительно перехватываются и преобразуются в нужную кодировку.

Аналогично поступаем с sys.stdin. Классу mystdin нужен метод readline, который вызывает стандартный readline объекта sys.stdout (который сохранен в переменной self.origin) и возвращает считанную строку перекодированной.

Теперь становятся понятны загадочные строки в конце модуля send.py, в которых фигурируют объекты mi и mo. Они служат для переопределения потоков ввода-вывода.

Вторая неприятность заключается в том, что если имя файла содержит русские символы, то, будучи упакованным, оно исказится. Связано это с тем, что такие программы, как WinRAR, WinZip, 7-Zip и т. д., считают, что имена упакованных файлов должны быть в кодировке cp866. Но класс ZipFile стандартного модуля zipfile никаких преобразований имен файлов не выполняет, записывая их, как есть. Если предварительно преобразовать имя файла в нужную кодировку и в таком виде передать его функциям модуля, то возникает ошибка «Файл не найден», поскольку файла с полученным именем действительно не существует.

Ранее, когда я описывал отправку файлов по FTP, данная проблема также имела место, но была не столь актуальна, так как предполагалось, что и упаковка, и распаковка будут выполняться с помощью Python. В случае же электронной почты можно почти со стопроцентной уверенностью утверждать, что распаковываться архив будет чем угодно, но только не программой на Python.

Для частного решения указанной проблемы я слегка подправил библиотечный файл zipfile.py, размещающийся в каталоге Python, в подкаталоге Lib. Копируем этот модуль под именем zipfrusw.py, и в модуле myzip подгружаем этот измененный файл вместо zipfile. Именно это я и имел в виду, когда выше говорил о небольшой модификации myzip. Итак, в zipfrusw.py вносим следующие изменения. В исходную функцию close():

Листинг 8. Изменения в модуле zipfile: close

    def close(self):

    """Close the file, and for mode "w" and "a" write

    the ending records."""

.. .. .. .. .. ..

            self.fp.write(centdir)

#            self.fp.write(zinfo.filename)

           try:

               fn4zip = unicode(zinfo.filename,

        pymaconf.termcodepage).encode('cp866')

               self.fp.write(fn4zip)

           except:

               self.fp.write(zinfo.filename)

            self.fp.write(zinfo.extra)

.. .. .. .. .. ..

Синим выделены добавленные строки, зеленым – исключенная. То есть здесь мы преобразуем имя файла в нужную кодировку перед тем, как оно будет записано в файл архива. Для перекодировки используем функцию Unicode(), которая формирует строку в кодировке UTF, с последующим вызовом функции encode(), которая преобразует Unicode-строку в указанную кодировку.

Чтобы модуль умел читать собственные творения, потребуются изменения в функцию getinfo():

Листинг 9. Изменения в модуле zipfile: getinfo

    def getinfo(self, name):

        """Return the instance of ZipInfo given 'name'."""

    try:

            name = unicode(name, 'cp866').encode(pymaconf.termcodepage)

    except:

           pass

      return self.NameToInfo[name]

и в _RealGetContents():

Листинг 10. Изменения в модуле zipfile: _RealGetContents

    def _RealGetContents(self):

       """Read in the table of contents

       for the ZIP file."""

.. .. .. .. .. ..

           if self.debug > 2:

                 print centdir

           filename = fp.read(centdir[_CD_FILENAME_LENGTH])

           filename = unicode(filename, 'cp866').encode(pymaconf.termcodepage)

           # Create ZipInfo instance to store file information

           x = ZipInfo(filename)

.. .. .. .. .. ..

Теперь упакованные файлы сохраняют свои первоначальные имена при извлечении из архива, хотя должен признать, что всесторонние исследования я не проводил и не могу гарантировать, что подобный «хакинг» останется без последствий при другом использовании модуля. Я хотел лишь показать, что при желании исходные тексты стандартных модулей всегда можно подогнать под свои нужды.

Еще осталось сделать так, чтобы нашу утилиту можно было вызывать как «send», а не как «send.py». Для этого создадим bat-файл send.bat:

myprogspythonpython myprogsutilspymasend.py %1 %2 %3 %4 %5 %6 %7 %8 %9

К сожалению, в нем мы вынуждены использовать полные пути к файлам, что потребует редактирования каждый раз, когда нужно будет перенести утилиту в другое место. Кроме того, сценарию send.py в данном примере может быть передано только 9 параметров, что налагает ограничения на количество отправляемых за один раз файлов. Но зато командная строка стала выглядеть так, как нам хочется.

Итак, мы получили небольшую утилиту, которая позволяет отправлять файлы по электронной почте непосредственно из командной строки менеджера FAR. Благодаря использованию функции glob.glob() для формирования списка файлов мы получили возможность задавать в параметре files и шаблоны. Например, так мы отправим все файлы, имеющие расширение «py»:

sendtome files *.py as pyma

Утилита получилась достаточно гибкой – можно указать несколько файлов для отправки (или несколько шаблонов), задавать имя формируемого архива (по умолчанию используется имя первого файла или «archive.zip», если первым задан шаблон), отказаться от упаковки и передать файлы как есть. «Адресная книга» несколько упрощает ввод адресов электронной почты, позволяя указывать псевдонимы для тех адресов, которые вы наиболее часто используете. Чтобы не усложнять командную строку, тема и сопроводительный текст сообщения запрашиваются интерактивно. При желании такое поведение можно отключить, установив соответствующие переменные в pymaconf.py в ноль.

Из недостатков можно указать малоинформативный вывод сообщений об ошибках, не совсем точное следование стандартам, несколько громоздкий и неудобный для непосредственного просмотра формат лог-файла. Также в данной версии не предусмотрена одновременная отправка сразу на несколько адресов. В тело письма сейчас можно поместить только одну строку, поскольку нажатие клавиши Enter прекратит ввод. Однако, как и раньше, исходный код всегда доступен, и любой недостаток при наличии времени и желания можно легко превратить в достоинство.

Кстати, эта утилита замечательно работает и в FreeBSD. Нужно только подправить переменные в конфигурационном файле, отвечающие за кодировку, и вместо создания bat-файла просто переименовать send.py в send, снабдив его «магической» строчкой:

#!/usr/local/bin/python

Ну и не забыть сделать этот файл исполняемым и доступным для поиска по переменной окружения PATH. На моем сервере, где используется koi8-r, строки файла конфигурации, отвечающие за кодировки, выглядят таким образом:

Листинг 11. Изменения в pymaconf.py для FreeBSD

basecodepage  = "cp1251"

termcodepage  = "koi8-r"

mailcodepage  = "cp1251"

fzipcodepage  = "cp1251"

fsyscodepage  = "koi8-r"

Кодировку сообщений в исходных текстах можно оставить как есть (параметр basecodepage), единственное, в этом случае имена упаковываемых файлов в диагностических сообщениях будут нечитаемыми. Исправить это можно введением нескольких дополнительных перекодировок, но их уже и без того достаточно.

На других платформах работоспособность утилиты не проверялась, однако причин для проблем вроде бы никаких нет, и если вашей системой поддерживается Python, то должен работать и рассмотренный код.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru