pax_global_header00006660000000000000000000000064132164243210014510gustar00rootroot0000000000000052 comment=348cb5554d718fbf4f237430f65c16c5018b60fc flask-mail-0.9.1+dfsg1/000077500000000000000000000000001321642432100145375ustar00rootroot00000000000000flask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/000077500000000000000000000000001321642432100202335ustar00rootroot00000000000000flask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/PKG-INFO000066400000000000000000000016571321642432100213410ustar00rootroot00000000000000Metadata-Version: 1.1 Name: Flask-Mail Version: 0.9.1 Summary: Flask extension for sending email Home-page: https://github.com/rduplain/flask-mail Author: Ron DuPlain Author-email: ron.duplain@gmail.com License: BSD Description: Flask-Mail ---------- A Flask extension for sending email messages. Please refer to the online documentation for details. Links ````` * `documentation `_ Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules flask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/SOURCES.txt000066400000000000000000000014621321642432100221220ustar00rootroot00000000000000LICENSE MANIFEST.in README.rst flask_mail.py setup.cfg setup.py tests.py Flask_Mail.egg-info/PKG-INFO Flask_Mail.egg-info/SOURCES.txt Flask_Mail.egg-info/dependency_links.txt Flask_Mail.egg-info/not-zip-safe Flask_Mail.egg-info/requires.txt Flask_Mail.egg-info/top_level.txt docs/Makefile docs/changelog.rst docs/conf.py docs/index.rst docs/make.bat docs/_static/flask-mail.jpg docs/_static/flask-mail.png docs/_themes/.git docs/_themes/.gitignore docs/_themes/LICENSE docs/_themes/README docs/_themes/flask_theme_support.py docs/_themes/flask/layout.html docs/_themes/flask/relations.html docs/_themes/flask/theme.conf docs/_themes/flask/static/flasky.css_t docs/_themes/flask/static/small_flask.css docs/_themes/flask_small/layout.html docs/_themes/flask_small/theme.conf docs/_themes/flask_small/static/flasky.css_tflask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/dependency_links.txt000066400000000000000000000000011321642432100243010ustar00rootroot00000000000000 flask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/not-zip-safe000066400000000000000000000000011321642432100224610ustar00rootroot00000000000000 flask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/requires.txt000066400000000000000000000000151321642432100226270ustar00rootroot00000000000000Flask blinkerflask-mail-0.9.1+dfsg1/Flask_Mail.egg-info/top_level.txt000066400000000000000000000000131321642432100227570ustar00rootroot00000000000000flask_mail flask-mail-0.9.1+dfsg1/LICENSE000066400000000000000000000026511321642432100155500ustar00rootroot00000000000000Copyright (c) 2010 by danjac. Some rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. flask-mail-0.9.1+dfsg1/MANIFEST.in000066400000000000000000000002261321642432100162750ustar00rootroot00000000000000include LICENSE tests.py recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo prune docs/_build prune docs/_themes/.git flask-mail-0.9.1+dfsg1/PKG-INFO000066400000000000000000000016571321642432100156450ustar00rootroot00000000000000Metadata-Version: 1.1 Name: Flask-Mail Version: 0.9.1 Summary: Flask extension for sending email Home-page: https://github.com/rduplain/flask-mail Author: Ron DuPlain Author-email: ron.duplain@gmail.com License: BSD Description: Flask-Mail ---------- A Flask extension for sending email messages. Please refer to the online documentation for details. Links ````` * `documentation `_ Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules flask-mail-0.9.1+dfsg1/README.rst000066400000000000000000000003531321642432100162270ustar00rootroot00000000000000Flask-Mail ========== .. image:: https://secure.travis-ci.org/mattupstate/flask-mail.png?branch=master Flask-Mail is a Flask extension providing simple email sending capabilities. Documentation: http://packages.python.org/Flask-Mailflask-mail-0.9.1+dfsg1/docs/000077500000000000000000000000001321642432100154675ustar00rootroot00000000000000flask-mail-0.9.1+dfsg1/docs/Makefile000066400000000000000000000060761321642432100171400ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/flask-mail.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/flask-mail.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." flask-mail-0.9.1+dfsg1/docs/changelog.rst000066400000000000000000000000271321642432100201470ustar00rootroot00000000000000.. include:: ../CHANGESflask-mail-0.9.1+dfsg1/docs/conf.py000066400000000000000000000145711321642432100167760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # flask-mail documentation build configuration file, created by # sphinx-quickstart on Fri May 28 11:39:14 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) sys.path.append(os.path.abspath('_themes')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Flask-Mail' copyright = u'2010, Dan Jacob' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.9.1' # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. #pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'flask_small' #html_theme = 'default' html_theme_options = { 'index_logo': 'flask-mail.png', 'github_fork': None } # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'flask-maildoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'flask-mail.tex', u'flask-mail Documentation', u'Dan Jacob', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True flask-mail-0.9.1+dfsg1/docs/index.rst000066400000000000000000000165601321642432100173400ustar00rootroot00000000000000flask-mail ====================================== .. module:: flask-mail One of the most basic functions in a web application is the ability to send emails to your users. The **Flask-Mail** extension provides a simple interface to set up SMTP with your `Flask`_ application and to send messages from your views and scripts. Links ----- * `documentation `_ * `source `_ * :doc:`changelog ` Installing Flask-Mail --------------------- Install with **pip** and **easy_install**:: pip install Flask-Mail or download the latest version from version control:: git clone https://github.com/mattupstate/flask-mail.git cd flask-mail python setup.py install If you are using **virtualenv**, it is assumed that you are installing flask-mail in the same virtualenv as your Flask application(s). Configuring Flask-Mail ---------------------- **Flask-Mail** is configured through the standard Flask config API. These are the available options (each is explained later in the documentation): * **MAIL_SERVER** : default **'localhost'** * **MAIL_PORT** : default **25** * **MAIL_USE_TLS** : default **False** * **MAIL_USE_SSL** : default **False** * **MAIL_DEBUG** : default **app.debug** * **MAIL_USERNAME** : default **None** * **MAIL_PASSWORD** : default **None** * **MAIL_DEFAULT_SENDER** : default **None** * **MAIL_MAX_EMAILS** : default **None** * **MAIL_SUPPRESS_SEND** : default **app.testing** * **MAIL_ASCII_ATTACHMENTS** : default **False** In addition the standard Flask ``TESTING`` configuration option is used by **Flask-Mail** in unit tests (see below). Emails are managed through a ``Mail`` instance:: from flask import Flask from flask_mail import Mail app = Flask(__name__) mail = Mail(app) In this case all emails are sent using the configuration values of the application that was passed to the ``Mail`` class constructor. Alternatively you can set up your ``Mail`` instance later at configuration time, using the **init_app** method:: mail = Mail() app = Flask(__name__) mail.init_app(app) In this case emails will be sent using the configuration values from Flask's ``current_app`` context global. This is useful if you have multiple applications running in the same process but with different configuration options. Sending messages ---------------- To send a message first create a ``Message`` instance:: from flask_mail import Message @app.route("/") def index(): msg = Message("Hello", sender="from@example.com", recipients=["to@example.com"]) You can set the recipient emails immediately, or individually:: msg.recipients = ["you@example.com"] msg.add_recipient("somebodyelse@example.com") If you have set ``MAIL_DEFAULT_SENDER`` you don't need to set the message sender explicity, as it will use this configuration value by default:: msg = Message("Hello", recipients=["to@example.com"]) If the ``sender`` is a two-element tuple, this will be split into name and address:: msg = Message("Hello", sender=("Me", "me@example.com")) assert msg.sender == "Me " The message can contain a body and/or HTML:: msg.body = "testing" msg.html = "testing" Finally, to send the message, you use the ``Mail`` instance configured with your Flask application:: mail.send(msg) Bulk emails ----------- Usually in a web application you will be sending one or two emails per request. In certain situations you might want to be able to send perhaps dozens or hundreds of emails in a single batch - probably in an external process such as a command-line script or cronjob. In that case you do things slightly differently:: with mail.connect() as conn: for user in users: message = '...' subject = "hello, %s" % user.name msg = Message(recipients=[user.email], body=message, subject=subject) conn.send(msg) The connection to your email host is kept alive and closed automatically once all the messages have been sent. Some mail servers set a limit on the number of emails sent in a single connection. You can set the max amount of emails to send before reconnecting by specifying the **MAIL_MAX_EMAILS** setting. Attachments ----------- Adding attachments is straightforward:: with app.open_resource("image.png") as fp: msg.attach("image.png", "image/png", fp.read()) See the `API`_ for details. If ``MAIL_ASCII_ATTACHMENTS`` is set to **True**, filenames will be converted to an ASCII equivalent. This can be useful when using a mail relay that modify mail content and mess up Content-Disposition specification when filenames are UTF-8 encoded. The conversion to ASCII is a basic removal of non-ASCII characters. It should be fine for any unicode character that can be decomposed by NFKD into one or more ASCII characters. If you need romanization/transliteration (i.e `ß` → `ss`) then your application should do it and pass a proper ASCII string. Unit tests and suppressing emails --------------------------------- When you are sending messages inside of unit tests, or in a development environment, it's useful to be able to suppress email sending. If the setting ``TESTING`` is set to ``True``, emails will be suppressed. Calling ``send()`` on your messages will not result in any messages being actually sent. Alternatively outside a testing environment you can set ``MAIL_SUPPRESS_SEND`` to **False**. This will have the same effect. However, it's still useful to keep track of emails that would have been sent when you are writing unit tests. In order to keep track of dispatched emails, use the ``record_messages`` method:: with mail.record_messages() as outbox: mail.send_message(subject='testing', body='test', recipients=emails) assert len(outbox) == 1 assert outbox[0].subject == "testing" The **outbox** is a list of ``Message`` instances sent. The blinker package must be installed for this method to work. Note that the older way of doing things, appending the **outbox** to the ``g`` object, is now deprecated. Header injection ---------------- To prevent `header injection `_ attempts to send a message with newlines in the subject, sender or recipient addresses will result in a ``BadHeaderError``. Signalling support ------------------ .. versionadded:: 0.4 **Flask-Mail** now provides signalling support through a ``email_dispatched`` signal. This is sent whenever an email is dispatched (even if the email is not actually sent, i.e. in a testing environment). A function connecting to the ``email_dispatched`` signal takes a ``Message`` instance as a first argument, and the Flask app instance as an optional argument:: def log_message(message, app): app.logger.debug(message.subject) email_dispatched.connect(log_message) API --- .. module:: flask_mail .. autoclass:: Mail :members: send, connect, send_message .. autoclass:: Attachment .. autoclass:: Connection :members: send, send_message .. autoclass:: Message :members: attach, add_recipient .. _Flask: http://flask.pocoo.org .. _GitHub: http://github.com/mattupstate/flask-mail flask-mail-0.9.1+dfsg1/docs/make.bat000066400000000000000000000060071321642432100170770ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\flask-mail.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\flask-mail.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end flask-mail-0.9.1+dfsg1/flask_mail.py000066400000000000000000000430361321642432100172210ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ flaskext.mail ~~~~~~~~~~~~~ Flask extension for sending email. :copyright: (c) 2010 by Dan Jacob. :license: BSD, see LICENSE for more details. """ from __future__ import with_statement __version__ = '0.9.1' import re import blinker import smtplib import sys import time import unicodedata from email import charset from email.encoders import encode_base64 from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.header import Header from email.utils import formatdate, formataddr, make_msgid, parseaddr from contextlib import contextmanager from flask import current_app PY3 = sys.version_info[0] == 3 PY34 = PY3 and sys.version_info[1] >= 4 if PY3: string_types = str, text_type = str from email import policy message_policy = policy.SMTP else: string_types = basestring, text_type = unicode message_policy = None charset.add_charset('utf-8', charset.SHORTEST, None, 'utf-8') class FlaskMailUnicodeDecodeError(UnicodeDecodeError): def __init__(self, obj, *args): self.obj = obj UnicodeDecodeError.__init__(self, *args) def __str__(self): original = UnicodeDecodeError.__str__(self) return '%s. You passed in %r (%s)' % (original, self.obj, type(self.obj)) def force_text(s, encoding='utf-8', errors='strict'): """ Similar to smart_text, except that lazy instances are resolved to strings, rather than kept as lazy objects. If strings_only is True, don't convert (some) non-string-like objects. """ if isinstance(s, text_type): return s try: if not isinstance(s, string_types): if PY3: if isinstance(s, bytes): s = text_type(s, encoding, errors) else: s = text_type(s) elif hasattr(s, '__unicode__'): s = s.__unicode__() else: s = text_type(bytes(s), encoding, errors) else: s = s.decode(encoding, errors) except UnicodeDecodeError as e: if not isinstance(s, Exception): raise FlaskMailUnicodeDecodeError(s, *e.args) else: s = ' '.join([force_text(arg, encoding, strings_only, errors) for arg in s]) return s def sanitize_subject(subject, encoding='utf-8'): try: subject.encode('ascii') except UnicodeEncodeError: try: subject = Header(subject, encoding).encode() except UnicodeEncodeError: subject = Header(subject, 'utf-8').encode() return subject def sanitize_address(addr, encoding='utf-8'): if isinstance(addr, string_types): addr = parseaddr(force_text(addr)) nm, addr = addr try: nm = Header(nm, encoding).encode() except UnicodeEncodeError: nm = Header(nm, 'utf-8').encode() try: addr.encode('ascii') except UnicodeEncodeError: # IDN if '@' in addr: localpart, domain = addr.split('@', 1) localpart = str(Header(localpart, encoding)) domain = domain.encode('idna').decode('ascii') addr = '@'.join([localpart, domain]) else: addr = Header(addr, encoding).encode() return formataddr((nm, addr)) def sanitize_addresses(addresses, encoding='utf-8'): return map(lambda e: sanitize_address(e, encoding), addresses) def _has_newline(line): """Used by has_bad_header to check for \\r or \\n""" if line and ('\r' in line or '\n' in line): return True return False class Connection(object): """Handles connection to host.""" def __init__(self, mail): self.mail = mail def __enter__(self): if self.mail.suppress: self.host = None else: self.host = self.configure_host() self.num_emails = 0 return self def __exit__(self, exc_type, exc_value, tb): if self.host: self.host.quit() def configure_host(self): if self.mail.use_ssl: host = smtplib.SMTP_SSL(self.mail.server, self.mail.port) else: host = smtplib.SMTP(self.mail.server, self.mail.port) host.set_debuglevel(int(self.mail.debug)) if self.mail.use_tls: host.starttls() if self.mail.username and self.mail.password: host.login(self.mail.username, self.mail.password) return host def send(self, message, envelope_from=None): """Verifies and sends message. :param message: Message instance. :param envelope_from: Email address to be used in MAIL FROM command. """ assert message.send_to, "No recipients have been added" assert message.sender, ( "The message does not specify a sender and a default sender " "has not been configured") if message.has_bad_headers(): raise BadHeaderError if message.date is None: message.date = time.time() if self.host: self.host.sendmail(sanitize_address(envelope_from or message.sender), list(sanitize_addresses(message.send_to)), message.as_bytes() if PY3 else message.as_string(), message.mail_options, message.rcpt_options) email_dispatched.send(message, app=current_app._get_current_object()) self.num_emails += 1 if self.num_emails == self.mail.max_emails: self.num_emails = 0 if self.host: self.host.quit() self.host = self.configure_host() def send_message(self, *args, **kwargs): """Shortcut for send(msg). Takes same arguments as Message constructor. :versionadded: 0.3.5 """ self.send(Message(*args, **kwargs)) class BadHeaderError(Exception): pass class Attachment(object): """Encapsulates file attachment information. :versionadded: 0.3.5 :param filename: filename of attachment :param content_type: file mimetype :param data: the raw file data :param disposition: content-disposition (if any) """ def __init__(self, filename=None, content_type=None, data=None, disposition=None, headers=None): self.filename = filename self.content_type = content_type self.data = data self.disposition = disposition or 'attachment' self.headers = headers or {} class Message(object): """Encapsulates an email message. :param subject: email subject header :param recipients: list of email addresses :param body: plain text message :param html: HTML message :param sender: email sender address, or **MAIL_DEFAULT_SENDER** by default :param cc: CC list :param bcc: BCC list :param attachments: list of Attachment instances :param reply_to: reply-to address :param date: send date :param charset: message character set :param extra_headers: A dictionary of additional headers for the message :param mail_options: A list of ESMTP options to be used in MAIL FROM command :param rcpt_options: A list of ESMTP options to be used in RCPT commands """ def __init__(self, subject='', recipients=None, body=None, html=None, sender=None, cc=None, bcc=None, attachments=None, reply_to=None, date=None, charset=None, extra_headers=None, mail_options=None, rcpt_options=None): sender = sender or current_app.extensions['mail'].default_sender if isinstance(sender, tuple): sender = "%s <%s>" % sender self.recipients = recipients or [] self.subject = subject self.sender = sender self.reply_to = reply_to self.cc = cc or [] self.bcc = bcc or [] self.body = body self.html = html self.date = date self.msgId = make_msgid() self.charset = charset self.extra_headers = extra_headers self.mail_options = mail_options or [] self.rcpt_options = rcpt_options or [] self.attachments = attachments or [] @property def send_to(self): return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ()) def _mimetext(self, text, subtype='plain'): """Creates a MIMEText object with the given subtype (default: 'plain') If the text is unicode, the utf-8 charset is used. """ charset = self.charset or 'utf-8' return MIMEText(text, _subtype=subtype, _charset=charset) def _message(self): """Creates the email""" ascii_attachments = current_app.extensions['mail'].ascii_attachments encoding = self.charset or 'utf-8' attachments = self.attachments or [] if len(attachments) == 0 and not self.html: # No html content and zero attachments means plain text msg = self._mimetext(self.body) elif len(attachments) > 0 and not self.html: # No html and at least one attachment means multipart msg = MIMEMultipart() msg.attach(self._mimetext(self.body)) else: # Anything else msg = MIMEMultipart() alternative = MIMEMultipart('alternative') alternative.attach(self._mimetext(self.body, 'plain')) alternative.attach(self._mimetext(self.html, 'html')) msg.attach(alternative) if self.subject: msg['Subject'] = sanitize_subject(force_text(self.subject), encoding) msg['From'] = sanitize_address(self.sender, encoding) msg['To'] = ', '.join(list(set(sanitize_addresses(self.recipients, encoding)))) msg['Date'] = formatdate(self.date, localtime=True) # see RFC 5322 section 3.6.4. msg['Message-ID'] = self.msgId if self.cc: msg['Cc'] = ', '.join(list(set(sanitize_addresses(self.cc, encoding)))) if self.reply_to: msg['Reply-To'] = sanitize_address(self.reply_to, encoding) if self.extra_headers: for k, v in self.extra_headers.items(): msg[k] = v SPACES = re.compile(r'[\s]+', re.UNICODE) for attachment in attachments: f = MIMEBase(*attachment.content_type.split('/')) f.set_payload(attachment.data) encode_base64(f) filename = attachment.filename if filename and ascii_attachments: # force filename to ascii filename = unicodedata.normalize('NFKD', filename) filename = filename.encode('ascii', 'ignore').decode('ascii') filename = SPACES.sub(u' ', filename).strip() try: filename and filename.encode('ascii') except UnicodeEncodeError: if not PY3: filename = filename.encode('utf8') filename = ('UTF8', '', filename) f.add_header('Content-Disposition', attachment.disposition, filename=filename) for key, value in attachment.headers: f.add_header(key, value) msg.attach(f) if message_policy: msg.policy = message_policy return msg def as_string(self): return self._message().as_string() def as_bytes(self): if PY34: return self._message().as_bytes() else: # fallback for old Python (3) versions return self._message().as_string().encode(self.charset or 'utf-8') def __str__(self): return self.as_string() def __bytes__(self): return self.as_bytes() def has_bad_headers(self): """Checks for bad headers i.e. newlines in subject, sender or recipients. RFC5322: Allows multiline CRLF with trailing whitespace (FWS) in headers """ headers = [self.sender, self.reply_to] + self.recipients for header in headers: if _has_newline(header): return True if self.subject: if _has_newline(self.subject): for linenum, line in enumerate(self.subject.split('\r\n')): if not line: return True if linenum > 0 and line[0] not in '\t ': return True if _has_newline(line): return True if len(line.strip()) == 0: return True return False def is_bad_headers(self): from warnings import warn msg = 'is_bad_headers is deprecated, use the new has_bad_headers method instead.' warn(DeprecationWarning(msg), stacklevel=1) return self.has_bad_headers() def send(self, connection): """Verifies and sends the message.""" connection.send(self) def add_recipient(self, recipient): """Adds another recipient to the message. :param recipient: email address of recipient. """ self.recipients.append(recipient) def attach(self, filename=None, content_type=None, data=None, disposition=None, headers=None): """Adds an attachment to the message. :param filename: filename of attachment :param content_type: file mimetype :param data: the raw file data :param disposition: content-disposition (if any) """ self.attachments.append( Attachment(filename, content_type, data, disposition, headers)) class _MailMixin(object): @contextmanager def record_messages(self): """Records all messages. Use in unit tests for example:: with mail.record_messages() as outbox: response = app.test_client.get("/email-sending-view/") assert len(outbox) == 1 assert outbox[0].subject == "testing" You must have blinker installed in order to use this feature. :versionadded: 0.4 """ if not email_dispatched: raise RuntimeError("blinker must be installed") outbox = [] def _record(message, app): outbox.append(message) email_dispatched.connect(_record) try: yield outbox finally: email_dispatched.disconnect(_record) def send(self, message): """Sends a single message instance. If TESTING is True the message will not actually be sent. :param message: a Message instance. """ with self.connect() as connection: message.send(connection) def send_message(self, *args, **kwargs): """Shortcut for send(msg). Takes same arguments as Message constructor. :versionadded: 0.3.5 """ self.send(Message(*args, **kwargs)) def connect(self): """Opens a connection to the mail host.""" app = getattr(self, "app", None) or current_app try: return Connection(app.extensions['mail']) except KeyError: raise RuntimeError("The curent application was not configured with Flask-Mail") class _Mail(_MailMixin): def __init__(self, server, username, password, port, use_tls, use_ssl, default_sender, debug, max_emails, suppress, ascii_attachments=False): self.server = server self.username = username self.password = password self.port = port self.use_tls = use_tls self.use_ssl = use_ssl self.default_sender = default_sender self.debug = debug self.max_emails = max_emails self.suppress = suppress self.ascii_attachments = ascii_attachments class Mail(_MailMixin): """Manages email messaging :param app: Flask instance """ def __init__(self, app=None): self.app = app if app is not None: self.state = self.init_app(app) else: self.state = None def init_mail(self, config, debug=False, testing=False): return _Mail( config.get('MAIL_SERVER', '127.0.0.1'), config.get('MAIL_USERNAME'), config.get('MAIL_PASSWORD'), config.get('MAIL_PORT', 25), config.get('MAIL_USE_TLS', False), config.get('MAIL_USE_SSL', False), config.get('MAIL_DEFAULT_SENDER'), int(config.get('MAIL_DEBUG', debug)), config.get('MAIL_MAX_EMAILS'), config.get('MAIL_SUPPRESS_SEND', testing), config.get('MAIL_ASCII_ATTACHMENTS', False) ) def init_app(self, app): """Initializes your mail settings from the application settings. You can use this if you want to set up your Mail instance at configuration time. :param app: Flask application instance """ state = self.init_mail(app.config, app.debug, app.testing) # register extension with app app.extensions = getattr(app, 'extensions', {}) app.extensions['mail'] = state return state def __getattr__(self, name): return getattr(self.state, name, None) signals = blinker.Namespace() email_dispatched = signals.signal("email-dispatched", doc=""" Signal sent when an email is dispatched. This signal will also be sent in testing mode, even though the email will not actually be sent. """) flask-mail-0.9.1+dfsg1/setup.cfg000066400000000000000000000002451321642432100163610ustar00rootroot00000000000000[build_sphinx] source-dir = docs/ build-dir = docs/_build [upload_sphinx] upload-dir = docs/_build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 flask-mail-0.9.1+dfsg1/setup.py000066400000000000000000000024061321642432100162530ustar00rootroot00000000000000""" Flask-Mail ---------- A Flask extension for sending email messages. Please refer to the online documentation for details. Links ````` * `documentation `_ """ from setuptools import setup setup( name='Flask-Mail', version='0.9.1', url='https://github.com/rduplain/flask-mail', license='BSD', author='Dan Jacob', author_email='danjac354@gmail.com', maintainer='Ron DuPlain', maintainer_email='ron.duplain@gmail.com', description='Flask extension for sending email', long_description=__doc__, py_modules=[ 'flask_mail' ], test_suite='nose.collector', zip_safe=False, platforms='any', install_requires=[ 'Flask', 'blinker', ], tests_require=[ 'nose', 'blinker', 'speaklater', 'mock', ], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ] ) flask-mail-0.9.1+dfsg1/tests.py000066400000000000000000000664631321642432100162720ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import with_statement import base64 import email import unittest import time import re import mock from contextlib import contextmanager from email.header import Header from email import charset from flask import Flask from flask_mail import Mail, Message, BadHeaderError, sanitize_address, PY3 from speaklater import make_lazy_string class TestCase(unittest.TestCase): TESTING = True MAIL_DEFAULT_SENDER = "support@mysite.com" def setUp(self): self.app = Flask(__name__) self.app.config.from_object(self) self.assertTrue(self.app.testing) self.mail = Mail(self.app) self.ctx = self.app.test_request_context() self.ctx.push() def tearDown(self): self.ctx.pop() @contextmanager def mail_config(self, **settings): """ Context manager to alter mail config during a test and restore it after, even in case of a failure. """ original = {} state = self.mail.state for key in settings: assert hasattr(state, key) original[key] = getattr(state, key) setattr(state, key, settings[key]) yield # restore for k, v in original.items(): setattr(state, k, v) def assertIn(self, member, container, msg=None): if hasattr(unittest.TestCase, 'assertIn'): return unittest.TestCase.assertIn(self, member, container, msg) return self.assertTrue(member in container) def assertNotIn(self, member, container, msg=None): if hasattr(unittest.TestCase, 'assertNotIn'): return unittest.TestCase.assertNotIn(self, member, container, msg) return self.assertFalse(member in container) def assertIsNone(self, obj, msg=None): if hasattr(unittest.TestCase, 'assertIsNone'): return unittest.TestCase.assertIsNone(self, obj, msg) return self.assertTrue(obj is None) def assertIsNotNone(self, obj, msg=None): if hasattr(unittest.TestCase, 'assertIsNotNone'): return unittest.TestCase.assertIsNotNone(self, obj, msg) return self.assertTrue(obj is not None) class TestInitialization(TestCase): def test_init_mail(self): mail = self.mail.init_mail( self.app.config, self.app.debug, self.app.testing ) self.assertEquals(self.mail.state.__dict__, mail.__dict__) class TestMessage(TestCase): def test_initialize(self): msg = Message(subject="subject", recipients=["to@example.com"]) self.assertEqual(msg.sender, self.app.extensions['mail'].default_sender) self.assertEqual(msg.recipients, ["to@example.com"]) def test_recipients_properly_initialized(self): msg = Message(subject="subject") self.assertEqual(msg.recipients, []) msg2 = Message(subject="subject") msg2.add_recipient("somebody@here.com") self.assertEqual(len(msg2.recipients), 1) def test_esmtp_options_properly_initialized(self): msg = Message(subject="subject") self.assertEqual(msg.mail_options, []) self.assertEqual(msg.rcpt_options, []) msg = Message(subject="subject", mail_options=['BODY=8BITMIME']) self.assertEqual(msg.mail_options, ['BODY=8BITMIME']) msg2 = Message(subject="subject", rcpt_options=['NOTIFY=SUCCESS']) self.assertEqual(msg2.rcpt_options, ['NOTIFY=SUCCESS']) def test_sendto_properly_set(self): msg = Message(subject="subject", recipients=["somebody@here.com"], cc=["cc@example.com"], bcc=["bcc@example.com"]) self.assertEqual(len(msg.send_to), 3) msg.add_recipient("cc@example.com") self.assertEqual(len(msg.send_to), 3) def test_add_recipient(self): msg = Message("testing") msg.add_recipient("to@example.com") self.assertEqual(msg.recipients, ["to@example.com"]) def test_sender_as_tuple(self): msg = Message(subject="testing", sender=("tester", "tester@example.com")) self.assertEqual('tester ', msg.sender) def test_default_sender_as_tuple(self): self.app.extensions['mail'].default_sender = ('tester', 'tester@example.com') msg = Message(subject="testing") self.assertEqual('tester ', msg.sender) def test_reply_to(self): msg = Message(subject="testing", recipients=["to@example.com"], sender="spammer ", reply_to="somebody ", body="testing") response = msg.as_string() h = Header("Reply-To: %s" % sanitize_address('somebody ')) self.assertIn(h.encode(), str(response)) def test_send_without_sender(self): self.app.extensions['mail'].default_sender = None msg = Message(subject="testing", recipients=["to@example.com"], body="testing") self.assertRaises(AssertionError, self.mail.send, msg) def test_send_without_recipients(self): msg = Message(subject="testing", recipients=[], body="testing") self.assertRaises(AssertionError, self.mail.send, msg) def test_bcc(self): msg = Message(sender="from@example.com", subject="testing", recipients=["to@example.com"], body="testing", bcc=["tosomeoneelse@example.com"]) response = msg.as_string() self.assertNotIn("tosomeoneelse@example.com", str(response)) def test_cc(self): msg = Message(sender="from@example.com", subject="testing", recipients=["to@example.com"], body="testing", cc=["tosomeoneelse@example.com"]) response = msg.as_string() self.assertIn("Cc: tosomeoneelse@example.com", str(response)) def test_attach(self): msg = Message(subject="testing", recipients=["to@example.com"], body="testing") msg.attach(data=b"this is a test", content_type="text/plain") a = msg.attachments[0] self.assertIsNone(a.filename) self.assertEqual(a.disposition, 'attachment') self.assertEqual(a.content_type, "text/plain") self.assertEqual(a.data, b"this is a test") def test_bad_header_subject(self): msg = Message(subject="testing\r\n", sender="from@example.com", body="testing", recipients=["to@example.com"]) self.assertRaises(BadHeaderError, self.mail.send, msg) def test_multiline_subject(self): msg = Message(subject="testing\r\n testing\r\n testing \r\n \ttesting", sender="from@example.com", body="testing", recipients=["to@example.com"]) self.mail.send(msg) response = msg.as_string() self.assertIn("From: from@example.com", str(response)) self.assertIn("testing\r\n testing\r\n testing \r\n \ttesting", str(response)) def test_bad_multiline_subject(self): msg = Message(subject="testing\r\n testing\r\n ", sender="from@example.com", body="testing", recipients=["to@example.com"]) self.assertRaises(BadHeaderError, self.mail.send, msg) msg = Message(subject="testing\r\n testing\r\n\t", sender="from@example.com", body="testing", recipients=["to@example.com"]) self.assertRaises(BadHeaderError, self.mail.send, msg) msg = Message(subject="testing\r\n testing\r\n\n", sender="from@example.com", body="testing", recipients=["to@example.com"]) self.assertRaises(BadHeaderError, self.mail.send, msg) def test_bad_header_sender(self): msg = Message(subject="testing", sender="from@example.com\r\n", recipients=["to@example.com"], body="testing") self.assertIn('From: from@example.com', msg.as_string()) def test_bad_header_reply_to(self): msg = Message(subject="testing", sender="from@example.com", reply_to="evil@example.com\r", recipients=["to@example.com"], body="testing") self.assertIn('From: from@example.com', msg.as_string()) self.assertIn('To: to@example.com', msg.as_string()) self.assertIn('Reply-To: evil@example.com', msg.as_string()) def test_bad_header_recipient(self): msg = Message(subject="testing", sender="from@example.com", recipients=[ "to@example.com", "to\r\n@example.com"], body="testing") self.assertIn('To: to@example.com', msg.as_string()) def test_emails_are_sanitized(self): msg = Message(subject="testing", sender="sender\r\n@example.com", reply_to="reply_to\r\n@example.com", recipients=["recipient\r\n@example.com"]) self.assertIn('sender@example.com', msg.as_string()) self.assertIn('reply_to@example.com', msg.as_string()) self.assertIn('recipient@example.com', msg.as_string()) def test_plain_message(self): plain_text = "Hello Joe,\nHow are you?" msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body=plain_text) self.assertEqual(plain_text, msg.body) self.assertIn('Content-Type: text/plain', msg.as_string()) def test_message_str(self): msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body="some plain text") self.assertEqual(msg.as_string(), str(msg)) def test_plain_message_with_attachments(self): msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body="hello") msg.attach(data=b"this is a test", content_type="text/plain") self.assertIn('Content-Type: multipart/mixed', msg.as_string()) def test_plain_message_with_ascii_attachment(self): msg = Message(subject="subject", recipients=["to@example.com"], body="hello") msg.attach(data=b"this is a test", content_type="text/plain", filename='test doc.txt') self.assertIn('Content-Disposition: attachment; filename="test doc.txt"', msg.as_string()) def test_plain_message_with_unicode_attachment(self): msg = Message(subject="subject", recipients=["to@example.com"], body="hello") msg.attach(data=b"this is a test", content_type="text/plain", filename=u'ünicöde ←→ ✓.txt') parsed = email.message_from_string(msg.as_string()) self.assertIn(re.sub(r'\s+', ' ', parsed.get_payload()[1].get('Content-Disposition')), [ 'attachment; filename*="UTF8\'\'%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt"', 'attachment; filename*=UTF8\'\'%C3%BCnic%C3%B6de%20%E2%86%90%E2%86%92%20%E2%9C%93.txt' ]) def test_plain_message_with_ascii_converted_attachment(self): with self.mail_config(ascii_attachments=True): msg = Message(subject="subject", recipients=["to@example.com"], body="hello") msg.attach(data=b"this is a test", content_type="text/plain", filename=u'ünicödeß ←.→ ✓.txt') parsed = email.message_from_string(msg.as_string()) self.assertIn( 'Content-Disposition: attachment; filename="unicode . .txt"', msg.as_string()) def test_html_message(self): html_text = "

Hello World

" msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], html=html_text) self.assertEqual(html_text, msg.html) self.assertIn('Content-Type: multipart/alternative', msg.as_string()) def test_html_message_with_attachments(self): html_text = "

Hello World

" plain_text = 'Hello World' msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body=plain_text, html=html_text) msg.attach(data=b"this is a test", content_type="text/plain") self.assertEqual(html_text, msg.html) self.assertIn('Content-Type: multipart/alternative', msg.as_string()) parsed = email.message_from_string(msg.as_string()) self.assertEqual(len(parsed.get_payload()), 2) body, attachment = parsed.get_payload() self.assertEqual(len(body.get_payload()), 2) plain, html = body.get_payload() self.assertEqual(plain.get_payload(), plain_text) self.assertEqual(html.get_payload(), html_text) self.assertEqual(base64.b64decode(attachment.get_payload()), b'this is a test') def test_date_header(self): before = time.time() msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body="hello", date=time.time()) after = time.time() self.assertTrue(before <= msg.date <= after) dateFormatted = email.utils.formatdate(msg.date, localtime=True) self.assertIn('Date: ' + dateFormatted, msg.as_string()) def test_msgid_header(self): msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body="hello") # see RFC 5322 section 3.6.4. for the exact format specification r = re.compile(r"<\S+@\S+>").match(msg.msgId) self.assertIsNotNone(r) self.assertIn('Message-ID: ' + msg.msgId, msg.as_string()) def test_unicode_sender_tuple(self): msg = Message(subject="subject", sender=(u"ÄÜÖ → ✓", 'from@example.com>'), recipients=["to@example.com"]) self.assertIn('From: =?utf-8?b?w4TDnMOWIOKGkiDinJM=?= ', msg.as_string()) def test_unicode_sender(self): msg = Message(subject="subject", sender=u'ÄÜÖ → ✓ >', recipients=["to@example.com"]) self.assertIn('From: =?utf-8?b?w4TDnMOWIOKGkiDinJM=?= ', msg.as_string()) def test_unicode_headers(self): msg = Message(subject="subject", sender=u'ÄÜÖ → ✓ ', recipients=[u"Ä ", u"Ü "], cc=[u"Ö "]) response = msg.as_string() a1 = sanitize_address(u"Ä ") a2 = sanitize_address(u"Ü ") h1_a = Header("To: %s, %s" % (a1, a2)) h1_b = Header("To: %s, %s" % (a2, a1)) h2 = Header("From: %s" % sanitize_address(u"ÄÜÖ → ✓ ")) h3 = Header("Cc: %s" % sanitize_address(u"Ö ")) # Ugly, but there's no guaranteed order of the recipieints in the header try: self.assertIn(h1_a.encode(), response) except AssertionError: self.assertIn(h1_b.encode(), response) self.assertIn(h2.encode(), response) self.assertIn(h3.encode(), response) def test_unicode_subject(self): msg = Message(subject=make_lazy_string(lambda a: a, u"sübject"), sender='from@example.com', recipients=["to@example.com"]) self.assertIn('=?utf-8?q?s=C3=BCbject?=', msg.as_string()) def test_extra_headers(self): msg = Message(sender="from@example.com", subject="subject", recipients=["to@example.com"], body="hello", extra_headers={'X-Extra-Header': 'Yes'}) self.assertIn('X-Extra-Header: Yes', msg.as_string()) def test_message_charset(self): msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"], charset='us-ascii') # ascii body msg.body = "normal ascii text" self.assertIn('Content-Type: text/plain; charset="us-ascii"', msg.as_string()) # ascii html msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"], charset='us-ascii') msg.body = None msg.html = "

hello

" self.assertIn('Content-Type: text/html; charset="us-ascii"', msg.as_string()) # unicode body msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"]) msg.body = u"ünicöde ←→ ✓" self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string()) # unicode body and unicode html msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"]) msg.html = u"ünicöde ←→ ✓" self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string()) self.assertIn('Content-Type: text/html; charset="utf-8"', msg.as_string()) # unicode body and attachments msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"]) msg.html = None msg.attach(data=b"foobar", content_type='text/csv') self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string()) # unicode sender as tuple msg = Message(sender=(u"送信者", "from@example.com"), subject=u"表題", recipients=["foo@bar.com"], reply_to=u"返信先 ", charset='shift_jis') # japanese msg.body = u'内容' self.assertIn('From: =?iso-2022-jp?', msg.as_string()) self.assertNotIn('From: =?utf-8?', msg.as_string()) self.assertIn('Subject: =?iso-2022-jp?', msg.as_string()) self.assertNotIn('Subject: =?utf-8?', msg.as_string()) self.assertIn('Reply-To: =?iso-2022-jp?', msg.as_string()) self.assertNotIn('Reply-To: =?utf-8?', msg.as_string()) self.assertIn('Content-Type: text/plain; charset="iso-2022-jp"', msg.as_string()) # unicode subject sjis msg = Message(sender="from@example.com", subject=u"表題", recipients=["foo@bar.com"], charset='shift_jis') # japanese msg.body = u'内容' self.assertIn('Subject: =?iso-2022-jp?', msg.as_string()) self.assertIn('Content-Type: text/plain; charset="iso-2022-jp"', msg.as_string()) # unicode subject utf-8 msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"], charset='utf-8') msg.body = u'内容' self.assertIn('Subject: subject', msg.as_string()) self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string()) # ascii subject msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"], charset='us-ascii') msg.body = "normal ascii text" self.assertNotIn('Subject: =?us-ascii?', msg.as_string()) self.assertIn('Content-Type: text/plain; charset="us-ascii"', msg.as_string()) # default charset msg = Message(sender="from@example.com", subject="subject", recipients=["foo@bar.com"]) msg.body = "normal ascii text" self.assertNotIn('Subject: =?', msg.as_string()) self.assertIn('Content-Type: text/plain; charset="utf-8"', msg.as_string()) def test_empty_subject_header(self): msg = Message(sender="from@example.com", recipients=["foo@bar.com"]) msg.body = "normal ascii text" self.mail.send(msg) self.assertNotIn('Subject:', msg.as_string()) class TestMail(TestCase): def test_send(self): with self.mail.record_messages() as outbox: msg = Message(subject="testing", recipients=["tester@example.com"], body="test") self.mail.send(msg) self.assertIsNotNone(msg.date) self.assertEqual(len(outbox), 1) sent_msg = outbox[0] self.assertEqual(msg.sender, self.app.extensions['mail'].default_sender) def test_send_message(self): with self.mail.record_messages() as outbox: self.mail.send_message(subject="testing", recipients=["tester@example.com"], body="test") self.assertEqual(len(outbox), 1) msg = outbox[0] self.assertEqual(msg.subject, "testing") self.assertEqual(msg.recipients, ["tester@example.com"]) self.assertEqual(msg.body, "test") self.assertEqual(msg.sender, self.app.extensions['mail'].default_sender) class TestConnection(TestCase): def test_send_message(self): with self.mail.record_messages() as outbox: with self.mail.connect() as conn: conn.send_message(subject="testing", recipients=["to@example.com"], body="testing") self.assertEqual(len(outbox), 1) sent_msg = outbox[0] self.assertEqual(sent_msg.sender, self.app.extensions['mail'].default_sender) def test_send_single(self): with self.mail.record_messages() as outbox: with self.mail.connect() as conn: msg = Message(subject="testing", recipients=["to@example.com"], body="testing") conn.send(msg) self.assertEqual(len(outbox), 1) sent_msg = outbox[0] self.assertEqual(sent_msg.subject, "testing") self.assertEqual(sent_msg.recipients, ["to@example.com"]) self.assertEqual(sent_msg.body, "testing") self.assertEqual(sent_msg.sender, self.app.extensions['mail'].default_sender) def test_send_many(self): with self.mail.record_messages() as outbox: with self.mail.connect() as conn: for i in range(100): msg = Message(subject="testing", recipients=["to@example.com"], body="testing") conn.send(msg) self.assertEqual(len(outbox), 100) sent_msg = outbox[0] self.assertEqual(sent_msg.sender, self.app.extensions['mail'].default_sender) def test_send_without_sender(self): self.app.extensions['mail'].default_sender = None msg = Message(subject="testing", recipients=["to@example.com"], body="testing") with self.mail.connect() as conn: self.assertRaises(AssertionError, conn.send, msg) def test_send_without_recipients(self): msg = Message(subject="testing", recipients=[], body="testing") with self.mail.connect() as conn: self.assertRaises(AssertionError, conn.send, msg) def test_bad_header_subject(self): msg = Message(subject="testing\n\r", body="testing", recipients=["to@example.com"]) with self.mail.connect() as conn: self.assertRaises(BadHeaderError, conn.send, msg) def test_sendmail_with_ascii_recipient(self): with self.mail.connect() as conn: with mock.patch.object(conn, 'host') as host: msg = Message(subject="testing", sender="from@example.com", recipients=["to@example.com"], body="testing") conn.send(msg) host.sendmail.assert_called_once_with( "from@example.com", ["to@example.com"], msg.as_bytes() if PY3 else msg.as_string(), msg.mail_options, msg.rcpt_options ) def test_sendmail_with_non_ascii_recipient(self): with self.mail.connect() as conn: with mock.patch.object(conn, 'host') as host: msg = Message(subject="testing", sender="from@example.com", recipients=[u'ÄÜÖ → ✓ '], body="testing") conn.send(msg) host.sendmail.assert_called_once_with( "from@example.com", ["=?utf-8?b?w4TDnMOWIOKGkiDinJM=?= "], msg.as_bytes() if PY3 else msg.as_string(), msg.mail_options, msg.rcpt_options ) def test_sendmail_with_ascii_body(self): with self.mail.connect() as conn: with mock.patch.object(conn, 'host') as host: msg = Message(subject="testing", sender="from@example.com", recipients=["to@example.com"], body="body") conn.send(msg) host.sendmail.assert_called_once_with( "from@example.com", ["to@example.com"], msg.as_bytes() if PY3 else msg.as_string(), msg.mail_options, msg.rcpt_options ) def test_sendmail_with_non_ascii_body(self): with self.mail.connect() as conn: with mock.patch.object(conn, 'host') as host: msg = Message(subject="testing", sender="from@example.com", recipients=["to@example.com"], body=u"Öö") conn.send(msg) host.sendmail.assert_called_once_with( "from@example.com", ["to@example.com"], msg.as_bytes() if PY3 else msg.as_string(), msg.mail_options, msg.rcpt_options )