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