Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update after testing #12

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ required.

## First steps (Python)

This project is for Python 3.10 version.
This project is for Python 3.11 version.

To work on Python code you should install [Poetry](https://python-poetry.org/docs/#installation).

Expand Down
2 changes: 1 addition & 1 deletion cdk/lib/lambda-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class LambdaStack extends Stack {
this.lambdaFunction = new PythonFunction(this, 'EmailWorker', {
functionName: 'amy-email-worker',
architecture: Architecture.X86_64, // more expensive than ARM
runtime: Runtime.PYTHON_3_10,
runtime: Runtime.PYTHON_3_11,
entry: '../worker',
index: 'main.py',
handler: 'handler',
Expand Down
323 changes: 176 additions & 147 deletions cdk/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
"devDependencies": {
"@types/jest": "^29.2.3",
"@types/node": "18.11.9",
"aws-cdk": "^2.100.0",
"aws-cdk": "^2.137.0",
"ts-node": "^10.9.1",
"typescript": "~4.9.3"
},
"dependencies": {
"@aws-cdk/aws-lambda-python-alpha": "^2.82.0-alpha.0",
"aws-cdk-lib": "^2.54.0",
"@aws-cdk/aws-lambda-python-alpha": "^2.137.0-alpha.0",
"aws-cdk-lib": "^2.137.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
}
Expand Down
2 changes: 1 addition & 1 deletion worker/.python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10
3.11
4 changes: 2 additions & 2 deletions worker/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "MIT"
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
python = "^3.11"
boto3 = "^1.26.146"
aws-lambda-powertools = {extras = ["aws-sdk"], version = "^2.16.1"}
psycopg = {extras = ["binary"], version = "^3.1.9"}
Expand All @@ -27,7 +27,7 @@ pytest-asyncio = "^0.23.2"
flake8-pyproject = "^1.2.3"

[tool.mypy]
python_version = "3.10"
python_version = "3.11"
strict = true

[tool.isort]
Expand Down
24 changes: 16 additions & 8 deletions worker/src/handler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json
import logging
from typing import cast
from uuid import UUID

import httpx
from jinja2 import DebugUndefined, Environment
from jinja2.exceptions import TemplateSyntaxError
from pydantic_core import ValidationError

from src.api import ScheduledEmailController, UriError, context_entry, fetch_model_field
from src.email import render_email, send_email
Expand All @@ -13,6 +14,7 @@
ContextModel,
MailgunCredentials,
ScheduledEmail,
SinglePropertyLinkModel,
ToHeaderModel,
WorkerOutputEmail,
)
Expand Down Expand Up @@ -40,27 +42,33 @@ async def handle_email(
client: httpx.AsyncClient,
token_cache: TokenCache,
) -> WorkerOutputEmail:
id = email.id
id = email.pk
logger.info(f"Working on email {id}.")

locked_email = await controller.lock_by_id(id)
logger.info(f"Locked email {id}.")

try:
context = ContextModel(json.loads(locked_email.context_json))
except json.JSONDecodeError as exc:
context = ContextModel(locked_email.context_json)
except ValidationError as exc:
logger.error(f"Validation error: {exc}")
return await return_fail_email(
id,
f"Failed to read JSON from email context {id}. Error: {exc}",
f"Failed to read email context {id}.",
controller,
)

try:
recipients = ToHeaderModel(root=json.loads(locked_email.to_header_context_json))
except json.JSONDecodeError as exc:
recipients = ToHeaderModel(
root=cast(
list[SinglePropertyLinkModel], locked_email.to_header_context_json
)
)
except ValidationError as exc:
logger.error(f"Validation error: {exc}")
return await return_fail_email(
id,
f"Failed to read JSON from email recipients {id}. Error: {exc}",
f"Failed to read email recipients {id}.",
controller,
)

Expand Down
8 changes: 4 additions & 4 deletions worker/src/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,23 @@ class ScheduledEmailStatus(Enum):


class ScheduledEmail(BaseModel):
id: UUID
pk: UUID
created_at: datetime
last_updated_at: Optional[datetime]
state: ScheduledEmailStatus
scheduled_at: datetime
to_header: list[str]

# JSON, e.g. '[{"api_uri": "api:person#1", "property": "email"}]'
to_header_context_json: str
to_header_context_json: list[dict[str, Any]]
from_header: str
reply_to_header: str
cc_header: list[str]
bcc_header: list[str]
subject: str
body: str
context_json: str # JSON, e.g. '{"name": "John Doe"}'
template_id: UUID
context_json: dict[str, Any] # JSON, e.g. '{"name": "John Doe"}'
template: str # template name


class RenderedScheduledEmail(ScheduledEmail):
Expand Down
8 changes: 4 additions & 4 deletions worker/tests/test_api_scheduledemailcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
def scheduled_email_fixture() -> dict[str, Any]:
now = datetime.now(tz=timezone.utc)
return {
"id": uuid4(),
"pk": uuid4(),
"created_at": now,
"last_updated_at": now,
"state": "scheduled",
"scheduled_at": now,
"to_header": ["[email protected]"],
"to_header_context_json": '[{"api_uri": "api:person#1", "property": "email"}]',
"to_header_context_json": [{"api_uri": "api:person#1", "property": "email"}],
"from_header": "[email protected]",
"reply_to_header": "",
"cc_header": [],
"bcc_header": [],
"subject": "Sample email",
"body": "Hello, {{ name }}!",
"context_json": '{"name": "John"}',
"template_id": uuid4(),
"context_json": {"name": "John"},
"template": "Welcome email",
}


Expand Down
32 changes: 16 additions & 16 deletions worker/tests/test_email.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import UTC, datetime
from typing import Any
from unittest.mock import AsyncMock
from uuid import uuid4
Expand Down Expand Up @@ -57,23 +57,23 @@ def test_render_email() -> None:
# Arrange
engine = Environment(autoescape=True, undefined=DebugUndefined)
id_ = uuid4()
now_ = datetime.utcnow()
now_ = datetime.now(tz=UTC)
email = ScheduledEmail(
id=id_,
pk=id_,
created_at=now_,
last_updated_at=now_,
state=ScheduledEmailStatus.SCHEDULED,
scheduled_at=now_,
to_header=[],
to_header_context_json="[]",
to_header_context_json=[],
from_header="",
reply_to_header="",
cc_header=[],
bcc_header=[],
subject="Hello World and {{ name }}!",
body="Welcome, {{ name }}!",
context_json="{}",
template_id=id_,
context_json={},
template="Welcome email",
)
context = {"name": "John Doe"}
recipients = ["[email protected]", ""] # empty string should be filtered out
Expand All @@ -95,15 +95,15 @@ async def test_send_email() -> None:
# Arrange
client = AsyncMock()
id_ = uuid4()
now_ = datetime.utcnow()
now_ = datetime.now(tz=UTC)
email = RenderedScheduledEmail(
id=id_,
pk=id_,
created_at=now_,
last_updated_at=now_,
state=ScheduledEmailStatus.SCHEDULED,
scheduled_at=now_,
to_header=[],
to_header_context_json="[]",
to_header_context_json=[],
to_header_rendered=["[email protected]"],
from_header="",
reply_to_header="",
Expand All @@ -113,8 +113,8 @@ async def test_send_email() -> None:
subject_rendered="Hello World and John Doe!",
body="Welcome, {{ name }}!",
body_rendered="Welcome, John Doe!",
context_json="{}",
template_id=id_,
context_json={},
template="Welcome email",
)
credentials = MailgunCredentials(
MAILGUN_SENDER_DOMAIN="example.com",
Expand Down Expand Up @@ -146,15 +146,15 @@ async def test_send_email__outgoing_addresses_overwritten() -> None:
# Arrange
client = AsyncMock()
id_ = uuid4()
now_ = datetime.utcnow()
now_ = datetime.now(tz=UTC)
email = RenderedScheduledEmail(
id=id_,
pk=id_,
created_at=now_,
last_updated_at=now_,
state=ScheduledEmailStatus.SCHEDULED,
scheduled_at=now_,
to_header=["[email protected]"],
to_header_context_json="[]",
to_header_context_json=[],
to_header_rendered=["[email protected]"],
from_header="[email protected]",
reply_to_header="[email protected]",
Expand All @@ -164,8 +164,8 @@ async def test_send_email__outgoing_addresses_overwritten() -> None:
subject_rendered="Hello World and John Doe!",
body="Welcome, {{ name }}!",
body_rendered="Welcome, John Doe!",
context_json="{}",
template_id=id_,
context_json={},
template="Welcome email",
)
credentials = MailgunCredentials(
MAILGUN_SENDER_DOMAIN="example.com",
Expand Down
Loading