Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-django-mailer for openSUSE:Factory checked in at 2021-12-26 13:30:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-django-mailer (Old) and /work/SRC/openSUSE:Factory/.python-django-mailer.new.2520 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-mailer" Sun Dec 26 13:30:38 2021 rev:2 rq:942593 version:2.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-django-mailer/python-django-mailer.changes 2020-07-08 19:14:19.095387900 +0200 +++ /work/SRC/openSUSE:Factory/.python-django-mailer.new.2520/python-django-mailer.changes 2021-12-26 13:30:58.430978921 +0100 @@ -1,0 +2,18 @@ +Sun Dec 26 11:31:52 UTC 2021 - John Vandenberg <jay...@gmail.com> + +- Update to v2.1 + * The retry_deferred and send_mail commands rely on the log level + set in your django project now. The -c/--cron option in those + commands has been deprecated and the logic to configure log + levels and the message format has been removed. + * Changed logging to use module specific loggers to avoid + interfering with other loggers. + * Added MAILER_USE_FILE_LOCK setting to allow disabling file based + locking. + * Added -r option to purge_mail_log management command. + * Fixed deprecation warnings on Django 3.1 + * Use cached DNS_NAME for performance + * Added ability to override the default error handler via the + MAILER_ERROR_HANDLER settings key + +------------------------------------------------------------------- Old: ---- django-mailer-2.0.1.tar.gz New: ---- django-mailer-2.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-django-mailer.spec ++++++ --- /var/tmp/diff_new_pack.1dciVg/_old 2021-12-26 13:30:58.838979205 +0100 +++ /var/tmp/diff_new_pack.1dciVg/_new 2021-12-26 13:30:58.842979208 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-django-mailer # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-django-mailer -Version: 2.0.1 +Version: 2.1 Release: 0 Summary: A reusable Django app for queuing the sending of email License: MIT @@ -60,6 +60,6 @@ %files %{python_files} %doc AUTHORS CHANGES.rst README.rst %license LICENSE -%{python_sitelib}/* +%{python_sitelib}/*mailer*/ %changelog ++++++ django-mailer-2.0.1.tar.gz -> django-mailer-2.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/.travis.yml new/django-mailer-2.1/.travis.yml --- old/django-mailer-2.0.1/.travis.yml 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/.travis.yml 2020-10-11 19:47:09.000000000 +0200 @@ -31,8 +31,10 @@ env: TOXENV=py36-django22-test - python: 3.6 env: TOXENV=py36-django30-test - - python: 3.6 - env: TOXENV=py36-flake + - python: 3.8 + env: TOXENV=py38-django31-test + - python: 3.8 + env: TOXENV=py38-flake install: - pip install coveralls tox>=2.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/AUTHORS new/django-mailer-2.1/AUTHORS --- old/django-mailer-2.0.1/AUTHORS 2015-07-03 13:16:58.000000000 +0200 +++ new/django-mailer-2.1/AUTHORS 2020-12-05 11:39:47.000000000 +0100 @@ -11,3 +11,5 @@ * Jannis Leidel * Luke Plant * Renato Alves + * Paul Brown + * Sebastian Pipping diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/CHANGES.rst new/django-mailer-2.1/CHANGES.rst --- old/django-mailer-2.0.1/CHANGES.rst 2020-03-01 19:48:34.000000000 +0100 +++ new/django-mailer-2.1/CHANGES.rst 2020-12-05 11:54:27.000000000 +0100 @@ -1,6 +1,22 @@ Change log ========== +2.1 - 2020-12-05 +---------------- + +* The ``retry_deferred`` and ``send_mail`` commands rely on the log level set + in your django project now. The ``-c/--cron`` option in those commands has + been deprecated and the logic to configure log levels and the message + format has been removed. +* Changed logging to use module specific loggers to avoid interfering + with other loggers. +* Added ``MAILER_USE_FILE_LOCK`` setting to allow disabling file based locking. +* Added ``-r`` option to ``purge_mail_log`` management command. Thanks julienc91 +* Fixed deprecation warnings on Django 3.1 +* Use cached DNS_NAME for performance +* Added ability to override the default error handler via the ``MAILER_ERROR_HANDLER`` + settings key + 2.0.1 - 2020-03-01 ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/PKG-INFO new/django-mailer-2.1/PKG-INFO --- old/django-mailer-2.0.1/PKG-INFO 2020-03-01 20:25:48.000000000 +0100 +++ new/django-mailer-2.1/PKG-INFO 2020-12-05 11:56:51.692757600 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: django-mailer -Version: 2.0.1 +Version: 2.1 Summary: A reusable Django app for queuing the sending of email Home-page: http://github.com/pinax/django-mailer/ Author: Pinax Team @@ -82,7 +82,8 @@ * ``retry_deferred`` will move any deferred mail back into the normal queue (so it will be attempted again on the next ``send_mail``). - * ``purge_mail_log`` will remove old successful message logs from the database, to prevent it from filling up your database + * ``purge_mail_log`` will remove old successful message logs from the database, to prevent it from filling up your database. + Use the ``-r failure`` option to remove only failed message logs instead, or ``-r all`` to remove them all. You may want to set these up via cron to run regularly:: @@ -129,6 +130,60 @@ Unprocessed emails will be evaluated in the following delivery iterations. + Error handling + ============== + + django-mailer comes with a default error handler + ``mailer.engine.handle_delivery_exception``. + + It marks the related message as deferred for any of these exceptions: + + - ``smtplib.SMTPAuthenticationError`` + - ``smtplib.SMTPDataError`` + - ``smtplib.SMTPRecipientsRefused`` + - ``smtplib.SMTPSenderRefused`` + - ``socket.error`` + + Any other exceptions is re-raised. + That is done for backwords-compatiblity as well as for flexibility: + we would otherwise have to maintain an extensive and changing + list of exception types, which does not scale, and you get + the chance to do error handling that fits your environment like a glove. + + When the default behavior does not fit your environment, you can specify your + own custom delivery error handler through setting ``MAILER_ERROR_HANDLER``. + The value should be a string for use with Django's ``import_string``, + the default is ``"mailer.engine.handle_delivery_exception"``. + + Your handler is passed three arguments, in order: + + - ``connection`` ??? the backend connection instance that failed delivery + - ``message`` ??? the ``Message`` instance that failed delivery + - ``exc`` ??? the exception instance raised by the mailer backend + + Your handler should return a 2-tuple of: + + 1. a connection instance (or ``None`` to cause a new connection to be created) + 2. a string denoting the action taken by the handler, + either ``"sent"`` or ``"deferred"`` precisely + + For an example of a custom error handler:: + + def my_handler(connection, message, exc): + if isinstance(exc, SomeDeliveryException): + # trying to re-send this very message desparately + # (if you have good reason to) + [..] + status = 'sent' + elif isinstance(exc, SomeOtherException): + message.defer() + connection = None # i.e. ask for a new connection + status = 'deferred' + else: + six.reraise(*sys.exc_info()) + + return connection, status + Other settings ============== @@ -139,6 +194,9 @@ delete this file, and others in the same directory. With the default value of ``None`` django-mailer will use a path in current working directory. + If you need to disable the file-based locking, you can set the + ``MAILER_USE_FILE_LOCK`` setting to ``False``. + If you need to change the batch size used by django-mailer to save messages in ``mailer.backend.DbBackend``, you can set ``MAILER_MESSAGES_BATCH_SIZE`` to a value more suitable for you. This value, which defaults to `None`, will be passed to @@ -158,6 +216,22 @@ Change log ========== + 2.1 - 2020-12-05 + ---------------- + + * The ``retry_deferred`` and ``send_mail`` commands rely on the log level set + in your django project now. The ``-c/--cron`` option in those commands has + been deprecated and the logic to configure log levels and the message + format has been removed. + * Changed logging to use module specific loggers to avoid interfering + with other loggers. + * Added ``MAILER_USE_FILE_LOCK`` setting to allow disabling file based locking. + * Added ``-r`` option to ``purge_mail_log`` management command. Thanks julienc91 + * Fixed deprecation warnings on Django 3.1 + * Use cached DNS_NAME for performance + * Added ability to override the default error handler via the ``MAILER_ERROR_HANDLER`` + settings key + 2.0.1 - 2020-03-01 ------------------ @@ -291,4 +365,7 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Framework :: Django diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/README.rst new/django-mailer-2.1/README.rst --- old/django-mailer-2.0.1/README.rst 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/README.rst 2020-10-11 18:34:19.000000000 +0200 @@ -20,19 +20,16 @@ :target: https://pypi.python.org/pypi/django-mailer/ -Pinax ------ - -Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable Django apps, themes, and starter project templates. -This collection can be found at http://pinaxproject.com. - -This app was developed as part of the Pinax ecosystem but is just a Django app and can be used independently of other Pinax apps. - - django-mailer ------------- ``django-mailer`` is a reusable Django app for queuing the sending of email. +It works by storing email in the database for later sending. + +Keep in mind that file attachments are also temporarily stored in the database, which means if you are sending files larger than several hundred KB in size, you are likely to run into database limitations on how large your query can be. If this happens, you'll either need to fall back to using Django's default mail backend, or increase your database limits (a procedure that depends on which database you are using). + +django-mailer was developed as part of the `Pinax ecosystem <http://pinaxproject.com>`_ but is just a Django app and can be used independently of other Pinax apps. + Requirements ------------ @@ -78,29 +75,37 @@ 0 0 * * * (/path/to/your/python /path/to/your/manage.py purge_mail_log 7 >> ~/cron_mail_purge.log 2>&1) -Documentation -------------- +Use the `-r failure` option to remove only failed log entries instead, or `-r all` to remove them all. -See ``usage.rst`` in the docs for more advanced use cases - https://github.com/pinax/django-mailer/blob/master/docs/usage.rst#usage. +Documentation and support +------------------------- + +See `usage.rst <https://github.com/pinax/django-mailer/blob/master/docs/usage.rst#usage>`_ +in the docs for more advanced use cases. The Pinax documentation is available at http://pinaxproject.com/pinax/. +This is an Open Source project maintained by volunteers, and outside this documentation the maintainers +do not offer other support. For cases where you have found a bug you can file a GitHub issue. +In case of any questions we recommend you join the `Pinax Slack team <http://slack.pinaxproject.com>`_ +and ping the Pinax team there instead of creating an issue on GitHub. You may also be able to get help on +other programming sites like `Stack Overflow <https://stackoverflow.com/>`_. + Contribute ---------- -See ``CONTRIBUTING.rst`` for information about contributing patches to ``django-mailer``. +See `CONTRIBUTING.rst <https://github.com/pinax/django-mailer/blob/master/CONTRIBUTING.rst>`_ for information about contributing patches to ``django-mailer``. -See this blog post http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/ including a video, or our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. +See this `blog post including a video <http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/>`_, or our `How to Contribute <http://pinaxproject.com/pinax/how_to_contribute/>`_ section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our `Ways to Contribute/What We Need Help With <http://pinaxproject.com/pinax/ways_to_contribute/>`_ section. -In case of any questions we recommend you join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. -We also highly recommend reading our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). +We also highly recommend reading our `Open Source and Self-Care blog post <http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/>`_. Code of Conduct --------------- -In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. +In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a `code of conduct <http://pinaxproject.com/pinax/code_of_conduct/>`_. We ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. @@ -108,4 +113,4 @@ Pinax Project Blog and Twitter ------------------------------ -For updates and news regarding the Pinax Project, please follow us on Twitter at @pinaxproject and check out our blog http://blog.pinaxproject.com. +For updates and news regarding the Pinax Project, please follow us on Twitter at @pinaxproject and check out `our blog <http://blog.pinaxproject.com>`_. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/docs/usage.rst new/django-mailer-2.1/docs/usage.rst --- old/django-mailer-2.0.1/docs/usage.rst 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/docs/usage.rst 2020-12-05 11:39:47.000000000 +0100 @@ -74,7 +74,8 @@ * ``retry_deferred`` will move any deferred mail back into the normal queue (so it will be attempted again on the next ``send_mail``). -* ``purge_mail_log`` will remove old successful message logs from the database, to prevent it from filling up your database +* ``purge_mail_log`` will remove old successful message logs from the database, to prevent it from filling up your database. + Use the ``-r failure`` option to remove only failed message logs instead, or ``-r all`` to remove them all. You may want to set these up via cron to run regularly:: @@ -121,6 +122,60 @@ Unprocessed emails will be evaluated in the following delivery iterations. +Error handling +============== + +django-mailer comes with a default error handler +``mailer.engine.handle_delivery_exception``. + +It marks the related message as deferred for any of these exceptions: + +- ``smtplib.SMTPAuthenticationError`` +- ``smtplib.SMTPDataError`` +- ``smtplib.SMTPRecipientsRefused`` +- ``smtplib.SMTPSenderRefused`` +- ``socket.error`` + +Any other exceptions is re-raised. +That is done for backwords-compatiblity as well as for flexibility: +we would otherwise have to maintain an extensive and changing +list of exception types, which does not scale, and you get +the chance to do error handling that fits your environment like a glove. + +When the default behavior does not fit your environment, you can specify your +own custom delivery error handler through setting ``MAILER_ERROR_HANDLER``. +The value should be a string for use with Django's ``import_string``, +the default is ``"mailer.engine.handle_delivery_exception"``. + +Your handler is passed three arguments, in order: + +- ``connection`` ??? the backend connection instance that failed delivery +- ``message`` ??? the ``Message`` instance that failed delivery +- ``exc`` ??? the exception instance raised by the mailer backend + +Your handler should return a 2-tuple of: + +1. a connection instance (or ``None`` to cause a new connection to be created) +2. a string denoting the action taken by the handler, + either ``"sent"`` or ``"deferred"`` precisely + +For an example of a custom error handler:: + + def my_handler(connection, message, exc): + if isinstance(exc, SomeDeliveryException): + # trying to re-send this very message desparately + # (if you have good reason to) + [..] + status = 'sent' + elif isinstance(exc, SomeOtherException): + message.defer() + connection = None # i.e. ask for a new connection + status = 'deferred' + else: + six.reraise(*sys.exc_info()) + + return connection, status + Other settings ============== @@ -131,6 +186,9 @@ delete this file, and others in the same directory. With the default value of ``None`` django-mailer will use a path in current working directory. +If you need to disable the file-based locking, you can set the +``MAILER_USE_FILE_LOCK`` setting to ``False``. + If you need to change the batch size used by django-mailer to save messages in ``mailer.backend.DbBackend``, you can set ``MAILER_MESSAGES_BATCH_SIZE`` to a value more suitable for you. This value, which defaults to `None`, will be passed to diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/runtests.py new/django-mailer-2.1/runtests.py --- old/django-mailer-2.0.1/runtests.py 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/runtests.py 2020-10-11 19:21:59.000000000 +0200 @@ -8,6 +8,7 @@ from django.test.utils import get_runner warnings.simplefilter("always", DeprecationWarning) +warnings.simplefilter("always", PendingDeprecationWarning) DEFAULT_SETTINGS = dict( INSTALLED_APPS=[ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/setup.cfg new/django-mailer-2.1/setup.cfg --- old/django-mailer-2.0.1/setup.cfg 2020-03-01 20:25:48.000000000 +0100 +++ new/django-mailer-2.1/setup.cfg 2020-12-05 11:56:51.692757600 +0100 @@ -3,7 +3,6 @@ [flake8] max-line-length = 100 -max-complexity = 10 exclude = src/mailer/migrations,build,.tox [egg_info] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/setup.py new/django-mailer-2.1/setup.py --- old/django-mailer-2.0.1/setup.py 2020-03-01 19:49:12.000000000 +0100 +++ new/django-mailer-2.1/setup.py 2020-12-05 11:54:27.000000000 +0100 @@ -4,7 +4,7 @@ setup( name="django-mailer", - version="2.0.1", + version="2.1", description="A reusable Django app for queuing the sending of email", long_description=open("docs/usage.rst").read() + open("CHANGES.rst").read(), author="Pinax Team", @@ -24,6 +24,9 @@ "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Framework :: Django", ], install_requires=[ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/django_mailer.egg-info/PKG-INFO new/django-mailer-2.1/src/django_mailer.egg-info/PKG-INFO --- old/django-mailer-2.0.1/src/django_mailer.egg-info/PKG-INFO 2020-03-01 20:25:48.000000000 +0100 +++ new/django-mailer-2.1/src/django_mailer.egg-info/PKG-INFO 2020-12-05 11:56:51.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: django-mailer -Version: 2.0.1 +Version: 2.1 Summary: A reusable Django app for queuing the sending of email Home-page: http://github.com/pinax/django-mailer/ Author: Pinax Team @@ -82,7 +82,8 @@ * ``retry_deferred`` will move any deferred mail back into the normal queue (so it will be attempted again on the next ``send_mail``). - * ``purge_mail_log`` will remove old successful message logs from the database, to prevent it from filling up your database + * ``purge_mail_log`` will remove old successful message logs from the database, to prevent it from filling up your database. + Use the ``-r failure`` option to remove only failed message logs instead, or ``-r all`` to remove them all. You may want to set these up via cron to run regularly:: @@ -129,6 +130,60 @@ Unprocessed emails will be evaluated in the following delivery iterations. + Error handling + ============== + + django-mailer comes with a default error handler + ``mailer.engine.handle_delivery_exception``. + + It marks the related message as deferred for any of these exceptions: + + - ``smtplib.SMTPAuthenticationError`` + - ``smtplib.SMTPDataError`` + - ``smtplib.SMTPRecipientsRefused`` + - ``smtplib.SMTPSenderRefused`` + - ``socket.error`` + + Any other exceptions is re-raised. + That is done for backwords-compatiblity as well as for flexibility: + we would otherwise have to maintain an extensive and changing + list of exception types, which does not scale, and you get + the chance to do error handling that fits your environment like a glove. + + When the default behavior does not fit your environment, you can specify your + own custom delivery error handler through setting ``MAILER_ERROR_HANDLER``. + The value should be a string for use with Django's ``import_string``, + the default is ``"mailer.engine.handle_delivery_exception"``. + + Your handler is passed three arguments, in order: + + - ``connection`` ??? the backend connection instance that failed delivery + - ``message`` ??? the ``Message`` instance that failed delivery + - ``exc`` ??? the exception instance raised by the mailer backend + + Your handler should return a 2-tuple of: + + 1. a connection instance (or ``None`` to cause a new connection to be created) + 2. a string denoting the action taken by the handler, + either ``"sent"`` or ``"deferred"`` precisely + + For an example of a custom error handler:: + + def my_handler(connection, message, exc): + if isinstance(exc, SomeDeliveryException): + # trying to re-send this very message desparately + # (if you have good reason to) + [..] + status = 'sent' + elif isinstance(exc, SomeOtherException): + message.defer() + connection = None # i.e. ask for a new connection + status = 'deferred' + else: + six.reraise(*sys.exc_info()) + + return connection, status + Other settings ============== @@ -139,6 +194,9 @@ delete this file, and others in the same directory. With the default value of ``None`` django-mailer will use a path in current working directory. + If you need to disable the file-based locking, you can set the + ``MAILER_USE_FILE_LOCK`` setting to ``False``. + If you need to change the batch size used by django-mailer to save messages in ``mailer.backend.DbBackend``, you can set ``MAILER_MESSAGES_BATCH_SIZE`` to a value more suitable for you. This value, which defaults to `None`, will be passed to @@ -158,6 +216,22 @@ Change log ========== + 2.1 - 2020-12-05 + ---------------- + + * The ``retry_deferred`` and ``send_mail`` commands rely on the log level set + in your django project now. The ``-c/--cron`` option in those commands has + been deprecated and the logic to configure log levels and the message + format has been removed. + * Changed logging to use module specific loggers to avoid interfering + with other loggers. + * Added ``MAILER_USE_FILE_LOCK`` setting to allow disabling file based locking. + * Added ``-r`` option to ``purge_mail_log`` management command. Thanks julienc91 + * Fixed deprecation warnings on Django 3.1 + * Use cached DNS_NAME for performance + * Added ability to override the default error handler via the ``MAILER_ERROR_HANDLER`` + settings key + 2.0.1 - 2020-03-01 ------------------ @@ -291,4 +365,7 @@ Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Framework :: Django diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/mailer/__init__.py new/django-mailer-2.1/src/mailer/__init__.py --- old/django-mailer-2.0.1/src/mailer/__init__.py 2020-03-01 19:48:59.000000000 +0100 +++ new/django-mailer-2.1/src/mailer/__init__.py 2020-12-05 11:54:27.000000000 +0100 @@ -2,8 +2,9 @@ import warnings +import six -__version__ = '2.0.1' +__version__ = '2.1' def get_priority(priority): @@ -25,13 +26,17 @@ def send_mail(subject, message, from_email, recipient_list, priority=None, fail_silently=False, auth_user=None, auth_password=None): - from django.utils.encoding import force_text + if six.PY2: + # Only runs Django 1.11 + from django.utils.encoding import force_unicode as force_str + else: + from django.utils.encoding import force_str from mailer.models import make_message priority = get_priority(priority) # need to do this in case subject used lazy version of ugettext - subject = force_text(subject) - message = force_text(message) + subject = force_str(subject) + message = force_str(message) make_message(subject=subject, body=message, @@ -47,15 +52,19 @@ """ Function to queue HTML e-mails """ - from django.utils.encoding import force_text + if six.PY2: + # Only runs Django 1.11 + from django.utils.encoding import force_unicode as force_str + else: + from django.utils.encoding import force_str from django.core.mail import EmailMultiAlternatives from mailer.models import make_message priority = get_priority(priority) # need to do this in case subject used lazy version of ugettext - subject = force_text(subject) - message = force_text(message) + subject = force_str(subject) + message = force_str(message) msg = make_message(subject=subject, body=message, @@ -86,9 +95,13 @@ def mail_admins(subject, message, fail_silently=False, connection=None, priority=None): from django.conf import settings - from django.utils.encoding import force_text + if six.PY2: + # Only runs Django 1.11 + from django.utils.encoding import force_unicode as force_str + else: + from django.utils.encoding import force_str - return send_mail(settings.EMAIL_SUBJECT_PREFIX + force_text(subject), + return send_mail(settings.EMAIL_SUBJECT_PREFIX + force_str(subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS]) @@ -96,9 +109,13 @@ def mail_managers(subject, message, fail_silently=False, connection=None, priority=None): from django.conf import settings - from django.utils.encoding import force_text + if six.PY2: + # Only runs Django 1.11 + from django.utils.encoding import force_unicode as force_str + else: + from django.utils.encoding import force_str - return send_mail(settings.EMAIL_SUBJECT_PREFIX + force_text(subject), + return send_mail(settings.EMAIL_SUBJECT_PREFIX + force_str(subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/mailer/engine.py new/django-mailer-2.1/src/mailer/engine.py --- old/django-mailer-2.0.1/src/mailer/engine.py 2020-03-01 19:43:27.000000000 +0100 +++ new/django-mailer-2.1/src/mailer/engine.py 2020-12-05 11:39:47.000000000 +0100 @@ -3,16 +3,21 @@ import contextlib import logging import smtplib +import sys import time from socket import error as socket_error import lockfile +import six from django import VERSION as DJANGO_VERSION from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.mail import get_connection from django.core.mail.message import make_msgid +from django.core.mail.utils import DNS_NAME from django.db import DatabaseError, NotSupportedError, OperationalError, transaction +from django.utils.module_loading import import_string + from mailer.models import (RESULT_FAILURE, RESULT_SUCCESS, Message, MessageLog, get_message_id) if DJANGO_VERSION[0] >= 2: @@ -32,6 +37,8 @@ # in the current working directory. LOCK_PATH = getattr(settings, "MAILER_LOCK_PATH", None) +logger = logging.getLogger(__name__) + def prioritize(): """ @@ -84,7 +91,8 @@ def ensure_message_id(msg): if get_message_id(msg) is None: - msg.extra_headers['Message-ID'] = make_msgid() + # Use cached DNS_NAME for performance + msg.extra_headers['Message-ID'] = make_msgid(domain=DNS_NAME) def _limits_reached(sent, deferred): @@ -93,8 +101,8 @@ EMAIL_MAX_BATCH = getattr(settings, "MAILER_EMAIL_MAX_BATCH", None) if EMAIL_MAX_BATCH is not None and sent >= EMAIL_MAX_BATCH: - logging.info("EMAIL_MAX_BATCH (%s) reached, " - "stopping for this round", EMAIL_MAX_BATCH) + logger.info("EMAIL_MAX_BATCH (%s) reached, " + "stopping for this round", EMAIL_MAX_BATCH) return True # Stop sending emails in the current round if more than X emails get @@ -102,8 +110,8 @@ EMAIL_MAX_DEFERRED = getattr(settings, "MAILER_EMAIL_MAX_DEFERRED", None) if EMAIL_MAX_DEFERRED is not None and deferred >= EMAIL_MAX_DEFERRED: - logging.warning("EMAIL_MAX_DEFERRED (%s) reached, " - "stopping for this round", EMAIL_MAX_DEFERRED) + logger.warning("EMAIL_MAX_DEFERRED (%s) reached, " + "stopping for this round", EMAIL_MAX_DEFERRED) return True @@ -113,13 +121,33 @@ EMAIL_THROTTLE = getattr(settings, "MAILER_EMAIL_THROTTLE", 0) if EMAIL_THROTTLE: - logging.debug("Throttling email delivery. " - "Sleeping %s seconds", EMAIL_THROTTLE) + logger.debug("Throttling email delivery. " + "Sleeping %s seconds", EMAIL_THROTTLE) time.sleep(EMAIL_THROTTLE) +def handle_delivery_exception(connection, message, exc): + if isinstance(exc, (smtplib.SMTPAuthenticationError, + smtplib.SMTPDataError, + smtplib.SMTPRecipientsRefused, + smtplib.SMTPSenderRefused, + socket_error)): + message.defer() + logger.info("message deferred due to failure: %s" % exc) + MessageLog.objects.log(message, RESULT_FAILURE, log_message=str(exc)) + + connection = None # i.e. enforce creation of a new connection + status = 'deferred' + + return connection, status + + # The idea is (1) to be backwards compatible with existing behavior + # and (2) not have delivery errors go unnoticed + six.reraise(*sys.exc_info()) + + def acquire_lock(): - logging.debug("acquiring lock...") + logger.debug("acquiring lock...") if LOCK_PATH is not None: lock_file_path = LOCK_PATH else: @@ -130,19 +158,19 @@ try: lock.acquire(LOCK_WAIT_TIMEOUT) except lockfile.AlreadyLocked: - logging.debug("lock already in place. quitting.") + logger.error("lock already in place. quitting.") return False, lock except lockfile.LockTimeout: - logging.debug("waiting for the lock timed out. quitting.") + logger.error("waiting for the lock timed out. quitting.") return False, lock - logging.debug("acquired.") + logger.debug("acquired.") return True, lock def release_lock(lock): - logging.debug("releasing lock...") + logger.debug("releasing lock...") lock.release() - logging.debug("released.") + logger.debug("released.") def _require_no_backend_loop(mailer_email_backend): @@ -165,16 +193,24 @@ "django.core.mail.backends.smtp.EmailBackend" ) + # allows disabling file locking. The default is True + use_file_lock = getattr(settings, "MAILER_USE_FILE_LOCK", True) + + error_handler = import_string( + getattr(settings, 'MAILER_ERROR_HANDLER', + 'mailer.engine.handle_delivery_exception') + ) + _require_no_backend_loop(mailer_email_backend) - acquired, lock = acquire_lock() - if not acquired: - return + if use_file_lock: + acquired, lock = acquire_lock() + if not acquired: + return start_time = time.time() - deferred = 0 - sent = 0 + counts = {'deferred': 0, 'sent': 0} try: connection = None @@ -186,7 +222,7 @@ try: if connection is None: connection = get_connection(backend=mailer_email_backend) - logging.info("sending message '{0}' to {1}".format( + logger.info("sending message '{0}' to {1}".format( message.subject, ", ".join(message.to_addresses)) ) @@ -200,34 +236,28 @@ email.connection = None message.email = email # For the sake of MessageLog MessageLog.objects.log(message, RESULT_SUCCESS) - sent += 1 + counts['sent'] += 1 else: - logging.warning("message discarded due to failure in converting from DB. Added on '%s' with priority '%s'" % (message.when_added, message.priority)) # noqa + logger.warning("message discarded due to failure in converting from DB. Added on '%s' with priority '%s'" % (message.when_added, message.priority)) # noqa message.delete() - except (socket_error, smtplib.SMTPSenderRefused, - smtplib.SMTPRecipientsRefused, - smtplib.SMTPDataError, - smtplib.SMTPAuthenticationError) as err: - message.defer() - logging.info("message deferred due to failure: %s" % err) - MessageLog.objects.log(message, RESULT_FAILURE, log_message=str(err)) - deferred += 1 - # Get new connection, it case the connection itself has an error. - connection = None + except Exception as err: + connection, action_taken = error_handler(connection, message, err) + counts[action_taken] += 1 # Check if we reached the limits for the current run - if _limits_reached(sent, deferred): + if _limits_reached(counts['sent'], counts['deferred']): break _throttle_emails() finally: - release_lock(lock) + if use_file_lock: + release_lock(lock) - logging.info("") - logging.info("%s sent; %s deferred;" % (sent, deferred)) - logging.info("done in %.2f seconds" % (time.time() - start_time)) + logger.info("") + logger.info("%s sent; %s deferred;" % (counts['sent'], counts['deferred'])) + logger.info("done in %.2f seconds" % (time.time() - start_time)) def send_loop(): @@ -238,6 +268,6 @@ while True: while not Message.objects.all(): - logging.debug("sleeping for %s seconds before checking queue again" % EMPTY_QUEUE_SLEEP) + logger.debug("sleeping for %s seconds before checking queue again" % EMPTY_QUEUE_SLEEP) time.sleep(EMPTY_QUEUE_SLEEP) send_all() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/mailer/management/commands/purge_mail_log.py new/django-mailer-2.1/src/mailer/management/commands/purge_mail_log.py --- old/django-mailer-2.0.1/src/mailer/management/commands/purge_mail_log.py 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/src/mailer/management/commands/purge_mail_log.py 2020-12-05 11:39:47.000000000 +0100 @@ -1,16 +1,28 @@ import logging from django.core.management.base import BaseCommand -from mailer.models import MessageLog +from mailer.models import MessageLog, RESULT_SUCCESS, RESULT_FAILURE + +RESULT_CODES = { + 'success': [RESULT_SUCCESS], + 'failure': [RESULT_FAILURE], + 'all': [RESULT_SUCCESS, RESULT_FAILURE] +} + +logger = logging.getLogger(__name__) class Command(BaseCommand): help = "Delete mailer log" def add_arguments(self, parser): - parser.add_argument('days', nargs=1, type=int) + parser.add_argument('days', type=int) + parser.add_argument('-r', '--result', choices=RESULT_CODES.keys(), + help='Delete logs of messages with the given result code(s) ' + '(default: success)') def handle(self, *args, **options): - # Compatiblity with Django-1.6 - days = int(options.get('days', args)[0]) - count = MessageLog.objects.purge_old_entries(days) - logging.info("%s log entries deleted " % count) + days = options['days'] + result_codes = RESULT_CODES.get(options['result']) + + count = MessageLog.objects.purge_old_entries(days, result_codes) + logger.info("%s log entries deleted " % count) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/mailer/management/commands/retry_deferred.py new/django-mailer-2.1/src/mailer/management/commands/retry_deferred.py --- old/django-mailer-2.0.1/src/mailer/management/commands/retry_deferred.py 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/src/mailer/management/commands/retry_deferred.py 2020-12-05 11:39:47.000000000 +0100 @@ -1,18 +1,21 @@ import logging +import warnings from django.core.management.base import BaseCommand from mailer.models import Message from mailer.management.helpers import CronArgMixin +logger = logging.getLogger(__name__) + class Command(CronArgMixin, BaseCommand): help = "Attempt to resend any deferred mail." def handle(self, *args, **options): - if options['cron'] == 0: - logging.basicConfig(level=logging.DEBUG, format="%(message)s") - else: - logging.basicConfig(level=logging.ERROR, format="%(message)s") + if options['cron']: + warnings.warn("retry_deferred's -c/--cron option is no longer " + "necessary and will be removed in a future release", + DeprecationWarning) count = Message.objects.retry_deferred() # @@@ new_priority not yet supported - logging.info("%s message(s) retried" % count) + logger.info("%s message(s) retried" % count) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/mailer/management/commands/send_mail.py new/django-mailer-2.1/src/mailer/management/commands/send_mail.py --- old/django-mailer-2.0.1/src/mailer/management/commands/send_mail.py 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/src/mailer/management/commands/send_mail.py 2020-12-05 11:39:47.000000000 +0100 @@ -1,4 +1,5 @@ import logging +import warnings from django.conf import settings from django.core.management.base import BaseCommand @@ -10,18 +11,20 @@ # allow a sysadmin to pause the sending of mail temporarily. PAUSE_SEND = getattr(settings, "MAILER_PAUSE_SEND", False) +logger = logging.getLogger(__name__) + class Command(CronArgMixin, BaseCommand): help = "Do one pass through the mail queue, attempting to send all mail." def handle(self, *args, **options): if options['cron'] == 0: - logging.basicConfig(level=logging.DEBUG, format="%(message)s") - else: - logging.basicConfig(level=logging.ERROR, format="%(message)s") - logging.info("-" * 72) + warnings.warn("send_mail's -c/--cron option is no longer " + "necessary and will be removed in a future release", + DeprecationWarning) + logger.info("-" * 72) # if PAUSE_SEND is turned on don't do anything. if not PAUSE_SEND: send_all() else: - logging.info("sending is paused, quitting.") + logger.info("sending is paused, quitting.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/src/mailer/models.py new/django-mailer-2.1/src/mailer/models.py --- old/django-mailer-2.0.1/src/mailer/models.py 2019-09-23 19:24:14.000000000 +0200 +++ new/django-mailer-2.1/src/mailer/models.py 2020-12-05 11:39:47.000000000 +0100 @@ -5,6 +5,8 @@ import pickle import datetime +import six + try: from django.utils.encoding import python_2_unicode_compatible except ImportError: @@ -13,7 +15,10 @@ from django.utils.timezone import now as datetime_now from django.core.mail import EmailMessage from django.db import models -from django.utils.translation import ugettext_lazy as _ +if six.PY2: + from django.utils.translation import ugettext_lazy as _ +else: + from django.utils.translation import gettext_lazy as _ PRIORITY_HIGH = 1 @@ -30,6 +35,8 @@ PRIORITY_MAPPING = dict((label, v) for (v, label) in PRIORITIES) +logger = logging.getLogger(__name__) + def get_message_id(msg): # From django.core.mail.message: Email header names are case-insensitive @@ -108,6 +115,9 @@ @python_2_unicode_compatible class Message(models.Model): + """ + The email stored for later sending. + """ # The actual data - a pickled EmailMessage message_data = models.TextField() @@ -168,7 +178,7 @@ retval = [] for e in lst: if DontSendEntry.objects.has_address(e): - logging.info("skipping email to %s as on don't send list " % e.encode("utf-8")) + logger.info("skipping email to %s as on don't send list " % e.encode("utf-8")) else: retval.append(e) return retval @@ -251,9 +261,12 @@ log_message=log_message, ) - def purge_old_entries(self, days): + def purge_old_entries(self, days, result_codes=None): + if result_codes is None: + # retro-compatibility with previous versions + result_codes = [RESULT_SUCCESS] limit = datetime_now() - datetime.timedelta(days=days) - query = self.filter(when_attempted__lt=limit, result=RESULT_SUCCESS) + query = self.filter(when_attempted__lt=limit, result__in=result_codes) count = query.count() query.delete() return count @@ -261,6 +274,10 @@ @python_2_unicode_compatible class MessageLog(models.Model): + """ + A log entry which stores the result (and optionally a log message) for an + attempt to send a Message. + """ # fields from Message message_data = models.TextField() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/tests/test_mailer.py new/django-mailer-2.1/tests/test_mailer.py --- old/django-mailer-2.0.1/tests/test_mailer.py 2020-03-01 20:21:00.000000000 +0100 +++ new/django-mailer-2.1/tests/test_mailer.py 2020-12-05 11:39:47.000000000 +0100 @@ -12,7 +12,7 @@ from django.core.management import call_command from django.test import TestCase from django.utils.timezone import now as datetime_now -from mock import ANY, Mock, patch +from mock import Mock, patch import six import mailer @@ -90,20 +90,20 @@ self.assertEqual(Message.objects.count(), 0) def test_purge_old_entries(self): - # Send one successfully - with self.settings(MAILER_EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend"): - mailer.send_mail("Subject", "Body", "send...@example.com", - ["recipi...@example.com"]) - engine.send_all() - - # And one failure - with self.settings(MAILER_EMAIL_BACKEND="tests.FailingMailerEmailBackend"): - mailer.send_mail("Subject", "Body", "send...@example.com", - ["recipi...@example.com"]) - engine.send_all() - Message.objects.retry_deferred() - engine.send_all() + def send_mail(success): + backend = ("django.core.mail.backends.locmem.EmailBackend" + if success else "tests.FailingMailerEmailBackend") + with self.settings(MAILER_EMAIL_BACKEND=backend): + mailer.send_mail("Subject", "Body", "sen...@example.com", ["recipi...@example.com"]) + engine.send_all() + if not success: + Message.objects.retry_deferred() + engine.send_all() + + # 1 success, 1 failure, and purge only success + send_mail(True) + send_mail(False) with patch.object(mailer.models, 'datetime_now') as datetime_now_patch: datetime_now_patch.return_value = datetime_now() + datetime.timedelta(days=2) @@ -112,6 +112,25 @@ self.assertNotEqual(MessageLog.objects.filter(result=RESULT_FAILURE).count(), 0) self.assertEqual(MessageLog.objects.filter(result=RESULT_SUCCESS).count(), 0) + # 1 success, 1 failure, and purge only failures + send_mail(True) + + with patch.object(mailer.models, 'datetime_now') as datetime_now_patch: + datetime_now_patch.return_value = datetime_now() + datetime.timedelta(days=2) + call_command('purge_mail_log', '1', '-r', 'failure') + + self.assertEqual(MessageLog.objects.filter(result=RESULT_FAILURE).count(), 0) + self.assertNotEqual(MessageLog.objects.filter(result=RESULT_SUCCESS).count(), 0) + + # 1 success, 1 failure, and purge everything + send_mail(False) + + with patch.object(mailer.models, 'datetime_now') as datetime_now_patch: + datetime_now_patch.return_value = datetime_now() + datetime.timedelta(days=2) + call_command('purge_mail_log', '1', '-r', 'all') + + self.assertEqual(MessageLog.objects.count(), 0) + def test_send_loop(self): with self.settings(MAILER_EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend"): with patch("mailer.engine.send_all", side_effect=StopIteration) as send: @@ -263,12 +282,12 @@ self.assertEqual(Message.objects.deferred().count(), 0) with self.settings(MAILER_EMAIL_BACKEND="tests.FailingMailerEmailBackend", MAILER_EMAIL_MAX_DEFERRED=2): # noqa - # 2 will get deferred 3 remain undeferred - with patch("logging.warning") as w: + with patch('mailer.engine.logger.warning') as mock_warning: + # 2 will get deferred 3 remain undeferred engine.send_all() - w.assert_called_once() - arg = w.call_args[0][0] + mock_warning.assert_called_once() + arg = mock_warning.call_args[0][0] self.assertIn("EMAIL_MAX_DEFERRED", arg) self.assertIn("stopping for this round", arg) @@ -537,11 +556,11 @@ msg.save() - with patch("logging.warning") as w: + with patch('mailer.engine.logger.warning') as mock_warning: engine.send_all() - w.assert_called_once() - arg = w.call_args[0][0] + mock_warning.assert_called_once() + arg = mock_warning.call_args[0][0] self.assertIn("message discarded due to failure in converting from DB", arg) self.assertEqual(Message.objects.count(), 0) @@ -641,39 +660,32 @@ return call_command(command, cron=cron_value) # newer django; test parsing by passing argument as string + # --cron/c command option is deprecated return call_command(command, '--cron={}'.format(cron_value)) class CommandHelperTest(TestCase): def test_send_mail_no_cron(self): - with patch('mailer.management.commands.send_mail.logging') as logging: - call_command('send_mail') - logging.basicConfig.assert_called_with(level=logging.DEBUG, format=ANY) + call_command('send_mail') def test_send_mail_cron_0(self): - with patch('mailer.management.commands.send_mail.logging') as logging: - call_command_with_cron_arg('send_mail', 0) - logging.basicConfig.assert_called_with(level=logging.DEBUG, format=ANY) + # deprecated + call_command_with_cron_arg('send_mail', 0) def test_send_mail_cron_1(self): - with patch('mailer.management.commands.send_mail.logging') as logging: - call_command_with_cron_arg('send_mail', 1) - logging.basicConfig.assert_called_with(level=logging.ERROR, format=ANY) + # deprecated + call_command_with_cron_arg('send_mail', 1) def test_retry_deferred_no_cron(self): - with patch('mailer.management.commands.retry_deferred.logging') as logging: - call_command('retry_deferred') - logging.basicConfig.assert_called_with(level=logging.DEBUG, format=ANY) + call_command('retry_deferred') def test_retry_deferred_cron_0(self): - with patch('mailer.management.commands.retry_deferred.logging') as logging: - call_command_with_cron_arg('retry_deferred', 0) - logging.basicConfig.assert_called_with(level=logging.DEBUG, format=ANY) + # deprecated + call_command_with_cron_arg('retry_deferred', 0) def test_retry_deferred_cron_1(self): - with patch('mailer.management.commands.retry_deferred.logging') as logging: - call_command_with_cron_arg('retry_deferred', 1) - logging.basicConfig.assert_called_with(level=logging.ERROR, format=ANY) + # deprecated + call_command_with_cron_arg('retry_deferred', 1) class EmailBackendSettingLoopTest(TestCase): @@ -686,3 +698,32 @@ self.assertIn('mailer.backend.DbBackend', str(catcher.exception)) self.assertIn('EMAIL_BACKEND', str(catcher.exception)) self.assertIn('MAILER_EMAIL_BACKEND', str(catcher.exception)) + + +class UseFileLockTest(TestCase): + """Test the MAILER_USE_FILE_LOCK setting.""" + + def setUp(self): + # mocking return_value to prevent "ValueError: not enough values to unpack" + self.patcher_acquire_lock = patch("mailer.engine.acquire_lock", return_value=(True, True)) + self.patcher_release_lock = patch("mailer.engine.release_lock", return_value=(True, True)) + + self.mock_acquire_lock = self.patcher_acquire_lock.start() + self.mock_release_lock = self.patcher_release_lock.start() + + def test_mailer_use_file_lock_enabled(self): + with self.settings(MAILER_EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend"): + engine.send_all() + self.mock_acquire_lock.assert_called_once() + self.mock_release_lock.assert_called_once() + + def test_mailer_use_file_lock_disabled(self): + with self.settings(MAILER_USE_FILE_LOCK=False, + MAILER_EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend"): + engine.send_all() + self.mock_acquire_lock.assert_not_called() + self.mock_release_lock.assert_not_called() + + def tearDown(self): + self.patcher_acquire_lock.stop() + self.patcher_release_lock.stop() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-mailer-2.0.1/tox.ini new/django-mailer-2.1/tox.ini --- old/django-mailer-2.0.1/tox.ini 2020-03-01 20:06:59.000000000 +0100 +++ new/django-mailer-2.1/tox.ini 2020-12-05 11:54:27.000000000 +0100 @@ -4,8 +4,9 @@ py27-django111-test, py34-django{111,20}-test, {py35,py36}-django{111,20}-test, - py36-django{21,22,30}-test, - {py27,py34,py35,py36}-flake, + py38-django{21,22,30,31}-test, + py39-django31-test, + {py27,py38}-flake, checkmanifest, [testenv] @@ -14,6 +15,10 @@ py34: python3.4 py35: python3.5 py36: python3.6 + py37: python3.7 + py38: python3.8 + py39: python3.9 + commands = test: coverage run ./runtests.py flake: flake8 --statistics --benchmark @@ -25,12 +30,16 @@ py34: mock==3.0.5 py35: mock==3.0.5 py36: mock==4.0.1 - django111: Django==1.11.28 + py37: mock==4.0.1 + py38: mock==4.0.1 + py39: mock==4.0.1 + django111: Django==1.11.29 django20: Django==2.0.13 django21: Django==2.1.15 - django22: Django==2.2.10 - django30: Django==3.0.3 - flake: flake8==3.7.7 + django22: Django==2.2.16 + django30: Django==3.0.10 + django31: Django==3.1.2 + flake: flake8==3.8.4 py27-flake: Django<2.0 [testenv:checkmanifest]