mirror of
https://github.com/coddingtonbear/django-mailbox.git
synced 2026-07-09 14:32:34 +02:00
Support for Django 5.0 and python 3.12 + Fix CI (#277)
* Upgrade to last Django & Python supported versions * flake8 fixes * Add CI * Add final version of Django 5.0
This commit is contained in:
parent
462fdd3e49
commit
7fde5f7165
16 changed files with 119 additions and 89 deletions
40
.github/workflows/main.yml
vendored
Normal file
40
.github/workflows/main.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- '3.8'
|
||||
- '3.9'
|
||||
- '3.10'
|
||||
- '3.11'
|
||||
- '3.12'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
python -m pip install --upgrade 'tox>=4.0.0rc3'
|
||||
|
||||
- name: Run tox targets for ${{ matrix.python-version }}
|
||||
run: tox run -f py$(echo ${{ matrix.python-version }} | tr -d .)
|
||||
|
|
@ -1,6 +1,15 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
Unreleased
|
||||
-----
|
||||
|
||||
* Add Django 3.2, 4.0, 4.1, 4.2, 5.0 support
|
||||
* Remove support for deprecated Django versions
|
||||
|
||||
* Add Python 3.8, 3.9, 3.10, 3.11, 3.12 support
|
||||
* Remove support for deprecated Python versions
|
||||
|
||||
4.8.1
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,5 @@ from django.utils.translation import gettext_lazy as _
|
|||
class MailBoxConfig(AppConfig):
|
||||
name = 'django_mailbox'
|
||||
verbose_name = _("Mail Box")
|
||||
|
||||
default_auto_field = "django.db.models.AutoField"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import email
|
|||
import logging
|
||||
import mimetypes
|
||||
import os.path
|
||||
import sys
|
||||
import uuid
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
|
|
@ -42,7 +41,7 @@ class MailboxQuerySet(models.QuerySet):
|
|||
for mailbox in self.all():
|
||||
logger.debug('Receiving mail for %s' % mailbox)
|
||||
count += sum(1 for i in mailbox.get_new_mail())
|
||||
logger.debug('Received %d %s.', count, 'mails' if count != 1 else 'mail')
|
||||
logger.debug('Received %d %s.', count, 'mail(s)')
|
||||
|
||||
|
||||
class MailboxManager(models.Manager):
|
||||
|
|
@ -123,6 +122,13 @@ class Mailbox(models.Model):
|
|||
objects = MailboxManager()
|
||||
active_mailboxes = ActiveMailboxManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Mailbox')
|
||||
verbose_name_plural = _('Mailboxes')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def _protocol_info(self):
|
||||
return urlparse(self.uri)
|
||||
|
|
@ -448,13 +454,12 @@ class Mailbox(models.Model):
|
|||
|
||||
def get_new_mail(self, condition=None):
|
||||
"""Connect to this transport and fetch new messages."""
|
||||
new_mail = []
|
||||
connection = self.get_connection()
|
||||
if not connection:
|
||||
return
|
||||
for message in connection.get_message(condition):
|
||||
msg = self.process_incoming_message(message)
|
||||
if not msg is None:
|
||||
if msg is not None:
|
||||
yield msg
|
||||
self.last_polling = now()
|
||||
if django.VERSION >= (1, 5): # Django 1.5 introduces update_fields
|
||||
|
|
@ -483,14 +488,6 @@ class Mailbox(models.Model):
|
|||
)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Mailbox')
|
||||
verbose_name_plural = _('Mailboxes')
|
||||
|
||||
|
||||
class IncomingMessageManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(
|
||||
|
|
@ -757,7 +754,7 @@ class Message(models.Model):
|
|||
"""Returns an `email.message.EmailMessage` instance representing the
|
||||
contents of this message and all attachments.
|
||||
|
||||
See [email.message.EmailMessage]_ for more information as to what methods
|
||||
See [email.message.EmailMessage]_ for more information like what methods
|
||||
and properties are available on `email.message.EmailMessage` instances.
|
||||
|
||||
.. note::
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
from django.dispatch.dispatcher import Signal
|
||||
|
||||
message_received = Signal(providing_args=['message'])
|
||||
message_received = Signal() # providing_args=['message']
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import gzip
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import copy
|
||||
from unittest import mock
|
||||
|
|
@ -9,7 +8,7 @@ from django_mailbox.models import Mailbox, Message
|
|||
from django_mailbox.utils import convert_header_to_unicode
|
||||
from django_mailbox import utils
|
||||
from django_mailbox.tests.base import EmailMessageTestCase
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
__all__ = ['TestProcessEmail']
|
||||
|
|
@ -367,12 +366,12 @@ class TestProcessEmail(EmailMessageTestCase):
|
|||
email_object = self._get_email_object(
|
||||
'message_with_long_content.eml',
|
||||
)
|
||||
size = len(force_text(email_object.as_string()))
|
||||
size = len(force_str(email_object.as_string()))
|
||||
|
||||
msg = self.mailbox.process_incoming_message(email_object)
|
||||
|
||||
self.assertEqual(size,
|
||||
len(force_text(msg.get_email_object().as_string())))
|
||||
len(force_str(msg.get_email_object().as_string())))
|
||||
|
||||
def test_message_saved(self):
|
||||
message = self._get_email_object('generic_message.eml')
|
||||
|
|
@ -386,7 +385,7 @@ class TestProcessEmail(EmailMessageTestCase):
|
|||
|
||||
msg = self.mailbox.process_incoming_message(message)
|
||||
|
||||
self.assertNotEquals(msg.eml, None)
|
||||
self.assertNotEqual(msg.eml, None)
|
||||
|
||||
self.assertTrue(msg.eml.name.endswith('.eml'))
|
||||
|
||||
|
|
@ -408,7 +407,7 @@ class TestProcessEmail(EmailMessageTestCase):
|
|||
|
||||
msg = self.mailbox.process_incoming_message(message)
|
||||
|
||||
self.assertEquals(msg.eml, None)
|
||||
self.assertEqual(msg.eml, None)
|
||||
|
||||
def test_message_compressed(self):
|
||||
message = self._get_email_object('generic_message.eml')
|
||||
|
|
|
|||
|
|
@ -1,34 +1,25 @@
|
|||
from distutils.version import LooseVersion
|
||||
from unittest import mock
|
||||
|
||||
from django.core.management import call_command, CommandError
|
||||
from django.test import TestCase
|
||||
import django
|
||||
|
||||
|
||||
class CommandsTestCase(TestCase):
|
||||
def test_processincomingmessage_no_args(self):
|
||||
"""Check that processincomingmessage works with no args"""
|
||||
|
||||
|
||||
mailbox_name = None
|
||||
# Mock handle so that the test doesn't hang waiting for input. Note that we are only testing
|
||||
# the argument parsing here -- functionality should be tested elsewhere
|
||||
with mock.patch('django_mailbox.management.commands.processincomingmessage.Command.handle') as handle:
|
||||
# Don't care about the return value
|
||||
handle.return_value = None
|
||||
|
||||
|
||||
call_command('processincomingmessage')
|
||||
args, kwargs = handle.call_args
|
||||
|
||||
# Make sure that we called with the right arguments
|
||||
try:
|
||||
self.assertEqual(kwargs['mailbox_name'], mailbox_name)
|
||||
except KeyError:
|
||||
# Handle Django 1.7
|
||||
# It uses optparse instead of argparse, so instead of being
|
||||
# set to None, mailbox_name is simply left out altogether
|
||||
# Thus we expect an empty tuple here
|
||||
self.assertEqual(args, tuple())
|
||||
|
||||
self.assertEqual(kwargs['mailbox_name'], mailbox_name)
|
||||
|
||||
def test_processincomingmessage_with_arg(self):
|
||||
"""Check that processincomingmessage works with mailbox_name given"""
|
||||
|
|
@ -37,29 +28,14 @@ class CommandsTestCase(TestCase):
|
|||
|
||||
with mock.patch('django_mailbox.management.commands.processincomingmessage.Command.handle') as handle:
|
||||
handle.return_value = None
|
||||
|
||||
|
||||
call_command('processincomingmessage', mailbox_name)
|
||||
args, kwargs = handle.call_args
|
||||
try:
|
||||
self.assertEqual(kwargs['mailbox_name'], mailbox_name)
|
||||
except (AssertionError, KeyError):
|
||||
# Handle Django 1.7
|
||||
# It uses optparse instead of argparse, so instead of being
|
||||
# in kwargs, mailbox_name is in args
|
||||
self.assertEqual(args[0], mailbox_name)
|
||||
|
||||
self.assertEqual(kwargs['mailbox_name'], mailbox_name)
|
||||
|
||||
def test_processincomingmessage_too_many_args(self):
|
||||
"""Check that processincomingmessage raises an error if too many args"""
|
||||
# Only perform this test for Django versions greater than 1.7.*. This
|
||||
# is because, with optparse, too many arguments doesn't result in an
|
||||
# error, which means this test is worthless anyway
|
||||
# For the "compatibility" versions, unexpected arguments aren't handled
|
||||
# very well, and result in a TypeError
|
||||
if (LooseVersion(django.get_version()) >= LooseVersion('1.8') and
|
||||
LooseVersion(django.get_version()) < LooseVersion('1.10')):
|
||||
with self.assertRaises(TypeError):
|
||||
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg')
|
||||
# In 1.10 and later a proper CommandError should be raised
|
||||
elif LooseVersion(django.get_version()) >= LooseVersion('1.10'):
|
||||
with self.assertRaises(CommandError):
|
||||
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg')
|
||||
|
||||
with self.assertRaises(CommandError):
|
||||
call_command('processincomingmessage', 'foo_mailbox', 'invalid_arg')
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from django_mailbox.transports import ImapTransport, Pop3Transport
|
|||
FAKE_UID_SEARCH_ANSWER = (
|
||||
'OK',
|
||||
[
|
||||
b'18 19 20 21 22 23 24 25 26 27 28 29 ' +
|
||||
b'18 19 20 21 22 23 24 25 26 27 28 29 ' +
|
||||
b'30 31 32 33 34 35 36 37 38 39 40 41 42 43 44'
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,3 @@ class EmailTransport:
|
|||
message = email.message_from_bytes(contents)
|
||||
|
||||
return message
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import sys
|
||||
|
||||
|
||||
from .base import EmailTransport
|
||||
|
||||
|
|
|
|||
|
|
@ -63,4 +63,3 @@ class Office365Transport(EmailTransport):
|
|||
|
||||
o365message.delete()
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ sys.path.insert(0, os.path.abspath('..'))
|
|||
import inspect
|
||||
import django
|
||||
from django.utils.html import strip_tags
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
from django.conf import settings
|
||||
settings.configure(INSTALLED_APPS=['django_mailbox', ])
|
||||
django.setup()
|
||||
|
|
@ -265,11 +265,11 @@ def process_docstring(app, what, name, obj, options, lines):
|
|||
continue
|
||||
|
||||
# Decode and strip any html out of the field's help text
|
||||
help_text = strip_tags(force_text(field.help_text))
|
||||
help_text = strip_tags(force_str(field.help_text))
|
||||
|
||||
# Decode and capitalize the verbose name, for use if there isn't
|
||||
# any help text
|
||||
verbose_name = force_text(field.verbose_name).capitalize()
|
||||
verbose_name = force_str(field.verbose_name).capitalize()
|
||||
|
||||
if help_text:
|
||||
# Add the model field to the end of the docstring as a param
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
django>=3.1.1,<3.2
|
||||
django>=3.2,<4.0
|
||||
six
|
||||
|
|
|
|||
23
setup.py
23
setup.py
|
|
@ -32,25 +32,25 @@ setup(
|
|||
'gmail-oauth2': gmail_oauth2_require,
|
||||
'office365-oauth2': office365_oauth2_require
|
||||
},
|
||||
python_requires=">=3",
|
||||
python_requires=">=3.8",
|
||||
classifiers=[
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.0',
|
||||
'Framework :: Django :: 2.1',
|
||||
'Framework :: Django :: 2.2',
|
||||
'Framework :: Django :: 3.0',
|
||||
'Framework :: Django :: 3.2',
|
||||
'Framework :: Django :: 4.0',
|
||||
'Framework :: Django :: 4.1',
|
||||
'Framework :: Django :: 4.2',
|
||||
'Framework :: Django :: 5.0',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'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',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Topic :: Communications :: Email',
|
||||
'Topic :: Communications :: Email :: Post-Office',
|
||||
'Topic :: Communications :: Email :: Post-Office :: IMAP',
|
||||
|
|
@ -59,7 +59,4 @@ setup(
|
|||
],
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'six>=1.6.1'
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
pytest==2.9.1
|
||||
pytest-django==2.9.1
|
||||
mock==2.0.0
|
||||
pytest
|
||||
pytest-django
|
||||
mock
|
||||
|
|
|
|||
34
tox.ini
34
tox.ini
|
|
@ -2,20 +2,32 @@
|
|||
# sort by django version, next by python version
|
||||
envlist=
|
||||
flake8
|
||||
py{35,36}-django111
|
||||
py{35,36}-django20
|
||||
py{35,36,37}-django21
|
||||
py{35,36,37}-django22
|
||||
py{36,37,38}-django30
|
||||
py{310,311,312}-django50
|
||||
py{38,39,310,311}-django42
|
||||
py{38,39,310,311}-django41
|
||||
py{38,39,310}-django40
|
||||
py{38,39,310}-django32
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
3.10: py310
|
||||
3.11: py311
|
||||
3.12: py312
|
||||
|
||||
[testenv]
|
||||
passenv=EMAIL_IMAP_SERVER EMAIL_ACCOUNT EMAIL_PASSWORD EMAIL_SMTP_SERVER
|
||||
passenv=
|
||||
EMAIL_IMAP_SERVER
|
||||
EMAIL_ACCOUNT
|
||||
EMAIL_PASSWORD
|
||||
EMAIL_SMTP_SERVER
|
||||
deps=
|
||||
django111: django==1.11.*
|
||||
django20: django==2.0.*
|
||||
django21: django==2.0.*
|
||||
django22: django==2.0.*
|
||||
django30: django==3.0b1
|
||||
django50: Django==5.0,<5.1
|
||||
django42: Django>=4.2,<5.0
|
||||
django41: Django>=4.1,<4.2
|
||||
django40: Django>=4.0,<4.1
|
||||
django32: Django>=3.2,<4.0
|
||||
-r{toxinidir}/test_requirements.txt
|
||||
sitepackages=False
|
||||
commands=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue