Search code examples
pythonlambdamezzanine

Python lambda in mezzaninie utils


i cannot wrap my head round this lambda function (render). It's from a Mezzanine util:

def send_mail_template(subject, template, addr_from, addr_to, context=None,
                   attachments=None, fail_silently=None, addr_bcc=None,
                   headers=None):
    """
    Send email rendering text and html versions for the specified
    template name using the context dictionary passed in.

    EDITED FOR SIMPLICITY
    """

    context.update(context_settings())

    # Loads a template passing in vars as context.
    render = lambda type: loader.get_template("%s.%s" %
                      (template, type)).render(Context(context))

    # Create and send email.
    msg = EmailMultiAlternatives(subject, render("txt"),
                             addr_from, addr_to, addr_bcc,
                             headers=headers)
    msg.attach_alternative(render("html"), "text/html")
    msg.send(fail_silently=fail_silently)

I'm trying to pass a list of templates into my own version of the function. so the parameter "template", which is a string of a path to a template, becomes a list (templates) of strings of paths. Then i need to iterate over the list and apply whatever is happening in the lambda, then call the .render(Context(context)) on it.

this is the ugliest code ever but this shows what i need as an end result:

render = lambda type: loader.get_template("%s.%s" %
                  (templates[0], type))

render2 = lambda type: loader.get_template("%s.%s" %
                  (templates[1], type))

# Create and send email.
msg = EmailMultiAlternatives(subject,(render("txt")+render2('txt')).render(Context(context)),
                         addr_from, addr_to, addr_bcc,
                         headers=headers)
msg.attach_alternative((render("html")+render2('html').render(Context(context)), "text/html")
msg.send(fail_silently=fail_silently)

The above does work but is obviously just disgusting and the list will be of unknown length.

Please! Can anyone de-construct the lambda function?

Answer with help from Arthur Tacca

I'm trying to build an email body by stacking templates together, so need to pass in a list of templates that will be rendered as the txt and html body together as one. here's what works:

def render_templates(types,templates,context):
    template_body = ''
    for template in templates:
        template_body += loader.get_template("%s.%s" % (template, types)).render(Context(context))
    return template_body

def send_mail_template(subject, templates, addr_from, addr_to, context=None,
                   attachments=None, fail_silently=None, addr_bcc=None,
                   headers=None):
    """
    ...
    """
    context.update(context_settings())

    msg = EmailMultiAlternatives(subject, render_templates("txt",templates,context),
                             addr_from, to, [],
                             headers)
    msg.attach_alternative(render_templates("html",templates,context), "text/html")

    msg.send(fail_silently=fail_silently)

Solution

  • I'm not clear on why you're using a function/lambda at all rather than just (for the first example):

    rendered = loader.get_template("%s.%s" % (template, "txt")).render(Context(context))
    

    I guess this simplification only works because you've simplified the context you're doing this in; maybe in the real code you need to pass the render function somewhere to be called later. If not, you can get rid of the lambdas altogether.

    Here's a minor point but it might make things a little bit clearer: There is absolutely no difference between

    render = lambda type: loader.get_template("%s.%s" %
                      (templates[0], type))
    render2 = lambda type: loader.get_template("%s.%s" %
                      (templates[1], type))
    

    and:

    def render(type):
        return loader.get_template("%s.%s" % (templates[0], type))
    def render2(type):
        return loader.get_template("%s.%s" % (templates[1], type))
    

    (OK, one slight difference is that you get a clearer stack trace if there is an exception.) In fact the second form is better style (AKA is "more Pythonic"); the only reason to use a lambda is if the function is so short that it gets passed to another function without even assigning it to a variable.

    In your case this means you can use an iteration if you like:

    def render_all(types):
        result_list = []
        for type, template in zip(types, templates):
            result_list.append(loader.get_template("%s.%s" % (template, type)))
        return "".join(result_list)
    

    This is ripe for a list comprehension / generator:

    def render_all(types):
        return "".join(loader.get_template("%s.%s" % (template, type))
                       for type, template in zip(types, templates))