Coverage for src/couchers/models/activeness_probe.py: 100%
25 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-25 10:58 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-25 10:58 +0000
1import enum
2from datetime import datetime
3from typing import TYPE_CHECKING
5from sqlalchemy import BigInteger, CheckConstraint, DateTime, Enum, ForeignKey, Index, Integer, func
6from sqlalchemy.ext.hybrid import hybrid_property
7from sqlalchemy.orm import Mapped, mapped_column, relationship
9from couchers.models.base import Base
11if TYPE_CHECKING:
12 from couchers.models.users import User
15class ActivenessProbeStatus(enum.Enum):
16 # no response yet
17 pending = enum.auto()
19 # didn't respond on time
20 expired = enum.auto()
22 # responded that they're still active
23 still_active = enum.auto()
25 # responded that they're no longer active
26 no_longer_active = enum.auto()
29class ActivenessProbe(Base):
30 """
31 Activeness probes are used to gauge if users are still active: we send them a notification and ask them to respond,
32 we use this data both to help indicate response rate, as well as to make sure only those who are actively hosting
33 show up as such.
34 """
36 __tablename__ = "activeness_probes"
38 id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
40 user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
41 # the time this probe was initiated
42 probe_initiated: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
43 # the number of reminders sent for this probe
44 notifications_sent: Mapped[int] = mapped_column(Integer, server_default="0")
46 # the time of response
47 responded: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None)
48 # the response value
49 response: Mapped[ActivenessProbeStatus] = mapped_column(
50 Enum(ActivenessProbeStatus), default=ActivenessProbeStatus.pending
51 )
53 @hybrid_property
54 def is_pending(self) -> bool:
55 return self.responded == None
57 user: Mapped["User"] = relationship("User", back_populates="pending_activeness_probe")
59 __table_args__ = (
60 # a user can have at most one pending activeness probe at a time
61 Index(
62 "ix_activeness_probe_unique_pending_response",
63 user_id,
64 unique=True,
65 postgresql_where=responded.is_(None),
66 ),
67 # response time is none iff response is pending
68 CheckConstraint(
69 "(responded IS NULL AND response = 'pending') OR (responded IS NOT NULL AND response != 'pending')",
70 name="pending_has_no_responded",
71 ),
72 )