Skip to content

Instantly share code, notes, and snippets.

@anupdhml
Last active August 29, 2015 14:10
Show Gist options
  • Save anupdhml/2a99cebe8b416ad6f346 to your computer and use it in GitHub Desktop.
Save anupdhml/2a99cebe8b416ad6f346 to your computer and use it in GitHub Desktop.
Play secret santa!
#!/usr/bin/env python
# Randomly assign secret santas for a group of players, based on a json file
# Handles blacklist logic (eg: couples might not want to be paired)
# Also you are never your own secret santa.
# Optionally send email notification to the players, in case you want it to be
# 'secret' for you too.
#
# By <anupdhml@gmail.com> Nov 28 2014
#
# Usage: python secret_santa.py [--send-email] json_file_with_player_data'
#
# Dependencies: simplejson.
# a functional smtpserver for sending emails. In UNIX
# environments, sendmail will work.
#
# If sending emails, you might want to change the email vars to match your setup.
# Also tell people to check their spam folder.
#
# json file should be of the following format. Be careful with the commas.
"""
{
"Adam": {
"blacklist": [
"Eve"
],
"email": "adam@apple.com"
},
"Eve": {
"blacklist": [
"Adam"
],
"email": "eve@mourning.com"
},
"Krishna": {
"blacklist": [
],
"email": "krishna@mathura.com"
},
"Radha": {
"blacklist": [
"Adam",
"Eve",
"Zeus"
],
"email": "radha@krishna.com"
},
"Zeus": {
"blacklist": [
],
"email": "zeus@tuesday.com"
}
}
"""
# A person can't be gifted by those in their blacklist. (i.e. people in the
# blacklist can never be santa for the player to which that blacklist corresponds)
# For reverse to be true too, just add the person's name in the reverse blacklist.
import random
import sys
import simplejson
import smtplib
# For changing body and subject, look elsewhere
SMTPSERVER = 'localhost'
SENDER_EMAIL = 'secretsanta@secretsanta.com'
SENDER_NAME = 'Secret Santa'
SPEND_LIMIT = '$25'
def compute_santa_assignments(players):
"""Returns a dict with santa assignments (key santa, value victim. Inverse
also works but for the purposes of notifying, this is the standard here)
In case of invalid assignments (usually in the last two) return void
"""
santa_assignments = {}
random.shuffle(players)
santas_without_victims = list(players)
# find the santa for each player
for victim in players:
santa = random.choice(santas_without_victims)
blacklist = players_data[victim]['blacklist']
while santa == victim or santa in blacklist:
# TODO improve this check. works for now though
#if len(santas_without_victims) == 1:
#if len(santas_without_victims) <= 2:
if len(santas_without_victims) <= len(blacklist) + 1:
# this assignment was invalid so return now
return;
santa = random.choice(santas_without_victims)
santas_without_victims.remove(santa)
santa_assignments[santa] = victim
return santa_assignments
def send_email(sender_name, sender_email, receiver_emails, subject, body, smtpserver='localhost'):
"""email utility function"""
message = """From: {} <{}>
To: <{}>
Subject: {}
{}""".format(sender_name, sender_email, ','.join(receiver_emails), subject, body)
try:
smtp_obj = smtplib.SMTP(smtpserver)
smtp_obj.sendmail(sender_email, receiver_emails, message)
print 'Successfully sent email'
except SMTPException:
print 'Error: unable to send email'
def put(data, filename):
"""dumps data in a json file"""
try:
jsondata = simplejson.dumps(data, indent=4, skipkeys=True, sort_keys=True)
fd = open(filename, 'w')
fd.write(jsondata)
fd.close()
except IOError:
print 'ERROR writing', filename
pass
def get(filename):
"""get data from a json file"""
returndata = {}
try:
fd = open(filename, 'r')
text = fd.read()
fd.close()
returndata = simplejson.loads(text)
except (IOError, simplejson.scanner.JSONDecodeError) as e:
print 'COULD NOT LOAD:', filename
return returndata
if __name__ == '__main__':
if len(sys.argv) < 2 or len(sys.argv) > 3:
print 'usage: python secret_santa.py [--send-email] json_file_with_player_data'
sys.exit(1)
# Read from file
players_data = get(sys.argv[-1])
if not players_data:
print 'Check if the file exists. Also check if it is proper json.'
sys.exit(1)
# Extract the list of players
players = list(players_data.keys())
if len(players) < 3:
print 'At least 3 players are needed.'
sys.exit(1)
# Keep repeating the process in case of invalid assignments
santa_assignments = {}
count = 0
while not santa_assignments:
count+=1
# TODO improve this check. works for now though
if count > 10000:
print 'Looks like a valid assignment is not possible from the player data provided.'
print 'Check your blacklist logic.'
sys.exit(1)
santa_assignments = compute_santa_assignments(players)
# as a backup, save the assignment to a file. don't read it though!
put(santa_assignments, 'sanata_assignments.json')
# Notification for the assignments (on screen or via email)
for player in players:
if sys.argv[1] == '--send-email':
player_email = players_data[player]['email']
print '\nSending assignment email to', player, player_email
email_body = """Dear {},
Your victim for this year's secret santa is {}.
Please consider buying something nice. Try to spend at least {} :)
Do not reply to this email. Generated via:
https://gist.github.com/anupdhml/2a99cebe8b416ad6f346""".format(player, santa_assignments[player], SPEND_LIMIT)
send_email(
SENDER_NAME, SENDER_EMAIL, [player_email],
'Secret Santa Assignment', email_body, SMTPSERVER
)
else:
print player, 'is secret santa for', santa_assignments[player]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment