diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..583c0ba --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +GMAIL_ADDRESS= +GMAIL_TOKEN= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efabc00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Secrets: personal use only, never committed +.env + +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +build/ +dist/ + +# Virtual environments +.venv/ +venv/ + +# Test and tool caches +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.coverage +htmlcov/ + +# Generated documentation +docs/doxygen/ + +# Editors and OS +.idea/ +.vscode/ +.DS_Store +Thumbs.db diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..54b5519 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,23 @@ +# Doxygen configuration for the birthday wisher (Python). +# Run from the repository root: doxygen Doxyfile +# Output goes to docs/doxygen/ (git-ignored). + +PROJECT_NAME = "Birthday Wisher" +PROJECT_BRIEF = "Sends a random birthday letter by e-mail (Udemy 100 Days of Code, day 32)" +OUTPUT_DIRECTORY = docs/doxygen +INPUT = src README.md +FILE_PATTERNS = *.py *.md +RECURSIVE = YES +USE_MDFILE_AS_MAINPAGE = README.md + +OPTIMIZE_OUTPUT_JAVA = YES +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +JAVADOC_AUTOBRIEF = YES +AUTOLINK_SUPPORT = YES +SOURCE_BROWSER = YES + +GENERATE_HTML = YES +GENERATE_LATEX = NO +QUIET = YES +WARN_IF_UNDOCUMENTED = YES diff --git a/birthdays.csv b/birthdays.csv new file mode 100644 index 0000000..0f98a48 --- /dev/null +++ b/birthdays.csv @@ -0,0 +1,5 @@ +name,email,year,month,day +Alice Example,alice@example.com,1990,3,14 +Bob Example,bob@example.com,1985,10,17 +Carol Example,carol@example.com,2001,10,17 +Dave Example,dave@example.com,1978,12,24 diff --git a/letter_templates/letter_1.txt b/letter_templates/letter_1.txt new file mode 100644 index 0000000..4f763c4 --- /dev/null +++ b/letter_templates/letter_1.txt @@ -0,0 +1,7 @@ +Dear [NAME], + +Happy birthday! + +All the best for the year! + +Angela \ No newline at end of file diff --git a/letter_templates/letter_2.txt b/letter_templates/letter_2.txt new file mode 100644 index 0000000..415913c --- /dev/null +++ b/letter_templates/letter_2.txt @@ -0,0 +1,7 @@ +Hey [NAME], + +Happy birthday! Have a wonderful time today and eat lots of cake! + +Lots of love, + +Angela \ No newline at end of file diff --git a/letter_templates/letter_3.txt b/letter_templates/letter_3.txt new file mode 100644 index 0000000..5ed537e --- /dev/null +++ b/letter_templates/letter_3.txt @@ -0,0 +1,7 @@ +Dear [NAME], + +It's your birthday! Have a great day! + +All my love, + +Angela \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d9561b0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "birthday-wisher" +version = "0.1.0" +description = "Sends a random birthday letter by e-mail to everyone whose birthday is today (Udemy 100 Days of Code, day 32)." +readme = "README.md" +requires-python = ">=3.13" +license = { file = "LICENSE" } +dependencies = [] + +[project.optional-dependencies] +dev = ["pytest>=8"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] diff --git a/src/birthday_wisher/__init__.py b/src/birthday_wisher/__init__.py new file mode 100644 index 0000000..985749c --- /dev/null +++ b/src/birthday_wisher/__init__.py @@ -0,0 +1,4 @@ +"""! +@file __init__.py +@brief Birthday wisher: sends a random birthday letter to everyone whose birthday is today. +""" diff --git a/src/birthday_wisher/constants.py b/src/birthday_wisher/constants.py new file mode 100644 index 0000000..bb9bd9b --- /dev/null +++ b/src/birthday_wisher/constants.py @@ -0,0 +1,37 @@ +"""! +@file constants.py +@brief Constants of the birthday wisher: file names, placeholder, SMTP settings. +""" + +## Name of the CSV file with the people and their birth dates. +BIRTHDAYS_FILE: str = "birthdays.csv" + +## Folder with the letter templates. +LETTER_TEMPLATES_DIR: str = "letter_templates" + +## File name pattern of a letter template (the number is 1 to LETTER_COUNT). +LETTER_FILE_PATTERN: str = "letter_{number}.txt" + +## Number of letter templates the program chooses from. +LETTER_COUNT: int = 3 + +## Placeholder in a letter that is replaced by the person's name. +NAME_PLACEHOLDER: str = "[NAME]" + +## Columns of the CSV file. +CSV_COLUMNS: tuple[str, ...] = ("name", "email", "year", "month", "day") + +## SMTP server of Gmail. +SMTP_HOST: str = "smtp.gmail.com" + +## SMTP port that expects STARTTLS. +SMTP_PORT: int = 587 + +## Subject of the e-mail. +EMAIL_SUBJECT: str = "Happy Birthday" + +## Environment variable with the sender's e-mail address. +ENV_SENDER_ADDRESS: str = "GMAIL_ADDRESS" + +## Environment variable with the sender's app password (token). +ENV_SENDER_TOKEN: str = "GMAIL_TOKEN" diff --git a/tests/unit/test_constants.py b/tests/unit/test_constants.py new file mode 100644 index 0000000..1ffecf5 --- /dev/null +++ b/tests/unit/test_constants.py @@ -0,0 +1,15 @@ +"""Smoke test: the package imports and the constants are consistent.""" + +from birthday_wisher import constants + + +def test_letter_file_pattern_has_number_placeholder() -> None: + assert "{number}" in constants.LETTER_FILE_PATTERN + + +def test_csv_columns_start_with_name_and_email() -> None: + assert constants.CSV_COLUMNS[:2] == ("name", "email") + + +def test_smtp_port_expects_starttls() -> None: + assert constants.SMTP_PORT == 587