Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Notification and Mailing system in App Engine.

Anler
March 21, 2013

Notification and Mailing system in App Engine.

This is a talk I gave at my company regarding how to improve the code legibility and how to build it with a clear api and documentation.

Anler

March 21, 2013
Tweet

More Decks by Anler

Other Decks in Programming

Transcript

  1. Creating the email No way of seeing how an email

    looks like after being converted into a template. No way of testing how it will look like in different email clients.
  2. Sending the email PM cannot test that emails are being

    sent. Developer’s email tools are poor.
  3. Code smells if language == 'es': notification_remove_msg = 'No...' elif

    language == 'de': notification_remove_msg = 'No...' elif language == 'pt': notification_remove_msg = 'No...' else: notification_remove_msg = "Don't..."
  4. Dependencies not documented def send_notify(self, notify, params, force_send=False, log_send=True): """Send

    a notification to user Args: notify: type of notification params: data to fill templates """
  5. Language Subject Plain Text body HTML body Subject, Plain Text

    body and HTML body are sort of templates.
  6. Basic operations Load the notification template named name and with

    language lang from somewhere. Render each notification template (subject, text, html) with the actual data.
  7. “Loader” object class Loader(object): def get_subject(self, name, lang): """Get the

    subject""" def get_html(self, name, lang): """Get the html""" def get_text(self, name, lang): """Get the plain text"""
  8. “Environment” object class Environment(object): def __init__(self, loader, template_class): self.loader =

    loader self.template_class = template_class def get_notification(self, name, lang=None, **context): subject = self.template_class( self.loader.get_subject(name, lang)).render(**context) html = self.template_class( self.loader.get_html(name, lang)).render(**context) text = self.template_class( self.loader.get_text(name, lang)).render(**context) return Notification(name, lang, subject, html, text)
  9. class FileSystemLoader(Loader): SUBJECT_FILENAME = 'subject.txt' HTML_FILENAME = 'body.html' TEXT_FILENAME =

    'body.txt' def __init__(self, path): self.path = path def _read(self, name, lang, filename): with open(os.path.join(self.path, name, lang, filename)) as f: return f.read() def get_subject(self, name, lang): return self._read(name, lang, self.SUBJECT_FILENAME) def get_html(self, name, lang): return self._read(name, lang, self.HTML_FILENAME) def get_text(self, name, lang): try: text = self._read(name, lang, self.TEXT_FILENAME) except IOError: text = super().get_text(name, lang) return text
  10. class DjangoTemplateLoader(FileSystemLoader): def __init__(self, template_dirs=None): self.template_dirs = template_dirs def _read(self,

    name, lang, filename): template_name = '/'.join((name, lang, filename)) return load_template_source( template_name, self.template_dirs)[0]
  11. subject = 'email/%s/%s/subject.txt' % (name, language) message.subject = loader.get_template(subject) .render(Context(params))

    body = 'email/%s/%s/body.txt' % (name, language) message.body = loader.get_template(body).render(Context(params)) message.body += notification_remove_msg + ' ' + profile_notification_url html = 'email/%s/%s/body.html' % (name, language) message.html = loader.get_template(html).render(Context(params)) message.html += '<br /><a href="%s">%s</a>' % (profile_notification_url, notification_remove_msg) “Before”
  12. I don’t like App Engine email API because it mixes

    the way email are sent with the way they are represented.
  13. import smtplib from email.mime.text import MIMEText msg = MIMEText('Message') msg['Subject']

    = 'The subject' msg['From'] = me msg['To'] = you s = smtplib.SMTP('localhost') s.sendmail(me, [you], msg.as_string()) s.quit() I prefer the approach of the standard library
  14. Let’s try to refactor... from google.appengine.api import mail message =

    mail.EmailMessage(sender='...') message.to = self.user.email message.subject = '...' message.body = '...' message.html = '...' message.send()
  15. ...in order to support Different kinds of messages: The actual

    that sends the email. One that logs the email. One that does nothing.
  16. Refactor “...restructuring an existing body of code, altering its internal

    structure without changing its external behavior.”
  17. EMAIL_MESSAGE_SETTING = 'EMAIL_MESSAGE_CLASS' def EmailMessage(message_class=None, **kwargs): if message_class is None:

    if not hasattr(settings, EMAIL_MESSAGE_SETTING): raise ImproperlyConfigured message_class = getattr(settings, EMAIL_MESSAGE_SETTING) if isinstance(message_class, basestring): ... return message_class(**kwargs)
  18. class Message(MailerDelegator): pass class LoggingMessage(MailerDelegator): name = 'app.mail' logger =

    logging.getLogger(name) level = logging.DEBUG def send(self, *args, **kwargs): self.logger.log(self.level, self) Pick your “Message” class DummyMessage(MailerDelegator): def send(self, *args, **kwargs): pass
  19. class MailerDelegator(object): __slots__ = '_mailer' mailer_class = google.appengine.api.mail.EmailMessage def __init__(self,

    *args, **kwargs): object.__setattr__(self, '_mailer', self.mailer_class(*args, **kwargs)) def __setattr__(self, name, value): if name in self._mailer.PROPERTIES: setattr(self._mailer, name, value) else: super().__setattr__(name, value) def __getattr__(self, name): if not hasattr(self._mailer, name): raise AttributeError return getattr(self._mailer, name) def __str__(self): return self.to_mime_message().as_string()
  20. Creating the email (unsolved) No way of seeing how an

    email looks like after being converted into a template. (unsolved) No way of testing how it will look like in different email clients.
  21. Sending the email (unsolved) PM cannot test that emails are

    being sent. (solved) Developers email tools are poor.