{"id":20285424,"url":"https://github.com/mramshaw/secret-santa","last_synced_at":"2026-05-22T16:36:54.042Z","repository":{"id":92906337,"uuid":"159367360","full_name":"mramshaw/Secret-Santa","owner":"mramshaw","description":"A gift exchange (Secret Santa) program in Python","archived":false,"fork":false,"pushed_at":"2024-07-10T06:06:50.000Z","size":19,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-04T03:44:33.814Z","etag":null,"topics":["gift-exchange","pytest","pytest-benchmark","pytest-cov","python","python3","tdd"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mramshaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-27T16:39:58.000Z","updated_at":"2019-01-30T17:09:47.000Z","dependencies_parsed_at":"2024-11-14T14:27:25.033Z","dependency_job_id":"a742e990-e361-4e03-8867-f85f909535ba","html_url":"https://github.com/mramshaw/Secret-Santa","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/mramshaw/Secret-Santa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FSecret-Santa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FSecret-Santa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FSecret-Santa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FSecret-Santa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mramshaw","download_url":"https://codeload.github.com/mramshaw/Secret-Santa/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FSecret-Santa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33354039,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"online","status_checked_at":"2026-05-22T02:00:06.671Z","response_time":265,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gift-exchange","pytest","pytest-benchmark","pytest-cov","python","python3","tdd"],"created_at":"2024-11-14T14:26:35.304Z","updated_at":"2026-05-22T16:36:54.012Z","avatar_url":"https://github.com/mramshaw.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Secret Santa\n\n[![Known Vulnerabilities](http://snyk.io/test/github/mramshaw/Secret-Santa/badge.svg?style=plastic\u0026targetFile=requirements.txt)](http://snyk.io/test/github/mramshaw/Secret-Santa?style=plastic\u0026targetFile=requirements.txt)\n[![Build status](http://travis-ci.org/mramshaw/Secret-Santa.svg?branch=master)](http://travis-ci.org/mramshaw/Secret-Santa)\n[![Coverage Status](http://codecov.io/github/mramshaw/Secret-Santa/coverage.svg?branch=master)](http://codecov.io/github/mramshaw/Secret-Santa?branch=master)\n[![GitHub release](http://img.shields.io/github/release/mramshaw/Secret-Santa.svg?style=flat-square)](http://github.com/mramshaw/Secret-Santa/releases)\n\nA simple Secret Santa gift exchange\n\n## Motivation\n\nAt seasonal parties and other gatherings, attendees may register for a gift exchange.\nPartners of attendees cannot receive gifts from that attendee (and vice-versa).\nEveryone else will receive a gift from a random attendee.\n\nEach attendee (and partner) must have a unique name. Duplicate names will create exceptions.\n\n[TDD](http://en.wikipedia.org/wiki/Test-driven_development) was used for this exercise\n with the [pytest](http://docs.pytest.org/en/latest/) framework.\n\n[This was a fun exercise - I knocked out my first effort in a half-day or so. But it turned\n out that I had not fully understood the problem. Like a lot of random walks, the solution\n is not always deterministic. This led to some re-work. Luckily, TDD makes this relatively\n easy, at least in terms of testing time.]\n\n## Prerequisites\n\nInstall prerequisites as follows:\n\n    $ pip install --user -r requirements.txt\n\nOr (for Python 3):\n\n    $ pip3 install --user -r requirements.txt\n\n## Tests\n\nRun unit tests as follows:\n\n    $ pytest -v\n\nOr (for Python 3):\n\n    $ python3 -m pytest -v\n\nThis should look as follows:\n\n```bash\n$ pytest -v\n==================================================================================================================== test session starts =====================================================================================================================\nplatform linux2 -- Python 2.7.12, pytest-3.10.1, py-1.7.0, pluggy-0.8.0 -- /usr/bin/python\ncachedir: .pytest_cache\nrootdir: /home/owner/Documents/Python/Secret-Santa, inifile:\ncollected 11 items\n\nsecret_santa_test.py::test_ExceptionWithDuplicateFamilyMember PASSED                                                                                                                                                                                   [  9%]\nsecret_santa_test.py::test_ExceptionWithPartnerAsDuplicatedFamilyMember PASSED                                                                                                                                                                         [ 18%]\nsecret_santa_test.py::test_ExceptionWithDuplicatePartner PASSED                                                                                                                                                                                        [ 27%]\nsecret_santa_test.py::test_canAddFamilyMembers PASSED                                                                                                                                                                                                  [ 36%]\nsecret_santa_test.py::test_getUnmatchedMembersCount PASSED                                                                                                                                                                                             [ 45%]\nsecret_santa_test.py::test_checkForValidGiver PASSED                                                                                                                                                                                                   [ 54%]\nsecret_santa_test.py::test_ExceptionWithOnlyPartners PASSED                                                                                                                                                                                            [ 63%]\nsecret_santa_test.py::test_ExceptionWithOnlyFamily PASSED                                                                                                                                                                                              [ 72%]\nsecret_santa_test.py::test_canSolveGoodSolution1 PASSED                                                                                                                                                                                                [ 81%]\nsecret_santa_test.py::test_canSolveGoodSolution2 PASSED                                                                                                                                                                                                [ 90%]\nsecret_santa_test.py::test_canSolveGoodSolution3 PASSED                                                                                                                                                                                                [100%]\n\n================================================================================================================= 11 passed in 0.09 seconds ==================================================================================================================\n$\n```\n\n## Code Coverage\n\nThere seem to be two main options for Python code coverage reporting:\n\n* [coverage](http://pypi.org/project/coverage/)\n* [pytest-cov](http://pytest-cov.readthedocs.io/en/latest/readme.html)\n\nAs we are already using `pytest` we will use the second option.\n\n[We will not need _parallel_ or _distributed_ testing, so no need to install `pytest-xdist` at this time.]\n\nWe can get code coverage statistics as follows:\n\n```bash\n$ pytest -v --cov=./\n==================================================================================================================== test session starts =====================================================================================================================\nplatform linux2 -- Python 2.7.12, pytest-3.10.1, py-1.7.0, pluggy-0.8.0 -- /usr/bin/python\ncachedir: .pytest_cache\nrootdir: /home/owner/Documents/Python/Secret-Santa, inifile:\nplugins: cov-2.6.0\ncollected 11 items\n\nsecret_santa_test.py::test_ExceptionWithDuplicateFamilyMember PASSED                                                                                                                                                                                   [  9%]\nsecret_santa_test.py::test_ExceptionWithPartnerAsDuplicatedFamilyMember PASSED                                                                                                                                                                         [ 18%]\nsecret_santa_test.py::test_ExceptionWithDuplicatePartner PASSED                                                                                                                                                                                        [ 27%]\nsecret_santa_test.py::test_canAddFamilyMembers PASSED                                                                                                                                                                                                  [ 36%]\nsecret_santa_test.py::test_getUnmatchedMembersCount PASSED                                                                                                                                                                                             [ 45%]\nsecret_santa_test.py::test_checkForValidGiver PASSED                                                                                                                                                                                                   [ 54%]\nsecret_santa_test.py::test_ExceptionWithOnlyPartners PASSED                                                                                                                                                                                            [ 63%]\nsecret_santa_test.py::test_ExceptionWithOnlyFamily PASSED                                                                                                                                                                                              [ 72%]\nsecret_santa_test.py::test_canSolveGoodSolution1 PASSED                                                                                                                                                                                                [ 81%]\nsecret_santa_test.py::test_canSolveGoodSolution2 PASSED                                                                                                                                                                                                [ 90%]\nsecret_santa_test.py::test_canSolveGoodSolution3 PASSED                                                                                                                                                                                                [100%]\n\n---------- coverage: platform linux2, python 2.7.12-final-0 ----------\nName                   Stmts   Miss  Cover\n------------------------------------------\nsecret_santa.py           74     16    78%\nsecret_santa_test.py      64      0   100%\n------------------------------------------\nTOTAL                    138     16    88%\n\n\n================================================================================================================= 11 passed in 0.10 seconds ==================================================================================================================\n$\n```\n\nCode coverage is __88%__ which seems acceptable.\n\n[Opinions differ on what is an acceptable level of code coverage.\nAs 100% code coverage is not always reasonable (for instance in this\ncase), my opinion is that 70% is a minimum acceptable value. But a\nhigher level of code coverage is of course very desirable.]\n\nOf course, we can drill down into the code with an HTML report as well:\n\n```bash\n$ pytest -v --cov=./ --cov-report=html\n==================================================================================================================== test session starts =====================================================================================================================\nplatform linux2 -- Python 2.7.12, pytest-3.10.1, py-1.7.0, pluggy-0.8.0 -- /usr/bin/python\ncachedir: .pytest_cache\nrootdir: /home/owner/Documents/Python/Secret-Santa, inifile:\nplugins: cov-2.6.0\ncollected 11 items\n\nsecret_santa_test.py::test_ExceptionWithDuplicateFamilyMember PASSED                                                                                                                                                                                   [  9%]\nsecret_santa_test.py::test_ExceptionWithPartnerAsDuplicatedFamilyMember PASSED                                                                                                                                                                         [ 18%]\nsecret_santa_test.py::test_ExceptionWithDuplicatePartner PASSED                                                                                                                                                                                        [ 27%]\nsecret_santa_test.py::test_canAddFamilyMembers PASSED                                                                                                                                                                                                  [ 36%]\nsecret_santa_test.py::test_getUnmatchedMembersCount PASSED                                                                                                                                                                                             [ 45%]\nsecret_santa_test.py::test_checkForValidGiver PASSED                                                                                                                                                                                                   [ 54%]\nsecret_santa_test.py::test_ExceptionWithOnlyPartners PASSED                                                                                                                                                                                            [ 63%]\nsecret_santa_test.py::test_ExceptionWithOnlyFamily PASSED                                                                                                                                                                                              [ 72%]\nsecret_santa_test.py::test_canSolveGoodSolution1 PASSED                                                                                                                                                                                                [ 81%]\nsecret_santa_test.py::test_canSolveGoodSolution2 PASSED                                                                                                                                                                                                [ 90%]\nsecret_santa_test.py::test_canSolveGoodSolution3 PASSED                                                                                                                                                                                                [100%]\n\n---------- coverage: platform linux2, python 2.7.12-final-0 ----------\nCoverage HTML written to dir htmlcov\n\n\n================================================================================================================= 11 passed in 0.09 seconds ==================================================================================================================\n$\n```\n\nLooking at `htmlcov/index.html` and drilling down into `secret_santa.py` we can see that\nwe do not have any coverage in our `main` routine (this is expected) but there are also\ntwo exceptions that do not get tested. The second is a catch-all, so cannot be fixed.\n\nHowever, the first exception not being tested is an oversight. This means another test\nshould be written to check for this exception. And so code coverage has highlighted a\nsoft area in our testing. This is unlikely to be critical, but better safe than sorry.\n\n[Adding a test for the first uncaught exception raises the code coverage to __89%__.]\n\n## Benchmarks\n\nCapturing historical benchmarks is probably yet another ___best practice___.\n\nWhenever code changes result in a substantial difference in execution time, this needs\nto be investigated. Of course, to do so we will need to capture historical benchmarks.\n\nThe [pytest-benchmark](http://pypi.org/project/pytest-benchmark/) module was designed\nfor just such a purpose.\n\nRun them as follows:\n\n```bash\n$ pytest -v --benchmark-only --benchmark-autosave\n==================================================================================================================== test session starts =====================================================================================================================\nplatform linux2 -- Python 2.7.12, pytest-3.10.1, py-1.7.0, pluggy-0.8.0 -- /usr/bin/python\ncachedir: .pytest_cache\nbenchmark: 3.2.2 (defaults: timer=time.time disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)\nrootdir: /home/owner/Documents/Python/Secret-Santa, inifile:\nplugins: cov-2.6.0, benchmark-3.2.2\ncollected 20 items\n\nsecret_santa_test.py::test_ExceptionWithDuplicateAttendeeName SKIPPED                                                                                                                                                                                  [  5%]\nsecret_santa_test.py::test_ExceptionWithPartnerAsDuplicatedAttendee SKIPPED                                                                                                                                                                            [ 10%]\nsecret_santa_test.py::test_ExceptionWithDuplicatedAttendee SKIPPED                                                                                                                                                                                     [ 15%]\nsecret_santa_test.py::test_ExceptionWithDuplicatedPartner SKIPPED                                                                                                                                                                                      [ 20%]\nsecret_santa_test.py::test_canAddAttendees SKIPPED                                                                                                                                                                                                     [ 25%]\nsecret_santa_test.py::test_getUnmatchedAttendeesCount SKIPPED                                                                                                                                                                                          [ 30%]\nsecret_santa_test.py::test_checkForValidGiver SKIPPED                                                                                                                                                                                                  [ 35%]\nsecret_santa_test.py::test_NoValidSolution1 SKIPPED                                                                                                                                                                                                    [ 40%]\nsecret_santa_test.py::test_NoValidSolution2 SKIPPED                                                                                                                                                                                                    [ 45%]\nsecret_santa_test.py::test_NoValidSolution3 SKIPPED                                                                                                                                                                                                    [ 50%]\nsecret_santa_test.py::test_canShuffleAttendees SKIPPED                                                                                                                                                                                                 [ 55%]\nsecret_santa_test.py::test_resetUnmatchedAttendeesCount SKIPPED                                                                                                                                                                                        [ 60%]\nsecret_santa_test.py::test_canSolveGoodSolution1 SKIPPED                                                                                                                                                                                               [ 65%]\nsecret_santa_test.py::test_canSolveGoodSolution2 SKIPPED                                                                                                                                                                                               [ 70%]\nsecret_santa_test.py::test_canSolveFlintstones1 SKIPPED                                                                                                                                                                                                [ 75%]\nsecret_santa_test.py::test_canSolveFlintstones2 SKIPPED                                                                                                                                                                                                [ 80%]\nsecret_santa_test.py::test_cannotSolveFlintstones1 SKIPPED                                                                                                                                                                                             [ 85%]\nsecret_santa_test.py::test_cannotSolveFlintstones2 SKIPPED                                                                                                                                                                                             [ 90%]\nsecret_santa_test.py::test_canSolveBenchmark1 PASSED                                                                                                                                                                                                   [ 95%]\nsecret_santa_test.py::test_canSolveBenchmark2 PASSED                                                                                                                                                                                                   [100%]\nSaved benchmark data in: /home/owner/Documents/Python/Secret-Santa/.benchmarks/Linux-CPython-2.7-64bit/0014_8a13a4792486bbd1810a8463f268a7c841cdb6bf_20190127_224954_uncommited-changes.json\n\n\n\n-------------------------------------------------------------------------------------- benchmark: 2 tests --------------------------------------------------------------------------------------\nName (time in us)               Min                Max               Mean            StdDev             Median               IQR            Outliers  OPS (Kops/s)            Rounds  Iterations\n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\ntest_canSolveBenchmark2     16.9277 (1.0)      34.0939 (1.0)      17.9517 (1.0)      0.7467 (1.0)      17.8814 (1.0)      0.2384 (1.0)     5924;5924       55.7049 (1.0)       27777           1\ntest_canSolveBenchmark1     17.8814 (1.06)     44.1074 (1.29)     19.0119 (1.06)     1.3694 (1.83)     19.0735 (1.07)     0.2384 (1.0)      607;3869       52.5987 (0.94)      14267           1\n------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\nLegend:\n  Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.\n  OPS: Operations Per Second, computed as 1 / Mean\n============================================================================================================ 2 passed, 18 skipped in 3.22 seconds ============================================================================================================\n$\n```\n\n## Run\n\nRun the application as follows:\n\n    $ python secret_santa.py\n\nThis should look something like the following:\n\n```bash\n$ python secret_santa.py\nEnter gathering attendees and their partners\n\nAttendee (or CR to stop): Fred\nAttendee's partner (CR if none): Wilma\n\nAttendee (or CR to stop): Barney\nAttendee's partner (CR if none): Betty\n\nAttendee (or CR to stop): Pebbles\nAttendee's partner (CR if none):\n\nAttendee (or CR to stop): Bambam\nAttendee's partner (CR if none):\n\nAttendee (or CR to stop):\n\nAll gathering attendees entered, working out exchanges\n\nSolved = {'Pebbles': 'Bambam', 'Barney': 'Wilma', 'Fred': 'Pebbles', 'Betty': 'Fred', 'Bambam': 'Betty', 'Wilma': 'Barney'} \n\nPebbles \u003c= Bambam\nBarney \u003c= Wilma\nFred \u003c= Pebbles\nBetty \u003c= Fred\nBambam \u003c= Betty\nWilma \u003c= Barney\n$\n```\n\n## Failure\n\nFor some combinations of gift exchangers, a solution may not be possible.\n\nIn that case an error message will be printed and the app will terminate:\n\n```bash\n$ python secret_santa.py\nEnter gathering attendees and their partners\n\nAttendee (or CR to stop): fred\nAttendee's partner (CR if none): wilma\n\nAttendee (or CR to stop):\n\nAll gathering attendees entered, working out exchanges\n\nNot enough unpartnered members for a solution!\n$\n```\n\n## Retries\n\nFor some combinations of gift exchangers, the algorithm may not produce a solution.\n\nIn that case, the user will be prompted to retry. This should look like:\n\n```bash\n$ python secret_santa.py\nEnter gathering attendees and their partners\n\nAttendee (or CR to stop): fred\nAttendee's partner (CR if none): wilma\n\nAttendee (or CR to stop): pebbles\nAttendee's partner (CR if none):\n\nAttendee (or CR to stop): dino\nAttendee's partner (CR if none):\n\nAttendee (or CR to stop):\n\nAll gathering attendees entered, working out exchanges\n\nFailed to solve, retry ('n' to stop)?\nSolved = {'pebbles': 'fred', 'dino': 'wilma', 'wilma': 'dino', 'fred': 'pebbles'}\n\npebbles \u003c= fred\ndino \u003c= wilma\nwilma \u003c= dino\nfred \u003c= pebbles\n\nI hope your gathering is successful!\n$\n```\n\nOf course, the user can stop the retries by entering \"__n__\" at any time:\n\n```bash\n$ python secret_santa.py\nEnter gathering attendees and their partners\n\nAttendee (or CR to stop): fred\nAttendee's partner (CR if none): wilma\n\nAttendee (or CR to stop): pebbles\nAttendee's partner (CR if none):\n\nAttendee (or CR to stop): dino\nAttendee's partner (CR if none):\n\nAttendee (or CR to stop):\n\nAll gathering attendees entered, working out exchanges\n\nFailed to solve, retry ('n' to stop)? n\nOkay, stopping now\n$\n```\n\n## Versions\n\nThere are some slight version differences between Python 2 and Python 3.\n\n#### Python 2\n\n* python __2.7.12__\n* pytest __3.10.1__\n* pytest-benchmark __3.2.2__\n* pytest-cov __2.6.0__\n\n#### Python 3\n\n* python __3.5.2__\n* pytest __4.1.1__\n* pytest-benchmark __3.2.2__\n* pytest-cov __2.6.1__\n\n## To Do\n\n- [x] Add custom exceptions\n- [x] Add logic for unsolvable cases\n- [x] Add retry logic for bad solutions\n- [x] Add coverage reporting\n- [x] Increase code coverage\n- [x] Add benchmarks for historical comparison purposes\n- [x] Refactor to extend to seasonal (rather than simply family) gatherings\n- [x] Conform code to `pylint`\n- [x] Conform code to `pycodestyle`\n- [x] Conform code to `pydocstyle`\n- [x] Conform code to `pydoc`\n- [x] Make code Python 2 and Python 3 compatible\n- [ ] Optional enhancement - prevent circular gift exchanges\n- [ ] Optional enhancement - prevent intra-family gift exchanges\n- [ ] Optional enhancement - allow for more than 1 present\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmramshaw%2Fsecret-santa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmramshaw%2Fsecret-santa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmramshaw%2Fsecret-santa/lists"}