Skip to content

Commit

Permalink
Tests for personal and work email addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Jul 11, 2024
1 parent 97f085e commit cbe2c26
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 16 deletions.
15 changes: 15 additions & 0 deletions funnel/models/email_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import hashlib
import unicodedata
import warnings
from collections.abc import MutableMapping
from datetime import datetime
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Self, cast, overload

import base58
import idna
from mxsniff import mxsniff
from pyisemail import is_email
from pyisemail.diagnosis import BaseDiagnosis
from sqlalchemy import event, inspect
Expand Down Expand Up @@ -408,6 +410,19 @@ def is_available_for(self, owner: Account | None) -> bool:
return False
return True

def is_public_provider(self, cache: MutableMapping | None = None) -> bool:
"""
Check if this email address is from a known public provider (Gmail, etc).
This performs a DNS lookup for unknown domains. Providing a cache is highly
recommended. The cache object must implement the
:class:`~collections.abc.MutableMapping` API.
"""
if not self.domain:
raise ValueError("Can't lookup a removed email address.")
lookup = mxsniff(self.domain, cache=cache)
return lookup['public']

@delivery_state.transition(None, delivery_state.SENT)
def mark_sent(self) -> None:
"""Record fact of an email message being sent to this address."""
Expand Down
39 changes: 39 additions & 0 deletions funnel/views/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,45 @@ def user_not_likely_throwaway(obj: Account) -> bool:
return obj.is_verified or bool(obj.phone)


@Account.features('has_work_email', cached_property=True)
def account_has_work_email(obj: Account) -> bool:
"""Confirm the user has a work email address associated with their account."""
if not obj.emails:
return False
# TODO: Provide cache
return any(not ae.email_address.is_public_provider() for ae in obj.emails)


@Account.features('has_personal_email', cached_property=True)
def account_has_personal_email(obj: Account) -> bool:
"""Confirm the user has a personal email address associated with their account."""
if not obj.emails:
return False
# TODO: Provide cache
return any(ae.email_address.is_public_provider() for ae in obj.emails)


@Account.features('may_need_to_add_email', cached_property=True)
def account_may_need_to_add_email(obj: Account) -> bool:
"""Check if the user missing work or personal email addresses."""
if not obj.emails:
return True
has_work_email = False
has_personal_email = False
for ae in obj.emails:
# TODO: Provide cache
is_public = ae.email_address.is_public_provider()
if is_public:
has_personal_email = True
if has_work_email:
return False
else:
has_work_email = True
if has_personal_email:
return False
return True


_quoted_str_re = re.compile('"(.*?)"')
_quoted_ua_re = re.compile(r'"(.*?)"\s*;\s*v\s*=\s*"(.*?)",?\s*')
_fake_ua_re = re.compile(
Expand Down
1 change: 1 addition & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ lazy-loader
linkify-it-py
markdown-it-py
mdit-py-plugins
mxsniff
oauth2client
passlib
phonenumbers
Expand Down
22 changes: 12 additions & 10 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SHA1:5f90855f3a570ce53e6e6a8535075ec897910b30
# SHA1:c6a3d6e9d18a2218ada8d272e4e27ac8855b5f0a
#
# This file is autogenerated by pip-compile-multi
# To update, run:
Expand Down Expand Up @@ -66,7 +66,7 @@ cachelib==0.9.0
# via flask-caching
cachetools==5.3.3
# via premailer
certifi==2024.6.2
certifi==2024.7.4
# via
# httpcore
# httpx
Expand Down Expand Up @@ -138,7 +138,7 @@ flask-executor==1.0.0
# via -r requirements/base.in
flask-flatpages==0.8.2
# via -r requirements/base.in
flask-mailman==1.1.0
flask-mailman==1.1.1
# via -r requirements/base.in
flask-migrate==4.0.7
# via
Expand Down Expand Up @@ -269,7 +269,9 @@ multidict==6.0.5
# aiohttp
# yarl
mxsniff==0.3.5
# via baseframe
# via
# -r requirements/base.in
# baseframe
mypy-extensions==1.0.0
# via typing-inspect
oauth2client==4.1.3
Expand All @@ -287,17 +289,17 @@ packaging==24.1
# marshmallow
passlib==1.7.4
# via -r requirements/base.in
phonenumbers==8.13.39
phonenumbers==8.13.40
# via -r requirements/base.in
playwright==1.44.0
playwright==1.45.0
# via -r requirements/base.in
premailer==3.10.0
# via -r requirements/base.in
progressbar2==4.4.2
# via -r requirements/base.in
psycopg[binary]==3.1.19
psycopg[binary]==3.2.1
# via -r requirements/base.in
psycopg-binary==3.1.19
psycopg-binary==3.2.1
# via psycopg
pyasn1==0.6.0
# via
Expand Down Expand Up @@ -406,7 +408,7 @@ semantic-version==2.10.0
# via
# baseframe
# coaster
sentry-sdk==2.7.1
sentry-sdk==2.9.0
# via baseframe
six==1.16.0
# via
Expand Down Expand Up @@ -449,7 +451,7 @@ tuspy==1.0.3
# via pyvimeo
tweepy==4.14.0
# via -r requirements/base.in
twilio==9.2.2
twilio==9.2.3
# via -r requirements/base.in
types-python-dateutil==2.9.0.20240316
# via arrow
Expand Down
8 changes: 4 additions & 4 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ gherkin-official==24.0.0
# via reformat-gherkin
icdiff==2.0.7
# via pytest-icdiff
identify==2.5.36
identify==2.6.0
# via pre-commit
isort==5.13.2
# via pylint
Expand Down Expand Up @@ -80,7 +80,7 @@ pyupgrade==3.16.0
# via -r requirements/dev.in
reformat-gherkin==3.0.1
# via -r requirements/dev.in
ruff==0.5.0
ruff==0.5.1
# via -r requirements/dev.in
tokenize-rt==5.2.0
# via pyupgrade
Expand Down Expand Up @@ -118,7 +118,7 @@ types-pillow==10.2.0.20240520
# via -r requirements/dev.in
types-pyopenssl==24.1.0.20240425
# via types-redis
types-pytest-lazy-fixture==0.6.3.20240310
types-pytest-lazy-fixture==0.6.3.20240707
# via -r requirements/dev.in
types-pytz==2024.1.0.20240417
# via -r requirements/dev.in
Expand All @@ -128,7 +128,7 @@ types-redis==4.6.0.20240425
# via -r requirements/dev.in
types-requests==2.32.0.20240622
# via -r requirements/dev.in
types-setuptools==70.1.0.20240627
types-setuptools==70.3.0.20240710
# via types-cffi
types-zxcvbn==4.4.1.20240106
# via -r requirements/dev.in
Expand Down
4 changes: 2 additions & 2 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pytest-dotenv==0.5.2
# via -r requirements/test.in
pytest-env==1.1.3
# via -r requirements/test.in
pytest-playwright==0.5.0
pytest-playwright==0.5.1
# via -r requirements/test.in
pytest-pretty==1.2.0
# via -r requirements/test.in
Expand All @@ -78,7 +78,7 @@ sttable==0.0.1
# via -r requirements/test.in
text-unidecode==1.3
# via python-slugify
tomlkit==0.12.5
tomlkit==0.13.0
# via -r requirements/test.in
typeguard==4.3.0
# via -r requirements/test.in
Expand Down

0 comments on commit cbe2c26

Please sign in to comment.