diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2ff985a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 +max_line_length = 88 + +[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] +indent_size = 2 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dbb67e3..73cbb2d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,6 +20,6 @@ Ensure each step in the contributing guide is complete, especially the following - Add tests that demonstrate the correct behavior of the change. Tests should fail without the change. - Add or update relevant docs, in the docs folder and in code. -- Add an entry in CHANGES.rst summarizing the change and linking to the issue. +- Add an entry in CHANGES.md summarizing the change and linking to the issue. - Add `.. versionchanged::` entries in any relevant code docs. --> diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 20d6321..2834942 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -26,7 +26,6 @@ jobs: - {python: '3.10'} - {python: '3.9'} - {python: '3.8'} - - {name: Minimal, python: '3.12', tox: minimal} steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 diff --git a/.gitignore b/.gitignore index 23d1773..62c1b88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,10 @@ -*.py[co] - -# Packages -*.egg -*.egg-info -dist -*build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox - -#Translations -*.mo - -#Mr Developer -.mr.developer.cfg +.idea/ +.vscode/ +.venv*/ +venv*/ +__pycache__/ +dist/ +.coverage* +htmlcov/ +.tox/ +docs/_build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e875b7e..2f501b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.2 + rev: v0.4.5 hooks: - id: ruff - id: ruff-format diff --git a/CHANGES b/CHANGES deleted file mode 100644 index e0e28a6..0000000 --- a/CHANGES +++ /dev/null @@ -1,112 +0,0 @@ -Flask-Mail Changelog -==================== - -Here you can see the full list of changes between each Flask-Mail release. - - -Version 0.9.1 -------------- - -Released September 28 2014 - -- Add an option for force ASCII file attachments -- Fix `force_text` function -- Fix some Python 3 support regarding email policy -- Added support ESMTP options -- Fixed various unicode issues related to message attachments, subjects, and email addresses - - -Version 0.9.0 -------------- - -Released June 14 2013 - -- Added initial Python 3 support - - -Version 0.8.2 -------------- - -Released April 11 2013 - -- Removed annoying stray print statement - - -Version 0.8.1 -------------- - -Released April 04 2013 - -- Fixed a bug with the new state object - - -Version 0.8.0 -------------- - -Released April 03 2013 - -- Fixed a bug with duplicate recipients -- Changed configuration options to be less confusing (**backward incopatible**), - update settings accordingly: - - `DEFAULT_MAIL_SENDER` is now `MAIL_DEFAULT_SENDER` - - `DEFAULT_MAX_EMAILS` is now `MAIL_MAX_EMAILS` - - `MAIL_FAIL_SILENTLY` is no longer used - - `MAIL_SUPPRESS_SEND` now defaults to `TESTING` setting value. -- General API clean as things were happening in a few different places - - -Version 0.7.6 -------------- - -Released March 11 2013 - -- Fix bug with cc, and bcc fields not being lists - - -Version 0.7.5 -------------- - -Released March 03 2013 - -- Fix bug with non-ascii characters in email address -- MAIL_FAIL_SILENTLY config value defaults to False -- Bcc header no longer set as some mail servers forward it to the recipient - - -Version 0.7.4 -------------- - -Released November 20th 2012 - -- Allow messages to be sent without a body - - -Version 0.7.3 -------------- - -Released September 27th 2012 - -- Add extra_headers to Message class - - -Version 0.7.2 -------------- - -Released September 16th 2012 - -- Add __str__ method to Message class -- Add message character set option which defaults to utf-8 - - -Version 0.7.1 -------------- - -Released September 5th 2012 - -- Date and message ID headers specified - - -Version 0.7 and prior ---------------------- - -Initial development by Dan Jacob and Ron DuPlain. Previously there was not a change log. diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..2e0c3fb --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,108 @@ +## Version 0.10.0 + +Unreleased + +- Drop support for Python < 3.8. +- Use `pyproject.toml` for packaging metadata. +- Use `flit_core` as build backend. +- Apply code formatting and linting tools. + + +## Version 0.9.1 + +Released 2014-09-28 + +- Add an option for force ASCII file attachments +- Fix `force_text` function +- Fix some Python 3 support regarding email policy. +- Support ESMTP options. +- Fix various Unicode issues related to message attachments, subjects, and + email addresses. + + +## Version 0.9.0 + +Released 2013-06-14 + +- Add initial Python 3 support. + + +## Version 0.8.2 + +Released 2013-04-11 + +- Remove stray `print` statement. + + +## Version 0.8.1 + +Released 2013-04-04 + +- Fix a bug with the new state object. + + +## Version 0.8.0 + +Released 2013-04-03 + +- Fix a bug with duplicate recipients. +- Change configuration options to be less confusing. Update settings accordin + ly: + - `DEFAULT_MAIL_SENDER` is now `MAIL_DEFAULT_SENDER`. + - `DEFAULT_MAX_EMAILS` is now `MAIL_MAX_EMAILS`. + - `MAIL_FAIL_SILENTLY` is no longer used. + - `MAIL_SUPPRESS_SEND` now defaults to `TESTING` setting value. +- General API cleanup as things were happening in a few different places. + + +## Version 0.7.6 + +Released 2013-03-11 + +- Fix bug with cc, and bcc fields not being lists. + + +## Version 0.7.5 + +Released 2013-03-03 + +- Fix bug with non-ascii characters in email address. +- `MAIL_FAIL_SILENTLY` config value defaults to `False`. +- Bcc header no longer set as some mail servers forward it to the recipient. + + +## Version 0.7.4 + +Released 2012-11-20 + +- Allow messages to be sent without a body. + + +## Version 0.7.3 + +Released 2012-09-27 + +- Add `extra_headers` to `Message` class. + + +## Version 0.7.2 + +Released 2012-09-16 + +- Add `__str__` method to `Message` class. +- Add message character set option which defaults to utf-8. + + +## Version 0.7.1 + +Released 2012-09-05 + +- Date and message ID headers specified. + + +## Version 0.7.0 + +Released 2012-08-29 + +- Initial development by Dan Jacob and Ron DuPlain. Previously there was not a + change log. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b6f9cdc..0000000 --- a/LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Copyright (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. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..a5f1b46 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder nor the names of its + contributors may 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 HOLDER 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. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c7ff9d1..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include LICENSE tests.py -recursive-include docs * -recursive-exclude docs *.pyc -recursive-exclude docs *.pyo -prune docs/_build -prune docs/_themes/.git diff --git a/README.md b/README.md index 8eb451e..c8b5121 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ # Flask-Mail -Flask-Mail is an extension for Flask that makes it easy to send emails from your Flask application. It simplifies the process of integrating email functionality, allowing you to focus on building great features for your application. +Flask-Mail is an extension for [Flask] that makes it easy to send emails from +your application. It simplifies the process of integrating email functionality, +allowing you to focus on building great features for your application. -## Installation +[flask]: https://flask.palletsprojects.com -You can install Flask-Mail using pip: -```bash -pip install Flask-Mail -``` +## Pallets Community Ecosystem + +> [!IMPORTANT]\ +> This project is part of the Pallets Community Ecosystem. Pallets is the open +> source organization that maintains Flask; Pallets-Eco enables community +> maintenance of related projects. If you are interested in helping maintain +> this project, please reach out on [the Pallets Discord server][discord]. -## Usage +[discord]: https://discord.gg/pallets -To use Flask-Mail in your Flask application, you need to import and configure it. Here's a simple example: + +## A Simple Example ```python from flask import Flask @@ -39,13 +45,3 @@ def send_email(): mail.send(msg) return 'Email sent succesfully!' ``` - -Make sure to replace placeholder values in the configuration with your actual email server details. - -## Documentation - -For more detailed information and advanced usage, please refer to the official [Flask-Mail documentation](https://pythonhosted.org/Flask-Mail/). - -## Contributing - -If you encounter any issues or have suggestions for improvements, feel free to open an issue or submit a pull request on the [GitHub repository](https://github.com/pallets-eco/flask-mail). diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 34526d7..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# 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." diff --git a/docs/_static/flask-mail.jpg b/docs/_static/flask-mail.jpg deleted file mode 100644 index bf9d492..0000000 Binary files a/docs/_static/flask-mail.jpg and /dev/null differ diff --git a/docs/_static/theme.css b/docs/_static/theme.css new file mode 100644 index 0000000..35e705c --- /dev/null +++ b/docs/_static/theme.css @@ -0,0 +1 @@ +@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&family=Source+Code+Pro:ital,wght@0,400;0,700;1,400;1,700&display=swap'); diff --git a/docs/_themes b/docs/_themes deleted file mode 160000 index 0269f3d..0000000 --- a/docs/_themes +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0269f3d188eaff07c44a800f35f7351f9e0aefb0 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..ecdb7d5 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,23 @@ +# API + +Anything documented here is part of the public API that Flask-Mail provides, +unless otherwise indicated. Anything not documented here is considered internal +or private and may change at any time. + +```{eval-rst} +.. module:: flask_mail + +.. autoclass:: Mail + :members: init_app, send, connect, send_message + +.. autoclass:: Attachment + +.. autoclass:: Connection + :members: send, send_message + +.. autoclass:: Message + :members: attach, add_recipient + +.. autodata:: email_dispatched + :annotation: +``` diff --git a/docs/changes.md b/docs/changes.md index 9f24941..5f522c2 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,3 +1,4 @@ -```{eval-rst} -.. include:: ../CHANGES +# Changes + +```{include} ../CHANGES.md ``` diff --git a/docs/conf.py b/docs/conf.py index 461ca7e..1202c49 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,90 +1,54 @@ -# -# 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 importlib.metadata -# -- General configuration ----------------------------------------------------- +# Project -------------------------------------------------------------- -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +project = "Flask-Mail" +version = release = importlib.metadata.version("flask-mail").partition(".dev")[0] + +# General -------------------------------------------------------------- + +default_role = "code" extensions = [ - "myst_parser", "sphinx.ext.autodoc", + "sphinx.ext.extlinks", "sphinx.ext.intersphinx", - "pallets_sphinx_themes", + "sphinxcontrib.log_cabinet", + "myst_parser", ] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The encoding of source files. -# source_encoding = 'utf-8' - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = "Flask-Mail" -copyright = "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 = [] +autodoc_member_order = "bysource" +autodoc_typehints = "description" +autodoc_preserve_defaults = True +extlinks = { + "issue": ("https://github.com/pallets-eco/flask-mail/issues/%s", "#%s"), + "pr": ("https://github.com/pallets-eco/flask-mail/pull/%s", "#%s"), +} +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "flask": ("https://flask.palletsprojects.com", None), +} +myst_enable_extensions = [ + "fieldlist", +] +myst_heading_anchors = 2 # HTML ----------------------------------------------------------------- -html_theme = "flask" +html_theme = "furo" html_static_path = ["_static"] +html_css_files = ["theme.css"] html_copy_source = False +html_logo = "_static/flask-mail.png" +html_theme_options = { + "source_repository": "https://github.com/pallets-eco/flask-mail/", + "source_branch": "main", + "source_directory": "docs/", + "light_css_variables": { + "font-stack": "'Atkinson Hyperlegible', sans-serif", + "font-stack--monospace": "'Source Code Pro', monospace", + }, +} +pygments_style = "default" +pygments_style_dark = "github-dark" html_show_copyright = False html_use_index = False html_domain_indices = False diff --git a/docs/index.md b/docs/index.md index 58a7111..1665cb6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,53 +1,101 @@ -# flask-mail +# Flask-Mail -```{eval-rst} -.. module:: flask-mail +```{currentmodule} 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 +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 +[flask]: https://flask.palletsprojects.com -- [documentation](https://flask-mail.readthedocs.io/en/latest/) -- [source](http://github.com/pallets-eco/flask-mail) -- {doc}`changes ` +```{toctree} +:hidden: + +api +changes +license +``` -## Installing Flask-Mail -Install with **pip**: +## Installing + +Install from PyPI using an installer such as pip: ```sh -pip install Flask-Mail +$ pip install Flask-Mail +``` + + +## Configuring + +Flask-Mail is configured through the standard Flask config API. These are the +available options (each is explained later in the documentation): + +```{data} MAIL_SERVER +:type: str +:value: 'localhost' +``` + +```{data} MAIL_PORT +:type: int +:value: 25 +``` + +```{data} MAIL_USE_TLS +:type: bool +:value: False +``` + +```{data} MAIL_USE_SSL +:type: bool +:value: False ``` -If you are using **virtualenv**, it is assumed that you are installing flask-mail -in the same virtualenv as your Flask application(s). +```{data} MAIL_DEBUG +:type: bool +:value: app.debug +``` -## Configuring Flask-Mail +```{data} MAIL_USERNAME +:type: str | None +:value: None +``` -**Flask-Mail** is configured through the standard Flask config API. These are the available -options (each is explained later in the documentation): +```{data} MAIL_PASSWORD +:type: str | None +:value: None +``` -- **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** +```{data} MAIL_DEFAULT_SENDER +:type: str | None +:value: None +``` -In addition the standard Flask `TESTING` configuration option is used by **Flask-Mail** -in unit tests (see below). +```{data} MAIL_MAX_EMAILS +:type: int | None +:value: None +``` -Emails are managed through a `Mail` instance: +```{data} MAIL_SUPPRESS_SEND +:type: bool +:value: app.testing +``` + +```{data} MAIL_ASCII_ATTACHMENTS +:type: bool +:value: False +``` + +In addition, the standard Flask `TESTING` configuration option is used by +Flask-Mail in unit tests (see below). + + +## The extension instance + +Emails are managed through a {class}`.Mail` instance: ```py from flask import Flask @@ -57,11 +105,11 @@ 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. +In this case all emails are sent using the configuration values of the +application that was passed to the {class}`.Mail` class constructor. -Alternatively you can set up your `Mail` instance later at configuration time, using the -**init_app** method: +Alternatively you can set up your {class}`.Mail` instance later at configuration +time, using the {meth}`~.Mail.init_app` method: ```py mail = Mail() @@ -70,29 +118,33 @@ 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. +In this case emails will be sent using the configuration values from Flask's +{data}`~flask.current_app` context global. This is useful if you have +multiple applications running in the same process but with different +configuration options. ```{admonition} Load email configuration -Note that Flask-Mail needs the configuration parameters to create a mail handler, so you have to make -sure to load your configuration **before** the initialization of Flask-Mail (either use `Mail` class -constructor or `init_app` method). +Note that Flask-Mail needs the configuration parameters to create a mail +handler, so you have to make sure to load your configuration before the +initialization of Flask-Mail (either using {class}`Mail` constructor or +{meth}`~.Mail.init_app` method). ``` + ## Sending messages -To send a message first create a `Message` instance: +To send a message first create a {class}`.Message` instance: ```py from flask_mail import Message @app.route("/") def index(): - - msg = Message("Hello", - sender="from@example.com", - recipients=["to@example.com"]) + msg = Message( + subject="Hello", + sender="from@example.com", + recipients=["to@example.com"], + ) ``` You can set the recipient emails immediately, or individually: @@ -102,21 +154,23 @@ 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: +If you have set {data}`.MAIL_DEFAULT_SENDER` you don't need to set the message sender +explicity, as it will use this configuration value by default: ```py -msg = Message("Hello", - recipients=["to@example.com"]) +msg = Message( + subject="Hello", + recipients=["to@example.com"], +) ``` -If the `sender` is a two-element tuple, this will be split into name -and address: +If the `sender` is a two-element tuple, this will be split into name and address: ```py -msg = Message("Hello", - sender=("Me", "me@example.com")) - +msg = Message( + subject="Hello", + sender=("Me", "me@example.com"), +) assert msg.sender == "Me " ``` @@ -127,36 +181,41 @@ msg.body = "testing" msg.html = "testing" ``` -Finally, to send the message, you use the `Mail` instance configured with your Flask application: +Finally, to send the message, you use the {class}`.Mail` instance configured +with your Flask application: ```py 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. +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: ```py with mail.connect() as conn: for user in users: - message = '...' - subject = "hello, %s" % user.name - msg = Message(recipients=[user.email], - body=message, - subject=subject) - + msg = Message( + subject=f"hello, {user.name}", + body="...", + recipients=[user.email], + ) conn.send(msg) ``` -The connection to your email host is kept alive and closed automatically once all the messages have been sent. +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 {data}`.MAIL_MAX_EMAILS` setting. -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 @@ -167,68 +226,61 @@ 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 +If {data}`.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 +When you are sending messages inside 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. +If the setting `TESTING` is set to `True`, emails will be suppressed. Calling +{meth}`Message.send` will not result in any messages being actually sent. -Alternatively outside a testing environment you can set `MAIL_SUPPRESS_SEND` to `True`. -This will have the same effect. +Alternatively outside a testing environment you can set +{data}`.MAIL_SUPPRESS_SEND` to `True`. 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. +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` +In order to keep track of dispatched emails, use the {meth}`~.Mail.record_messages` method: ```py with mail.record_messages() as outbox: - - mail.send_message(subject='testing', - body='test', - recipients=emails) - + 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 `outbox` is a list of {class}`.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](http://www.nyphp.org/PHundamentals/8_Preventing-Email-Header-Injection) attempts to send -a message with newlines in the subject, sender or recipient addresses will result in a `BadHeaderError`. - -## Signalling support +To prevent header injection, attempts to send a message with newlines in the +subject, sender or recipient addresses will result in a `BadHeaderError`. -```{versionadded} 0.4 -``` +## Signalling support -**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). +Flask-Mail provides signalling support through a {data}`.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: +A function connecting to the {data}`.email_dispatched` signal takes a +{class}`.Message` instance as a first argument, and the Flask app instance as an +optional argument: ```py def log_message(message, app): @@ -236,36 +288,3 @@ def log_message(message, app): email_dispatched.connect(log_message) ``` - -## API - -```{eval-rst} -.. module:: flask_mail -``` - -```{eval-rst} -.. autoclass:: Mail - :members: send, connect, send_message -``` - -```{eval-rst} -.. autoclass:: Attachment -``` - -```{eval-rst} -.. autoclass:: Connection - :members: send, send_message -``` - -```{eval-rst} -.. autoclass:: Message - :members: attach, add_recipient -``` - -```{toctree} -:maxdepth: 2 - -changes -``` - -[flask]: https://flask.palletsprojects.com/ diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 0000000..8460d36 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,5 @@ +# BSD-3-Clause License + +```{literalinclude} ../LICENSE.txt +:language: text +``` diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index f8446ae..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,113 +0,0 @@ -@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 diff --git a/pyproject.toml b/pyproject.toml index 5b21559..ce74e75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,26 +1,22 @@ [project] name = "Flask-Mail" -version = "0.9.1" +version = "0.10.0.dev" description = "Flask extension for sending email" readme = "README.md" +license = { file = "LICENSE.txt" } authors = [{ name = "Dan Jacob" }] maintainers = [{ name = "Pallets Ecosystem", email = "contact@palletsprojects.com" }] classifiers = [ "Development Status :: 4 - Beta", - "Environment :: Web Environment", - "Intended Audience :: Developers", + "Framework :: Flask", "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", "Programming Language :: Python", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content", - "Topic :: Software Development :: Libraries :: Python Modules", ] +requires-python = ">=3.8" dependencies = [ "flask", "blinker", ] -license = { file = "LICENSE" } -requires-python = ">=3.8" [project.urls] Documentation = "https://flask-mail.readthedocs.io" @@ -39,6 +35,31 @@ name = "flask_mail" testpaths = ["tests"] filterwarnings = ["error"] +[tool.coverage.run] +branch = true +source = ["flask_mail", "tests"] + +[tool.coverage.paths] +source = ["src", "*/site-packages"] + +[tool.mypy] +python_version = "3.8" +files = ["src/flask_mail", "tests"] +show_error_codes = true +pretty = true +strict = true + +[[tool.mypy.overrides]] +module = [ + "sqlparse.*" +] +ignore_missing_imports = true + +[tool.pyright] +pythonVersion = "3.8" +include = ["src/flask_mail", "tests"] +typeCheckingMode = "basic" + [tool.ruff] src = ["src"] fix = true @@ -51,7 +72,7 @@ select = [ "E", # pycodestyle error "F", # pyflakes "I", # isort - "UP", # pyupgrade + "UP", # pyupgrade "W", # pycodestyle warning ] ignore-init-module-imports = true diff --git a/requirements/dev.in b/requirements/dev.in index 69884fb..7dd622e 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -1,3 +1,4 @@ -r tests.txt +-r typing.txt tox pre-commit diff --git a/requirements/dev.txt b/requirements/dev.txt index 9df6da2..354d159 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,12 +4,20 @@ # # pip-compile dev.in # +blinker==1.8.2 + # via + # -r typing.txt + # flask cachetools==5.3.3 # via tox cfgv==3.4.0 # via pre-commit chardet==5.2.0 # via tox +click==8.1.7 + # via + # -r typing.txt + # flask colorama==0.4.6 # via tox distlib==0.3.8 @@ -17,24 +25,59 @@ distlib==0.3.8 exceptiongroup==1.2.1 # via # -r tests.txt + # -r typing.txt # pytest filelock==3.14.0 # via # tox # virtualenv +flask==3.0.3 + # via + # -r typing.txt + # flask-sqlalchemy +flask-sqlalchemy==3.1.1 + # via -r typing.txt identify==2.5.36 # via pre-commit +importlib-metadata==7.1.0 + # via + # -r typing.txt + # flask iniconfig==2.0.0 # via # -r tests.txt + # -r typing.txt # pytest +itsdangerous==2.2.0 + # via + # -r typing.txt + # flask +jinja2==3.1.4 + # via + # -r typing.txt + # flask +markupsafe==2.1.5 + # via + # -r typing.txt + # jinja2 + # werkzeug mock==5.1.0 # via -r tests.txt +mypy==1.10.0 + # via -r typing.txt +mypy-extensions==1.0.0 + # via + # -r typing.txt + # mypy nodeenv==1.8.0 - # via pre-commit + # via + # -r typing.txt + # pre-commit + # pyright packaging==24.0 # via # -r tests.txt + # -r typing.txt # pyproject-api # pytest # tox @@ -45,30 +88,64 @@ platformdirs==4.2.2 pluggy==1.5.0 # via # -r tests.txt + # -r typing.txt # pytest # tox pre-commit==3.5.0 # via -r dev.in pyproject-api==1.6.1 # via tox +pyright==1.1.364 + # via -r typing.txt pytest==8.2.1 - # via -r tests.txt + # via + # -r tests.txt + # -r typing.txt pyyaml==6.0.1 # via pre-commit speaklater==1.3 # via -r tests.txt +sqlalchemy==2.0.30 + # via + # -r typing.txt + # flask-sqlalchemy tomli==2.0.1 # via # -r tests.txt + # -r typing.txt + # mypy # pyproject-api # pytest # tox tox==4.15.0 # via -r dev.in +types-docutils==0.21.0.20240423 + # via + # -r typing.txt + # types-pygments +types-pygments==2.18.0.20240506 + # via -r typing.txt +types-setuptools==69.5.0.20240522 + # via + # -r typing.txt + # types-pygments +typing-extensions==4.11.0 + # via + # -r typing.txt + # mypy + # sqlalchemy virtualenv==20.26.2 # via # pre-commit # tox +werkzeug==3.0.3 + # via + # -r typing.txt + # flask +zipp==3.18.2 + # via + # -r typing.txt + # importlib-metadata # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/docs.in b/requirements/docs.in index 54a947a..aa18ca3 100644 --- a/requirements/docs.in +++ b/requirements/docs.in @@ -1,4 +1,4 @@ +furo myst-parser -pallets-sphinx-themes sphinx sphinxcontrib-log-cabinet diff --git a/requirements/docs.txt b/requirements/docs.txt index 4d43c23..0e2330b 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -8,6 +8,8 @@ alabaster==0.7.13 # via sphinx babel==2.15.0 # via sphinx +beautifulsoup4==4.12.3 + # via furo certifi==2024.2.2 # via requests charset-normalizer==3.3.2 @@ -16,6 +18,8 @@ docutils==0.20.1 # via # myst-parser # sphinx +furo==2024.5.6 + # via -r docs.in idna==3.7 # via requests imagesize==1.4.1 @@ -39,27 +43,30 @@ mdurl==0.1.2 myst-parser==3.0.1 # via -r docs.in packaging==24.0 + # via sphinx +pygments==2.18.0 # via - # pallets-sphinx-themes + # furo # sphinx -pallets-sphinx-themes==2.1.3 - # via -r docs.in -pygments==2.18.0 - # via sphinx pytz==2024.1 # via babel pyyaml==6.0.1 # via myst-parser -requests==2.32.1 +requests==2.32.2 # via sphinx snowballstemmer==2.2.0 # via sphinx +soupsieve==2.5 + # via beautifulsoup4 sphinx==7.1.2 # via # -r docs.in + # furo # myst-parser - # pallets-sphinx-themes + # sphinx-basic-ng # sphinxcontrib-log-cabinet +sphinx-basic-ng==1.0.0b2 + # via furo sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 diff --git a/requirements/tests.in b/requirements/tests.in index 23a9ca5..4d96b18 100644 --- a/requirements/tests.in +++ b/requirements/tests.in @@ -1,3 +1,2 @@ -mock pytest speaklater diff --git a/requirements/tests.txt b/requirements/tests.txt index 08f9683..2350e9d 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -8,8 +8,6 @@ exceptiongroup==1.2.1 # via pytest iniconfig==2.0.0 # via pytest -mock==5.1.0 - # via -r tests.in packaging==24.0 # via pytest pluggy==1.5.0 diff --git a/requirements/typing.in b/requirements/typing.in new file mode 100644 index 0000000..8be59c5 --- /dev/null +++ b/requirements/typing.in @@ -0,0 +1,3 @@ +mypy +pyright +pytest diff --git a/requirements/typing.txt b/requirements/typing.txt new file mode 100644 index 0000000..8613d28 --- /dev/null +++ b/requirements/typing.txt @@ -0,0 +1,33 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile typing.in +# +exceptiongroup==1.2.1 + # via pytest +iniconfig==2.0.0 + # via pytest +mypy==1.10.0 + # via -r typing.in +mypy-extensions==1.0.0 + # via mypy +nodeenv==1.8.0 + # via pyright +packaging==24.0 + # via pytest +pluggy==1.5.0 + # via pytest +pyright==1.1.364 + # via -r typing.in +pytest==8.2.1 + # via -r typing.in +tomli==2.0.1 + # via + # mypy + # pytest +typing-extensions==4.11.0 + # via mypy + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 06842c4..0000000 --- a/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[build_sphinx] -source-dir = docs/ -build-dir = docs/_build - -[upload_sphinx] -upload-dir = docs/_build/html diff --git a/src/flask_mail/__init__.py b/src/flask_mail/__init__.py index 28569cb..351e681 100644 --- a/src/flask_mail/__init__.py +++ b/src/flask_mail/__init__.py @@ -1,14 +1,4 @@ -""" -flaskext.mail -~~~~~~~~~~~~~ - -Flask extension for sending email. - -:copyright: (c) 2010 by Dan Jacob. -:license: BSD, see LICENSE for more details. -""" - -__version__ = "0.9.1" +__version__ = "0.10.0.dev" import re import smtplib diff --git a/tox.ini b/tox.ini index 483c297..70a441b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] envlist = py3{12,11,10,9,8} - minimal style + docs skip_missing_interpreters = true [testenv] @@ -13,10 +13,6 @@ use_frozen_constraints = true deps = -r requirements/tests.txt commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs} -[testenv:minimal] -deps = -commands = python -c "from flask_mail import Mail" - [testenv:style] deps = pre-commit skip_install = true @@ -43,3 +39,4 @@ commands = pip-compile dev.in -q {posargs:-U} pip-compile docs.in -q {posargs:-U} pip-compile tests.in -q {posargs:-U} + pip-compile typing.in -q {posargs:-U}