sender是一个微型的邮件发送模块,作者自述是受到了Flask-Mail的启发,实质上是对python内置模块smtplib和email模块的封装,提供了更加容易使用的接口。学习此项目,我认为可以有如下收获:
项目地址: https://github.com/fengsp/sender
sender项目同时支持python2.7和python3,除了在源码里针对python2和3差异化的部分做兼容外,必须在安装包制作上做相应的配置,否则生成的安装包只能支持2或者3。
除了setup.py文件需要编写外,还需要编写setup.cfg,文件内容:
[wheel]
universal = 1
这表示生成的whl安装包将同时支持python2和python3,安装包的名称是:sender-0.3-py2.py3-none-any.whl,关键就在于安装包的名称里有py2和py3字样。如果只有py3,那么只能在python3环境里安装,如果是在python2环境里,使用pip指定安装版本为0.3,pip会提示找不到合适的版本。
整个项目所有的源码都在sender.py文件中,在setup.py文件中,通过配置py_modules来设置需要封入安装包的源码
py_modules= ['sender',],
实现自动化管理,需要编写Makefile文件,这是一个很古老的技术了,可以追溯到上世纪70年代,不过至今仍广泛使用,如果你对c和c++了解一点的话就能明白什么是宝刀不老。
sender项目里的Makefile内容如下
all: clean-pyc test
test:
python test_sender.py
tox-test:
tox
clean-pyc:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
lines:
find . -name "*.py"|xargs cat|wc -l
release:
python setup.py register
python setup.py sdist upload
python setup.py bdist_wheel upload
使用时,可以指定需要执行的命令,比如
make lines # 查看脚本的代码函数
make clean-pyc # 去除pyc,pyo文件
make release # 发布程序
应当说,这是一种比较好的实现python项目自动化管理的手段和方式,将常用的指令写在Makefile文件中,随时可以查看和执行。
该源码中,主要有3个大类,分别是Mail, Connection 和 Message, Message负责构建邮件内容,Connection负责建立与邮件服务器的连接,Mail负责提供给使用者调用接口。
from sender import Mail
mail = Mail(host='smtp.163.com',
username='xigongda200608',
password='密码',
port=25)
mail.send_message("Hello", fromaddr="xigongda200608@163.com",
to="to@163.com", body="Hello world!")
由于邮件的内容很简单,因此并没有使用Message来创建邮件内容对象,在send_message方法里,会自行创建
def send_message(self, *args, **kwargs):
"""Shortcut for send.
"""
self.send(Message(*args, **kwargs))
在send方法里,会创建与邮件服务器之间的连接
@property
def connection(self):
"""Open one connection to the SMTP server.
"""
return Connection(self)
def send(self, message_or_messages):
"""Sends a single messsage or multiple messages.
:param message_or_messages: one message instance or one iterable of
message instances.
"""
try:
messages = iter(message_or_messages)
except TypeError:
messages = [message_or_messages]
with self.connection as c:
for message in messages:
if self.fromaddr and not message.fromaddr:
message.fromaddr = self.fromaddr
message.validate()
c.send(message)
send方法可一次发送多封邮件,使用的是同一个连接,这里有一点值得大家来学习,Connection类实现了__enter__ 和 __exit__ 方法,实现这两个方法的类就是上下文管理器。你可以使用with语句进入上下文环境,退出时,上下文管理器自动的进行清理工作
def __enter__(self):
if self.mail.use_ssl:
server = smtplib.SMTP_SSL(self.mail.host, self.mail.port)
else:
server = smtplib.SMTP(self.mail.host, self.mail.port)
# Set the debug output level
if self.mail.debug_level is not None:
server.set_debuglevel(int(self.mail.debug_level))
if self.mail.use_tls:
server.starttls()
if self.mail.username and self.mail.password:
server.login(self.mail.username, self.mail.password)
self.server = server
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.server.quit()
__enter__方法执行时,创建SMTP对象sever, 随后进行登录,__exit__方法执行时,server.quit方法被执行,退出登录。
MIMEText 与 MIMEMultipart 是两种不同的MIME邮件体类型,MIMEText是纯文本,MIMEMultipart是超文本,如果你的邮件里只有文字,那么使用MIMEText即可,如果邮件里有附件,或者想要发送html作为邮件内容,则需要使用MIMEMultipart。
想要发送html邮件,那么需要提供html的源码,注意,一定是源码,而不是html文件。
源码里有一段
msg = MIMEMultipart()
alternative = MIMEMultipart('alternative')
alternative.attach(MIMEText(self.body, 'plain', self.charset))
alternative.attach(MIMEText(self.html, 'html', self.charset))
msg.attach(alternative)
本意是创建一个alternative类型的邮件体,支持纯文本与超文本共存,但我在实践中发现,html的内容可以正常显示,纯文本内容不能同时显示。
from sender import Mail
from sender import Attachment
mail = Mail(host='smtp.163.com',
username='xigongda200608',
password='密码',
port=25)
with open('pig.jpg', 'rb')as f:
attachment = Attachment("pig.jpg", "image/jpeg", f.read())
mail.send_message("Hello", fromaddr="xigongda200608@163.com",
to="to@163.com", body="Hello world!",
html='<b>Hello</b>',
attachments=[attachment])
Message类中最重要的方法是as_string, 该方法首先要创建一个MIMEText或者一个MIMEMultipart对象msg,附件的对象需要通过使用attach方法添加到msg对象中。
添加附件,需要创建MIMEBase类型的对象,步骤是比较固定的
f = MIMEBase(*attachment.content_type.split('/')) # 指定maintype 和subtype
f.set_payload(attachment.data) # 设置载荷
encode_base64(f) # base64编码
if attachment.filename is None:
filename = str(None)
else:
filename = force_text(attachment.filename, self.charset)
f.add_header('Content-Disposition', attachment.disposition,
filename=filename) # 设置header
for key, value in attachment.headers.items():
f.add_header(key, value)
msg.attach(f)
源码里有一段使用ascii 进行编码的部分被我去掉了,如果不去掉,中文名称的附件会显示为乱码。
sender库并没有支持该功能,以下内容已经超出了源码的解读范围,权当是对技术问题的一个探讨。
在html添加图片,有两种方法,一种是把图片进行base64编码,放到html中,另一种是添加MIMEImage,先来看第一种
import base64
from sender import Mail
from sender import Attachment
mail = Mail(host='smtp.163.com',
username='xigongda200608',
password='密码',
port=25)
with open('pig.jpg', 'rb')as f:
base64_data = base64.b64encode(f.read())
base64_data = base64_data.decode('utf-8') # 转成字符串
html = '<html><body><img src="data:image/jpg;base64,%s" alt="image1"></body></html>' % base64_data
mail.send_message("Hello", fromaddr="xigongda200608@163.com",
to="to@163.com", body="Hello world!",
html=html)
第二种,需要添加MIMEImage对象,由于sender模块并不支持,我修改了源码
第一步,增加HtmlImage类
from email.mime.image import MIMEImage
class HtmlImage():
def __init__(self, data, id):
self.data = data
self.id = id
第二步,Message初始化函数里增加htmlimages=None参数
def __init__(self, subject=None, to=None, body=None, html=None,
fromaddr=None, cc=None, bcc=None, attachments=None,
reply_to=None, date=None, charset='utf-8',
extra_headers=None, mail_options=None, rcpt_options=None, htmlimages=None):
self.htmlimages = htmlimages
第三步,修改as_string方法
if self.htmlimages:
for htmlimage in self.htmlimages:
image = MIMEImage(htmlimage.data)
image.add_header('Content-ID', htmlimage.id)
msg.attach(image)
return msg.as_string()
主程序
from sender import Mail
from sender import Attachment, HtmlImage
mail = Mail(host='smtp.163.com',
username='xigongda200608',
password='密码',
port=25)
with open('pig.jpg', 'rb')as f:
data = f.read()
html_image = HtmlImage(data, 'insert_image_to_html')
html = '<html><body><img src="cid:insert_image_to_html" alt="image1"></body></html>'
mail.send_message("Hello", fromaddr="xigongda200608@163.com",
to="to@163.com", body="Hello world!",
html=html, htmlimages=[html_image])
QQ交流群: 211426309