0

I'm trying to make an automated task that (1) collects CSV data about wildfires from the internet, (2) reads its contents to see where wildfires in the CSV have occurred in the past 3 days, (3) visualizes them on a map, (4) and sends an email to a specific alias, with text and a map about where they have occurred.

In the email (4), I want to mention the locations of wildfires, which are in the form of a string:

print(provinces_on_fire_str)
Out[0]: "Province1, Province2"

I used Content-ID to add an image to the e-mail body, using the following code:

# set the plaintext body
msg.set_content('This is a plain text body.')

# create content-ID for the image
image_cid = make_msgid()

# alternative HTML body
msg.add_alternative("""\
<html>
    <body>
        <p>This e-mail contains information about fires reported in the past 3 days.<br>
        The VIIRS sensor has reported wildfires in the following provinces: {provinces_on_fire_str} 
        
        This e-mail was sent automatically. 
        </p>
        <img src="cid:{image_cid}">
    </body>
</html>
""".format(image_cid=image_cid[1:-1]), subtype="html")

# attach image to mail
with open(f"path/toimage/{today}.png", 'rb') as img:
    maintype, subtype = mimetypes.guess_type(img.name)[0].split("/")
    msg.get_payload()[1].add_related(img.read(),
                                     maintype=maintype,
                                     subtype=subtype,
                                     cid=image_cid)

This returns an error, implying no such object as "provinces_on_fire_str" exists for the HTML code. Without the "provinces_on_fire_str" variable in the HTML body, the expected output email is the following (albeit this lacks the text explanation of where they occurred):

enter image description here

Now, the obvious thing that came to my mind is to convert the HTML body part to an f-string, so I can add the "Province1, Province2" values to the e-mail text. But adding f before the e-mail string breaks the image_cid (though the Province1, Province2 values are included in the ultimate e-mail).

.add_alternative with f-string input:

# alternative HTML body
msg.add_alternative(f"""\
<html>
    <body>
        <p>This e-mail contains information about fires reported in the past 3 days.<br>
        The VIIRS sensor has reported wildfires in the following provinces: {provinces_on_fire_str}
        
        This e-mail was sent automatically.
        </p>
        <img src="cid:{image_cid}">
    </body>
</html>
""".format(image_cid=image_cid[1:-1]), subtype="html")

Output email:

enter image description here

How do I pass the string values of provinces_on_fire_str into the HTML code without breaking the image_cid?

emil
  • 25
  • 9
  • Please read [ask] and [mre]. It is not necessary to talk about sending an email, or showing images of rendered HTML, to understand the problem. We only need to see the templates, and the result of filling them in. – Karl Knechtel Jun 03 '22 at 06:47
  • Notice in any event that the `subtype="html"` argument for `.format` is useless, because there is no `{subtype}` placeholder. – Karl Knechtel Jun 03 '22 at 07:00

1 Answers1

0

Consider a simpler example:

foo = 1
bar = 2
print('{foo} == {bar}'.format(foo=foo+1))

It does not work, because bar is not looked up automatically. The .format method of strings is just a method; it does not do any magic. It does not know about the caller's local variables foo and bar; it must be passed all the information that should be used. Of course, because we are passing it information explicitly, we can make modifications.

We can solve the error by simply including the missing argument:

foo = 1
bar = 2
print('{foo} == {bar}'.format(foo=foo+1, bar=bar))

f-strings are magic, or rather, syntactic sugar. They are translated into an equivalent .format call at compile time. They are not a different kind of string; after the compile-time translation, a perfectly ordinary string has a perfectly ordinary .format method called upon it.

If we do

foo = 1
bar = 2
print(f'{foo+1} == {bar}')

then that is already equivalent to the fixed .format version. We can use expressions in the {} placeholders, not just variable names. Notice that this already does the work; we should not have an explicit .format call on the result.

If we have just

foo = 1
bar = 2
print(f'{foo} == {bar}')

then of course we lose the modification of the foo value. If you want to use a modified foo in the formatted output, then either describe the modification in the f-string, or else modify the variable beforehand.


Translating that to the original code, we can either do:

msg.add_alternative(f"""\
<html>
    <body>
        <p>This e-mail contains information about fires reported in the past 3 days.<br>
        The VIIRS sensor has reported wildfires in the following provinces: {provinces_on_fire_str}
        
        This e-mail was sent automatically.
        </p>
        <img src="cid:{image_cid[1:-1]}">
    </body>
</html>
""")

or:

image_cid = image_cid[1:-1]
msg.add_alternative(f"""\
<html>
    <body>
        <p>This e-mail contains information about fires reported in the past 3 days.<br>
        The VIIRS sensor has reported wildfires in the following provinces: {provinces_on_fire_str}
        
        This e-mail was sent automatically.
        </p>
        <img src="cid:{image_cid}">
    </body>
</html>
""")
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153