Coverage for rfpy/web/hooks/webapp.py: 98%
55 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
1import logging
2from typing import Dict, Callable
4from webob import Request, Response
5from webob.exc import HTTPNotAcceptable, HTTPMethodNotAllowed
6from sqlalchemy.orm.exc import NoResultFound
8from rfpy.model import EmailNotification
9from rfpy.web.base import WSGIApp
12log = logging.getLogger(__name__)
15class WebhookApp(WSGIApp):
16 routes: Dict[str, Callable] = {}
18 def validate_user(self, request):
19 pass
21 def build_sux(self):
22 pass
24 @classmethod
25 def register_route(cls, path: str, func: Callable):
26 cls.routes[path.lstrip("/")] = func
29class PostmarkVars:
30 def __init__(self, data: dict):
31 self.message_id = data["MessageID"]
32 self.recipient = data["Recipient"]
33 self.tag = data.get("Tag", None)
34 self.api_dict = data
37class NotificationHook:
38 """Base class for handling Postmark webhook POST requests"""
40 new_status: EmailNotification.Status
42 def __call__(self, request: Request):
43 try:
44 session = request.session
45 data = self.extract(request)
46 try:
47 em = self.lookup(session, data.message_id)
48 except HTTPNotAcceptable as hna:
49 return hna
50 self.check_emails_match(data, em)
51 self.update_notification_record(data, em)
52 em.delivery_status = self.new_status
54 session.commit()
56 log.info("Updated notification %s to status %s", em, self.new_status)
58 except NoResultFound:
59 log.warning(
60 "EmailNotification with message ID %s not found", data.message_id
61 )
63 except Exception:
64 log.exception("Error handling webhook from postmark")
66 finally:
67 session.close()
68 return Response("Thanks so much. That was delightful.")
70 def extract(self, request: Request) -> PostmarkVars:
71 if request.method.upper() != "POST":
72 raise HTTPMethodNotAllowed(
73 "Webhook for email delivery events - use POST to update"
74 )
75 return PostmarkVars(request.json)
77 def lookup(self, session, message_id: str) -> EmailNotification:
78 return (
79 session.query(EmailNotification)
80 .filter(EmailNotification.message_id == message_id)
81 .one()
82 )
84 def check_emails_match(self, data: PostmarkVars, em: EmailNotification):
85 if data.recipient != em.email:
86 m = "Recipient email %s for MessageId %s does not match recorded email %s"
87 log.warning(m, data.recipient, data.message_id, em.email)
89 def update_notification_record(self, data: PostmarkVars, em: EmailNotification):
90 raise NotImplementedError