Skip to content

Commit cae6712

Browse files
committed
updated nits
1 parent ecb9a97 commit cae6712

File tree

8 files changed

+105
-108
lines changed

8 files changed

+105
-108
lines changed
Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,6 @@
1-
import json
21
from abc import ABC, abstractmethod
3-
from dataclasses import dataclass
4-
from enum import Enum
5-
from typing import Generic, TypeVar
62

7-
T = TypeVar("T")
8-
9-
10-
class TemplateData(ABC):
11-
def get_formatted_string(self) -> str:
12-
class_dict = self.__dict__
13-
try:
14-
formatted_string = json.dumps(class_dict) # Try to convert to a JSON string
15-
except (TypeError, ValueError) as e:
16-
# Handle errors and return a message instead
17-
return f"Error in converting data to JSON: {e}"
18-
19-
return formatted_string
20-
21-
22-
@dataclass
23-
class TestEmailData(TemplateData):
24-
name: str
25-
date: str
26-
27-
28-
class EmailTemplate(Enum):
29-
TEST = "Test"
30-
31-
32-
@dataclass
33-
class EmailContent(Generic[T]):
34-
recipient: str
35-
data: T
3+
from app.schemas.email_template import EmailContent, EmailTemplateType
364

375

386
class IEmailService(ABC):
@@ -42,18 +10,20 @@ class IEmailService(ABC):
4210
"""
4311

4412
@abstractmethod
45-
def send_email(self, template: EmailTemplate, content: EmailContent) -> dict:
46-
"""
47-
Sends an email with the given parameters.
48-
49-
:param to: Recipient's email address
50-
:type to: str
51-
:param subject: Subject of the email
52-
:type subject: str
53-
:param body: HTML body content of the email
54-
:type body: str
55-
:return: Provider-specific metadata (like message ID, thread ID, label IDs)
56-
:rtype: dict
57-
:raises Exception: if email was not sent successfully
13+
def send_email(
14+
self, templateType: EmailTemplateType, content: EmailContent
15+
) -> dict:
16+
"""Send an email using the given template and content with a
17+
respective service provider.
18+
19+
Args:
20+
templateType (EmailTemplateType): Specifies the template
21+
to be used for the email
22+
content (EmailContent): Contains the recipient and data
23+
to be used in the email
24+
25+
Returns:
26+
dict: Provider-specific metadata if any
27+
(like message ID, thread ID, label IDs)
5828
"""
5929
pass
Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from abc import ABC, abstractmethod
22

3-
from app.interfaces.email_service import EmailContent, EmailTemplate
3+
from app.schemas.email_template import EmailContent, EmailTemplateType
44

55

66
class IEmailServiceProvider(ABC):
@@ -10,21 +10,19 @@ class IEmailServiceProvider(ABC):
1010
"""
1111

1212
@abstractmethod
13-
def send_email(self, template: EmailTemplate, content: EmailContent) -> dict:
14-
"""
15-
Sends an email using the provider's service.
13+
def send_email(
14+
self, templateType: EmailTemplateType, content: EmailContent
15+
) -> dict:
16+
"""_summary_
17+
18+
Args:
19+
templateType (EmailTemplate): Helps provider determine which
20+
template to use for the given email
21+
content (EmailContent): Contains the recipient and data to be
22+
used in the email
1623
17-
:param recipient: Email address of the recipient
18-
:type recipient: str
19-
:param subject: Subject of the email
20-
:type subject: str
21-
:param body_html: HTML body content of the email
22-
:type body_html: str
23-
:param body_text: Plain text content of the email
24-
:type body_text: str
25-
:return: Provider-specific metadata related to the sent email
26-
(like message ID, status, etc.)
27-
:rtype: dict
28-
:raises Exception: if the email fails to send
24+
Returns:
25+
dict: Provider-specific metadata if any
26+
(like message ID, thread ID, label IDs)
2927
"""
3028
pass
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@
22

33
from fastapi import APIRouter, Depends
44

5-
from app.interfaces.email_service import (
6-
EmailContent,
7-
EmailTemplate,
8-
IEmailService,
9-
TestEmailData,
10-
)
11-
from app.services.email.email_service import EmailService
12-
from app.services.email.email_service_provider import (
5+
from app.interfaces.email_service import IEmailService
6+
from app.schemas.email_template import EmailContent, EmailTemplateType, TestEmailData
7+
from app.services.email.amazon_ses_provider import (
138
get_email_service_provider,
149
)
10+
from app.services.email.email_service import EmailService
1511

1612
router = APIRouter(
1713
prefix="/email",
@@ -31,7 +27,7 @@ async def send_welcome_email(
3127
email_service: Annotated[IEmailService, Depends(get_email_service)],
3228
):
3329
return email_service.send_email(
34-
template=EmailTemplate.TEST,
30+
templateType=EmailTemplateType.TEST,
3531
content=EmailContent[TestEmailData](
3632
recipient=recipient, data=TestEmailData(name=user_name, date="2021-12-01")
3733
),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import json
2+
from abc import ABC
3+
from dataclasses import dataclass
4+
from enum import Enum
5+
from typing import Generic, TypeVar
6+
7+
EmailData = TypeVar("EmailData")
8+
9+
10+
class TemplateData(ABC):
11+
def get_formatted_string(self) -> str:
12+
class_dict = self.__dict__
13+
try:
14+
formatted_string = json.dumps(class_dict) # Try to convert to a JSON string
15+
except (TypeError, ValueError) as e:
16+
raise Exception(f"Error converting class to JSON: {e}")
17+
18+
return formatted_string
19+
20+
21+
@dataclass
22+
class TestEmailData(TemplateData):
23+
name: str
24+
date: str
25+
26+
27+
class EmailTemplateType(Enum):
28+
TEST = "Test"
29+
30+
31+
@dataclass
32+
class EmailContent(Generic[EmailData]):
33+
recipient: str
34+
data: EmailData

backend/app/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dotenv import load_dotenv
66
from fastapi import FastAPI
77

8-
from app.routes import email
8+
from app.routes import email_test
99

1010
load_dotenv()
1111

@@ -32,7 +32,7 @@ async def lifespan(_: FastAPI):
3232
app = FastAPI(lifespan=lifespan)
3333
app.include_router(user.router)
3434

35-
app.include_router(email.router)
35+
app.include_router(email_test.router)
3636

3737

3838
@app.get("/")

backend/app/services/email/email_service_provider.py renamed to backend/app/services/email/amazon_ses_provider.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,26 @@
33
import boto3
44
from botocore.exceptions import ClientError
55

6-
from app.interfaces.email_service import EmailContent, EmailTemplate
76
from app.interfaces.email_service_provider import IEmailServiceProvider
7+
from app.schemas.email_template import EmailContent, EmailTemplateType
88

99

1010
class AmazonSESEmailProvider(IEmailServiceProvider):
11+
"""Amazon SES Email Provider.
12+
13+
This class is responsible for sending emails using Amazon SES.
14+
"""
15+
16+
"""
17+
Args:
18+
aws_access_key (str): AWS Access Key ID
19+
aws_secret_key (str): AWS Secret Access Key
20+
region (str): AWS region where SES is configured
21+
source_email (str): Email address from which the email will be sent
22+
is_sandbox (bool): If True, the amazon provider will only be able to send emails
23+
to previously verified email addresses and domains
24+
"""
25+
1126
def __init__(
1227
self,
1328
aws_access_key: str,
@@ -30,28 +45,28 @@ def __init__(
3045
response = self.ses_client.list_verified_email_addresses()
3146
self.verified_emails = response.get("VerifiedEmailAddresses", [])
3247

33-
def _verify_email(self, email: str):
34-
if not self.is_sandbox:
35-
return
48+
def _verify_email(self, email: str, templateType: EmailTemplateType) -> None:
3649
try:
37-
if email not in self.verified_emails:
50+
if self.is_sandbox and email not in self.verified_emails:
3851
self.ses_client.verify_email_identity(EmailAddress=email)
3952
print(f"Verification email sent to {email}.")
53+
if self.ses_client.get_template(TemplateName=templateType.value):
54+
print(f"Template {templateType.value} exists.")
4055
except Exception as e:
4156
print(f"Failed to verify email: {e}")
4257

43-
def send_email(self, template: EmailTemplate, content: EmailContent) -> dict:
58+
def send_email(
59+
self, templateType: EmailTemplateType, content: EmailContent
60+
) -> dict:
4461
try:
45-
self._verify_email(content.recipient)
46-
47-
self.ses_client.get_template(TemplateName=template.value)
62+
self._verify_email(content.recipient, templateType)
4863

4964
template_data = content.data.get_formatted_string()
5065

5166
response = self.ses_client.send_templated_email(
5267
Source=self.source_email,
5368
Destination={"ToAddresses": [content.recipient]},
54-
Template=template.value,
69+
Template=templateType.value,
5570
TemplateData=template_data,
5671
)
5772

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,19 @@
11
import logging
22

3-
from app.interfaces.email_service import EmailContent, EmailTemplate, IEmailService, T
3+
from app.interfaces.email_service import IEmailService
44
from app.interfaces.email_service_provider import IEmailServiceProvider
5+
from app.schemas.email_template import EmailContent, EmailData, EmailTemplateType
56

67

7-
# TODO (Mayank, Nov 30th) - Implement the email service methods and use User object
88
class EmailService(IEmailService):
99
def __init__(self, provider: IEmailServiceProvider):
1010
self.provider = provider
1111
self.logger = logging.getLogger(__name__)
1212

13-
# def render_templates(self, template: EmailTemplate) -> tuple[str, str]:
14-
# html_path = self.template_dir / f"{template.value}.html"
15-
# text_path = self.template_dir / f"{template.value}.txt"
16-
17-
# # Check if both files exist
18-
# if not html_path.exists():
19-
# raise FileNotFoundError(f"HTML template not found: {html_path}")
20-
# if not text_path.exists():
21-
# raise FileNotFoundError(f"Text template not found: {text_path}")
22-
23-
# # Read the templates
24-
# html_template = html_path.read_text(encoding="utf-8")
25-
# text_template = text_path.read_text(encoding="utf-8")
26-
27-
# return html_template, text_template
28-
29-
def send_email(self, template: EmailTemplate, content: EmailContent[T]) -> dict:
13+
def send_email(
14+
self, templateType: EmailTemplateType, content: EmailContent[EmailData]
15+
) -> dict:
3016
self.logger.info(
31-
f"Sending email to {content.recipient} with template {template.value}"
17+
f"Sending email to {content.recipient} with template {templateType.value}"
3218
)
33-
# html_template, text_template = self.render_templates(template)
34-
return self.provider.send_email(template, content)
19+
return self.provider.send_email(templateType, content)

backend/app/utilities/ses/ses_init.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ def load_file_content(file_path: str) -> str:
3838
return ""
3939

4040

41-
templates_metadata = load_templates_metadata(TEMPLATES_FILE)
42-
43-
4441
# Function to create SES template
4542
def create_ses_template(template_metadata):
4643
name = template_metadata["TemplateName"]
@@ -68,6 +65,8 @@ def create_ses_template(template_metadata):
6865

6966
# Ensure SES templates are available at app startup
7067
def ensure_ses_templates():
68+
templates_metadata = load_templates_metadata(TEMPLATES_FILE)
69+
7170
for template_metadata in templates_metadata:
7271
name = template_metadata["TemplateName"]
7372
try:

0 commit comments

Comments
 (0)