tptools 0.7.2__tar.gz → 0.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tptools-0.7.2 → tptools-0.7.4}/PKG-INFO +27 -1
- {tptools-0.7.2 → tptools-0.7.4}/README.md +26 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_matches.py +55 -1
- {tptools-0.7.2 → tptools-0.7.4}/tests/conftest.py +44 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_match.py +5 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_devcourtmap.py +24 -0
- tptools-0.7.4/tests/test_match.py +116 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmatch.py +136 -1
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_util.py +43 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_squoresrv.py +4 -3
- {tptools-0.7.2 → tptools-0.7.4}/tptools/_version.py +2 -2
- {tptools-0.7.2 → tptools-0.7.4}/tptools/devcourtmap.py +11 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/match.py +3 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/match.py +5 -1
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpmatch.py +36 -3
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/squoresrv.py +4 -1
- {tptools-0.7.2 → tptools-0.7.4}/tptools/util.py +18 -0
- tptools-0.7.2/tests/test_match.py +0 -52
- {tptools-0.7.2 → tptools-0.7.4}/.gitignore +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/.pre-commit-config.yaml +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/LICENSE +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/app.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/assets/DisableWindowsPathSizeLimit.reg +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/assets/screenshots/squore-feeds.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/assets/screenshots/win-firewall.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/assets/screenshots/win-install-python.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/conftest.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/httpsrv.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/anon_tournament.sqlite +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/conftest.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_entries.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_playermatches.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_settings.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_tournament.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/pyproject.toml +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/cli/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/cli/test_tpshortcut.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/conftest.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_config.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_court.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_draw.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_entry.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_matchesfeed.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_matchessection.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_clubname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_countryname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_courtname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_drawname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_paircombine.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_playername.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_policybase.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_0ensure_run_from_project_root.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_court.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_draw.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_drawtype.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_entry.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_filewatcher.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_mixins_comparable.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_mixins_repr.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_mixins_str.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_paramsmodel.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_slot.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tournament.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpclub.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpcountry.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpcourt.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpdraw.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpentry.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpevent.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tplocation.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmatchmaker.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmatchstatus.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmodel.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpplayer.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpplayermatch.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpplayermatchstatus.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpsettings.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpstage.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/conftest.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_cli.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_debug.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_post.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_sq_stdout.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_stdout.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_tp.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_tp_recv.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_util.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/basemodel.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/cli/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/cli/tpshortcut.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/court.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/draw.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/drawtype.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/entry.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/config.toml +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AQ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AX.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BB.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BJ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BQ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BV.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CV.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CX.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DJ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ENG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ER.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ES.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ET.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FJ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GB.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GP.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GQ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ID.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IQ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JP.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KP.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LB.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LV.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ME.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ML.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MP.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MQ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MV.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MX.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NIR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NP.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/OM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/QA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SB.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SCO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SJ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ST.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SV.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SX.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TD.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TH.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TJ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TO.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TR.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TV.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/US.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UY.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UZ.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VC.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VG.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VI.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VN.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VU.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/WAL.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/WF.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/WS.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/XK.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/YE.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/YT.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ZA.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ZM.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ZW.png +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/index.html +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/settings.json +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/basemodel.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/config.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/court.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/draw.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/entry.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/feed.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/section.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/filewatcher.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/comparable.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/repr.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/str.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/clubname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/countryname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/courtname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/drawname.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/paircombine.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/playername.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/policybase.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/paramsmodel.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/py.typed +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/slot.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/sqlmodels.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tournament.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/__init__.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/assets/favicon.ico +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/cli.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/debug.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/post.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/sq_stdout.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/stdout.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/tp.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/tp_recv.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/util.py +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/tptools/winscripts/tpsrv-tp.bat +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/util/tp2sqlite.sh +0 -0
- {tptools-0.7.2 → tptools-0.7.4}/watchtests.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tptools
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: A set of tools to export data from TournamentSoftware
|
|
5
5
|
Author-email: "martin f. krafft" <tptools@pobox.madduck.net>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -364,6 +364,9 @@ one or more `--url` arguments and will post the data there.
|
|
|
364
364
|
| `post` | A simple tournament representation (players, draws, courts, matches) |
|
|
365
365
|
| `sq-stdout` | A view of the data sent to Squore (see below) |
|
|
366
366
|
|
|
367
|
+
Matches in the `stdout` and `post` output carry an `errors` array. See [Invalid
|
|
368
|
+
match data](#invalid-match-data) below.
|
|
369
|
+
|
|
367
370
|
### Forwarding TP data
|
|
368
371
|
|
|
369
372
|
If you prefer to work mostly on another machine than the one running
|
|
@@ -560,6 +563,29 @@ instead of the TP file as argument to the `tp` subcommand.
|
|
|
560
563
|
|
|
561
564
|
## Known Problems
|
|
562
565
|
|
|
566
|
+
### Invalid match data
|
|
567
|
+
|
|
568
|
+
TournamentSoftware sometimes stores set scores that cannot happen in the
|
|
569
|
+
configured scoring format. An example is 15-11 in a "Par 11" tournament: a set
|
|
570
|
+
ends at 14-11, so 15-11 is impossible. Another example is a match whose two
|
|
571
|
+
player rows disagree about the scoreline.
|
|
572
|
+
|
|
573
|
+
`tptools` does not reject such a match. It loads the match and adds one string
|
|
574
|
+
to the match's `errors` array for each problem:
|
|
575
|
+
|
|
576
|
+
* a set score that cannot happen in the format, such as 15-11 in a "Par 11"
|
|
577
|
+
tournament;
|
|
578
|
+
* two player rows that disagree about the scoreline;
|
|
579
|
+
* a recorded winner who did not win more sets than the opponent.
|
|
580
|
+
|
|
581
|
+
When `errors` is empty, the match data is valid. An unplayed match has no
|
|
582
|
+
score to check, so it has no errors.
|
|
583
|
+
|
|
584
|
+
The `stdout` and `post` commands serve the match like any other, with the
|
|
585
|
+
`errors` array next to the data. The Squore endpoints do not include `errors`,
|
|
586
|
+
because the Squore schema has no field for them. Use `stdout` or `post` when
|
|
587
|
+
you need the `errors` array.
|
|
588
|
+
|
|
563
589
|
### BYEs in Group Draws
|
|
564
590
|
|
|
565
591
|
There seems to be no way to identify a match with a BYE in the database, and
|
|
@@ -318,6 +318,9 @@ one or more `--url` arguments and will post the data there.
|
|
|
318
318
|
| `post` | A simple tournament representation (players, draws, courts, matches) |
|
|
319
319
|
| `sq-stdout` | A view of the data sent to Squore (see below) |
|
|
320
320
|
|
|
321
|
+
Matches in the `stdout` and `post` output carry an `errors` array. See [Invalid
|
|
322
|
+
match data](#invalid-match-data) below.
|
|
323
|
+
|
|
321
324
|
### Forwarding TP data
|
|
322
325
|
|
|
323
326
|
If you prefer to work mostly on another machine than the one running
|
|
@@ -514,6 +517,29 @@ instead of the TP file as argument to the `tp` subcommand.
|
|
|
514
517
|
|
|
515
518
|
## Known Problems
|
|
516
519
|
|
|
520
|
+
### Invalid match data
|
|
521
|
+
|
|
522
|
+
TournamentSoftware sometimes stores set scores that cannot happen in the
|
|
523
|
+
configured scoring format. An example is 15-11 in a "Par 11" tournament: a set
|
|
524
|
+
ends at 14-11, so 15-11 is impossible. Another example is a match whose two
|
|
525
|
+
player rows disagree about the scoreline.
|
|
526
|
+
|
|
527
|
+
`tptools` does not reject such a match. It loads the match and adds one string
|
|
528
|
+
to the match's `errors` array for each problem:
|
|
529
|
+
|
|
530
|
+
* a set score that cannot happen in the format, such as 15-11 in a "Par 11"
|
|
531
|
+
tournament;
|
|
532
|
+
* two player rows that disagree about the scoreline;
|
|
533
|
+
* a recorded winner who did not win more sets than the opponent.
|
|
534
|
+
|
|
535
|
+
When `errors` is empty, the match data is valid. An unplayed match has no
|
|
536
|
+
score to check, so it has no errors.
|
|
537
|
+
|
|
538
|
+
The `stdout` and `post` commands serve the match like any other, with the
|
|
539
|
+
`errors` array next to the data. The Squore endpoints do not include `errors`,
|
|
540
|
+
because the Squore schema has no field for them. Use `stdout` or `post` when
|
|
541
|
+
you need the `errors` array.
|
|
542
|
+
|
|
517
543
|
### BYEs in Group Draws
|
|
518
544
|
|
|
519
545
|
There seems to be no way to identify a match with a BYE in the database, and
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import pathlib
|
|
2
|
+
import shutil
|
|
3
|
+
import sqlite3
|
|
4
|
+
from collections.abc import Generator, Iterable
|
|
2
5
|
|
|
3
6
|
import pytest
|
|
4
7
|
import pytest_subtests
|
|
8
|
+
from sqlmodel import Session, create_engine
|
|
5
9
|
|
|
10
|
+
from tptools import load_tournament
|
|
6
11
|
from tptools.tpmatch import TPMatch, TPMatchStatus
|
|
7
12
|
from tptools.util import ScoresType
|
|
8
13
|
|
|
9
14
|
type MatchIdTuple = tuple[int, int]
|
|
10
15
|
|
|
16
|
+
SQLITE_EXPORT = pathlib.Path(__file__).parent / "anon_tournament.sqlite"
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
def test_loading_all_matches(all_tpmatches: list[TPMatch]) -> None:
|
|
13
20
|
assert len(all_tpmatches) == 68
|
|
@@ -205,3 +212,50 @@ def test_match_scores(
|
|
|
205
212
|
|
|
206
213
|
|
|
207
214
|
# vim:fdm=marker:fdl=0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.fixture
|
|
218
|
+
def bad_score_db(tmp_path: pathlib.Path) -> Generator[pathlib.Path]:
|
|
219
|
+
"""A copy of the export where match 80-11 ends 15-11, inconsistently.
|
|
220
|
+
|
|
221
|
+
The two PlayerMatch rows store the scoreline in the same orientation, so
|
|
222
|
+
they disagree, reproducing the TP export that made load_tournament raise a
|
|
223
|
+
ValidationError.
|
|
224
|
+
"""
|
|
225
|
+
dest = tmp_path / "bad_scores.sqlite"
|
|
226
|
+
shutil.copy(SQLITE_EXPORT, dest)
|
|
227
|
+
with sqlite3.connect(dest) as conn:
|
|
228
|
+
# pm 1199 wins 15-11; pm 1200 stores the same orientation, not reversed
|
|
229
|
+
conn.execute(
|
|
230
|
+
"update PlayerMatch set winner=1, "
|
|
231
|
+
"team1set1=15, team2set1=11, "
|
|
232
|
+
"team1set2=0, team2set2=0, team1set3=0, team2set3=0 "
|
|
233
|
+
"where id=1199"
|
|
234
|
+
)
|
|
235
|
+
conn.execute(
|
|
236
|
+
"update PlayerMatch set winner=2, "
|
|
237
|
+
"team1set1=15, team2set1=11, "
|
|
238
|
+
"team1set2=0, team2set2=0, team1set3=0, team2set3=0 "
|
|
239
|
+
"where id=1200"
|
|
240
|
+
)
|
|
241
|
+
conn.commit()
|
|
242
|
+
yield dest
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@pytest.mark.asyncio
|
|
246
|
+
async def test_load_tournament_records_bad_scores_errors(
|
|
247
|
+
bad_score_db: pathlib.Path,
|
|
248
|
+
) -> None:
|
|
249
|
+
"""load_tournament loads the bad match and records its errors."""
|
|
250
|
+
engine = create_engine(f"sqlite:///{bad_score_db}")
|
|
251
|
+
session = Session(engine)
|
|
252
|
+
session.begin()
|
|
253
|
+
try:
|
|
254
|
+
tournament = await load_tournament(session)
|
|
255
|
+
finally:
|
|
256
|
+
session.close()
|
|
257
|
+
engine.dispose()
|
|
258
|
+
|
|
259
|
+
match = tournament.matches["80-11"]
|
|
260
|
+
assert match.errors
|
|
261
|
+
assert match.scores == [(15, 11)]
|
|
@@ -424,6 +424,50 @@ def TPMatchFactory() -> TPMatchFactoryType:
|
|
|
424
424
|
return match_maker
|
|
425
425
|
|
|
426
426
|
|
|
427
|
+
type ScorePairFactoryType = Callable[..., TPMatch]
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@pytest.fixture
|
|
431
|
+
def ScorePairFactory(tpmatch1: TPMatch) -> ScorePairFactoryType:
|
|
432
|
+
"""Build a TPMatch from a scored set list.
|
|
433
|
+
|
|
434
|
+
``scores`` are from pm1's perspective. ``pm2`` normally stores the reversed
|
|
435
|
+
scoreline and claims the opposite winner. Pass ``pm2_winner`` or
|
|
436
|
+
``pm2_scores`` to deliberately construct an inconsistent pair, e.g. as
|
|
437
|
+
produced by a faulty TP export.
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
def pair_maker(
|
|
441
|
+
scores: list[tuple[int, int]],
|
|
442
|
+
*,
|
|
443
|
+
pm1_winner: int = 1,
|
|
444
|
+
pm2_winner: int | None = None,
|
|
445
|
+
pm2_scores: list[tuple[int, int]] | None = None,
|
|
446
|
+
) -> TPMatch:
|
|
447
|
+
def apply(
|
|
448
|
+
pm: TPPlayerMatch,
|
|
449
|
+
pairs: list[tuple[int, int]],
|
|
450
|
+
winner: int,
|
|
451
|
+
) -> TPPlayerMatch:
|
|
452
|
+
update: dict[str, int] = {
|
|
453
|
+
"winner": winner,
|
|
454
|
+
**{f"team{a}set{g}": 0 for a in (1, 2) for g in range(1, 6)},
|
|
455
|
+
}
|
|
456
|
+
for g, (a, b) in enumerate(pairs, start=1):
|
|
457
|
+
update |= {f"team1set{g}": a, f"team2set{g}": b}
|
|
458
|
+
return pm.model_copy(update=update)
|
|
459
|
+
|
|
460
|
+
pm1 = apply(tpmatch1.pm1, scores, pm1_winner)
|
|
461
|
+
if pm2_scores is None:
|
|
462
|
+
pm2_scores = [(b, a) for a, b in scores]
|
|
463
|
+
if pm2_winner is None:
|
|
464
|
+
pm2_winner = 3 - pm1_winner if pm1_winner in (1, 2) else pm1_winner
|
|
465
|
+
pm2 = apply(tpmatch1.pm2, pm2_scores, pm2_winner)
|
|
466
|
+
return TPMatch(pm1=pm1, pm2=pm2)
|
|
467
|
+
|
|
468
|
+
return pair_maker
|
|
469
|
+
|
|
470
|
+
|
|
427
471
|
@pytest.fixture
|
|
428
472
|
def tpmatch1(
|
|
429
473
|
TPMatchFactory: TPMatchFactoryType,
|
|
@@ -16,3 +16,8 @@ def test_pending_match(sqmatch_pending: SquoreMatch) -> None:
|
|
|
16
16
|
assert sqmatch_pending.court is None
|
|
17
17
|
assert isinstance(sqmatch_pending.A, SquoreEntry)
|
|
18
18
|
assert sqmatch_pending.B == "Unknown"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_squore_match_dump_excludes_errors(sqmatch1: SquoreMatch) -> None:
|
|
22
|
+
"""The Squore wire format must not contain the errors array."""
|
|
23
|
+
assert "errors" not in sqmatch1.model_dump()
|
|
@@ -91,3 +91,27 @@ def test_find_abnormal_court_name(
|
|
|
91
91
|
devcourtmap: DeviceCourtMap, courts: dict[str, Court]
|
|
92
92
|
) -> None:
|
|
93
93
|
assert devcourtmap.find_court_for_ip("192.0.2.99", courts.values()) is None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_manual_add(devcourtmap: DeviceCourtMap, courts: dict[str, Court]) -> None:
|
|
97
|
+
devcourtmap.add_devcourt_mapping("192.0.2.14", "court10")
|
|
98
|
+
assert devcourtmap.find_court_for_ip("192.0.2.14", courts.values()) == courts.get(
|
|
99
|
+
"court10"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_manual_remove(devcourtmap: DeviceCourtMap, courts: dict[str, Court]) -> None:
|
|
104
|
+
assert devcourtmap.pop_devcourt_mapping("192.0.2.11") == 11
|
|
105
|
+
assert devcourtmap.find_court_for_ip("192.0.2.11", courts.values()) is None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_manual_remove_nonexisting(
|
|
109
|
+
devcourtmap: DeviceCourtMap, courts: dict[str, Court]
|
|
110
|
+
) -> None:
|
|
111
|
+
with pytest.raises(KeyError, match="No mapping from"):
|
|
112
|
+
assert devcourtmap.pop_devcourt_mapping("192.0.2.15")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_manual_add_does_not_overwrite(devcourtmap: DeviceCourtMap) -> None:
|
|
116
|
+
with pytest.raises(KeyError, match="already exists"):
|
|
117
|
+
devcourtmap.add_devcourt_mapping("192.0.2.11", "court")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from tptools import Match
|
|
6
|
+
|
|
7
|
+
from .conftest import ScorePairFactoryType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_repr(match1: Match) -> None:
|
|
11
|
+
assert repr(match1) == (
|
|
12
|
+
"Match(id='1-14', matchnr=14, draw=Draw(id=1, name='Baum', "
|
|
13
|
+
"stage.name='Qual', type=MONRAD, size=8), time=datetime(2025, 6, 1, 11, 30), "
|
|
14
|
+
"court=Court(id=1, name='C01', location.name='Sports4You'), "
|
|
15
|
+
"A=Entry(event.name='Herren 1', player1.name='Martin Krafft'), "
|
|
16
|
+
"B=Entry(event.name='Herren 1', player1.name='Iddo Hoeve'), "
|
|
17
|
+
"status=ready, starttime=None, endtime=None, winner=None, scores=-)"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_str(match1: Match) -> None:
|
|
22
|
+
assert str(match1) == "1-14 ready on C01 (Sports4You) @ 11:30"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_cmp_eq(match1: Match, match1copy: Match) -> None:
|
|
26
|
+
assert match1 == match1copy
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_cmp_ne(match1: Match, match2: Match) -> None:
|
|
30
|
+
assert match1 != match2
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_cmp_lt(match1: Match, match2: Match) -> None:
|
|
34
|
+
assert match1 < match2
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_cmp_le(match1: Match, match1copy: Match, match2: Match) -> None:
|
|
38
|
+
assert match1 < match2 and match1 <= match1copy
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_cmp_gt(match1: Match, match2: Match) -> None:
|
|
42
|
+
assert match2 > match1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_cmp_ge(match1: Match, match1copy: Match, match2: Match) -> None:
|
|
46
|
+
assert match2 > match1 and match1 >= match1copy
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parametrize("attr", ["time", "starttime", "endtime"])
|
|
50
|
+
def test_match_timestamps_have_timezone(
|
|
51
|
+
match1: Match, attr: str, now: datetime
|
|
52
|
+
) -> None:
|
|
53
|
+
m = Match(**match1.model_dump() | {attr: now}) # pyright: ignore[reportArgumentType]
|
|
54
|
+
assert getattr(m, attr).tzinfo is not None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_match_dump_includes_errors(ScorePairFactory: ScorePairFactoryType) -> None:
|
|
58
|
+
"""The generic Match dump carries the errors array."""
|
|
59
|
+
match = Match.from_tpmatch(ScorePairFactory([(15, 11)]))
|
|
60
|
+
assert match.errors
|
|
61
|
+
assert match.model_dump()["errors"] == match.errors
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
"scores",
|
|
66
|
+
[
|
|
67
|
+
[(11, 10)],
|
|
68
|
+
[(11, 12)],
|
|
69
|
+
[(15, 11)],
|
|
70
|
+
],
|
|
71
|
+
)
|
|
72
|
+
def test_match_errors_for_illegal_set_scores(
|
|
73
|
+
ScorePairFactory: ScorePairFactoryType, scores: list[tuple[int, int]]
|
|
74
|
+
) -> None:
|
|
75
|
+
"""A Match built from an illegal TPMatch carries the recorded errors."""
|
|
76
|
+
match = Match.from_tpmatch(ScorePairFactory(scores))
|
|
77
|
+
assert match.errors
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_match_errors_empty_for_legal_set_scores(
|
|
81
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""A Match built from a clean TPMatch has no errors."""
|
|
84
|
+
match = Match.from_tpmatch(ScorePairFactory([(11, 5)]))
|
|
85
|
+
assert match.errors == []
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_match_from_tpmatch_shallow_copies_errors(
|
|
89
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Match.from_tpmatch shallow-copies the TPMatch's errors.
|
|
92
|
+
|
|
93
|
+
The Match must get a list of its own (same contents, distinct object), so
|
|
94
|
+
that appending to one model's errors does not affect the other's.
|
|
95
|
+
"""
|
|
96
|
+
tpmatch = ScorePairFactory([(15, 11)])
|
|
97
|
+
tpmatch.errors.append("invented")
|
|
98
|
+
match = Match.from_tpmatch(tpmatch)
|
|
99
|
+
assert match.errors == tpmatch.errors
|
|
100
|
+
assert match.errors is not tpmatch.errors
|
|
101
|
+
match.errors.append("only on the match")
|
|
102
|
+
assert "only on the match" not in tpmatch.errors
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_match_errors_default_is_not_shared(
|
|
106
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""The Match errors default must come from default_factory=list.
|
|
109
|
+
|
|
110
|
+
A bare ``errors: list[str] = []`` default would be shared across all
|
|
111
|
+
instances: mutating one match's errors would leak into every other match.
|
|
112
|
+
"""
|
|
113
|
+
one = Match.from_tpmatch(ScorePairFactory([(11, 5)]))
|
|
114
|
+
two = Match.from_tpmatch(ScorePairFactory([(11, 5)]))
|
|
115
|
+
one.errors.append("marker")
|
|
116
|
+
assert two.errors == []
|
|
@@ -8,7 +8,7 @@ from tptools.slot import Bye, Slot, Unknown
|
|
|
8
8
|
from tptools.sqlmodels import TPEntry, TPPlayerMatch
|
|
9
9
|
from tptools.tpmatch import TPMatch, TPMatchStatus
|
|
10
10
|
|
|
11
|
-
from .conftest import TPMatchFactoryType
|
|
11
|
+
from .conftest import ScorePairFactoryType, TPMatchFactoryType
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def test_repr(tpmatch1: TPMatch) -> None:
|
|
@@ -248,3 +248,138 @@ def test_slot_accessor_explicit(slot1: Slot, slot2: Slot, tpmatch1: TPMatch) ->
|
|
|
248
248
|
# the order of set_slots() overrides the winner field
|
|
249
249
|
assert m.slot1 == slot2
|
|
250
250
|
assert m.slot2 == slot1
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# A tournament using "Par 11" scoring: a set is won by reaching 11 (or more)
|
|
254
|
+
# with a two-point margin. Therefore 11-10, 11-12, and 15-11 are all illegal.
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@pytest.mark.parametrize(
|
|
258
|
+
"scores",
|
|
259
|
+
[
|
|
260
|
+
[(11, 10)],
|
|
261
|
+
[(11, 12)],
|
|
262
|
+
[(15, 11)],
|
|
263
|
+
],
|
|
264
|
+
)
|
|
265
|
+
def test_illegal_set_scores(
|
|
266
|
+
ScorePairFactory: ScorePairFactoryType, scores: list[tuple[int, int]]
|
|
267
|
+
) -> None:
|
|
268
|
+
"""An illegal set score is flagged, not silently accepted or raised."""
|
|
269
|
+
m = ScorePairFactory(scores)
|
|
270
|
+
assert m.errors
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@pytest.mark.parametrize(
|
|
274
|
+
"scores",
|
|
275
|
+
[
|
|
276
|
+
[(11, 5)],
|
|
277
|
+
[(11, 9)],
|
|
278
|
+
[(13, 11)],
|
|
279
|
+
[(11, 4), (6, 11), (13, 11), (11, 8)],
|
|
280
|
+
],
|
|
281
|
+
)
|
|
282
|
+
def test_legal_set_scores_are_not_flagged(
|
|
283
|
+
ScorePairFactory: ScorePairFactoryType, scores: list[tuple[int, int]]
|
|
284
|
+
) -> None:
|
|
285
|
+
"""Legal set scores leave the match unflagged."""
|
|
286
|
+
m = ScorePairFactory(scores)
|
|
287
|
+
assert m.errors == []
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_errors_default_is_not_shared(
|
|
291
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
292
|
+
) -> None:
|
|
293
|
+
"""The errors default must come from default_factory=list, not a shared []
|
|
294
|
+
|
|
295
|
+
A bare ``errors: list[str] = []`` default would be shared across all
|
|
296
|
+
instances: mutating one match's errors would leak into every other match.
|
|
297
|
+
"""
|
|
298
|
+
one = ScorePairFactory([(11, 5)])
|
|
299
|
+
two = ScorePairFactory([(11, 5)])
|
|
300
|
+
one.errors.append("marker")
|
|
301
|
+
assert two.errors == []
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_inconsistent_pair_is_flagged_with_errors(
|
|
305
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
306
|
+
) -> None:
|
|
307
|
+
"""A 15-11 set whose two PlayerMatches disagree no longer raises.
|
|
308
|
+
|
|
309
|
+
This reproduces the real-world TP export where one set ends 15-11 and the
|
|
310
|
+
two PlayerMatch rows store the scoreline in the same orientation, so they
|
|
311
|
+
disagree about it. The match must load, but record the error(s) for
|
|
312
|
+
downstream alerting.
|
|
313
|
+
"""
|
|
314
|
+
# Both winners agree, but pm2 stores the scoreline in pm1's orientation.
|
|
315
|
+
# Previously this made TPMatch raise a ValidationError.
|
|
316
|
+
m = ScorePairFactory([(15, 11)], pm2_scores=[(15, 11)], pm2_winner=2)
|
|
317
|
+
assert m.errors
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@pytest.mark.parametrize(
|
|
321
|
+
"pm1_scores, pm2_scores, pm1_winner, pm2_winner",
|
|
322
|
+
[
|
|
323
|
+
# pm1 (A) wins the match 2-0
|
|
324
|
+
([(11, 5), (11, 6)], [(5, 11), (6, 11)], 1, 2),
|
|
325
|
+
# pm1 (A) wins 2-1
|
|
326
|
+
([(11, 5), (6, 11), (11, 7)], [(5, 11), (11, 6), (7, 11)], 1, 2),
|
|
327
|
+
# pm2 (B) wins 2-0
|
|
328
|
+
([(5, 11), (6, 11)], [(11, 5), (11, 6)], 2, 1),
|
|
329
|
+
],
|
|
330
|
+
)
|
|
331
|
+
def test_winner_matches_set_count(
|
|
332
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
333
|
+
pm1_scores: list[tuple[int, int]],
|
|
334
|
+
pm2_scores: list[tuple[int, int]],
|
|
335
|
+
pm1_winner: int,
|
|
336
|
+
pm2_winner: int,
|
|
337
|
+
) -> None:
|
|
338
|
+
"""The winner field agrees with who won more sets."""
|
|
339
|
+
m = ScorePairFactory(
|
|
340
|
+
pm1_scores,
|
|
341
|
+
pm1_winner=pm1_winner,
|
|
342
|
+
pm2_scores=pm2_scores,
|
|
343
|
+
pm2_winner=pm2_winner,
|
|
344
|
+
)
|
|
345
|
+
assert m.errors == []
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@pytest.mark.parametrize(
|
|
349
|
+
"pm1_scores, pm2_scores, pm1_winner, pm2_winner, won_by",
|
|
350
|
+
[
|
|
351
|
+
# winner says A, but pm2 won both sets
|
|
352
|
+
([(5, 11), (6, 11)], [(11, 5), (11, 6)], 1, 2, "A"),
|
|
353
|
+
# winner says B, but pm1 won both sets
|
|
354
|
+
([(11, 5), (11, 6)], [(5, 11), (6, 11)], 2, 1, "B"),
|
|
355
|
+
# winner says A, but the sets are split 1-1
|
|
356
|
+
([(11, 5), (6, 11)], [(5, 11), (11, 6)], 1, 2, "A"),
|
|
357
|
+
],
|
|
358
|
+
)
|
|
359
|
+
def test_winner_contradicts_set_count(
|
|
360
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
361
|
+
pm1_scores: list[tuple[int, int]],
|
|
362
|
+
pm2_scores: list[tuple[int, int]],
|
|
363
|
+
pm1_winner: int,
|
|
364
|
+
pm2_winner: int,
|
|
365
|
+
won_by: str,
|
|
366
|
+
) -> None:
|
|
367
|
+
"""A recorded winner who did not win more sets is flagged."""
|
|
368
|
+
m = ScorePairFactory(
|
|
369
|
+
pm1_scores,
|
|
370
|
+
pm1_winner=pm1_winner,
|
|
371
|
+
pm2_scores=pm2_scores,
|
|
372
|
+
pm2_winner=pm2_winner,
|
|
373
|
+
)
|
|
374
|
+
assert any(
|
|
375
|
+
e.startswith(f"winner '{won_by}' does not match the set count")
|
|
376
|
+
for e in m.errors
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def test_unplayed_match_has_no_set_count_error(
|
|
381
|
+
ScorePairFactory: ScorePairFactoryType,
|
|
382
|
+
) -> None:
|
|
383
|
+
"""An unplayed match records no set-count error, even without scores."""
|
|
384
|
+
m = ScorePairFactory([], pm1_winner=0, pm2_winner=0)
|
|
385
|
+
assert m.errors == []
|
|
@@ -439,6 +439,49 @@ def test_scores_to_string_gamesep() -> None:
|
|
|
439
439
|
assert util.scores_to_string([(1, 2), (3, 4), (5, 6)], gamesep="g") == "1-2g3-4g5-6"
|
|
440
440
|
|
|
441
441
|
|
|
442
|
+
@pytest.mark.parametrize(
|
|
443
|
+
"high, low",
|
|
444
|
+
[
|
|
445
|
+
(11, 0),
|
|
446
|
+
(11, 9),
|
|
447
|
+
(12, 10),
|
|
448
|
+
(13, 11),
|
|
449
|
+
(14, 12),
|
|
450
|
+
(15, 13),
|
|
451
|
+
(18, 16),
|
|
452
|
+
],
|
|
453
|
+
)
|
|
454
|
+
def test_is_score_legal(high: int, low: int) -> None:
|
|
455
|
+
assert util.is_score_legal(high, low)
|
|
456
|
+
# the order of the two scores does not matter
|
|
457
|
+
assert util.is_score_legal(low, high)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
@pytest.mark.parametrize(
|
|
461
|
+
"high, low",
|
|
462
|
+
[
|
|
463
|
+
(11, 10),
|
|
464
|
+
(11, 12),
|
|
465
|
+
(15, 11),
|
|
466
|
+
(14, 11),
|
|
467
|
+
(15, 12),
|
|
468
|
+
(10, 0),
|
|
469
|
+
],
|
|
470
|
+
)
|
|
471
|
+
def test_set_score_is_illegal(high: int, low: int) -> None:
|
|
472
|
+
assert not util.is_score_legal(high, low)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def test_is_score_legal_par() -> None:
|
|
476
|
+
assert util.is_score_legal(15, 13, par=15)
|
|
477
|
+
assert util.is_score_legal(15, 12, par=15)
|
|
478
|
+
assert util.is_score_legal(15, 0, par=15)
|
|
479
|
+
assert util.is_score_legal(16, 14, par=15)
|
|
480
|
+
assert util.is_score_legal(17, 15, par=15)
|
|
481
|
+
assert not util.is_score_legal(15, 14, par=15)
|
|
482
|
+
assert not util.is_score_legal(18, 15, par=15)
|
|
483
|
+
|
|
484
|
+
|
|
442
485
|
@pytest.mark.parametrize(
|
|
443
486
|
"input, sep, exp",
|
|
444
487
|
[
|
|
@@ -696,9 +696,10 @@ def test_settings_for_device_on_a_court(
|
|
|
696
696
|
|
|
697
697
|
assert resp.status_code == 200
|
|
698
698
|
settings = resp.json()
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
699
|
+
c = Court.model_validate(settings["customData"]["court"])
|
|
700
|
+
assert c.id == court_id
|
|
701
|
+
assert c.location is not None
|
|
702
|
+
assert c.location.id == location_id
|
|
702
703
|
assert settings["liveScoreDeviceId_customSuffix"] == suffix
|
|
703
704
|
assert settings["feedPostUrl"] == feed_idx, "the feed of the court is preselected"
|
|
704
705
|
assert "Pre-selected feed" in caplog.text
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -24,6 +24,17 @@ class DeviceCourtMap:
|
|
|
24
24
|
# so for convenience, flatten this:
|
|
25
25
|
return flatten_dict(devmap)
|
|
26
26
|
|
|
27
|
+
def add_devcourt_mapping(self, ip: str, court: int | str) -> None | Never:
|
|
28
|
+
if ip in self._devmap:
|
|
29
|
+
raise KeyError(f"A mapping from {ip} already exists")
|
|
30
|
+
self._devmap[ip] = court
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def pop_devcourt_mapping(self, ip: str) -> int | str | Never:
|
|
34
|
+
if ip not in self._devmap:
|
|
35
|
+
raise KeyError(f"No mapping from {ip} to remove")
|
|
36
|
+
return self._devmap.pop(ip)
|
|
37
|
+
|
|
27
38
|
@staticmethod
|
|
28
39
|
def normalise_court_name_for_matching(
|
|
29
40
|
courtname: str,
|
|
@@ -70,6 +70,9 @@ class SquoreMatch(Match[SquoreEntry, SquoreDraw, SquoreCourt]):
|
|
|
70
70
|
self, handler: SerializerFunctionWrapHandler
|
|
71
71
|
) -> SquoreMatchStruct:
|
|
72
72
|
ret: SquoreMatchStruct = handler(self)
|
|
73
|
+
# Squore does not know about our error reporting, and its struct forbids
|
|
74
|
+
# extra keys, so keep errors out of the Squore wire format.
|
|
75
|
+
cast(dict[str, Any], ret).pop("errors", None)
|
|
73
76
|
dt = self.time
|
|
74
77
|
ret["date"] = dt.date().strftime("%F") if dt is not None else None
|
|
75
78
|
ret["time"] = dt.time().strftime("%H:%M") if dt is not None else None
|
|
@@ -5,7 +5,7 @@ from typing import Annotated, Literal, Self, cast
|
|
|
5
5
|
from zoneinfo import ZoneInfo
|
|
6
6
|
|
|
7
7
|
import tzlocal
|
|
8
|
-
from pydantic import AfterValidator, BaseModel
|
|
8
|
+
from pydantic import AfterValidator, BaseModel, Field
|
|
9
9
|
|
|
10
10
|
from .court import Court
|
|
11
11
|
from .draw import Draw
|
|
@@ -42,6 +42,9 @@ class Match[EntryT: Entry = Entry, DrawT: Draw = Draw, CourtT: Court = Court](
|
|
|
42
42
|
endtime: Annotated[datetime, AfterValidator(_ensure_tzaware_time)] | None
|
|
43
43
|
winner: Literal["A"] | Literal["B"] | None = None
|
|
44
44
|
scores: ScoresType = []
|
|
45
|
+
# Problems recorded while loading the match, e.g. illegal set scores. This is
|
|
46
|
+
# a shallow copy of TPMatch.errors; downstream consumers should surface them.
|
|
47
|
+
errors: list[str] = Field(default_factory=list)
|
|
45
48
|
|
|
46
49
|
__repr_fields__ = [
|
|
47
50
|
"id",
|
|
@@ -108,6 +111,7 @@ class Match[EntryT: Entry = Entry, DrawT: Draw = Draw, CourtT: Court = Court](
|
|
|
108
111
|
endtime=tpmatch.endtime,
|
|
109
112
|
winner=winner,
|
|
110
113
|
scores=tpmatch.scores,
|
|
114
|
+
errors=list(tpmatch.errors),
|
|
111
115
|
**players,
|
|
112
116
|
)
|
|
113
117
|
|