Skip to content
This repository was archived by the owner on Jun 9, 2024. It is now read-only.

Commit 98ff6b5

Browse files
authored
Add support for specifing safe not encode strings (#12)
1 parent 3bf3cd6 commit 98ff6b5

File tree

4 files changed

+77
-5
lines changed

4 files changed

+77
-5
lines changed

pythonit_toolkit/emails/backends/ses.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import html
44
import boto3
55
from pythonit_toolkit.emails.templates import EmailTemplate
6+
from pythonit_toolkit.emails.utils import SafeString
67

78
from .base import EmailBackend
89

@@ -35,4 +36,12 @@ def send_email(
3536
)
3637

3738
def encode_vars(self, variables: dict[str, str]) -> dict[str, str]:
38-
return {key: html.escape(value) for key, value in variables.items()}
39+
vars = dict()
40+
41+
for key, value in variables.items():
42+
if not isinstance(value, SafeString):
43+
value = html.escape(value)
44+
45+
vars[key] = str(value)
46+
47+
return vars

pythonit_toolkit/emails/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from dataclasses import dataclass
12
import importlib
23

34
from .backends.base import EmailBackend
@@ -18,3 +19,18 @@ def get_email_backend(backend_path: str, **options: dict[str, str]) -> EmailBack
1819
EMAIL_BACKEND_CACHE[backend_path] = instance
1920

2021
return instance
22+
23+
24+
@dataclass
25+
class SafeString(str):
26+
original_str: str
27+
28+
def __str__(self) -> str:
29+
return self.original_str
30+
31+
32+
def mark_safe(string: str) -> SafeString:
33+
if string is None:
34+
raise ValueError("string cannot be None")
35+
36+
return SafeString(string)

tests/emails/backends/test_ses.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from unittest.mock import patch
2-
2+
from pythonit_toolkit.emails.utils import mark_safe
33
from pythonit_toolkit.emails.backends.ses import SESEmailBackend
44
from pythonit_toolkit.emails.templates import EmailTemplate
55
from ward import test
@@ -94,3 +94,27 @@ async def _():
9494
ReplyToAddresses=[],
9595
ConfigurationSetName='primary',
9696
)
97+
98+
99+
@test("safe string variables are not encoded")
100+
async def _():
101+
with patch("pythonit_toolkit.emails.backends.ses.boto3") as mock_boto:
102+
SESEmailBackend("production").send_email(
103+
template=EmailTemplate.RESET_PASSWORD,
104+
subject="Subject",
105+
from_="test@email.it",
106+
to="destination@email.it",
107+
variables={
108+
"safe": mark_safe('<a href="https://google.it">link</a>'),
109+
"not_safe": '<a href="https://google.it">link</a>',
110+
},
111+
)
112+
113+
mock_boto.client.return_value.send_templated_email.assert_called_once_with(
114+
Source="test@email.it",
115+
Destination={"ToAddresses": ["destination@email.it"]},
116+
Template="pythonit-production-reset-password",
117+
TemplateData='{"subject": "Subject", "safe": "<a href=\\"https://google.it\\">link</a>", "not_safe": "&lt;a href=&quot;https://google.it&quot;&gt;link&lt;/a&gt;"}',
118+
ReplyToAddresses=[],
119+
ConfigurationSetName='primary',
120+
)

tests/emails/test_utils.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from unittest.mock import patch
22

3-
from pythonit_toolkit.emails.utils import get_email_backend
4-
from ward import test
3+
from pythonit_toolkit.emails.utils import SafeString, get_email_backend, mark_safe
4+
from ward import test, raises
55

66

77
class TestBackend:
@@ -26,7 +26,8 @@ async def _():
2626
assert "tests.emails.test_utils.TestBackend" in str(type(loaded_backend))
2727

2828
with patch("pythonit_toolkit.emails.utils.importlib.import_module") as mock:
29-
loaded_backend = get_email_backend("tests.emails.test_utils.TestBackend")
29+
loaded_backend = get_email_backend(
30+
"tests.emails.test_utils.TestBackend")
3031

3132
assert "tests.emails.test_utils.TestBackend" in str(type(loaded_backend))
3233
assert not mock.called
@@ -46,3 +47,25 @@ async def _():
4647
)
4748
assert loaded_backend.a == 1
4849
assert loaded_backend.b == 2
50+
51+
52+
@test("mark safe")
53+
async def _():
54+
safe_string = mark_safe("abc")
55+
56+
assert isinstance(safe_string, SafeString)
57+
assert str(safe_string) == "abc"
58+
59+
60+
@test("mark safe with empty string")
61+
async def _():
62+
safe_string = mark_safe("")
63+
64+
assert isinstance(safe_string, SafeString)
65+
assert str(safe_string) == ""
66+
67+
68+
@test("None cannot be converted to safe string")
69+
async def _():
70+
with raises(ValueError):
71+
mark_safe(None)

0 commit comments

Comments
 (0)