Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions email_profile/core/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import os
from dataclasses import dataclass
from typing import Optional

from email_profile.providers import resolve_imap_host

Expand All @@ -29,15 +30,25 @@ class EmailFactories:
"""Build :class:`Credentials` without the user spelling out a hostname."""

@classmethod
def from_address(cls, address: str, password: str) -> Credentials:
"""Auto-discover the IMAP host from the email address."""
def from_address(
cls,
address: str,
password: str,
*,
port: Optional[int] = None,
ssl: Optional[bool] = None,
) -> Credentials:
"""Auto-discover the IMAP host from the email address.

``port`` and ``ssl`` override the discovered values when given.
"""
host = resolve_imap_host(address)
return Credentials(
server=host.host,
user=address,
password=password,
port=host.port,
ssl=host.ssl,
port=host.port if port is None else port,
ssl=host.ssl if ssl is None else ssl,
)

@classmethod
Expand All @@ -47,6 +58,9 @@ def from_env(
user_var: str = "EMAIL_USERNAME",
password_var: str = "EMAIL_PASSWORD",
load_dotenv: bool = True,
*,
port: Optional[int] = None,
ssl: Optional[bool] = None,
) -> Credentials:
"""Read credentials from env vars (or `.env`)."""
if load_dotenv:
Expand All @@ -66,6 +80,12 @@ def from_env(

server = os.environ.get(server_var)
if server:
return Credentials(server=server, user=user, password=password)
return Credentials(
server=server,
user=user,
password=password,
port=993 if port is None else port,
ssl=True if ssl is None else ssl,
)

return cls.from_address(user, password)
return cls.from_address(user, password, port=port, ssl=ssl)
24 changes: 16 additions & 8 deletions email_profile/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def __init__(
server: Optional[str] = None,
user: Optional[str] = None,
password: Optional[str] = None,
port: int = 993,
ssl: bool = True,
port: Optional[int] = None,
ssl: Optional[bool] = None,
storage: Optional[StorageABC] = None,
) -> None:
connection = self._resolve(server, user, password, port, ssl)
Expand Down Expand Up @@ -77,17 +77,21 @@ def _resolve(
server: Optional[str],
user: Optional[str],
password: Optional[str],
port: int,
ssl: bool,
port: Optional[int],
ssl: Optional[bool],
) -> Credentials:
if server is None and user is None and password is None:
return EmailFactories.from_env()
return EmailFactories.from_env(port=port, ssl=ssl)

if password is None and user is not None and server and "@" in server:
return EmailFactories.from_address(server, user)
return EmailFactories.from_address(
server, user, port=port, ssl=ssl
)

if server is None and user is not None and "@" in user and password:
return EmailFactories.from_address(user, password)
return EmailFactories.from_address(
user, password, port=port, ssl=ssl
)

if server is None or user is None or password is None:
raise TypeError(
Expand All @@ -97,7 +101,11 @@ def _resolve(
)

return Credentials(
server=server, user=user, password=password, port=port, ssl=ssl
server=server,
user=user,
password=password,
port=993 if port is None else port,
ssl=True if ssl is None else ssl,
)

@property
Expand Down
25 changes: 25 additions & 0 deletions tests/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,31 @@ def test_is_connected_true_after_connect(self):
self.assertFalse(app.is_connected)


class TestAutoDiscoveryOverrides(TestCase):
def test_kwargs_override_discovered_port_and_ssl(self):
from email_profile.core.types import IMAPHost

with patch(
"email_profile.core.credentials.resolve_imap_host",
return_value=IMAPHost("imap.gmail.com", port=993, ssl=True),
):
app = Email("u@gmail.com", "pw", port=2143, ssl=False)
self.assertEqual(app.port, 2143)
self.assertFalse(app.ssl)
self.assertEqual(app.server, "imap.gmail.com")

def test_no_override_keeps_discovered_values(self):
from email_profile.core.types import IMAPHost

with patch(
"email_profile.core.credentials.resolve_imap_host",
return_value=IMAPHost("imap.gmail.com", port=993, ssl=True),
):
app = Email("u@gmail.com", "pw")
self.assertEqual(app.port, 993)
self.assertTrue(app.ssl)


class TestConstructorOverloads(TestCase):
def test_three_positional_args_explicit(self):
app = Email("imap.x.com", "u", "pw")
Expand Down
Loading