Author SHA1 Message Date
Tirsvad 6d5aa89cd8 Set up the project skeleton (MIL-001)
pyproject.toml (Python >=3.13, no runtime dependencies, pytest as dev extra),
Python .gitignore, .env.example, constants.py, assignment data files,
Doxyfile and a smoke test.

Task: MIL-001#1
Task: MIL-001#2
Task: MIL-001#3
Task: MIL-001#4
Refs #2
Refs #3
Refs #4
Refs #5
2026-10-17 15:15:06 +08:00
11 changed files with 158 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
GMAIL_ADDRESS=
GMAIL_TOKEN=
+29
View File
@@ -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
+23
View File
@@ -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
+5
View File
@@ -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
1 name email year month day
2 Alice Example alice@example.com 1990 3 14
3 Bob Example bob@example.com 1985 10 17
4 Carol Example carol@example.com 2001 10 17
5 Dave Example dave@example.com 1978 12 24
+7
View File
@@ -0,0 +1,7 @@
Dear [NAME],
Happy birthday!
All the best for the year!
Angela
+7
View File
@@ -0,0 +1,7 @@
Hey [NAME],
Happy birthday! Have a wonderful time today and eat lots of cake!
Lots of love,
Angela
+7
View File
@@ -0,0 +1,7 @@
Dear [NAME],
It's your birthday! Have a great day!
All my love,
Angela
+22
View File
@@ -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"]
+4
View File
@@ -0,0 +1,4 @@
"""!
@file __init__.py
@brief Birthday wisher: sends a random birthday letter to everyone whose birthday is today.
"""
+37
View File
@@ -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"
+15
View File
@@ -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