···11# Secret Santa CLI
2233-This is a Secret Santa gane CLI tool.
33+This is a Secret Santa gane CLI tool. It takes a `.yaml` file to configure the game, setting up the participants and the exclusions.
44+55+Then, uses [Mailgun API](https://documentation.mailgun.com/docs/mailgun/user-manual/get-started/) to send the results of the draw via email.
66+77+## Game config
88+99+```yaml
1010+# Secret Santa game configuration
1111+secret-santa:
1212+ name: "Secret Santa Game 2024"
1313+ notification:
1414+ subject: "Secret Santa result!" # Subject of the email
1515+ template: "notification.html" # Jinja2 template name from templates folder. Optional.
1616+ participants:
1717+ - name: Alice
1818+ email: alice@example.com
1919+ - name: Bob
2020+ email: bob@example.com
2121+ - name: Carol
2222+ email: carol@example.com
2323+ - name: Frank
2424+ email: frank@example.com
2525+ - name: Dan
2626+ email: dan@example.com
2727+ exclusions:
2828+ - from: Alice
2929+ to: Bob
3030+ reverse: true # If true, the exclusion to -> from is also added. False by default.
3131+ comment: "They are a married"
3232+```
3333+3434+## Environment variables
3535+3636+Some environment variables have to be set in order to use Mailgun API. YOu can also create a local `.env` file with the variables.
3737+3838+```
3939+# Mailgun configuration
4040+SANTA_MAILGUN_API_URL=
4141+SANTA_MAILGUN_API_KEY=
4242+```
4343+4444+## Usage
4545+4646+This package uses `uv` to handle the dependencies and virtual environment. The script can be executed by running:
4747+4848+```bash
4949+uv run secretsanta <config_yaml_file> [--dry]
5050+```
5151+5252+- `<config_yaml_file>`:
5353+ Path to the YAML file with the game configuration.
5454+5555+- `--dry`:
5656+ Optional argument, to simulate the draw and not sending the results via
5757+ email.
···11"""This module handles the notification process of the result of a draw."""
2233import logging
44+from pathlib import Path
4556import httpx
67from jinja2 import Environment, FileSystemLoader, Template
7889from . import settings
99-from .models import Player, Game
1010from .draws import Draw
1111+from .models import Game, Player
11121213logger = logging.getLogger(__name__)
131414151516def single_notification(
1616- game: Game, template: Template, players: tuple[Player, Player]
1717+ game: Game,
1818+ template: Template,
1919+ players: tuple[Player, Player],
2020+ dry: bool = False,
1721) -> None:
1822 """Sends a single notification."""
1923···2630 to_name=_to.name,
2731 )
28322929- # set to email
3030- destinies = []
3131- if settings.debug and settings.debug_to_email:
3232- destinies = [settings.debug_to_email]
3333+ if dry:
3434+ print("From:", game.notification_from)
3535+ print("To:", _from.email)
3636+ print("Subject:", game.notification_subject)
3737+ print(content)
3338 else:
3434- destinies = [_from.email]
3535-3636- if destinies:
3739 response = httpx.post(
3840 f"{settings.mailgun_api_url}/messages",
3941 auth=("api", settings.mailgun_api_key.get_secret_value()),
4042 data={
4143 "from": game.notification_from,
4242- "to": destinies,
4444+ "to": [_from.email],
4345 "subject": game.notification_subject,
4446 "html": content,
4547 },
···4749 response.raise_for_status()
485049515050-def notify(game: Game, draw: Draw) -> None:
5252+def notify(game: Game, draw: Draw, dry: bool = False) -> None:
5153 """Notify the result of the draw in the game."""
52545355 # load template
5454- environment = Environment(loader=FileSystemLoader("templates/"))
5656+ templates_path = Path(__file__).parent / "templates"
5757+ environment = Environment(loader=FileSystemLoader(templates_path))
5558 template = environment.get_template(game.notification_template)
56595760 # iterate over solution
5861 for _from_name, _to_name in draw.solution:
5962 players = (game.players[_from_name], game.players[_to_name])
6060- single_notification(game=game, template=template, players=players)
6363+ single_notification(game=game, template=template, players=players, dry=dry)
+8
src/secretsanta/__main__.py
···11+"""The file __main__.py marks the main entry point for the application when running it
22+via runpy.
33+"""
44+55+if __name__ == "__main__":
66+ from .cli import app
77+88+ app()