diff --git a/.gitignore b/.gitignore index 5f938fd..a6873d9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ out/ build/ dist/ pip-wheel-metadata/ -.pytest_cache/ \ No newline at end of file +.pytest_cache/ +/.envrc +/.direnv diff --git a/django-stubs/core/mail/backends/console.pyi b/django-stubs/core/mail/backends/console.pyi new file mode 100644 index 0000000..80ba261 --- /dev/null +++ b/django-stubs/core/mail/backends/console.pyi @@ -0,0 +1,3 @@ +from django.core.mail.backends.base import BaseEmailBackend + +class EmailBackend(BaseEmailBackend): ... diff --git a/django-stubs/core/mail/backends/dummy.pyi b/django-stubs/core/mail/backends/dummy.pyi new file mode 100644 index 0000000..80ba261 --- /dev/null +++ b/django-stubs/core/mail/backends/dummy.pyi @@ -0,0 +1,3 @@ +from django.core.mail.backends.base import BaseEmailBackend + +class EmailBackend(BaseEmailBackend): ... diff --git a/django-stubs/core/mail/backends/filebased.pyi b/django-stubs/core/mail/backends/filebased.pyi new file mode 100644 index 0000000..80ba261 --- /dev/null +++ b/django-stubs/core/mail/backends/filebased.pyi @@ -0,0 +1,3 @@ +from django.core.mail.backends.base import BaseEmailBackend + +class EmailBackend(BaseEmailBackend): ... diff --git a/django-stubs/core/mail/backends/locmem.pyi b/django-stubs/core/mail/backends/locmem.pyi new file mode 100644 index 0000000..80ba261 --- /dev/null +++ b/django-stubs/core/mail/backends/locmem.pyi @@ -0,0 +1,3 @@ +from django.core.mail.backends.base import BaseEmailBackend + +class EmailBackend(BaseEmailBackend): ... diff --git a/django-stubs/core/mail/backends/smtp.pyi b/django-stubs/core/mail/backends/smtp.pyi index 80ba261..cce1770 100644 --- a/django-stubs/core/mail/backends/smtp.pyi +++ b/django-stubs/core/mail/backends/smtp.pyi @@ -1,3 +1,18 @@ +import smtplib +import threading +from typing import Optional, Union + from django.core.mail.backends.base import BaseEmailBackend -class EmailBackend(BaseEmailBackend): ... +class EmailBackend(BaseEmailBackend): + host: str = ... + port: int = ... + username: str = ... + password: str = ... + use_tls: bool = ... + use_ssl: bool = ... + timeout: Optional[int] = ... + ssl_keyfile: Optional[str] = ... + ssl_certfile: Optional[str] = ... + connection: Union[smtplib.SMTP_SSL, smtplib.SMTP, None] = ... + _lock: threading.RLock = ... diff --git a/django-stubs/core/mail/message.pyi b/django-stubs/core/mail/message.pyi index 21bec05..ef98c9c 100644 --- a/django-stubs/core/mail/message.pyi +++ b/django-stubs/core/mail/message.pyi @@ -1,8 +1,10 @@ from email._policybase import Policy # type: ignore +from email.message import Message +from email.mime.base import MIMEBase from email.mime.message import MIMEMessage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, overload utf8_charset: Any utf8_charset_qp: Any @@ -43,6 +45,11 @@ class SafeMIMEMultipart(MIMEMixin, MIMEMultipart): self, _subtype: str = ..., boundary: None = ..., _subparts: None = ..., encoding: str = ..., **_params: Any ) -> None: ... +_AttachmentContent = Union[bytes, EmailMessage, Message, SafeMIMEText, str] +_AttachmentTuple = Union[ + Tuple[str, _AttachmentContent], Tuple[Optional[str], _AttachmentContent, str], Tuple[str, _AttachmentContent, None] +] + class EmailMessage: content_subtype: str = ... mixed_subtype: str = ... @@ -62,42 +69,42 @@ class EmailMessage: subject: str = ..., body: Optional[str] = ..., from_email: Optional[str] = ..., - to: Optional[Union[Sequence[str], str]] = ..., - bcc: Optional[Union[Sequence[str], str]] = ..., + to: Optional[Sequence[str]] = ..., + bcc: Optional[Sequence[str]] = ..., connection: Optional[Any] = ..., - attachments: Optional[Union[List[Tuple[str, Union[str, bytes], str]], List[MIMEText]]] = ..., + attachments: Optional[Sequence[Union[MIMEBase, _AttachmentTuple]]] = ..., headers: Optional[Dict[str, str]] = ..., - cc: Optional[Union[Sequence[str], str]] = ..., - reply_to: Optional[Union[List[Optional[str]], str]] = ..., + cc: Optional[Sequence[str]] = ..., + reply_to: Optional[Sequence[str]] = ..., ) -> None: ... def get_connection(self, fail_silently: bool = ...) -> Any: ... # TODO: when typeshed gets more types for email.Message, move it to MIMEMessage, now it has too many false-positives def message(self) -> Any: ... def recipients(self) -> List[str]: ... def send(self, fail_silently: bool = ...) -> int: ... - def attach( - self, - filename: Optional[Union[MIMEText, str]] = ..., - content: Optional[Union[bytes, EmailMessage, SafeMIMEText, str]] = ..., - mimetype: Optional[str] = ..., - ) -> None: ... + @overload + def attach(self, filename: MIMEText = ...) -> None: ... + @overload + def attach(self, filename: None = ..., content: _AttachmentContent = ..., mimetype: str = ...) -> None: ... + @overload + def attach(self, filename: str = ..., content: _AttachmentContent = ..., mimetype: Optional[str] = ...) -> None: ... def attach_file(self, path: str, mimetype: Optional[str] = ...) -> None: ... class EmailMultiAlternatives(EmailMessage): alternative_subtype: str = ... - alternatives: Any = ... + alternatives: Sequence[Tuple[_AttachmentContent, str]] = ... def __init__( self, subject: str = ..., body: str = ..., from_email: Optional[str] = ..., - to: Optional[List[str]] = ..., - bcc: Optional[List[str]] = ..., + to: Optional[Sequence[str]] = ..., + bcc: Optional[Sequence[str]] = ..., connection: Optional[Any] = ..., - attachments: None = ..., + attachments: Optional[Sequence[Union[MIMEBase, _AttachmentTuple]]] = ..., headers: Optional[Dict[str, str]] = ..., - alternatives: Optional[List[Tuple[str, str]]] = ..., - cc: None = ..., - reply_to: None = ..., + alternatives: Optional[Sequence[Tuple[_AttachmentContent, str]]] = ..., + cc: Optional[Sequence[str]] = ..., + reply_to: Optional[Sequence[str]] = ..., ) -> None: ... - def attach_alternative(self, content: str, mimetype: str) -> None: ... + def attach_alternative(self, content: _AttachmentContent, mimetype: str) -> None: ... diff --git a/scripts/enabled_test_modules.py b/scripts/enabled_test_modules.py index af06cd4..f7c7477 100644 --- a/scripts/enabled_test_modules.py +++ b/scripts/enabled_test_modules.py @@ -5,7 +5,7 @@ import re IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters', 'migrations', 'sitemaps_tests', 'staticfiles_tests', 'modeladmin', 'model_forms', 'generic_views', 'forms_tests', 'flatpages_tests', 'admin_utils', - 'admin_ordering', 'admin_changelist', 'admin_views', 'mail', 'redirects_tests', + 'admin_ordering', 'admin_changelist', 'admin_views', 'redirects_tests', 'invalid_models_tests', 'i18n', 'migrate_signals', 'model_formsets', 'template_tests', 'template_backends', 'test_runner', 'admin_scripts', 'sites_tests', 'inline_formsets', 'foreign_object', 'cache', 'test_client', 'test_client_regress'} @@ -227,6 +227,17 @@ IGNORED_ERRORS = { "Cannot resolve keyword 'porcupine' into field", 'Argument 1 to "set" of "RelatedManager" has incompatible type "int"', ], + 'mail': [ + 'List item 1 has incompatible type "None"; expected "str"', + 'Incompatible return value type (got "None", expected "bool")', + 'Argument 1 to "push" of "SMTPChannel" has incompatible type "str"; expected "bytes"', + 'Value of type "Union[List[Message], str, bytes, None]" is not indexable', + 'Incompatible types in assignment ' + + '(expression has type "bool", variable has type "Union[SMTP_SSL, SMTP, None]")', + re.compile( + r'Item "(int|str)" of "Union\[Message, str, int, Any\]" has no attribute "(get_content_type|get_filename)"' + ) + ], 'messages_tests': [ 'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"', 'Too many arguments',