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.
Files changed (397) hide show
  1. {tptools-0.7.2 → tptools-0.7.4}/PKG-INFO +27 -1
  2. {tptools-0.7.2 → tptools-0.7.4}/README.md +26 -0
  3. {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_matches.py +55 -1
  4. {tptools-0.7.2 → tptools-0.7.4}/tests/conftest.py +44 -0
  5. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_match.py +5 -0
  6. {tptools-0.7.2 → tptools-0.7.4}/tests/test_devcourtmap.py +24 -0
  7. tptools-0.7.4/tests/test_match.py +116 -0
  8. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmatch.py +136 -1
  9. {tptools-0.7.2 → tptools-0.7.4}/tests/test_util.py +43 -0
  10. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_squoresrv.py +4 -3
  11. {tptools-0.7.2 → tptools-0.7.4}/tptools/_version.py +2 -2
  12. {tptools-0.7.2 → tptools-0.7.4}/tptools/devcourtmap.py +11 -0
  13. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/match.py +3 -0
  14. {tptools-0.7.2 → tptools-0.7.4}/tptools/match.py +5 -1
  15. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpmatch.py +36 -3
  16. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/squoresrv.py +4 -1
  17. {tptools-0.7.2 → tptools-0.7.4}/tptools/util.py +18 -0
  18. tptools-0.7.2/tests/test_match.py +0 -52
  19. {tptools-0.7.2 → tptools-0.7.4}/.gitignore +0 -0
  20. {tptools-0.7.2 → tptools-0.7.4}/.pre-commit-config.yaml +0 -0
  21. {tptools-0.7.2 → tptools-0.7.4}/LICENSE +0 -0
  22. {tptools-0.7.2 → tptools-0.7.4}/app.py +0 -0
  23. {tptools-0.7.2 → tptools-0.7.4}/assets/DisableWindowsPathSizeLimit.reg +0 -0
  24. {tptools-0.7.2 → tptools-0.7.4}/assets/screenshots/squore-feeds.png +0 -0
  25. {tptools-0.7.2 → tptools-0.7.4}/assets/screenshots/win-firewall.png +0 -0
  26. {tptools-0.7.2 → tptools-0.7.4}/assets/screenshots/win-install-python.png +0 -0
  27. {tptools-0.7.2 → tptools-0.7.4}/conftest.py +0 -0
  28. {tptools-0.7.2 → tptools-0.7.4}/httpsrv.py +0 -0
  29. {tptools-0.7.2 → tptools-0.7.4}/integration/__init__.py +0 -0
  30. {tptools-0.7.2 → tptools-0.7.4}/integration/anon_tournament.sqlite +0 -0
  31. {tptools-0.7.2 → tptools-0.7.4}/integration/conftest.py +0 -0
  32. {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_entries.py +0 -0
  33. {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_playermatches.py +0 -0
  34. {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_settings.py +0 -0
  35. {tptools-0.7.2 → tptools-0.7.4}/integration/test_loading_tournament.py +0 -0
  36. {tptools-0.7.2 → tptools-0.7.4}/pyproject.toml +0 -0
  37. {tptools-0.7.2 → tptools-0.7.4}/tests/__init__.py +0 -0
  38. {tptools-0.7.2 → tptools-0.7.4}/tests/cli/__init__.py +0 -0
  39. {tptools-0.7.2 → tptools-0.7.4}/tests/cli/test_tpshortcut.py +0 -0
  40. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/__init__.py +0 -0
  41. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/__init__.py +0 -0
  42. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/conftest.py +0 -0
  43. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_config.py +0 -0
  44. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_court.py +0 -0
  45. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_draw.py +0 -0
  46. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_entry.py +0 -0
  47. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_matchesfeed.py +0 -0
  48. {tptools-0.7.2 → tptools-0.7.4}/tests/ext/squore/test_matchessection.py +0 -0
  49. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_clubname.py +0 -0
  50. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_countryname.py +0 -0
  51. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_courtname.py +0 -0
  52. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_drawname.py +0 -0
  53. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_paircombine.py +0 -0
  54. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_playername.py +0 -0
  55. {tptools-0.7.2 → tptools-0.7.4}/tests/namepolicy/test_policybase.py +0 -0
  56. {tptools-0.7.2 → tptools-0.7.4}/tests/test_0ensure_run_from_project_root.py +0 -0
  57. {tptools-0.7.2 → tptools-0.7.4}/tests/test_court.py +0 -0
  58. {tptools-0.7.2 → tptools-0.7.4}/tests/test_draw.py +0 -0
  59. {tptools-0.7.2 → tptools-0.7.4}/tests/test_drawtype.py +0 -0
  60. {tptools-0.7.2 → tptools-0.7.4}/tests/test_entry.py +0 -0
  61. {tptools-0.7.2 → tptools-0.7.4}/tests/test_filewatcher.py +0 -0
  62. {tptools-0.7.2 → tptools-0.7.4}/tests/test_mixins_comparable.py +0 -0
  63. {tptools-0.7.2 → tptools-0.7.4}/tests/test_mixins_repr.py +0 -0
  64. {tptools-0.7.2 → tptools-0.7.4}/tests/test_mixins_str.py +0 -0
  65. {tptools-0.7.2 → tptools-0.7.4}/tests/test_paramsmodel.py +0 -0
  66. {tptools-0.7.2 → tptools-0.7.4}/tests/test_slot.py +0 -0
  67. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tournament.py +0 -0
  68. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpclub.py +0 -0
  69. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpcountry.py +0 -0
  70. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpcourt.py +0 -0
  71. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpdraw.py +0 -0
  72. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpentry.py +0 -0
  73. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpevent.py +0 -0
  74. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tplocation.py +0 -0
  75. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmatchmaker.py +0 -0
  76. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmatchstatus.py +0 -0
  77. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpmodel.py +0 -0
  78. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpplayer.py +0 -0
  79. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpplayermatch.py +0 -0
  80. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpplayermatchstatus.py +0 -0
  81. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpsettings.py +0 -0
  82. {tptools-0.7.2 → tptools-0.7.4}/tests/test_tpstage.py +0 -0
  83. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/__init__.py +0 -0
  84. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/conftest.py +0 -0
  85. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_cli.py +0 -0
  86. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_debug.py +0 -0
  87. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_post.py +0 -0
  88. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_sq_stdout.py +0 -0
  89. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_stdout.py +0 -0
  90. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_tp.py +0 -0
  91. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_tp_recv.py +0 -0
  92. {tptools-0.7.2 → tptools-0.7.4}/tests/tpsrv/test_util.py +0 -0
  93. {tptools-0.7.2 → tptools-0.7.4}/tptools/__init__.py +0 -0
  94. {tptools-0.7.2 → tptools-0.7.4}/tptools/basemodel.py +0 -0
  95. {tptools-0.7.2 → tptools-0.7.4}/tptools/cli/__init__.py +0 -0
  96. {tptools-0.7.2 → tptools-0.7.4}/tptools/cli/tpshortcut.py +0 -0
  97. {tptools-0.7.2 → tptools-0.7.4}/tptools/court.py +0 -0
  98. {tptools-0.7.2 → tptools-0.7.4}/tptools/draw.py +0 -0
  99. {tptools-0.7.2 → tptools-0.7.4}/tptools/drawtype.py +0 -0
  100. {tptools-0.7.2 → tptools-0.7.4}/tptools/entry.py +0 -0
  101. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/__init__.py +0 -0
  102. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/__init__.py +0 -0
  103. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/config.toml +0 -0
  104. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AD.png +0 -0
  105. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AE.png +0 -0
  106. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AF.png +0 -0
  107. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AG.png +0 -0
  108. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AI.png +0 -0
  109. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AL.png +0 -0
  110. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AM.png +0 -0
  111. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AO.png +0 -0
  112. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AQ.png +0 -0
  113. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AR.png +0 -0
  114. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AS.png +0 -0
  115. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AT.png +0 -0
  116. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AU.png +0 -0
  117. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AW.png +0 -0
  118. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AX.png +0 -0
  119. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/AZ.png +0 -0
  120. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BA.png +0 -0
  121. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BB.png +0 -0
  122. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BD.png +0 -0
  123. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BE.png +0 -0
  124. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BF.png +0 -0
  125. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BG.png +0 -0
  126. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BH.png +0 -0
  127. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BI.png +0 -0
  128. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BJ.png +0 -0
  129. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BL.png +0 -0
  130. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BM.png +0 -0
  131. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BN.png +0 -0
  132. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BO.png +0 -0
  133. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BQ.png +0 -0
  134. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BR.png +0 -0
  135. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BS.png +0 -0
  136. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BT.png +0 -0
  137. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BV.png +0 -0
  138. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BW.png +0 -0
  139. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BY.png +0 -0
  140. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/BZ.png +0 -0
  141. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CA.png +0 -0
  142. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CC.png +0 -0
  143. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CD.png +0 -0
  144. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CF.png +0 -0
  145. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CG.png +0 -0
  146. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CH.png +0 -0
  147. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CI.png +0 -0
  148. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CK.png +0 -0
  149. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CL.png +0 -0
  150. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CM.png +0 -0
  151. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CN.png +0 -0
  152. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CO.png +0 -0
  153. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CR.png +0 -0
  154. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CU.png +0 -0
  155. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CV.png +0 -0
  156. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CW.png +0 -0
  157. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CX.png +0 -0
  158. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CY.png +0 -0
  159. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/CZ.png +0 -0
  160. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DE.png +0 -0
  161. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DJ.png +0 -0
  162. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DK.png +0 -0
  163. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DM.png +0 -0
  164. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DO.png +0 -0
  165. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/DZ.png +0 -0
  166. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EC.png +0 -0
  167. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EE.png +0 -0
  168. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EG.png +0 -0
  169. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/EH.png +0 -0
  170. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ENG.png +0 -0
  171. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ER.png +0 -0
  172. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ES.png +0 -0
  173. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ET.png +0 -0
  174. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FI.png +0 -0
  175. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FJ.png +0 -0
  176. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FK.png +0 -0
  177. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FM.png +0 -0
  178. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FO.png +0 -0
  179. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/FR.png +0 -0
  180. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GA.png +0 -0
  181. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GB.png +0 -0
  182. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GD.png +0 -0
  183. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GE.png +0 -0
  184. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GF.png +0 -0
  185. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GG.png +0 -0
  186. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GH.png +0 -0
  187. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GI.png +0 -0
  188. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GL.png +0 -0
  189. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GM.png +0 -0
  190. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GN.png +0 -0
  191. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GP.png +0 -0
  192. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GQ.png +0 -0
  193. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GR.png +0 -0
  194. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GS.png +0 -0
  195. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GT.png +0 -0
  196. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GU.png +0 -0
  197. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GW.png +0 -0
  198. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/GY.png +0 -0
  199. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HK.png +0 -0
  200. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HM.png +0 -0
  201. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HN.png +0 -0
  202. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HR.png +0 -0
  203. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HT.png +0 -0
  204. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/HU.png +0 -0
  205. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ID.png +0 -0
  206. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IE.png +0 -0
  207. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IL.png +0 -0
  208. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IM.png +0 -0
  209. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IN.png +0 -0
  210. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IO.png +0 -0
  211. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IQ.png +0 -0
  212. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IR.png +0 -0
  213. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IS.png +0 -0
  214. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/IT.png +0 -0
  215. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JE.png +0 -0
  216. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JM.png +0 -0
  217. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JO.png +0 -0
  218. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/JP.png +0 -0
  219. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KE.png +0 -0
  220. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KG.png +0 -0
  221. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KH.png +0 -0
  222. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KI.png +0 -0
  223. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KM.png +0 -0
  224. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KN.png +0 -0
  225. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KP.png +0 -0
  226. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KR.png +0 -0
  227. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KW.png +0 -0
  228. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KY.png +0 -0
  229. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/KZ.png +0 -0
  230. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LA.png +0 -0
  231. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LB.png +0 -0
  232. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LC.png +0 -0
  233. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LI.png +0 -0
  234. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LK.png +0 -0
  235. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LR.png +0 -0
  236. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LS.png +0 -0
  237. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LT.png +0 -0
  238. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LU.png +0 -0
  239. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LV.png +0 -0
  240. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/LY.png +0 -0
  241. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MA.png +0 -0
  242. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MC.png +0 -0
  243. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MD.png +0 -0
  244. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ME.png +0 -0
  245. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MF.png +0 -0
  246. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MG.png +0 -0
  247. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MH.png +0 -0
  248. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MK.png +0 -0
  249. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ML.png +0 -0
  250. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MM.png +0 -0
  251. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MN.png +0 -0
  252. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MO.png +0 -0
  253. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MP.png +0 -0
  254. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MQ.png +0 -0
  255. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MR.png +0 -0
  256. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MS.png +0 -0
  257. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MT.png +0 -0
  258. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MU.png +0 -0
  259. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MV.png +0 -0
  260. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MW.png +0 -0
  261. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MX.png +0 -0
  262. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MY.png +0 -0
  263. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/MZ.png +0 -0
  264. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NA.png +0 -0
  265. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NC.png +0 -0
  266. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NE.png +0 -0
  267. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NF.png +0 -0
  268. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NG.png +0 -0
  269. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NI.png +0 -0
  270. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NIR.png +0 -0
  271. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NL.png +0 -0
  272. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NO.png +0 -0
  273. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NP.png +0 -0
  274. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NR.png +0 -0
  275. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NU.png +0 -0
  276. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/NZ.png +0 -0
  277. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/OM.png +0 -0
  278. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PA.png +0 -0
  279. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PE.png +0 -0
  280. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PF.png +0 -0
  281. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PG.png +0 -0
  282. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PH.png +0 -0
  283. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PK.png +0 -0
  284. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PL.png +0 -0
  285. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PM.png +0 -0
  286. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PN.png +0 -0
  287. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PR.png +0 -0
  288. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PS.png +0 -0
  289. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PT.png +0 -0
  290. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PW.png +0 -0
  291. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/PY.png +0 -0
  292. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/QA.png +0 -0
  293. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RE.png +0 -0
  294. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RO.png +0 -0
  295. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RS.png +0 -0
  296. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RU.png +0 -0
  297. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/RW.png +0 -0
  298. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SA.png +0 -0
  299. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SB.png +0 -0
  300. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SC.png +0 -0
  301. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SCO.png +0 -0
  302. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SD.png +0 -0
  303. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SE.png +0 -0
  304. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SG.png +0 -0
  305. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SH.png +0 -0
  306. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SI.png +0 -0
  307. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SJ.png +0 -0
  308. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SK.png +0 -0
  309. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SL.png +0 -0
  310. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SM.png +0 -0
  311. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SN.png +0 -0
  312. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SO.png +0 -0
  313. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SR.png +0 -0
  314. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SS.png +0 -0
  315. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ST.png +0 -0
  316. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SV.png +0 -0
  317. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SX.png +0 -0
  318. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SY.png +0 -0
  319. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/SZ.png +0 -0
  320. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TC.png +0 -0
  321. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TD.png +0 -0
  322. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TF.png +0 -0
  323. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TG.png +0 -0
  324. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TH.png +0 -0
  325. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TJ.png +0 -0
  326. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TK.png +0 -0
  327. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TL.png +0 -0
  328. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TM.png +0 -0
  329. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TN.png +0 -0
  330. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TO.png +0 -0
  331. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TR.png +0 -0
  332. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TT.png +0 -0
  333. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TV.png +0 -0
  334. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TW.png +0 -0
  335. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/TZ.png +0 -0
  336. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UA.png +0 -0
  337. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UG.png +0 -0
  338. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UM.png +0 -0
  339. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/US.png +0 -0
  340. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UY.png +0 -0
  341. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/UZ.png +0 -0
  342. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VA.png +0 -0
  343. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VC.png +0 -0
  344. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VE.png +0 -0
  345. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VG.png +0 -0
  346. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VI.png +0 -0
  347. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VN.png +0 -0
  348. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/VU.png +0 -0
  349. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/WAL.png +0 -0
  350. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/WF.png +0 -0
  351. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/WS.png +0 -0
  352. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/XK.png +0 -0
  353. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/YE.png +0 -0
  354. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/YT.png +0 -0
  355. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ZA.png +0 -0
  356. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ZM.png +0 -0
  357. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/ZW.png +0 -0
  358. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/flags/index.html +0 -0
  359. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/assets/settings.json +0 -0
  360. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/basemodel.py +0 -0
  361. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/config.py +0 -0
  362. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/court.py +0 -0
  363. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/draw.py +0 -0
  364. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/entry.py +0 -0
  365. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/feed.py +0 -0
  366. {tptools-0.7.2 → tptools-0.7.4}/tptools/ext/squore/section.py +0 -0
  367. {tptools-0.7.2 → tptools-0.7.4}/tptools/filewatcher.py +0 -0
  368. {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/__init__.py +0 -0
  369. {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/comparable.py +0 -0
  370. {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/repr.py +0 -0
  371. {tptools-0.7.2 → tptools-0.7.4}/tptools/mixins/str.py +0 -0
  372. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/__init__.py +0 -0
  373. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/clubname.py +0 -0
  374. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/countryname.py +0 -0
  375. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/courtname.py +0 -0
  376. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/drawname.py +0 -0
  377. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/paircombine.py +0 -0
  378. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/playername.py +0 -0
  379. {tptools-0.7.2 → tptools-0.7.4}/tptools/namepolicy/policybase.py +0 -0
  380. {tptools-0.7.2 → tptools-0.7.4}/tptools/paramsmodel.py +0 -0
  381. {tptools-0.7.2 → tptools-0.7.4}/tptools/py.typed +0 -0
  382. {tptools-0.7.2 → tptools-0.7.4}/tptools/slot.py +0 -0
  383. {tptools-0.7.2 → tptools-0.7.4}/tptools/sqlmodels.py +0 -0
  384. {tptools-0.7.2 → tptools-0.7.4}/tptools/tournament.py +0 -0
  385. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/__init__.py +0 -0
  386. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/assets/favicon.ico +0 -0
  387. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/cli.py +0 -0
  388. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/debug.py +0 -0
  389. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/post.py +0 -0
  390. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/sq_stdout.py +0 -0
  391. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/stdout.py +0 -0
  392. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/tp.py +0 -0
  393. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/tp_recv.py +0 -0
  394. {tptools-0.7.2 → tptools-0.7.4}/tptools/tpsrv/util.py +0 -0
  395. {tptools-0.7.2 → tptools-0.7.4}/tptools/winscripts/tpsrv-tp.bat +0 -0
  396. {tptools-0.7.2 → tptools-0.7.4}/util/tp2sqlite.sh +0 -0
  397. {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.2
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
- from collections.abc import Iterable
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
- assert settings["customData"] == {
700
- "court": {"id": court_id, "location_id": location_id}
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.2'
22
- __version_tuple__ = version_tuple = (0, 7, 2)
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