Skip to content

Commit

Permalink
Merge pull request #21 from voynow/20-implement-side-effect-free-testing
Browse files Browse the repository at this point in the history
20 implement side effect free testing
  • Loading branch information
voynow authored Aug 24, 2024
2 parents 13c49e3 + 8cdf50a commit e475256
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 135 deletions.
17 changes: 17 additions & 0 deletions src/email_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,20 @@ def send_email(
return api_instance.send_transac_email(send_smtp_email)
except ProtocolError:
return api_instance.send_transac_email(send_smtp_email)


def mock_send_email(
subject: str,
html_content: str,
to: Dict[str, str],
sender: Dict[str, str] = {
"name": "Jamie Voynow",
"email": "[email protected]",
},
) -> sib_api_v3_sdk.CreateSmtpEmail:
"""Mock version of send_email for testing"""
print(f"Subject: {subject}")
print(f"To: {to}")
print(f"Sender: {sender}")
print(f"HTML Content: {html_content[:100] + '...'}")
return sib_api_v3_sdk.CreateSmtpEmail(message_id="12345")
66 changes: 41 additions & 25 deletions src/lambda_function.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from datetime import datetime, timedelta, timezone
from typing import Callable, Dict

from stravalib.client import Client

Expand All @@ -25,15 +26,20 @@
from src.training_week import generate_training_week
from src.training_week_update import get_updated_training_week
from src.types.mid_week_analysis import MidWeekAnalysis
from src.types.training_week import TrainingWeekWithPlanning
from src.types.user_row import UserRow


def get_athlete_full_name(strava_client: Client) -> str:
def get_athlete_full_name(strava_client) -> str:
athlete = strava_client.get_athlete()
return f"{athlete.firstname} {athlete.lastname}"


def run_gen_training_week_process(user: UserRow) -> None:
def run_weekly_update_process(
user: UserRow,
upsert_fn: Callable[[str, TrainingWeekWithPlanning], None],
email_fn: Callable[[Dict[str, str], str, str], None],
) -> None:
sysmsg_base = f"{COACH_ROLE}\nYour client has included the following preferences: {user.preferences}\n"
strava_client = get_strava_client(user.athlete_id)

Expand All @@ -46,18 +52,19 @@ def run_gen_training_week_process(user: UserRow) -> None:
day_of_week_summaries=day_of_week_summaries,
)

upsert_training_week_with_coaching(
athlete_id=user.athlete_id,
training_week_with_coaching=training_week_with_coaching,
)
send_email(
upsert_fn(user.athlete_id, training_week_with_coaching)
email_fn(
to={"email": user.email, "name": get_athlete_full_name(strava_client)},
subject="Training Schedule Just Dropped 🏃",
html_content=training_week_to_html(training_week_with_coaching),
)


def run_update_training_week_process(user: UserRow) -> None:
def run_mid_week_update_process(
user: UserRow,
upsert_fn: Callable[[str, MidWeekAnalysis, TrainingWeekWithPlanning], None],
email_fn: Callable[[Dict[str, str], str, str], None],
) -> None:
sysmsg_base = f"{COACH_ROLE}\nYour client has included the following preferences: {user.preferences}\n"
strava_client = get_strava_client(user.athlete_id)

Expand All @@ -73,12 +80,8 @@ def run_update_training_week_process(user: UserRow) -> None:
sysmsg_base=sysmsg_base, mid_week_analysis=mid_week_analysis
)

upsert_training_week_update(
athlete_id=user.athlete_id,
mid_week_analysis=mid_week_analysis,
training_week_update_with_planning=training_week_update_with_planning,
)
send_email(
upsert_fn(user.athlete_id, mid_week_analysis, training_week_update_with_planning)
email_fn(
to={"email": user.email, "name": get_athlete_full_name(strava_client)},
subject="Training Schedule Update 🏃",
html_content=training_week_update_to_html(
Expand All @@ -88,16 +91,29 @@ def run_update_training_week_process(user: UserRow) -> None:
)


def lambda_handler(event, context):
def core_executor(user: UserRow) -> None:
"""
On sundays, generate a new training week, otherwise update the current training week
"""
# get current time in EST
est = timezone(timedelta(hours=-5))
datetime_now_est = datetime.now(tz=timezone.utc).astimezone(est)

# day 6 is Sunday
if datetime_now_est.weekday() == 6:
run_weekly_update_process(
user=user,
upsert_fn=upsert_training_week_with_coaching,
email_fn=send_email,
)
else:
run_mid_week_update_process(
user=user,
upsert_fn=upsert_training_week_update,
email_fn=send_email,
)

for user in list_users():

# get current time in EST
est = timezone(timedelta(hours=-5))
datetime_now_est = datetime.now(tz=timezone.utc).astimezone(est)

# weekday 6 is Sunday
if datetime_now_est.weekday() == 6:
run_gen_training_week_process(user)
else:
run_update_training_week_process(user)
def lambda_handler(event, context):
"""Main entry point for production workload"""
[core_executor(user) for user in list_users()]
49 changes: 49 additions & 0 deletions src/supabase_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,32 @@ def upsert_training_week_with_coaching(
return response


def mock_upsert_training_week_with_coaching(
athlete_id: int,
training_week_with_coaching: TrainingWeekWithCoaching,
) -> APIResponse:
"""
Mock version of upsert_training_week_with_coaching for testing.
:param athlete_id: The ID of the athlete.
:param training_week_with_coaching: An instance of TrainingWeekWithCoaching.
:return: Mocked APIResponse object.
"""
row_data = {
"athlete_id": athlete_id,
"training_week_planning": training_week_with_coaching.training_week_planning,
"training_week": [
session.dict() for session in training_week_with_coaching.training_week
],
"typical_week_training_review": training_week_with_coaching.typical_week_training_review,
"weekly_mileage_target": training_week_with_coaching.weekly_mileage_target,
}

print(json.dumps(row_data, indent=4))
print(f"{training_week_with_coaching.total_weekly_mileage=}")
return APIResponse(data=[row_data], count=1)


def get_training_week_with_coaching(athlete_id: int) -> TrainingWeekWithCoaching:
"""
Get the most recent training_week_with_coaching row by athlete_id
Expand Down Expand Up @@ -173,6 +199,29 @@ def upsert_training_week_update(
return response


def mock_upsert_training_week_update(
athlete_id: int,
mid_week_analysis: MidWeekAnalysis,
training_week_update_with_planning: TrainingWeekWithPlanning,
) -> APIResponse:
"""Mock version of upsert_training_week_update for testing"""

row_data = {
"athlete_id": athlete_id,
"activities": [activity.dict() for activity in mid_week_analysis.activities],
"training_week": [
session.dict() for session in mid_week_analysis.training_week
],
"planning": training_week_update_with_planning.planning,
"training_week_update": [
session.dict()
for session in training_week_update_with_planning.training_week
],
}
print(json.dumps(row_data, indent=4))
return APIResponse(data=[row_data], count=1)


def get_training_week_update(athlete_id: int) -> TrainingWeekWithPlanning:
"""Get the most recent training_week_update row by athlete_id"""

Expand Down
2 changes: 1 addition & 1 deletion src/training_week.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def get_weekly_mileage_target(
sysmsg = f"""{sysmsg_base}\nYou will be provided summary statistics (aggregated by week) of your client's training over the past several weeks."""
usermsg = f"""Starting from earliest to most recent, here are the weekly summaries:
{weekly_summaries}
As the coach, prescribe your client a target weekly mileage and long run range for next week. Be specific and refer to the data provided. Write 2-3 sentences while being as concise as possible."""
As the coach, prescribe your client a target weekly mileage and long run range for next week. Be conservative when increasing volume & distance, it's important that the goals are very achievable. Be specific and refer to the data provided. Write 3-4 sentences while being as concise as possible."""
return get_completion(
[{"role": "assistant", "content": sysmsg}, {"role": "user", "content": usermsg}]
)
Expand Down
Loading

0 comments on commit e475256

Please sign in to comment.