vlrdevapi 3.1.0__tar.gz → 3.1.1__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 (240) hide show
  1. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/docs-check.yml +7 -0
  2. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/publish.yml +26 -0
  3. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/CHANGELOG.md +18 -0
  4. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/PKG-INFO +5 -2
  5. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/README.md +3 -1
  6. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/pyproject.toml +3 -2
  7. vlrdevapi-3.1.1/scripts/check_docs_schema.py +478 -0
  8. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/scripts/download_fixtures.py +14 -2
  9. vlrdevapi-3.1.1/server.json +32 -0
  10. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/__init__.py +1 -1
  11. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/uv.lock +573 -518
  12. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.gitattributes +0 -0
  13. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/cleanup-caches.yml +0 -0
  16. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/test.yml +0 -0
  17. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.gitignore +0 -0
  18. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.nvmrc +0 -0
  19. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.python-version +0 -0
  20. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.readthedocs.yaml +0 -0
  21. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/CODE_OF_CONDUCT.md +0 -0
  22. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/CONTRIBUTING.md +0 -0
  23. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/LICENCE +0 -0
  24. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/SECURITY.md +0 -0
  25. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/SUPPORT.md +0 -0
  26. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/scripts/check_mdx_examples.py +0 -0
  27. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/__init__.pyi +0 -0
  28. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_base.py +0 -0
  29. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_cache.py +0 -0
  30. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_client.py +0 -0
  31. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/__init__.py +0 -0
  32. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/_common.py +0 -0
  33. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/__init__.py +0 -0
  34. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/models.py +0 -0
  35. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/namespace.py +0 -0
  36. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
  37. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/parser.py +0 -0
  38. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/__init__.py +0 -0
  39. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/models.py +0 -0
  40. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/namespace.py +0 -0
  41. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
  42. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/parser.py +0 -0
  43. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/__init__.py +0 -0
  44. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/models.py +0 -0
  45. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/namespace.py +0 -0
  46. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
  47. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/parser.py +0 -0
  48. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/namespace.py +0 -0
  49. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/namespace.pyi +0 -0
  50. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/__init__.py +0 -0
  51. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/models.py +0 -0
  52. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/namespace.py +0 -0
  53. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
  54. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/parser.py +0 -0
  55. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/__init__.py +0 -0
  56. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/models.py +0 -0
  57. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/namespace.py +0 -0
  58. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
  59. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/parser.py +0 -0
  60. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/__init__.py +0 -0
  61. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/models.py +0 -0
  62. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/namespace.py +0 -0
  63. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
  64. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/parser.py +0 -0
  65. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/__init__.py +0 -0
  66. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/common.py +0 -0
  67. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
  68. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/models.py +0 -0
  69. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
  70. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
  71. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/parser.py +0 -0
  72. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/__init__.py +0 -0
  73. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/models.py +0 -0
  74. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/namespace.py +0 -0
  75. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
  76. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/parser.py +0 -0
  77. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/namespace.py +0 -0
  78. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/namespace.pyi +0 -0
  79. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
  80. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
  81. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
  82. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
  83. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/parser.py +0 -0
  84. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/__init__.py +0 -0
  85. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/__init__.py +0 -0
  86. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/models.py +0 -0
  87. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/namespace.py +0 -0
  88. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/namespace.pyi +0 -0
  89. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/parser.py +0 -0
  90. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/common.py +0 -0
  91. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/__init__.py +0 -0
  92. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/models.py +0 -0
  93. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/namespace.py +0 -0
  94. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/namespace.pyi +0 -0
  95. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/parser.py +0 -0
  96. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/namespace.py +0 -0
  97. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/namespace.pyi +0 -0
  98. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/__init__.py +0 -0
  99. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/_common_parsing.py +0 -0
  100. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/__init__.py +0 -0
  101. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/models.py +0 -0
  102. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/namespace.py +0 -0
  103. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
  104. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/parser.py +0 -0
  105. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/__init__.py +0 -0
  106. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/models.py +0 -0
  107. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/namespace.py +0 -0
  108. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
  109. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/parser.py +0 -0
  110. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/__init__.py +0 -0
  111. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/models.py +0 -0
  112. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/namespace.py +0 -0
  113. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
  114. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/parser.py +0 -0
  115. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/namespace.py +0 -0
  116. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/namespace.pyi +0 -0
  117. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/__init__.py +0 -0
  118. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/models.py +0 -0
  119. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/namespace.py +0 -0
  120. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/namespace.pyi +0 -0
  121. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/parser.py +0 -0
  122. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/__init__.py +0 -0
  123. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/models.py +0 -0
  124. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/namespace.py +0 -0
  125. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
  126. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/parser.py +0 -0
  127. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/__init__.py +0 -0
  128. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/models.py +0 -0
  129. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/namespace.py +0 -0
  130. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
  131. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/parser.py +0 -0
  132. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/__init__.py +0 -0
  133. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/_utils.py +0 -0
  134. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/__init__.py +0 -0
  135. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/models.py +0 -0
  136. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/namespace.py +0 -0
  137. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
  138. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/parser.py +0 -0
  139. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/__init__.py +0 -0
  140. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/models.py +0 -0
  141. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/namespace.py +0 -0
  142. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
  143. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/parser.py +0 -0
  144. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/match_namespace.py +0 -0
  145. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
  146. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/namespace.py +0 -0
  147. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/namespace.pyi +0 -0
  148. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/__init__.py +0 -0
  149. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/models.py +0 -0
  150. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/namespace.py +0 -0
  151. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
  152. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/parser.py +0 -0
  153. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/__init__.py +0 -0
  154. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/models.py +0 -0
  155. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/namespace.py +0 -0
  156. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
  157. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/parser.py +0 -0
  158. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/__init__.py +0 -0
  159. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/models.py +0 -0
  160. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/namespace.py +0 -0
  161. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/namespace.pyi +0 -0
  162. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/parser.py +0 -0
  163. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
  164. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/models.py +0 -0
  165. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/namespace.py +0 -0
  166. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
  167. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/parser.py +0 -0
  168. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/__init__.py +0 -0
  169. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/models.py +0 -0
  170. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/namespace.py +0 -0
  171. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
  172. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/parser.py +0 -0
  173. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/__init__.py +0 -0
  174. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
  175. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
  176. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
  177. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
  178. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/parser.py +0 -0
  179. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/__init__.py +0 -0
  180. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/models.py +0 -0
  181. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/namespace.py +0 -0
  182. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
  183. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/parser.py +0 -0
  184. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/namespace.py +0 -0
  185. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/namespace.pyi +0 -0
  186. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/__init__.py +0 -0
  187. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/models.py +0 -0
  188. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/namespace.py +0 -0
  189. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
  190. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/parser.py +0 -0
  191. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/__init__.py +0 -0
  192. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/models.py +0 -0
  193. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/namespace.py +0 -0
  194. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
  195. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/parser.py +0 -0
  196. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/__init__.py +0 -0
  197. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/models.py +0 -0
  198. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/namespace.py +0 -0
  199. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
  200. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/parser.py +0 -0
  201. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
  202. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/models.py +0 -0
  203. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
  204. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
  205. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/parser.py +0 -0
  206. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
  207. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
  208. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
  209. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
  210. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/parser.py +0 -0
  211. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/__init__.py +0 -0
  212. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/pagination.py +0 -0
  213. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/paths.py +0 -0
  214. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/team_enrichment.py +0 -0
  215. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/team_parsing.py +0 -0
  216. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/text.py +0 -0
  217. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/__init__.py +0 -0
  218. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/countries.py +0 -0
  219. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/datetime.py +0 -0
  220. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/export.py +0 -0
  221. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/mappings.py +0 -0
  222. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/prizes.py +0 -0
  223. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/regions.py +0 -0
  224. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/timezone.py +0 -0
  225. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/exceptions.py +0 -0
  226. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/fetcher.py +0 -0
  227. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/__init__.py +0 -0
  228. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/_common.py +0 -0
  229. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/server.py +0 -0
  230. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/__init__.py +0 -0
  231. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/composite.py +0 -0
  232. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/events.py +0 -0
  233. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/matches.py +0 -0
  234. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/news.py +0 -0
  235. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/players.py +0 -0
  236. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/series.py +0 -0
  237. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/teams.py +0 -0
  238. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/py.typed +0 -0
  239. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/validators.py +0 -0
  240. {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/wrangler.jsonc +0 -0
@@ -5,11 +5,15 @@ on:
5
5
  paths:
6
6
  - 'official-docs/**'
7
7
  - 'docs-py/**'
8
+ - 'src/**'
9
+ - 'scripts/**'
8
10
  push:
9
11
  branches: [main]
10
12
  paths:
11
13
  - 'official-docs/**'
12
14
  - 'docs-py/**'
15
+ - 'src/**'
16
+ - 'scripts/**'
13
17
 
14
18
  jobs:
15
19
  check:
@@ -24,6 +28,9 @@ jobs:
24
28
  - name: Install package
25
29
  run: uv sync --group dev --all-extras
26
30
 
31
+ - name: Validate documentation schemas against Python library
32
+ run: uv run scripts/check_docs_schema.py
33
+
27
34
  # Restore fixture cache scoped to the current ref (branch or main) and version prefix (v3).
28
35
  # This ensures caches are never shared across unrelated branches or older versions.
29
36
  - name: Restore fixture cache
@@ -75,3 +75,29 @@ jobs:
75
75
 
76
76
  - name: Publish to PyPI
77
77
  uses: pypa/gh-action-pypi-publish@release/v1
78
+
79
+ mcp-publish:
80
+ name: Publish to MCP Registry
81
+ runs-on: ubuntu-latest
82
+ needs:
83
+ - pypi-publish
84
+
85
+ permissions:
86
+ id-token: write
87
+ contents: read
88
+
89
+ steps:
90
+ - uses: actions/checkout@v7
91
+
92
+ - name: Install mcp-publisher
93
+ run: |
94
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
95
+
96
+ - name: Wait for PyPI propagation
97
+ run: sleep 15
98
+
99
+ - name: Authenticate to MCP Registry
100
+ run: ./mcp-publisher login github-oidc
101
+
102
+ - name: Publish to MCP Registry
103
+ run: ./mcp-publisher publish
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [3.1.1] - 2026-11-13
9
+
10
+ ### Added
11
+
12
+ - **Official MCP Registry Manifest** — Added `server.json` compliant with the Model Context Protocol registry schema (`registry.modelcontextprotocol.io`), registering `io.github.vanshbordia/vlrdevapi` with stdio transport and `uvx` runtime arguments.
13
+ - **Automated MCP Registry Publishing Workflow** — Extended GitHub Actions release pipeline (`publish.yml`) with automated OIDC authentication (`id-token: write`) via `mcp-publisher`.
14
+ - **PyPI MCP Ownership Verification** — Added package ownership verification marker to `README.md`.
15
+
16
+ ### Fixed
17
+
18
+ - **MCP Registry CLI Entrypoint** — Expose `vlrdevapi` console script alias to `vlrdevapi.mcp.server:main` alongside `vlrdevapi-mcp` so MCP registry clients launch the server using the package identifier.
19
+ - **Windows Timezone Support** — Added `tzdata>=2025.1; sys_platform == 'win32'` as a core dependency to resolve `ZoneInfoNotFoundError` when running `vlrdevapi` or `uvx` on Windows.
20
+
21
+ ### Changed
22
+
23
+ - **Dependency Upgrades** — Bumped core Python dependencies (Pydantic, Pandas, Polars, PyArrow, Pytest, Ruff) and documentation website dependencies (Next.js 16.4.6, Fumadocs 16.19, Tailwind CSS 4.3.5).
24
+
8
25
  ## [3.1.0] - 2026-10-25
9
26
 
10
27
  ### Added
@@ -485,6 +502,7 @@ The API surface, module structure, and type system are all new.
485
502
  - `aiohttp` and `asyncio` dependencies.
486
503
  - v1.x match/event/team parsing modules.
487
504
 
505
+ [3.1.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v3.1.0...v3.1.1
488
506
  [3.1.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v3.0.0...v3.1.0
489
507
  [3.0.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.4.1...v3.0.0
490
508
  [2.4.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.4.0...v2.4.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vlrdevapi
3
- Version: 3.1.0
3
+ Version: 3.1.1
4
4
  Summary: A Python library to scrape data from vlr.gg
5
5
  Project-URL: Homepage, https://vlrdevapi.pages.dev/
6
6
  Project-URL: Repository, https://github.com/Vanshbordia/vlrdevapi
@@ -42,6 +42,7 @@ Requires-Python: >=3.11
42
42
  Requires-Dist: httpx>=0.28.1
43
43
  Requires-Dist: pydantic>=2.14.1
44
44
  Requires-Dist: selectolax>=1.0.0
45
+ Requires-Dist: tzdata>=2025.1; sys_platform == 'win32'
45
46
  Provides-Extra: all
46
47
  Requires-Dist: mcp>=1.2.0; extra == 'all'
47
48
  Requires-Dist: openpyxl>=3.1.0; extra == 'all'
@@ -88,6 +89,8 @@ Description-Content-Type: text/markdown
88
89
 
89
90
  </div>
90
91
 
92
+ <!-- mcp-name: io.github.vanshbordia/vlrdevapi -->
93
+
91
94
  ---
92
95
 
93
96
  **vlrdevapi** is a high-performance, type-safe Python client for extracting and analyzing Valorant esports data from [VLR.gg](https://www.vlr.gg/). Built for competitive analysts, data scientists, Discord bot creators, and esports engineers, it delivers structured [Pydantic](https://docs.pydantic.dev/) models with full type hinting, built-in rate limiting, automatic retries, and comprehensive coverage across all VLR.gg entities.
@@ -373,7 +376,7 @@ print(players.to_markdown())
373
376
 
374
377
  ## Model Context Protocol (MCP) Server
375
378
 
376
- VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents:
379
+ VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents (`io.github.vanshbordia/vlrdevapi` on the official MCP Registry):
377
380
 
378
381
  ```bash
379
382
  # Run server over stdio
@@ -17,6 +17,8 @@
17
17
 
18
18
  </div>
19
19
 
20
+ <!-- mcp-name: io.github.vanshbordia/vlrdevapi -->
21
+
20
22
  ---
21
23
 
22
24
  **vlrdevapi** is a high-performance, type-safe Python client for extracting and analyzing Valorant esports data from [VLR.gg](https://www.vlr.gg/). Built for competitive analysts, data scientists, Discord bot creators, and esports engineers, it delivers structured [Pydantic](https://docs.pydantic.dev/) models with full type hinting, built-in rate limiting, automatic retries, and comprehensive coverage across all VLR.gg entities.
@@ -302,7 +304,7 @@ print(players.to_markdown())
302
304
 
303
305
  ## Model Context Protocol (MCP) Server
304
306
 
305
- VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents:
307
+ VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents (`io.github.vanshbordia/vlrdevapi` on the official MCP Registry):
306
308
 
307
309
  ```bash
308
310
  # Run server over stdio
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vlrdevapi"
7
- version = "3.1.0"
7
+ version = "3.1.1"
8
8
  description = "A Python library to scrape data from vlr.gg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -37,6 +37,7 @@ dependencies = [
37
37
  "httpx>=0.28.1",
38
38
  "pydantic>=2.14.1",
39
39
  "selectolax>=1.0.0",
40
+ "tzdata>=2025.1; sys_platform == 'win32'",
40
41
  ]
41
42
 
42
43
  [project.urls]
@@ -80,6 +81,7 @@ all = [
80
81
  ]
81
82
 
82
83
  [project.scripts]
84
+ vlrdevapi = "vlrdevapi.mcp.server:main"
83
85
  vlrdevapi-mcp = "vlrdevapi.mcp.server:main"
84
86
 
85
87
  [dependency-groups]
@@ -123,4 +125,3 @@ exclude = [
123
125
  "/.pytest_cache",
124
126
  "/.ruff_cache",
125
127
  ]
126
-
@@ -0,0 +1,478 @@
1
+ """Validate documentation TypeTable schemas against live Python library models.
2
+
3
+ Performs static and dynamic introspection of vlrdevapi against the official
4
+ documentation (English and all translations: es, pt-br, zh-cn).
5
+
6
+ Checks:
7
+ 1. Python Signatures vs English Docs:
8
+ - Ensures all function parameters in vlrdevapi match the '## Parameters' table.
9
+ 2. Live Python Models vs English Docs:
10
+ - Introspects Pydantic models and Enums from vlrdevapi.
11
+ - Verifies that every documented field exists on the live Python model.
12
+ - Catches phantom / outdated fields (e.g. 'dates', 'tag', 'score' on wrong models).
13
+ 3. Cross-Language Parity (English vs es, pt-br, zh-cn):
14
+ - Verifies that all translations have the exact same number of tables.
15
+ - Verifies that tables appear in the exact same sequence.
16
+ - Verifies that fields in every table match the exact same order and types.
17
+
18
+ CI/CD usage:
19
+ uv run scripts/check_docs_schema.py
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import inspect
26
+ import re
27
+ import sys
28
+ from enum import Enum
29
+ from pathlib import Path
30
+ from typing import Any, NamedTuple
31
+
32
+ from pydantic import BaseModel
33
+
34
+ import vlrdevapi
35
+
36
+ REPO_ROOT = Path(__file__).resolve().parent.parent
37
+ DOCS_DIR = REPO_ROOT / "official-docs" / "content" / "docs" / "reference"
38
+ LANGUAGES = ("es", "pt-br", "zh-cn")
39
+
40
+
41
+ class FieldDef(NamedTuple):
42
+ name: str
43
+ type_str: str
44
+ default_str: str | None
45
+ required: bool
46
+ line: int
47
+
48
+
49
+ class TableDef(NamedTuple):
50
+ heading: str
51
+ model_name: str | None
52
+ fields: dict[str, FieldDef]
53
+ start_line: int
54
+
55
+
56
+ class DocDef(NamedTuple):
57
+ path: Path
58
+ sig: str | None
59
+ ret_name: str | None
60
+ tables: list[TableDef]
61
+
62
+
63
+ class Issue(NamedTuple):
64
+ file_path: Path
65
+ line: int
66
+ category: str
67
+ table_name: str
68
+ message: str
69
+ expected: str | None = None
70
+ found: str | None = None
71
+
72
+
73
+ def get_all_subclasses(cls: type) -> set[type]:
74
+ """Recursively collect all subclasses defined under vlrdevapi."""
75
+ subclasses = set()
76
+ for sub in cls.__subclasses__():
77
+ if sub.__module__.startswith("vlrdevapi"):
78
+ subclasses.add(sub)
79
+ subclasses.update(get_all_subclasses(sub))
80
+ return subclasses
81
+
82
+
83
+ def build_type_catalog() -> tuple[dict[str, type[BaseModel]], dict[str, type[Enum]]]:
84
+ """Dynamically discover all Pydantic models and Enums from vlrdevapi."""
85
+ models: dict[str, type[BaseModel]] = {
86
+ cls.__name__: cls for cls in get_all_subclasses(BaseModel)
87
+ }
88
+ enums: dict[str, type[Enum]] = {
89
+ cls.__name__: cls for cls in get_all_subclasses(Enum)
90
+ }
91
+ return models, enums
92
+
93
+
94
+ def parse_mdx(file_path: Path) -> DocDef:
95
+ """Parse an MDX file into structured table definitions with line numbers."""
96
+ content = file_path.read_text(encoding="utf-8")
97
+ lines = content.splitlines()
98
+
99
+ # Extract signature e.g. matches.completed(
100
+ sig_m = re.search(r"##\s+Signature\s+```python\s*([a-zA-Z0-9_\.]+)\(", content)
101
+ sig = sig_m.group(1) if sig_m else None
102
+
103
+ # Extract return type name if present in ## Returns - Model
104
+ ret_m = re.search(r"##\s+(?:Returns|Retorno|Retorno -|返回类型)\s+-\s+`?([a-zA-Z0-9_]+)`?", content)
105
+ ret_name = ret_m.group(1) if ret_m else None
106
+
107
+ # Locate each <TypeTable ... />
108
+ table_matches = list(re.finditer(r"<TypeTable\s+type=\{\{(.*?)\}\}\s*/>", content, re.DOTALL))
109
+ tables: list[TableDef] = []
110
+
111
+ for m in table_matches:
112
+ start_char = m.start()
113
+ start_line = content[:start_char].count("\n") + 1
114
+
115
+ # Look backward for closest markdown heading
116
+ pre_text = content[:start_char]
117
+ headings = list(re.finditer(r"(#{2,3}\s+[^\n]+)", pre_text))
118
+ last_heading = headings[-1].group(1).strip() if headings else "Unknown Section"
119
+
120
+ # Check for model name in heading: e.g. `CompletedMatchEntry` Fields
121
+ name_matches = re.findall(r"`([a-zA-Z0-9_]+)`", last_heading)
122
+ inferred_model = name_matches[0] if name_matches else None
123
+
124
+ # Parse fields
125
+ body = m.group(1)
126
+ body_start_line = start_line
127
+ field_matches = re.finditer(r"[\"']?([a-zA-Z0-9_]+)[\"']?\s*:\s*\{([^}]+)\}", body)
128
+ fields: dict[str, FieldDef] = {}
129
+
130
+ for fm in field_matches:
131
+ fname = fm.group(1)
132
+ fbody = fm.group(2)
133
+ fline = body_start_line + body[:fm.start()].count("\n")
134
+
135
+ tm = re.search(r"type\s*:\s*[\"']([^\"']+)[\"']", fbody)
136
+ type_str = tm.group(1) if tm else ""
137
+
138
+ dm = re.search(r"default\s*:\s*([^\n,]+)", fbody)
139
+ default_str = dm.group(1).strip() if dm else None
140
+
141
+ req_m = re.search(r"required\s*:\s*(true|false)", fbody, re.IGNORECASE)
142
+ required = req_m.group(1).lower() == "true" if req_m else False
143
+
144
+ fields[fname] = FieldDef(
145
+ name=fname,
146
+ type_str=type_str,
147
+ default_str=default_str,
148
+ required=required,
149
+ line=fline,
150
+ )
151
+
152
+ tables.append(TableDef(
153
+ heading=last_heading,
154
+ model_name=inferred_model,
155
+ fields=fields,
156
+ start_line=start_line,
157
+ ))
158
+
159
+ return DocDef(
160
+ path=file_path,
161
+ sig=sig,
162
+ ret_name=ret_name,
163
+ tables=tables,
164
+ )
165
+
166
+
167
+ def resolve_callable(sig: str) -> Any:
168
+ """Dynamically resolve a Python callable from vlrdevapi using dot path."""
169
+ obj: Any = vlrdevapi
170
+ for part in sig.split("."):
171
+ if not hasattr(obj, part):
172
+ return None
173
+ obj = getattr(obj, part)
174
+
175
+ # If it is a namespace class with __call__, get __call__
176
+ if hasattr(obj, "__call__") and not inspect.isfunction(obj) and not inspect.ismethod(obj):
177
+ return obj.__call__
178
+ return obj
179
+
180
+
181
+ def validate_doc_against_python(
182
+ doc: DocDef,
183
+ model_catalog: dict[str, type[BaseModel]],
184
+ enum_catalog: dict[str, type[Enum]],
185
+ ) -> list[Issue]:
186
+ """Validate canonical English MDX against live Python signatures and models."""
187
+ issues: list[Issue] = []
188
+ if not doc.sig:
189
+ return issues
190
+
191
+ fn = resolve_callable(doc.sig)
192
+ if fn is None:
193
+ issues.append(Issue(
194
+ file_path=doc.path,
195
+ line=1,
196
+ category="UNRESOLVED_CALLABLE",
197
+ table_name="Signature",
198
+ message=f"Callable '{doc.sig}' could not be resolved from vlrdevapi",
199
+ ))
200
+ return issues
201
+
202
+ # 1. Parameter Table vs Signature
203
+ try:
204
+ sig_obj = inspect.signature(fn)
205
+ py_params = [p for p in sig_obj.parameters if p != "self"]
206
+ except (ValueError, TypeError):
207
+ py_params = []
208
+
209
+ param_tables = [t for t in doc.tables if "Parameters" in t.heading]
210
+ if param_tables:
211
+ ptable = param_tables[0]
212
+ doc_params = list(ptable.fields.keys())
213
+ missing_params = set(py_params) - set(doc_params)
214
+ extra_params = set(doc_params) - set(py_params)
215
+
216
+ for mp in sorted(missing_params):
217
+ param_obj = sig_obj.parameters.get(mp)
218
+ default_val = param_obj.default if param_obj else None
219
+ issues.append(Issue(
220
+ file_path=doc.path,
221
+ line=ptable.start_line,
222
+ category="MISSING_PARAMETER",
223
+ table_name=ptable.heading,
224
+ message=f"Parameter '{mp}' exists in Python signature {doc.sig}() but is missing from Parameters table",
225
+ expected=f"{mp} (default: {default_val})",
226
+ ))
227
+
228
+ for ep in sorted(extra_params):
229
+ fdef = ptable.fields[ep]
230
+ issues.append(Issue(
231
+ file_path=doc.path,
232
+ line=fdef.line,
233
+ category="EXTRA_PARAMETER",
234
+ table_name=ptable.heading,
235
+ message=f"Parameter '{ep}' documented in Parameters table does not exist in Python signature {doc.sig}()",
236
+ found=ep,
237
+ ))
238
+
239
+ if not missing_params and not extra_params and py_params != doc_params:
240
+ issues.append(Issue(
241
+ file_path=doc.path,
242
+ line=ptable.start_line,
243
+ category="PARAMETER_ORDER_MISMATCH",
244
+ table_name=ptable.heading,
245
+ message="Parameter order mismatch between Python signature and documentation",
246
+ expected=str(py_params),
247
+ found=str(doc_params),
248
+ ))
249
+
250
+ # 2. Documented Models vs Live Python Models / Enums
251
+ for table in doc.tables:
252
+ if "Parameters" in table.heading:
253
+ continue
254
+
255
+ # Extract all candidate names from heading
256
+ names = re.findall(r"`([a-zA-Z0-9_]+)`", table.heading)
257
+ if not names and doc.ret_name and "Returns" in table.heading:
258
+ names = [doc.ret_name]
259
+
260
+ matched_model: type[BaseModel] | None = None
261
+ matched_enum: type[Enum] | None = None
262
+
263
+ # Check candidate names (try in order, preferring more specific e.g. SideStats over PlayerStats)
264
+ for n in reversed(names):
265
+ if n in model_catalog:
266
+ matched_model = model_catalog[n]
267
+ break
268
+ if n in enum_catalog:
269
+ matched_enum = enum_catalog[n]
270
+ break
271
+
272
+ if matched_model is not None:
273
+ py_fields = matched_model.model_fields
274
+ doc_fields = table.fields
275
+
276
+ # Check for extra fields documented that do NOT exist in the Python model
277
+ for doc_fname, fdef in doc_fields.items():
278
+ if doc_fname not in py_fields:
279
+ issues.append(Issue(
280
+ file_path=doc.path,
281
+ line=fdef.line,
282
+ category="PHANTOM_FIELD",
283
+ table_name=matched_model.__name__,
284
+ message=f"Field '{doc_fname}' is documented in table '{matched_model.__name__}', but does NOT exist on the Python model '{matched_model.__module__}.{matched_model.__name__}'",
285
+ found=doc_fname,
286
+ expected=f"One of: {sorted(py_fields.keys())}",
287
+ ))
288
+
289
+ elif matched_enum is not None:
290
+ enum_members = set(matched_enum.__members__.keys())
291
+ doc_fields = table.fields
292
+
293
+ for doc_mname, fdef in doc_fields.items():
294
+ if doc_mname not in enum_members:
295
+ issues.append(Issue(
296
+ file_path=doc.path,
297
+ line=fdef.line,
298
+ category="PHANTOM_ENUM_MEMBER",
299
+ table_name=matched_enum.__name__,
300
+ message=f"Enum member '{doc_mname}' is documented in '{matched_enum.__name__}', but does not exist in Python enum '{matched_enum.__module__}.{matched_enum.__name__}'",
301
+ found=doc_mname,
302
+ expected=f"One of: {sorted(enum_members)}",
303
+ ))
304
+
305
+ return issues
306
+
307
+
308
+ def validate_translations_against_en(en_doc: DocDef, tr_doc: DocDef, lang: str) -> list[Issue]:
309
+ """Validate a translation MDX against the canonical English MDX."""
310
+ issues: list[Issue] = []
311
+
312
+ # 1. Table count parity
313
+ if len(en_doc.tables) != len(tr_doc.tables):
314
+ issues.append(Issue(
315
+ file_path=tr_doc.path,
316
+ line=1,
317
+ category="TABLE_COUNT_MISMATCH",
318
+ table_name="Document",
319
+ message=f"Table count mismatch: English document has {len(en_doc.tables)} tables, but {lang} translation has {len(tr_doc.tables)} tables",
320
+ expected=f"{len(en_doc.tables)} tables",
321
+ found=f"{len(tr_doc.tables)} tables",
322
+ ))
323
+ return issues
324
+
325
+ # 2. Table-by-table structure, keys, order, and types
326
+ for idx, (en_table, tr_table) in enumerate(zip(en_doc.tables, tr_doc.tables)):
327
+ t_name = en_table.model_name or en_table.heading
328
+ en_keys = list(en_table.fields.keys())
329
+ tr_keys = list(tr_table.fields.keys())
330
+
331
+ missing_in_tr = set(en_keys) - set(tr_keys)
332
+ extra_in_tr = set(tr_keys) - set(en_keys)
333
+
334
+ for m_key in sorted(missing_in_tr):
335
+ en_fdef = en_table.fields[m_key]
336
+ issues.append(Issue(
337
+ file_path=tr_doc.path,
338
+ line=tr_table.start_line,
339
+ category="MISSING_TRANSLATION_FIELD",
340
+ table_name=t_name,
341
+ message=f"Table {idx} ({t_name}): Missing field '{m_key}' in {lang} translation",
342
+ expected=f"Field '{m_key}' with type '{en_fdef.type_str}'",
343
+ ))
344
+
345
+ for e_key in sorted(extra_in_tr):
346
+ tr_fdef = tr_table.fields[e_key]
347
+ issues.append(Issue(
348
+ file_path=tr_doc.path,
349
+ line=tr_fdef.line,
350
+ category="EXTRA_TRANSLATION_FIELD",
351
+ table_name=t_name,
352
+ message=f"Table {idx} ({t_name}): Extra field '{e_key}' found in {lang} translation that does not exist in English document",
353
+ found=e_key,
354
+ ))
355
+
356
+ # Check exact field ordering
357
+ if not missing_in_tr and not extra_in_tr and en_keys != tr_keys:
358
+ # Pinpoint the first index where order diverges
359
+ mismatch_idx = next(i for i, (ek, tk) in enumerate(zip(en_keys, tr_keys)) if ek != tk)
360
+ issues.append(Issue(
361
+ file_path=tr_doc.path,
362
+ line=tr_table.fields[tr_keys[mismatch_idx]].line,
363
+ category="FIELD_ORDER_MISMATCH",
364
+ table_name=t_name,
365
+ message=f"Table {idx} ({t_name}): Field ordering differs from English at position {mismatch_idx}",
366
+ expected=f"Field '{en_keys[mismatch_idx]}'",
367
+ found=f"Field '{tr_keys[mismatch_idx]}'",
368
+ ))
369
+
370
+ # Check field types
371
+ for k in en_keys:
372
+ if k in tr_table.fields:
373
+ en_f = en_table.fields[k]
374
+ tr_f = tr_table.fields[k]
375
+ if en_f.type_str != tr_f.type_str:
376
+ issues.append(Issue(
377
+ file_path=tr_doc.path,
378
+ line=tr_f.line,
379
+ category="TYPE_MISMATCH",
380
+ table_name=t_name,
381
+ message=f"Table {idx} ({t_name}): Field '{k}' type mismatch",
382
+ expected=en_f.type_str,
383
+ found=tr_f.type_str,
384
+ ))
385
+
386
+ return issues
387
+
388
+
389
+ def main() -> int:
390
+ parser = argparse.ArgumentParser(description="Validate documentation schemas against live Python library.")
391
+ parser.add_argument("--strict", action="store_true", default=True, help="Exit with code 1 if any issues are found.")
392
+ args = parser.parse_args()
393
+
394
+ model_catalog, enum_catalog = build_type_catalog()
395
+
396
+ en_files = sorted(
397
+ p for p in DOCS_DIR.rglob("*.mdx")
398
+ if not any(p.name.endswith(f".{lang}.mdx") for lang in LANGUAGES)
399
+ and p.name != "index.mdx"
400
+ and p.parent.name != "mcp"
401
+ )
402
+
403
+ all_issues: list[Issue] = []
404
+ total_tables = 0
405
+ total_translations = 0
406
+
407
+ print("============================================================")
408
+ print("VLRDEVAPI DOCUMENTATION SCHEMA VALIDATOR")
409
+ print(f"Loaded {len(model_catalog)} Pydantic models & {len(enum_catalog)} Enums from live library")
410
+ print(f"Auditing {len(en_files)} canonical reference docs across {len(LANGUAGES)} languages...")
411
+ print("============================================================\n")
412
+
413
+ for en_path in en_files:
414
+ en_doc = parse_mdx(en_path)
415
+ total_tables += len(en_doc.tables)
416
+
417
+ # 1. Validate English against live Python library
418
+ py_issues = validate_doc_against_python(en_doc, model_catalog, enum_catalog)
419
+ all_issues.extend(py_issues)
420
+
421
+ # 2. Validate each language translation against English
422
+ for lang in LANGUAGES:
423
+ tr_path = en_path.parent / f"{en_path.stem}.{lang}.mdx"
424
+ if not tr_path.exists():
425
+ all_issues.append(Issue(
426
+ file_path=en_path.parent / f"{en_path.stem}.{lang}.mdx",
427
+ line=1,
428
+ category="MISSING_FILE",
429
+ table_name="File",
430
+ message=f"Missing translation file: {tr_path.name}",
431
+ ))
432
+ continue
433
+
434
+ total_translations += 1
435
+ tr_doc = parse_mdx(tr_path)
436
+ total_tables += len(tr_doc.tables)
437
+ tr_issues = validate_translations_against_en(en_doc, tr_doc, lang)
438
+ all_issues.extend(tr_issues)
439
+
440
+ # Report results
441
+ if all_issues:
442
+ print(f"FAIL: Found {len(all_issues)} schema issue(s) across documentation:\n")
443
+ # Group issues by file
444
+ by_file: dict[Path, list[Issue]] = {}
445
+ for iss in all_issues:
446
+ by_file.setdefault(iss.file_path, []).append(iss)
447
+
448
+ for fpath, issues_list in sorted(by_file.items(), key=lambda x: str(x[0])):
449
+ rel_path = fpath.relative_to(REPO_ROOT) if fpath.is_relative_to(REPO_ROOT) else fpath
450
+ print(f"--- {rel_path} ({len(issues_list)} issue{'s' if len(issues_list) > 1 else ''}) ---")
451
+ for iss in issues_list:
452
+ print(f" Line {iss.line} [{iss.category}] in {iss.table_name}:")
453
+ print(f" Issue: {iss.message}")
454
+ if iss.expected is not None:
455
+ print(f" Expected: {iss.expected}")
456
+ if iss.found is not None:
457
+ print(f" Found: {iss.found}")
458
+ print()
459
+
460
+ print("============================================================")
461
+ print("VALIDATION FAILED")
462
+ print(f" Total issues: {len(all_issues)} in {len(by_file)} files")
463
+ print("============================================================")
464
+ return 1
465
+
466
+ print("SUCCESS: All documentation tables and translation schemas are 100% in sync with the live library!")
467
+ print("============================================================")
468
+ print("VALIDATION SUMMARY")
469
+ print(f" Canonical English docs checked: {len(en_files)}")
470
+ print(f" Translation files verified: {total_translations}")
471
+ print(f" Total tables validated: {total_tables}")
472
+ print(" Schema mismatches: 0")
473
+ print("============================================================")
474
+ return 0
475
+
476
+
477
+ if __name__ == "__main__":
478
+ sys.exit(main())
@@ -279,6 +279,11 @@ ROUTES += [
279
279
  "team/8050/overview_light.html",
280
280
  {},
281
281
  ),
282
+ (
283
+ "/team/4330/fancy-united-esports",
284
+ "team/4330/overview_light.html",
285
+ {},
286
+ ),
282
287
  (
283
288
  "/team/matches/120/?core_id=all&group=completed&page=2",
284
289
  "team/120/completed_matches_page2.html",
@@ -491,8 +496,8 @@ for _gid in GAMES_BO5_2026:
491
496
  ]
492
497
 
493
498
  # ── Upcoming series (best_of + TBD map tabs) ─────────────────
494
- SERIES_ID_UPCOMING = 773433
495
- SERIES_SLUG_UPCOMING = "mibr-gc-vs-gentle-mates-gc-game-changers-2026-championship-s-o-paulo-ubsf"
499
+ SERIES_ID_UPCOMING = 784392
500
+ SERIES_SLUG_UPCOMING = "fancy-united-esports-vs-original-gaming-predator-league-2027-vietnam-gf"
496
501
 
497
502
  ROUTES.append(
498
503
  (
@@ -501,6 +506,13 @@ ROUTES.append(
501
506
  {},
502
507
  ),
503
508
  )
509
+ ROUTES.append(
510
+ (
511
+ "/773433/mibr-gc-vs-gentle-mates-gc-game-changers-2026-championship-s-o-paulo-ubsf",
512
+ "series/773433/overview.html",
513
+ {},
514
+ ),
515
+ )
504
516
 
505
517
  # ── Replay/logs tab fixtures (series.replay) ──────────────────
506
518
  SERIES_LOG_SAMPLES = [