vlrdevapi 2.2.0__tar.gz → 2.4.0__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 (215) hide show
  1. vlrdevapi-2.4.0/CHANGELOG.md +287 -0
  2. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/PKG-INFO +14 -3
  3. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/README.md +12 -1
  4. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/pyproject.toml +1 -1
  5. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/scripts/check_mdx_examples.py +1 -1
  6. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/scripts/download_fixtures.py +157 -2
  7. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/__init__.py +17 -7
  8. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/__init__.pyi +15 -0
  9. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_client.py +2 -0
  10. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/models.py +4 -0
  11. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/parser.py +47 -3
  12. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/common.py +30 -6
  13. vlrdevapi-2.4.0/src/vlrdevapi/_news/__init__.py +5 -0
  14. vlrdevapi-2.4.0/src/vlrdevapi/_news/article/__init__.py +4 -0
  15. vlrdevapi-2.4.0/src/vlrdevapi/_news/article/models.py +41 -0
  16. vlrdevapi-2.4.0/src/vlrdevapi/_news/article/namespace.py +86 -0
  17. vlrdevapi-2.4.0/src/vlrdevapi/_news/article/namespace.pyi +19 -0
  18. vlrdevapi-2.4.0/src/vlrdevapi/_news/article/parser.py +457 -0
  19. vlrdevapi-2.4.0/src/vlrdevapi/_news/common.py +48 -0
  20. vlrdevapi-2.4.0/src/vlrdevapi/_news/list/__init__.py +4 -0
  21. vlrdevapi-2.4.0/src/vlrdevapi/_news/list/models.py +55 -0
  22. vlrdevapi-2.4.0/src/vlrdevapi/_news/list/namespace.py +85 -0
  23. vlrdevapi-2.4.0/src/vlrdevapi/_news/list/namespace.pyi +19 -0
  24. vlrdevapi-2.4.0/src/vlrdevapi/_news/list/parser.py +109 -0
  25. vlrdevapi-2.4.0/src/vlrdevapi/_news/namespace.py +127 -0
  26. vlrdevapi-2.4.0/src/vlrdevapi/_news/namespace.pyi +21 -0
  27. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/_utils.py +56 -2
  28. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/namespace.py +20 -7
  29. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/parser.py +7 -2
  30. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/models.py +29 -0
  31. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/namespace.py +3 -1
  32. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/parser.py +120 -46
  33. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/namespace.py +17 -5
  34. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/parser.py +6 -1
  35. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/paths.py +13 -0
  36. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/__init__.py +23 -1
  37. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/datetime.py +32 -0
  38. vlrdevapi-2.4.0/src/vlrdevapi/commons/regions.py +242 -0
  39. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/validators.py +1 -1
  40. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/uv.lock +1 -1
  41. vlrdevapi-2.2.0/CHANGELOG.md +0 -148
  42. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.gitattributes +0 -0
  43. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  44. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  45. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/cleanup-caches.yml +0 -0
  46. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/docs-check.yml +0 -0
  47. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/publish.yml +0 -0
  48. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/test.yml +0 -0
  49. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.gitignore +0 -0
  50. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.python-version +0 -0
  51. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.readthedocs.yaml +0 -0
  52. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/CODE_OF_CONDUCT.md +0 -0
  53. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/CONTRIBUTING.md +0 -0
  54. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/LICENCE +0 -0
  55. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/SECURITY.md +0 -0
  56. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/SUPPORT.md +0 -0
  57. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_base.py +0 -0
  58. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_cache.py +0 -0
  59. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/__init__.py +0 -0
  60. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/_common.py +0 -0
  61. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/__init__.py +0 -0
  62. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/namespace.py +0 -0
  63. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
  64. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/__init__.py +0 -0
  65. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/models.py +0 -0
  66. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/namespace.py +0 -0
  67. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
  68. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/parser.py +0 -0
  69. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/__init__.py +0 -0
  70. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/models.py +0 -0
  71. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/namespace.py +0 -0
  72. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
  73. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/parser.py +0 -0
  74. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/namespace.py +0 -0
  75. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/namespace.pyi +0 -0
  76. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/__init__.py +0 -0
  77. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/models.py +0 -0
  78. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/namespace.py +0 -0
  79. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
  80. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/parser.py +0 -0
  81. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/__init__.py +0 -0
  82. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/models.py +0 -0
  83. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/namespace.py +0 -0
  84. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
  85. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/parser.py +0 -0
  86. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/__init__.py +0 -0
  87. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/models.py +0 -0
  88. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/namespace.py +0 -0
  89. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
  90. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/parser.py +0 -0
  91. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/__init__.py +0 -0
  92. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
  93. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/models.py +0 -0
  94. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
  95. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
  96. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/parser.py +0 -0
  97. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/__init__.py +0 -0
  98. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/models.py +0 -0
  99. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/namespace.py +0 -0
  100. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
  101. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/parser.py +0 -0
  102. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/namespace.py +0 -0
  103. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/namespace.pyi +0 -0
  104. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
  105. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
  106. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
  107. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
  108. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/parser.py +0 -0
  109. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/__init__.py +0 -0
  110. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/_common_parsing.py +0 -0
  111. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/__init__.py +0 -0
  112. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/models.py +0 -0
  113. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/namespace.py +0 -0
  114. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
  115. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/parser.py +0 -0
  116. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/__init__.py +0 -0
  117. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/models.py +0 -0
  118. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/namespace.py +0 -0
  119. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
  120. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/parser.py +0 -0
  121. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/__init__.py +0 -0
  122. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/models.py +0 -0
  123. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/namespace.py +0 -0
  124. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
  125. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/parser.py +0 -0
  126. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/namespace.py +0 -0
  127. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/namespace.pyi +0 -0
  128. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/__init__.py +0 -0
  129. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/models.py +0 -0
  130. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/namespace.py +0 -0
  131. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
  132. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/parser.py +0 -0
  133. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/__init__.py +0 -0
  134. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/models.py +0 -0
  135. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/namespace.py +0 -0
  136. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
  137. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/parser.py +0 -0
  138. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/__init__.py +0 -0
  139. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/__init__.py +0 -0
  140. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/models.py +0 -0
  141. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
  142. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/__init__.py +0 -0
  143. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
  144. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/match_namespace.py +0 -0
  145. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
  146. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/namespace.py +0 -0
  147. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/namespace.pyi +0 -0
  148. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/__init__.py +0 -0
  149. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/models.py +0 -0
  150. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/namespace.py +0 -0
  151. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
  152. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/parser.py +0 -0
  153. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/__init__.py +0 -0
  154. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/models.py +0 -0
  155. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/namespace.py +0 -0
  156. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
  157. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/parser.py +0 -0
  158. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
  159. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/models.py +0 -0
  160. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
  161. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/__init__.py +0 -0
  162. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/models.py +0 -0
  163. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/namespace.py +0 -0
  164. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
  165. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/parser.py +0 -0
  166. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/__init__.py +0 -0
  167. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
  168. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
  169. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
  170. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
  171. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/parser.py +0 -0
  172. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/__init__.py +0 -0
  173. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/models.py +0 -0
  174. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/namespace.py +0 -0
  175. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
  176. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/parser.py +0 -0
  177. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/namespace.py +0 -0
  178. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/namespace.pyi +0 -0
  179. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/__init__.py +0 -0
  180. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/models.py +0 -0
  181. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/namespace.py +0 -0
  182. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
  183. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/parser.py +0 -0
  184. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/__init__.py +0 -0
  185. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/models.py +0 -0
  186. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/namespace.py +0 -0
  187. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
  188. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/parser.py +0 -0
  189. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/__init__.py +0 -0
  190. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/models.py +0 -0
  191. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/namespace.py +0 -0
  192. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
  193. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/parser.py +0 -0
  194. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
  195. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/models.py +0 -0
  196. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
  197. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
  198. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/parser.py +0 -0
  199. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
  200. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
  201. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
  202. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
  203. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/parser.py +0 -0
  204. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/__init__.py +0 -0
  205. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/pagination.py +0 -0
  206. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/team_enrichment.py +0 -0
  207. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/team_parsing.py +0 -0
  208. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/countries.py +0 -0
  209. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/mappings.py +0 -0
  210. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/prizes.py +0 -0
  211. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/timezone.py +0 -0
  212. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/exceptions.py +0 -0
  213. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/fetcher.py +0 -0
  214. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/py.typed +0 -0
  215. {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/wrangler.jsonc +0 -0
@@ -0,0 +1,287 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [2.4.0] - 2026-08-19
9
+
10
+ ### Added
11
+
12
+ - **Bo1 series support** — single-map series now resolve games from
13
+ `.vm-stats-game` containers when nav tabs are absent. `resolve_game_id`
14
+ raises `ParsingError` instead of silently returning wrong data on
15
+ out-of-range or empty game lists.
16
+ - **Event region resolution** — `_parse_breadcrumb` resolves `subregion=`
17
+ breadcrumb links (e.g. "Japan", "North America") to their parent region
18
+ via `commons.regions.resolve_subregion_to_region`. A new
19
+ `EventRegion.subregion` field preserves the original subregion name
20
+ (e.g. `"Japan"`); `None` for `region=` links.
21
+ - **Event country-to-region fallback** — when no breadcrumb region is
22
+ present, the `region_location` country flag is resolved via
23
+ `resolve_country_to_region` to infer the parent region.
24
+ - **Event region deduplication** — duplicate region entries (from both
25
+ `region=` and `subregion=` breadcrumbs for the same parent region)
26
+ are collapsed, preferring the entry that carries a subregion name.
27
+ - **`commons.regions` module** — new shared utilities for region
28
+ resolution: `VALID_REGIONS`, `SUBREGION_TO_REGION` (17 VCL/GC
29
+ subregions), `COUNTRY_TO_REGION` (~100+ countries),
30
+ `resolve_subregion_to_region`, and `resolve_country_to_region`.
31
+ - **`commons.__init__` re-exports** — `resolve_subregion_to_region`,
32
+ `resolve_country_to_region`, `VALID_REGIONS`, `SUBREGION_TO_REGION`,
33
+ and `COUNTRY_TO_REGION` are now re-exported from `vlrdevapi.commons`.
34
+ - **Series forfeit detection** — new `ForfeitInfo` model on
35
+ `SeriesInfo.forfeit` exposes `forfeited`, `team`, `team_id`, and
36
+ `reason` fields. Parser detects `"forfeited by TEAM"` in the
37
+ `match-header-vs-note` element and maps the team name to the
38
+ corresponding `team1`/`team2` for ID resolution. Note-level
39
+ forfeit text (e.g. "Blind Esports Forfeit Map 2") is also parsed.
40
+ - **Series match notes** — new `SeriesInfo.notes` list captures
41
+ non-veto `match-header-note` text such as technical pause warnings
42
+ and lobby remake notices.
43
+
44
+ ### Changed
45
+
46
+ - **Series economy enrichment** — team matching now tries abbreviation
47
+ names from `.ovw-player-tag` elements first (e.g. "SEN"), then falls
48
+ back to full team names. This fixes mismatches where the economy tab
49
+ shows a different abbreviation than the overview header.
50
+ - **Series rounds enrichment** — same abbreviation-first matching
51
+ strategy as economy.
52
+ - **Series info veto parsing** — Bo1 veto fallback now handles the
53
+ "bans + decider" pattern (e.g. `"Ban1 remains"`) in addition to
54
+ pick/ban. The PICK badge is excluded from map names via
55
+ `span.text(deep=False)`.
56
+ - **Series info notes parsing** — the parser now iterates all
57
+ `match-header-note` elements instead of only the first. This
58
+ correctly separates forfeit reasons, informational notes, and
59
+ map veto text when multiple notes are present.
60
+
61
+ ### Fixed
62
+
63
+ - **Economy bank/spend parsing** — `int()` calls on `.rnd-sq` `title`
64
+ attributes and `.rnd-num` text are wrapped in `try/except ValueError`;
65
+ malformed values default to `0` instead of crashing the parser.
66
+ - **Event region from location country** — events that only have a
67
+ "Location" label with a flag (no separate "Region" label) now resolve
68
+ the region from `location.country` via `resolve_country_to_region`.
69
+ Previously the country-to-region fallback only checked `region_location`.
70
+ - **Phantom pagination in matches** — terminal pages no longer report
71
+ `has_next_page = True` (from DaviAlcanfor PR #50).
72
+
73
+ ### Documentation
74
+
75
+ - **Event info reference** — `EventRegion` fields table now documents
76
+ the `subregion` field. Region example demonstrates subregion access.
77
+ `regions` field description explains deduplication and International
78
+ collapse.
79
+ - **Series info reference** — `ForfeitInfo` fields table documents the
80
+ new `forfeit` nested model. `notes` field description explains the
81
+ distinction between forfeit reasons and informational notes.
82
+ - **Series economy reference** — docstrings reference correct model
83
+ types (`RoundEconomyData`, `bank_team1`).
84
+ - **Series rounds reference** — docstrings reference correct model
85
+ types (`RoundData`, `winner_team_name`).
86
+ - **`commons.regions` module** — docstrings describe all mapping
87
+ dictionaries and resolution functions.
88
+ - **Project structure** — contributing guide updated with actual
89
+ underscore-prefixed directory names and new modules (`_news/`,
90
+ `_utils/`, `commons/regions.py`).
91
+ - **Module docstrings** — added to `economy/parser.py`,
92
+ `rounds/parser.py`, and `commons/__init__.py`.
93
+
94
+ ## [2.3.0] - 2026-08-11
95
+
96
+ ### Added
97
+
98
+ - **News namespace** — `vlrdevapi.news(page=1)` lists news items from
99
+ vlr.gg/news, each with `title`, `subtitle`, `link`, `country_name`,
100
+ `date`, and `author`, plus `has_next_page` and `page_number` on the
101
+ returned page.
102
+ - **News article content** — `vlrdevapi.news.article(article_id)` fetches
103
+ a single news article with its `title`, `author`, `date`, associated
104
+ `event_name`/`event_link`, and the full `content` body text. The body
105
+ is also available as Markdown via `content_md`, preserving headings,
106
+ lists, links, emphasis, and clip embeds.
107
+ - **News out-of-range pages** — requesting a news page beyond the last
108
+ available page (e.g. `page=176`) now raises `NotFoundError` instead of
109
+ silently returning an empty result.
110
+
111
+ ### Changed
112
+
113
+ - **News article Markdown links are absolute** — links in `content_md` are
114
+ prefixed with `https://www.vlr.gg` when they were relative (e.g.
115
+ `/player/5132/mada` → `https://www.vlr.gg/player/5132/mada`). Absolute
116
+ and `www.`-prefixed URLs are left untouched. `NewsArticle.event_link` is
117
+ now an absolute URL as well.
118
+
119
+ ### Fixed
120
+
121
+ - **News article clip embeds** — `content_md` clip embeds now link to the
122
+ directly openable watch URL (e.g. `https://clips.twitch.tv/<slug>`) for
123
+ Twitch clips, Twitch VODs, YouTube, and Soop embeds, instead of the embed
124
+ URL which could not be opened in a new tab.
125
+ - **News article Markdown whitespace/emphasis** — inline emphasis and bold
126
+ render correctly around links: source indentation whitespace no longer
127
+ produces double spaces, and `*`/`**` markers no longer wrap whitespace.
128
+ - **News article dates are timezone-aware** — the article date is parsed
129
+ through the shared `commons.parse_vlr_iso_datetime` helper. Offset-aware
130
+ `datetime` attributes are converted to UTC as-is; naive values are
131
+ localised to the client's `source_tz` (or UTC) instead of the local
132
+ machine timezone.
133
+ - **Unknown article IDs raise `NotFoundError`** — vlr.gg returns a generic
134
+ HTTP 200 page for non-existent article IDs; `news.article()` now detects
135
+ the missing article card and raises `NotFoundError` instead of returning
136
+ an empty article.
137
+ - **News article Markdown fidelity** — `content_md` now renders nested
138
+ lists, tables (as pipe tables), and inline `code` verbatim (whitespace
139
+ preserved). Links whose href is `www.`-prefixed are upgraded to
140
+ `https://`, fixing broken relative links in Markdown.
141
+ - **Documentation** — news reference pages updated (article, list, index),
142
+ plus the getting-started guide, main docs index, and README now cover the
143
+ news namespace; model/parser docstrings describe the current behaviour.
144
+
145
+ ## [2.2.0] - 2026-08-11
146
+
147
+ ### Changed
148
+
149
+ - **Events list dates without a year** — `event.list()` dates are rendered
150
+ on vlr.gg without a year (e.g. `"Jul 9 – Aug 23"`). These now use a
151
+ sentinel year of `2019` instead of the current year. Any parsed date with
152
+ `year < 2020` means the source omitted the year, so guard with
153
+ `event.start_date.year < 2020` before relying on it.
154
+ - **Series performance field renames (breaking)** — `AdvStatsEntry` fields
155
+ were renamed to descriptive names: `econ` → `economy`, `pl` → `plants`,
156
+ and `de` → `defuses`. Update any code referencing the old field names.
157
+ The series performance reference docs were updated to match.
158
+
159
+ ### Fixed
160
+
161
+ - **Python 3.13/3.15 date parsing deprecation** — `datetime.strptime` emits
162
+ a `DeprecationWarning` when parsing a day-of-month without a year (and will
163
+ change behavior in Python 3.15). All such parses now supply an explicit
164
+ year derived from context (reference date, page-header year, or the
165
+ sentinel above).
166
+ - **Series per-game stats by game number** — `players()`, `rounds()`,
167
+ `performance()`, and `economy()` game identifiers can now be a 1-based
168
+ game number within the series, resolved to the real VLR game ID from the
169
+ page's game nav tabs. A real VLR game ID is still accepted unchanged;
170
+ previously passing a game number produced empty results.
171
+ - **Series economy and rounds scope** — economy and round-by-round data are
172
+ now parsed from the selected game's section only. The economy parser
173
+ previously collected tables from the whole page, so a per-game request
174
+ could include data from other maps.
175
+ - **Series performance player IDs** — the performance tab renders player
176
+ cells without links, leaving player IDs as `0`. IDs are now recovered from
177
+ the series overview tab (keyed by team abbreviation and player name) and
178
+ attached to kill-matrix entries, advanced stats, and notable-round victims.
179
+
180
+ ## [2.1.0] - 2026-08-07
181
+
182
+ ### Added
183
+
184
+ - **Event teams player rosters** — `event.teams()` now includes a `players`
185
+ list on each `Team`, containing each player's `name` and `id` parsed from
186
+ the event page. The `players` field is always present and defaults to `[]`
187
+ when roster data is unavailable. A new `TeamPlayer` model was added and exposed
188
+ from the `_event.teams` submodule.
189
+
190
+ ### Changed
191
+
192
+ - **Documentation** — event teams reference updated with the new `players`
193
+ field, a `TeamPlayer` fields table, and a roster iteration example.
194
+
195
+ ## [2.0.1] - 2026-07-29
196
+
197
+ ### Fixed
198
+
199
+ - **Series player stats** — overview tab HTML changed from `<table>` to
200
+ `<div>` on vlr.gg; updated CSS selectors to match the new structure.
201
+ - **Player agent stats** — agent table class changed from `wf-table` to
202
+ `st-table.mod-agent-rows`; column count reduced from 17 to 16 (FKPR and
203
+ FDPR columns consolidated into a single FK:FD ratio column).
204
+ - **Player profile** — same agent table selector fix; the profile fallback
205
+ to `timespan=all` now works correctly.
206
+ - **Team fixtures** — updated stale test assertions for NRG Haven map
207
+ stats (games played, wins, losses, etc.) and total winnings to match
208
+ current vlr.gg data.
209
+
210
+ ### Removed
211
+
212
+ - `fkpr` and `fdpr` fields from `AgentStats` model — vlr.gg no longer
213
+ exposes separate First Kills Per Round / First Deaths Per Round columns.
214
+ The `first_kills` and `first_deaths` total fields remain available.
215
+
216
+ ### Changed
217
+
218
+ - **Documentation** — agents schema reference updated to reflect the
219
+ removal of `fkpr` and `fdpr`.
220
+
221
+ ## [2.0.0] - 2026-07-07
222
+
223
+ ### Major Rewrite
224
+
225
+ Complete rewrite of the library. v2.0.0 replaces the asynchronous
226
+ aiohttp-based v1.x with a synchronous httpx-based architecture.
227
+ The API surface, module structure, and type system are all new.
228
+
229
+ ### Added
230
+
231
+ - **Sync-first client** - `VLRClient` with context manager support and
232
+ thread-safe parallel enrichment for bulk operations.
233
+ - **Curried access pattern** - bind a player/team/series/event ID once,
234
+ then chain sub-methods without re-passing the ID.
235
+ - **Event namespace** - list with pagination and filtering (tier, region,
236
+ status), plus info, matches, stages, standings, and teams.
237
+ - **Match listing namespace** - live, upcoming (paginated), and completed
238
+ (paginated) match feeds with team enrichment.
239
+ - **Player namespace** - info, teams (current/past), agent stats
240
+ (30d/60d/90d/all), match history (paginated), and consolidated profile.
241
+ - **Series namespace** - info (veto, games, scores), player stats per game,
242
+ round-by-round data, performance (kill matrices, advanced stats),
243
+ economy (buy types, spend analysis), and VOD links.
244
+ - **Team namespace** - info, roster, stats (per-map with optional agent
245
+ composition), placements, transactions, completed and upcoming matches.
246
+ - **Pydantic v2 models** - fully typed with Google-style docstrings and
247
+ field descriptions.
248
+ - **Built-in resilience** - configurable retry with exponential/linear/
249
+ constant backoff, jitter, and token-bucket rate limiting.
250
+ - **Thread-safe LRU cache** - bounded `LRUCache` for team data to avoid
251
+ redundant HTTP requests during enrichment.
252
+ - **Input validation** - `@sanitize_and_validate` decorator with positive-ID
253
+ checks and Pydantic type coercion.
254
+ - **Custom exceptions** - typed hierarchy (`NotFoundError`, `RateLimitError`,
255
+ `ParsingError`, `ValidationError`, etc.) for clean error handling.
256
+ - **Documentation** - Zensical/MkDocs site via `docs-py/` (ReadTheDocs)
257
+ with auto-generated API reference from docstrings.
258
+ - **Official website** - Next.js + Fumadocs site via `official-docs/`
259
+ (Cloudflare Pages) with interactive code examples.
260
+ - **Doc validation** - `scripts/check_mdx_examples.py` validates syntax,
261
+ imports, and live execution of all code examples in docs.
262
+
263
+ ### Changed
264
+
265
+ - **Architecture** - migrated from async aiohttp to synchronous httpx.
266
+ No more `await` or `async with` required.
267
+ - **No explicit session management** - `VLRClient` handles connection
268
+ pooling, retries, and rate limiting internally.
269
+ - **Type system** - replaced raw dict returns with Pydantic v2 models
270
+ for all endpoints.
271
+ - **Build system** - moved from setuptools to hatchling with `uv` for
272
+ dependency management.
273
+ - **Python requirement** - raised minimum to 3.11.
274
+
275
+ ### Removed
276
+
277
+ - All v1.x async endpoints (`get_event`, `get_team`, `get_player`, etc.)
278
+ are removed. See the new module-level or client-based API.
279
+ - `aiohttp` and `asyncio` dependencies.
280
+ - v1.x match/event/team parsing modules.
281
+
282
+ [2.4.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.3.0...v2.4.0
283
+ [2.3.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.2.0...v2.3.0
284
+ [2.2.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.1.0...v2.2.0
285
+ [2.1.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.0.1...v2.1.0
286
+ [2.0.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.0.0...v2.0.1
287
+ [2.0.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v1.6.2...v2.0.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: vlrdevapi
3
- Version: 2.2.0
3
+ Version: 2.4.0
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
@@ -68,7 +68,7 @@ Access Valorant esports data from VLR.gg with a clean, type-safe Python API. Get
68
68
 
69
69
  ## Features
70
70
 
71
- - **Complete Data Access** - Events, matches, players, teams, and series
71
+ - **Complete Data Access** - Events, matches, players, teams, series, and news
72
72
  - **Type-Safe** - Pydantic models with rich type hints and field descriptions
73
73
  - **Production-Ready** - Retry logic, rate limiting, and LRU-cached enrichment
74
74
  - **Curried Access** - Bind a player/team/series ID once, then call sub-methods without re-passing
@@ -181,6 +181,17 @@ performance = s.performance() # Advanced performance stats
181
181
  economy = s.economy(game_id=1) # Economy data
182
182
  ```
183
183
 
184
+ ### News
185
+
186
+ ```python
187
+ page = client.news(page=1) # Browse the news listing
188
+ for item in page.news[:5]:
189
+ print(item.title)
190
+
191
+ article = client.news.article(734100) # Fetch a full article
192
+ print(article.content_md) # Article body as Markdown
193
+ ```
194
+
184
195
  ## Documentation
185
196
 
186
197
  - **Official Docs** - [vlrdevapi.pages.dev/docs](https://vlrdevapi.pages.dev/docs)
@@ -20,7 +20,7 @@ Access Valorant esports data from VLR.gg with a clean, type-safe Python API. Get
20
20
 
21
21
  ## Features
22
22
 
23
- - **Complete Data Access** - Events, matches, players, teams, and series
23
+ - **Complete Data Access** - Events, matches, players, teams, series, and news
24
24
  - **Type-Safe** - Pydantic models with rich type hints and field descriptions
25
25
  - **Production-Ready** - Retry logic, rate limiting, and LRU-cached enrichment
26
26
  - **Curried Access** - Bind a player/team/series ID once, then call sub-methods without re-passing
@@ -133,6 +133,17 @@ performance = s.performance() # Advanced performance stats
133
133
  economy = s.economy(game_id=1) # Economy data
134
134
  ```
135
135
 
136
+ ### News
137
+
138
+ ```python
139
+ page = client.news(page=1) # Browse the news listing
140
+ for item in page.news[:5]:
141
+ print(item.title)
142
+
143
+ article = client.news.article(734100) # Fetch a full article
144
+ print(article.content_md) # Article body as Markdown
145
+ ```
146
+
136
147
  ## Documentation
137
148
 
138
149
  - **Official Docs** - [vlrdevapi.pages.dev/docs](https://vlrdevapi.pages.dev/docs)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vlrdevapi"
7
- version = "2.2.0"
7
+ version = "2.4.0"
8
8
  description = "A Python library to scrape data from vlr.gg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -39,7 +39,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
39
39
  DOCS_DIR = REPO_ROOT / "official-docs" / "content"
40
40
  DEFAULT_FIXTURES_DIR = REPO_ROOT / ".cache" / "mdx-html"
41
41
 
42
- DYNAMIC_PATHS = frozenset({"/matches", "/matches/results", "/events"})
42
+ DYNAMIC_PATHS = frozenset({"/matches", "/matches/results", "/events", "/news"})
43
43
 
44
44
 
45
45
  class Result(NamedTuple):
@@ -29,6 +29,15 @@ ROUTES += [
29
29
  ("/matches/results/?page=2", "matches/results_page2.html", {}),
30
30
  ]
31
31
 
32
+ # ── News ───────────────────────────────────────────────────────
33
+ ROUTES += [
34
+ ("/news", "news/news.html", {}),
35
+ ("/news/?page=2", "news/news_page2.html", {}),
36
+ ("/news/?page=300", "news/news_page300.html", {}),
37
+ ("/734100", "news/article_734100.html", {}),
38
+ ("/720033/americas-recent-victors-start-strong-in-week-1", "news/article_720033.html", {}),
39
+ ]
40
+
32
41
  # ── Events ─────────────────────────────────────────────────────
33
42
  ROUTES += [
34
43
  ("/events", "events/events.html", {}),
@@ -100,6 +109,9 @@ ADDITIONAL_EVENTS = [
100
109
  (2949, "gameon-productivity-and-technology-tournament-2026"),
101
110
  (2847, "challengers-2026-japan-split-1"),
102
111
  (58, "100t-x-cashapp-gamers-for-equality"),
112
+ (2763, "tesfed-turkish-cup-2025"),
113
+ (2544, "morocco-gaming-expo-2025"),
114
+ (3074, "raidiant-academy-2026"),
103
115
  ]
104
116
 
105
117
  for _eid, _eslug in ADDITIONAL_EVENTS:
@@ -118,8 +130,8 @@ ROUTES += [
118
130
 
119
131
  # ── Event standings pages (for standings filtering tests) ──────
120
132
  ROUTES += [
121
- ("/event/2283", "event/2283/overview.html", {}),
122
- ("/event/2682", "event/2682_americas_kickoff/overview.html", {}),
133
+ ("/event/2283/valorant-champions-2025", "event/2283_valorant-champions-2025/overview.html", {}),
134
+ ("/event/2682/vct-2026-americas-kickoff", "event/2682_vct-2026-americas-kickoff/overview.html", {}),
123
135
  ]
124
136
 
125
137
  # ── Event matches pages ─────────────────────────────────────────
@@ -311,10 +323,153 @@ ROUTES += [
311
323
  ]
312
324
 
313
325
 
326
+ # ── Series 704037 Bo1 (single-map game, no nav tabs, no veto) ─
327
+ SERIES_ID_BO1 = 704037
328
+ SERIES_SLUG_BO1 = "mushoku-vs-fallen-angels-game-changers-2026-emea-stage-2-promotion-relegation-r1"
329
+ GAME_ID_BO1 = 274641
330
+
331
+ ROUTES += [
332
+ (
333
+ f"/{SERIES_ID_BO1}/{SERIES_SLUG_BO1}",
334
+ f"series/{SERIES_ID_BO1}/overview.html",
335
+ {},
336
+ ),
337
+ (
338
+ f"/{SERIES_ID_BO1}?game={GAME_ID_BO1}&tab=overview",
339
+ f"series/{SERIES_ID_BO1}/game_{GAME_ID_BO1}_overview.html",
340
+ {},
341
+ ),
342
+ (
343
+ f"/{SERIES_ID_BO1}?game={GAME_ID_BO1}&tab=performance",
344
+ f"series/{SERIES_ID_BO1}/game_{GAME_ID_BO1}_performance.html",
345
+ {},
346
+ ),
347
+ (
348
+ f"/{SERIES_ID_BO1}?game={GAME_ID_BO1}&tab=economy",
349
+ f"series/{SERIES_ID_BO1}/game_{GAME_ID_BO1}_economy.html",
350
+ {},
351
+ ),
352
+ ]
353
+
354
+ # ── Series 64819 Bo1 (single-map, bans + decider veto) ────────
355
+ SERIES_ID_BO1_VETO = 64819
356
+ GAME_ID_BO1_VETO = 65212
357
+
358
+ ROUTES += [
359
+ (
360
+ f"/{SERIES_ID_BO1_VETO}",
361
+ f"series/{SERIES_ID_BO1_VETO}/overview.html",
362
+ {},
363
+ ),
364
+ (
365
+ f"/{SERIES_ID_BO1_VETO}?game={GAME_ID_BO1_VETO}&tab=overview",
366
+ f"series/{SERIES_ID_BO1_VETO}/game_{GAME_ID_BO1_VETO}_overview.html",
367
+ {},
368
+ ),
369
+ (
370
+ f"/{SERIES_ID_BO1_VETO}?game={GAME_ID_BO1_VETO}&tab=performance",
371
+ f"series/{SERIES_ID_BO1_VETO}/game_{GAME_ID_BO1_VETO}_performance.html",
372
+ {},
373
+ ),
374
+ (
375
+ f"/{SERIES_ID_BO1_VETO}?game={GAME_ID_BO1_VETO}&tab=economy",
376
+ f"series/{SERIES_ID_BO1_VETO}/game_{GAME_ID_BO1_VETO}_economy.html",
377
+ {},
378
+ ),
379
+ ]
380
+
381
+ # ── Series 30788 Bo1 (single-map, pick veto) ──────────────────
382
+ SERIES_ID_BO1_PICK = 30788
383
+ GAME_ID_BO1_PICK = 47971
384
+
385
+ ROUTES += [
386
+ (
387
+ f"/{SERIES_ID_BO1_PICK}",
388
+ f"series/{SERIES_ID_BO1_PICK}/overview.html",
389
+ {},
390
+ ),
391
+ (
392
+ f"/{SERIES_ID_BO1_PICK}?game={GAME_ID_BO1_PICK}&tab=overview",
393
+ f"series/{SERIES_ID_BO1_PICK}/game_{GAME_ID_BO1_PICK}_overview.html",
394
+ {},
395
+ ),
396
+ (
397
+ f"/{SERIES_ID_BO1_PICK}?game={GAME_ID_BO1_PICK}&tab=performance",
398
+ f"series/{SERIES_ID_BO1_PICK}/game_{GAME_ID_BO1_PICK}_performance.html",
399
+ {},
400
+ ),
401
+ (
402
+ f"/{SERIES_ID_BO1_PICK}?game={GAME_ID_BO1_PICK}&tab=economy",
403
+ f"series/{SERIES_ID_BO1_PICK}/game_{GAME_ID_BO1_PICK}_economy.html",
404
+ {},
405
+ ),
406
+ ]
407
+
408
+ # ── Series: forfeit and notes parsing ───────────────────────
409
+ SERIES_FORFEIT_NOTES = [
410
+ (477343, "shopify-rebellion-gold-vs-yfp-x-game-changers-2025-north-america-stage-1-r3"),
411
+ (67357, "t1-vs-tsm-champions-tour-north-america-stage-1-challengers-lr4"),
412
+ (155313, "blind-esports-vs-onlyfriends-nsg-yuddha-phase-2-all-india-championship-playoffs-lr1"),
413
+ (459856, "team-liquid-vs-karmine-corp-champions-tour-2025-emea-stage-1-w5"),
414
+ (715117, "shopify-rebellion-black-vs-2game-esports-vcl-26-americas-last-chance-qualifier-main-event-gf"),
415
+ ]
416
+
417
+ for _sid, _sslug in SERIES_FORFEIT_NOTES:
418
+ ROUTES.append((f"/{_sid}/{_sslug}", f"series/{_sid}/overview.html", {}))
419
+
420
+
421
+ def _parse_last_page(html: str) -> int | None:
422
+ """Parse the highest page number from vlr.gg pagination controls."""
423
+ from selectolax.parser import HTMLParser
424
+
425
+ doc = HTMLParser(html)
426
+ pagination = doc.css_first("div.action-container-pages")
427
+ if pagination is None:
428
+ return None
429
+ max_page = 0
430
+ for el in pagination.css("a.btn.mod-page, span.btn.mod-page"):
431
+ try:
432
+ num = int(el.text(strip=True))
433
+ if num > max_page:
434
+ max_page = num
435
+ except ValueError:
436
+ continue
437
+ return max_page if max_page > 0 else None
438
+
439
+
440
+ async def _download_news_last_page(client: httpx.AsyncClient) -> None:
441
+ """Dynamically determine and download the last news page."""
442
+ out = FIXTURES_DIR / "news" / "news_last_page.html"
443
+ out.parent.mkdir(parents=True, exist_ok=True)
444
+ if out.exists():
445
+ print("SKIP (exists) news/news_last_page.html")
446
+ return
447
+
448
+ print("GET /news (to find last page number)")
449
+ try:
450
+ resp = await client.get("/news")
451
+ resp.raise_for_status()
452
+ last_page = _parse_last_page(resp.text)
453
+ if last_page is None:
454
+ print(" WARNING: could not parse last page number")
455
+ return
456
+
457
+ last_path = f"/news/?page={last_page}"
458
+ print(f"GET {BASE_URL}{last_path}")
459
+ resp2 = await client.get(last_path)
460
+ resp2.raise_for_status()
461
+ out.write_text(resp2.text, encoding="utf-8")
462
+ print(f" -> {out.relative_to(FIXTURES_DIR.parent)}")
463
+ except (httpx.HTTPStatusError, httpx.RequestError) as e:
464
+ print(f" ERROR {e}")
465
+
466
+
314
467
  async def download_all():
315
468
  async with httpx.AsyncClient(
316
469
  base_url=BASE_URL, headers=HEADERS, follow_redirects=True, timeout=30
317
470
  ) as client:
471
+ await _download_news_last_page(client)
472
+
318
473
  total = len(ROUTES)
319
474
  for i, (path, filename, cookies) in enumerate(ROUTES, 1):
320
475
  out = FIXTURES_DIR / filename
@@ -1,8 +1,8 @@
1
1
  """vlrdevapi — A Python library to scrape data from vlr.gg.
2
2
 
3
3
  Provides a synchronous, typed interface for accessing match listings,
4
- event data, tournament info, team/player profiles, and statistics
5
- from vlr.gg.
4
+ event data, tournament info, team/player profiles, statistics, and
5
+ news from vlr.gg.
6
6
 
7
7
  Examples:
8
8
  Module-level access (using a default client):
@@ -23,17 +23,25 @@ Examples:
23
23
  >>> matches.matches[0].score
24
24
  '2-1'
25
25
 
26
+ News listing and articles:
27
+
28
+ >>> page = vlrdevapi.news(page=1)
29
+ >>> article = vlrdevapi.news.article(page.news[0].id)
30
+ >>> article.title != ''
31
+ True
32
+
26
33
  """
27
34
 
28
35
  import atexit as _atexit
36
+ import contextlib
29
37
  from typing import TYPE_CHECKING
30
38
 
31
39
  from vlrdevapi._client import VLRClient
32
- import contextlib
33
40
 
34
41
  if TYPE_CHECKING:
35
42
  from vlrdevapi._event.namespace import EventNamespace
36
43
  from vlrdevapi._matches.namespace import MatchesNamespace
44
+ from vlrdevapi._news.namespace import NewsNamespace
37
45
  from vlrdevapi._player.namespace import PlayerNamespace
38
46
  from vlrdevapi._series.namespace import SeriesNamespace
39
47
  from vlrdevapi._team.namespace import TeamNamespace
@@ -43,22 +51,23 @@ if TYPE_CHECKING:
43
51
  player: PlayerNamespace
44
52
  team: TeamNamespace
45
53
  matches: MatchesNamespace
54
+ news: NewsNamespace
46
55
  client: type[VLRClient]
47
56
 
48
- __version__ = "2.2.0"
57
+ __version__ = "2.4.0"
49
58
 
50
59
  _default_client: "VLRClient | None" = None
51
60
 
52
61
 
53
62
  def _get_default_client() -> "VLRClient":
54
- global _default_client # noqa: PLW0603
63
+ global _default_client
55
64
  if _default_client is None:
56
65
  _default_client = VLRClient()
57
66
  return _default_client
58
67
 
59
68
 
60
69
  def _cleanup_default_client() -> None:
61
- global _default_client # noqa: PLW0603
70
+ global _default_client
62
71
  if _default_client is not None:
63
72
  with contextlib.suppress(OSError):
64
73
  _default_client.close()
@@ -67,7 +76,7 @@ def _cleanup_default_client() -> None:
67
76
 
68
77
  _atexit.register(_cleanup_default_client)
69
78
 
70
- _BOUND_NAMES = frozenset({"event", "series", "player", "team", "matches"})
79
+ _BOUND_NAMES = frozenset({"event", "series", "player", "team", "matches", "news"})
71
80
 
72
81
 
73
82
  def __getattr__(name: str) -> object:
@@ -89,6 +98,7 @@ __all__ = [
89
98
  "client",
90
99
  "event",
91
100
  "matches",
101
+ "news",
92
102
  "player",
93
103
  "series",
94
104
  "team",
@@ -1,6 +1,7 @@
1
1
  from vlrdevapi._client import VLRClient
2
2
  from vlrdevapi._event.namespace import EventNamespace
3
3
  from vlrdevapi._matches.namespace import MatchesNamespace
4
+ from vlrdevapi._news.namespace import NewsNamespace
4
5
  from vlrdevapi._player.namespace import PlayerNamespace
5
6
  from vlrdevapi._series.namespace import SeriesNamespace
6
7
  from vlrdevapi._team.namespace import TeamNamespace
@@ -69,6 +70,20 @@ Examples:
69
70
  >>> completed = vlrdevapi.matches.completed()
70
71
  """
71
72
 
73
+ news: NewsNamespace
74
+ """Access news from vlr.gg: news listings and full article content.
75
+
76
+ Returns:
77
+ NewsNamespace instance bound to the default client. Call it with a
78
+ ``page`` number to browse the news listing, or use ``.article()`` to
79
+ fetch a single article (plain text and Markdown).
80
+
81
+ Examples:
82
+ >>> page = vlrdevapi.news(page=1)
83
+ >>> article = vlrdevapi.news.article(734100)
84
+ >>> print(article.content_md)
85
+ """
86
+
72
87
  client: type[VLRClient]
73
88
  """Create an explicit VLRClient for connection management.
74
89