Skip to content

Commit

Permalink
Split PhoneNumberMixin into OptionalPhoneNumberMixin
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Dec 14, 2023
1 parent d834649 commit 36e96d6
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 23 deletions.
1 change: 0 additions & 1 deletion funnel/models/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -1916,7 +1916,6 @@ class AccountPhone(PhoneNumberMixin, BaseMixin, Model):
"""A phone number linked to an account."""

__tablename__ = 'account_phone'
__phone_optional__ = False
__phone_unique__ = True
__phone_is_exclusive__ = True
__phone_for__ = 'account'
Expand Down
10 changes: 5 additions & 5 deletions funnel/models/email_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class EmailAddress(BaseMixin, Model):
#: Contains the name of the relationship in the :class:`EmailAddress` model
__backrefs__: ClassVar[set[str]] = set()
#: These backrefs claim exclusive use of the email address for their linked owner.
#: See :class:`EmailAddressMixin` for implementation detail
#: See :class:`OptionalEmailAddressMixin` for implementation detail
__exclusive_backrefs__: ClassVar[set[str]] = set()

#: The email address, centrepiece of this model. Case preserving.
Expand Down Expand Up @@ -894,7 +894,7 @@ def _send_refcount_event_remove(


def _send_refcount_event_before_delete(
_mapper: Any, _connection: Any, target: EmailAddressMixin
_mapper: Any, _connection: Any, target: OptionalEmailAddressMixin
) -> None:
if target.email_address:
emailaddress_refcount_dropping.send(target.email_address)
Expand All @@ -908,7 +908,7 @@ def _setup_refcount_events() -> None:


def _email_address_mixin_set_validator(
target: EmailAddressMixin,
target: OptionalEmailAddressMixin,
value: EmailAddress | None,
old_value: EmailAddress | None,
_initiator: Any,
Expand All @@ -921,9 +921,9 @@ def _email_address_mixin_set_validator(
raise EmailAddressInUseError("This email address it not available")


@event.listens_for(EmailAddressMixin, 'mapper_configured', propagate=True)
@event.listens_for(OptionalEmailAddressMixin, 'mapper_configured', propagate=True)
def _email_address_mixin_configure_events(
_mapper: Any, cls: type[EmailAddressMixin]
_mapper: Any, cls: type[OptionalEmailAddressMixin]
) -> None:
event.listen(cls.email_address, 'set', _email_address_mixin_set_validator)
event.listen(cls, 'before_delete', _send_refcount_event_before_delete)
Expand Down
1 change: 0 additions & 1 deletion funnel/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ class SmsMessage(PhoneNumberMixin, BaseMixin, Model):
"""An outbound SMS message."""

__tablename__ = 'sms_message'
__phone_optional__ = False
__phone_unique__ = False
__phone_is_exclusive__ = False
phone_number_reference_is_active: bool = False
Expand Down
58 changes: 45 additions & 13 deletions funnel/models/phone_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
'canonical_phone_number',
'phone_blake2b160_hash',
'PhoneNumber',
'OptionalPhoneNumberMixin',
'PhoneNumberMixin',
]

Expand Down Expand Up @@ -256,7 +257,7 @@ class PhoneNumber(BaseMixin, Model):
#: Contains the name of the relationship in the :class:`PhoneNumber` model
__backrefs__: ClassVar[set[str]] = set()
#: These backrefs claim exclusive use of the phone number for their linked owner.
#: See :class:`PhoneNumberMixin` for implementation detail
#: See :class:`OptionalPhoneNumberMixin` for implementation detail
__exclusive_backrefs__: ClassVar[set[str]] = set()

#: The phone number, centrepiece of this model. Stored normalized in E164 format.
Expand Down Expand Up @@ -733,7 +734,7 @@ def get_numbers(cls, prefix: str, remove: bool = True) -> set[str]:


@declarative_mixin
class PhoneNumberMixin:
class OptionalPhoneNumberMixin:
"""
Mixin class for models that refer to :class:`PhoneNumber`.
Expand All @@ -757,7 +758,7 @@ class PhoneNumberMixin:

@declared_attr
@classmethod
def phone_number_id(cls) -> Mapped[int]:
def phone_number_id(cls) -> Mapped[int | None]:
"""Foreign key to phone_number table."""
return sa_orm.mapped_column(
sa.Integer,
Expand All @@ -769,7 +770,7 @@ def phone_number_id(cls) -> Mapped[int]:

@declared_attr
@classmethod
def phone_number(cls) -> Mapped[PhoneNumber]:
def phone_number(cls) -> Mapped[PhoneNumber | None]:
"""Instance of :class:`PhoneNumber` as a relationship."""
backref_name = 'used_in_' + cls.__tablename__
PhoneNumber.__backrefs__.add(backref_name)
Expand All @@ -795,17 +796,17 @@ def phone(self) -> str | None:
return None

@phone.setter
def phone(self, value: str | None) -> None:
def phone(self, __value: str | None) -> None:
if self.__phone_for__:
if value is not None:
if __value is not None:
self.phone_number = PhoneNumber.add_for(
getattr(self, self.__phone_for__), value
getattr(self, self.__phone_for__), __value
)
else:
self.phone_number = None
else:
if value is not None:
self.phone_number = PhoneNumber.add(value)
if __value is not None:
self.phone_number = PhoneNumber.add(__value)
else:
self.phone_number = None

Expand All @@ -829,6 +830,37 @@ def transport_hash(self) -> str | None:
)


@declarative_mixin
class PhoneNumberMixin(OptionalPhoneNumberMixin):
"""Non-optional version of :class:`OptionalPhoneNumberMixin`."""

__phone_optional__: ClassVar[bool] = False

if TYPE_CHECKING:

@declared_attr
@classmethod
def phone_number_id(cls) -> Mapped[int]: # type: ignore[override]
...

@declared_attr
@classmethod
def phone_number(cls) -> Mapped[PhoneNumber]: # type: ignore[override]
...

@property # type: ignore[override]
def phone(self) -> str:
...

@phone.setter
def phone(self, __value: str) -> None:
...

@property
def transport_hash(self) -> str:
...


def _clear_cached_properties(target: PhoneNumber) -> None:
"""Clear cached properties in :class:`PhoneNumber`."""
for attr in ('parsed', 'formatted'):
Expand Down Expand Up @@ -885,7 +917,7 @@ def _send_refcount_event_remove(


def _send_refcount_event_before_delete(
_mapper: Any, _connection: Any, target: PhoneNumberMixin
_mapper: Any, _connection: Any, target: OptionalPhoneNumberMixin
) -> None:
if target.phone_number:
phonenumber_refcount_dropping.send(target.phone_number)
Expand All @@ -899,7 +931,7 @@ def _setup_refcount_events() -> None:


def _phone_number_mixin_set_validator(
target: PhoneNumberMixin,
target: OptionalPhoneNumberMixin,
value: PhoneNumber | None,
old_value: PhoneNumber | None,
_initiator: Any,
Expand All @@ -911,9 +943,9 @@ def _phone_number_mixin_set_validator(
raise PhoneNumberInUseError("This phone number it not available")


@event.listens_for(PhoneNumberMixin, 'mapper_configured', propagate=True)
@event.listens_for(OptionalPhoneNumberMixin, 'mapper_configured', propagate=True)
def _phone_number_mixin_configure_events(
_mapper: Any, cls: type[PhoneNumberMixin]
_mapper: Any, cls: type[OptionalPhoneNumberMixin]
) -> None:
event.listen(cls.phone_number, 'set', _phone_number_mixin_set_validator)
event.listen(cls, 'before_delete', _send_refcount_event_before_delete)
Expand Down
9 changes: 6 additions & 3 deletions tests/unit/models/phone_number_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class PhoneLink(models.PhoneNumberMixin, models.BaseMixin, models.Model):
"""Test model connecting PhoneUser to PhoneNumber."""

__tablename__ = 'test_phone_link'
__phone_optional__ = False
__phone_unique__ = True
__phone_for__ = 'phoneuser'
__phone_is_exclusive__ = True
Expand All @@ -67,12 +66,16 @@ class PhoneLink(models.PhoneNumberMixin, models.BaseMixin, models.Model):
)
phoneuser: Mapped[PhoneUser] = relationship(PhoneUser)

class PhoneDocument(models.PhoneNumberMixin, models.BaseMixin, models.Model):
class PhoneDocument(
models.OptionalPhoneNumberMixin, models.BaseMixin, models.Model
):
"""Test model unaffiliated to a user that has a phone number attached."""

__tablename__ = 'test_phone_document'

class PhoneLinkedDocument(models.PhoneNumberMixin, models.BaseMixin, models.Model):
class PhoneLinkedDocument(
models.OptionalPhoneNumberMixin, models.BaseMixin, models.Model
):
"""Test model that accepts an optional user and an optional phone."""

__tablename__ = 'test_phone_linked_document'
Expand Down

0 comments on commit 36e96d6

Please sign in to comment.