vlrdevapi 2.4.0__tar.gz → 2.4.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 (218) hide show
  1. vlrdevapi-2.4.1/.nvmrc +1 -0
  2. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.readthedocs.yaml +3 -2
  3. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/CHANGELOG.md +38 -0
  4. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/PKG-INFO +4 -4
  5. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/pyproject.toml +11 -11
  6. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/scripts/check_mdx_examples.py +31 -0
  7. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/scripts/download_fixtures.py +1 -1
  8. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/__init__.py +1 -1
  9. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_base.py +1 -1
  10. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/parser.py +2 -1
  11. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/parser.py +2 -1
  12. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/parser.py +2 -1
  13. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/parser.py +2 -1
  14. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/parser.py +2 -1
  15. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/parser.py +2 -1
  16. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/common.py +2 -1
  17. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/parser.py +2 -1
  18. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/parser.py +2 -1
  19. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/parser.py +2 -1
  20. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/parser.py +2 -1
  21. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/common.py +1 -1
  22. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/parser.py +2 -1
  23. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/_common_parsing.py +1 -1
  24. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/parser.py +1 -1
  25. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/parser.py +1 -1
  26. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/parser.py +2 -1
  27. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/parser.py +1 -1
  28. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/parser.py +1 -1
  29. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/_utils.py +1 -1
  30. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/namespace.py +14 -11
  31. vlrdevapi-2.4.1/src/vlrdevapi/_series/economy/parser.py +169 -0
  32. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/parser.py +10 -1
  33. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/parser.py +2 -1
  34. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/parser.py +54 -16
  35. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/namespace.py +1 -1
  36. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/parser.py +1 -1
  37. vlrdevapi-2.4.1/src/vlrdevapi/_series/vods/parser.py +50 -0
  38. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/parser.py +2 -1
  39. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/namespace.py +1 -1
  40. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/parser.py +1 -1
  41. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/parser.py +2 -1
  42. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/parser.py +2 -1
  43. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/parser.py +2 -1
  44. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/parser.py +2 -1
  45. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/parser.py +2 -1
  46. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/pagination.py +1 -1
  47. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/team_enrichment.py +1 -1
  48. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/team_parsing.py +1 -1
  49. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/timezone.py +1 -1
  50. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/fetcher.py +1 -1
  51. vlrdevapi-2.4.1/uv.lock +1165 -0
  52. vlrdevapi-2.4.0/src/vlrdevapi/_series/economy/parser.py +0 -144
  53. vlrdevapi-2.4.0/src/vlrdevapi/_series/vods/parser.py +0 -30
  54. vlrdevapi-2.4.0/uv.lock +0 -1049
  55. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.gitattributes +0 -0
  56. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  57. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  58. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/cleanup-caches.yml +0 -0
  59. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/docs-check.yml +0 -0
  60. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/publish.yml +0 -0
  61. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/test.yml +0 -0
  62. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.gitignore +0 -0
  63. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.python-version +0 -0
  64. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/CODE_OF_CONDUCT.md +0 -0
  65. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/CONTRIBUTING.md +0 -0
  66. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/LICENCE +0 -0
  67. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/README.md +0 -0
  68. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/SECURITY.md +0 -0
  69. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/SUPPORT.md +0 -0
  70. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/__init__.pyi +0 -0
  71. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_cache.py +0 -0
  72. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_client.py +0 -0
  73. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/__init__.py +0 -0
  74. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/_common.py +0 -0
  75. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/__init__.py +0 -0
  76. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/models.py +0 -0
  77. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/namespace.py +0 -0
  78. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
  79. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/__init__.py +0 -0
  80. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/models.py +0 -0
  81. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/namespace.py +0 -0
  82. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
  83. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/__init__.py +0 -0
  84. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/models.py +0 -0
  85. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/namespace.py +0 -0
  86. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
  87. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/namespace.py +0 -0
  88. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/namespace.pyi +0 -0
  89. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/__init__.py +0 -0
  90. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/models.py +0 -0
  91. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/namespace.py +0 -0
  92. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
  93. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/__init__.py +0 -0
  94. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/models.py +0 -0
  95. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/namespace.py +0 -0
  96. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
  97. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/__init__.py +0 -0
  98. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/models.py +0 -0
  99. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/namespace.py +0 -0
  100. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
  101. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/__init__.py +0 -0
  102. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
  103. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/models.py +0 -0
  104. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
  105. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
  106. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/__init__.py +0 -0
  107. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/models.py +0 -0
  108. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/namespace.py +0 -0
  109. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
  110. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/namespace.py +0 -0
  111. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/namespace.pyi +0 -0
  112. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
  113. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
  114. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
  115. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
  116. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/__init__.py +0 -0
  117. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/__init__.py +0 -0
  118. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/models.py +0 -0
  119. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/namespace.py +0 -0
  120. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/namespace.pyi +0 -0
  121. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/__init__.py +0 -0
  122. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/models.py +0 -0
  123. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/namespace.py +0 -0
  124. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/namespace.pyi +0 -0
  125. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/namespace.py +0 -0
  126. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/namespace.pyi +0 -0
  127. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/__init__.py +0 -0
  128. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/__init__.py +0 -0
  129. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/models.py +0 -0
  130. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/namespace.py +0 -0
  131. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
  132. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/__init__.py +0 -0
  133. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/models.py +0 -0
  134. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/namespace.py +0 -0
  135. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
  136. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/__init__.py +0 -0
  137. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/models.py +0 -0
  138. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/namespace.py +0 -0
  139. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
  140. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/namespace.py +0 -0
  141. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/namespace.pyi +0 -0
  142. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/__init__.py +0 -0
  143. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/models.py +0 -0
  144. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/namespace.py +0 -0
  145. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
  146. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/__init__.py +0 -0
  147. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/models.py +0 -0
  148. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/namespace.py +0 -0
  149. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
  150. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/__init__.py +0 -0
  151. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/__init__.py +0 -0
  152. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/models.py +0 -0
  153. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
  154. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/__init__.py +0 -0
  155. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/models.py +0 -0
  156. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/namespace.py +0 -0
  157. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
  158. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/match_namespace.py +0 -0
  159. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
  160. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/namespace.py +0 -0
  161. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/namespace.pyi +0 -0
  162. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/__init__.py +0 -0
  163. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/models.py +0 -0
  164. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/namespace.py +0 -0
  165. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
  166. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/__init__.py +0 -0
  167. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/models.py +0 -0
  168. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/namespace.py +0 -0
  169. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
  170. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
  171. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/models.py +0 -0
  172. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
  173. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/__init__.py +0 -0
  174. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/models.py +0 -0
  175. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/namespace.py +0 -0
  176. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
  177. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/__init__.py +0 -0
  178. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
  179. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
  180. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
  181. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
  182. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/__init__.py +0 -0
  183. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/models.py +0 -0
  184. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
  185. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/namespace.py +0 -0
  186. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/namespace.pyi +0 -0
  187. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/__init__.py +0 -0
  188. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/models.py +0 -0
  189. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/namespace.py +0 -0
  190. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
  191. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/__init__.py +0 -0
  192. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/models.py +0 -0
  193. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/namespace.py +0 -0
  194. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
  195. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/__init__.py +0 -0
  196. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/models.py +0 -0
  197. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/namespace.py +0 -0
  198. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
  199. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
  200. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/models.py +0 -0
  201. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
  202. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
  203. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
  204. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
  205. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
  206. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
  207. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/__init__.py +0 -0
  208. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/paths.py +0 -0
  209. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/__init__.py +0 -0
  210. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/countries.py +0 -0
  211. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/datetime.py +0 -0
  212. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/mappings.py +0 -0
  213. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/prizes.py +0 -0
  214. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/regions.py +0 -0
  215. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/exceptions.py +0 -0
  216. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/py.typed +0 -0
  217. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/validators.py +0 -0
  218. {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/wrangler.jsonc +0 -0
vlrdevapi-2.4.1/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 26.10.0
@@ -6,10 +6,11 @@ build:
6
6
  python: latest
7
7
  jobs:
8
8
  install:
9
- - pip install zensical mkdocstrings mkdocstrings-python
9
+ - pip install uv
10
+ - uv sync --group dev
10
11
  build:
11
12
  html:
12
- - cd docs-py && zensical build
13
+ - cd docs-py && uv run zensical build
13
14
  post_build:
14
15
  - mkdir -p $READTHEDOCS_OUTPUT/html/
15
16
  - cp --recursive docs-py/site/* $READTHEDOCS_OUTPUT/html/
@@ -5,6 +5,43 @@ 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
+ ## [2.4.1] - 2026-10-17
9
+
10
+ ### Fixed
11
+
12
+ - **Series players/stats parsing** — newer vlr.gg overview tables add a
13
+ dedicated `.mod-agents` cell before the stat columns. Stat cells are now
14
+ mapped dynamically around that column, and agents are read from the new
15
+ cell. This fixes `SideStats` validation errors and misaligned values
16
+ (rating, ACS, KDA, KAST, ADR, HS%, first kills/deaths).
17
+ - **Series economy parsing** — the round-by-round economy grid now uses the
18
+ newer `.vlr-rounds.ecr` markup (`.ecr-bank`, `.ecc-c`, `.ecc-sq`). Banks,
19
+ spends, buy types, and round winners are read from it, with the legacy
20
+ `.wf-table-inset.mod-econ` table kept as a fallback.
21
+ - **Series VODs parsing** — VODs are now read from the newer `.sm-vods`
22
+ module (`.sm-vod-name` labels, `a.sm-ext` links), keeping the legacy
23
+ `.match-vods` container as a fallback.
24
+ - **Series game map names** — the game header now renders the duration as a
25
+ sibling of the map name. The map name is read from `.map-name`, so the
26
+ duration is no longer concatenated (e.g. `Pearl` instead of `Pearl35:48`).
27
+
28
+ ### Changed
29
+
30
+ - **Series economy team matching** — team/abbreviation resolution is now
31
+ case-insensitive, so team IDs resolve for pages that render short tags.
32
+ - **HTML parsing backend** — parsing now uses selectolax's Lexbor engine
33
+ (`selectolax.lexbor`) instead of the discontinued Modest engine
34
+ (`selectolax.parser`), which selectolax removed in 1.0. This is an internal
35
+ change: the public API, CSS selectors, and parsed output are unchanged.
36
+ - **Dependencies** — raised the `selectolax` minimum to `>=1.0.0` and moved
37
+ `pydantic` to the 2.14 series.
38
+
39
+ ### Documentation
40
+
41
+ - **Changelog** — this release is mirrored across `CHANGELOG.md`, the
42
+ ReadTheDocs changelog (`docs-py/docs/changelog.md`), and the official
43
+ site changelog (`official-docs/app/(home)/changelog/page.tsx`).
44
+
8
45
  ## [2.4.0] - 2026-08-19
9
46
 
10
47
  ### Added
@@ -279,6 +316,7 @@ The API surface, module structure, and type system are all new.
279
316
  - `aiohttp` and `asyncio` dependencies.
280
317
  - v1.x match/event/team parsing modules.
281
318
 
319
+ [2.4.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.4.0...v2.4.1
282
320
  [2.4.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.3.0...v2.4.0
283
321
  [2.3.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.2.0...v2.3.0
284
322
  [2.2.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.1.0...v2.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vlrdevapi
3
- Version: 2.4.0
3
+ Version: 2.4.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
@@ -38,11 +38,11 @@ Classifier: Topic :: Games/Entertainment
38
38
  Classifier: Topic :: Internet :: WWW/HTTP
39
39
  Requires-Python: >=3.11
40
40
  Requires-Dist: httpx>=0.28.1
41
- Requires-Dist: pydantic>=2.12.5
42
- Requires-Dist: selectolax>=0.4.7
41
+ Requires-Dist: pydantic>=2.14.1
42
+ Requires-Dist: selectolax>=1.0.0
43
43
  Provides-Extra: test
44
44
  Requires-Dist: pytest-cov>=7.1.0; extra == 'test'
45
- Requires-Dist: pytest>=8.0; extra == 'test'
45
+ Requires-Dist: pytest>=9.1.1; extra == 'test'
46
46
  Requires-Dist: respx>=0.23.1; extra == 'test'
47
47
  Description-Content-Type: text/markdown
48
48
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vlrdevapi"
7
- version = "2.4.0"
7
+ version = "2.4.1"
8
8
  description = "A Python library to scrape data from vlr.gg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -22,8 +22,8 @@ classifiers = [
22
22
  ]
23
23
  dependencies = [
24
24
  "httpx>=0.28.1",
25
- "pydantic>=2.12.5",
26
- "selectolax>=0.4.7",
25
+ "pydantic>=2.14.1",
26
+ "selectolax>=1.0.0",
27
27
  ]
28
28
 
29
29
  [project.urls]
@@ -33,22 +33,22 @@ Documentation = "https://vlrdevapi.pages.dev/docs"
33
33
 
34
34
  [project.optional-dependencies]
35
35
  test = [
36
- "pytest>=8.0",
36
+ "pytest>=9.1.1",
37
37
  "respx>=0.23.1",
38
38
  "pytest-cov>=7.1.0",
39
39
  ]
40
40
 
41
41
  [dependency-groups]
42
42
  dev = [
43
- "ruff>=0.15.10",
44
- "pytest>=8.0",
45
- "tzdata>=2026.1",
46
- "ty>=0.0.31",
43
+ "ruff>=0.17.1",
44
+ "pytest>=9.1.1",
45
+ "tzdata>=2026.5",
46
+ "ty>=0.0.89",
47
47
  "respx>=0.23.1",
48
48
  "pytest-cov>=7.1.0",
49
- "zensical>=0.0.46",
50
- "mkdocstrings>=0.28.0",
51
- "mkdocstrings-python>=1.16.0",
49
+ "zensical>=0.0.71",
50
+ "mkdocstrings>=1.0.7",
51
+ "mkdocstrings-python>=2.0.9",
52
52
  ]
53
53
 
54
54
  [tool.pytest.ini_options]
@@ -6,6 +6,12 @@ executes examples. An ``httpx.Client.get`` interceptor de-duplicates
6
6
  fetches within a run and can replay validation offline against HTML
7
7
  fixtures saved under ``.cache/mdx-html/`` (gitignored).
8
8
 
9
+ Blocks may carry two markers (both stripped from rendered docs by the
10
+ remark plugin in ``official-docs/source.config.ts``):
11
+
12
+ ``# doc-check: skip`` never execute (non-runnable demo code)
13
+ ``# doc-check: allow-empty`` execute, but empty output is acceptable
14
+
9
15
  CI/CD usage:
10
16
  python scripts/check_mdx_examples.py # warm + offline replay
11
17
  python scripts/check_mdx_examples.py --skip-warm # offline replay only
@@ -401,6 +407,26 @@ def has_skip_marker(code: str) -> bool:
401
407
  return any(line.strip().startswith("# doc-check: skip") for line in code.splitlines())
402
408
 
403
409
 
410
+ def has_allow_empty_marker(code: str) -> bool:
411
+ """Blocks may opt out of the empty-output rule with `# doc-check: allow-empty`.
412
+
413
+ Use for examples that filter live data and can legitimately match nothing
414
+ on a given day. The block still runs and still fails on exceptions or
415
+ timeouts; only the "no output" requirement is relaxed.
416
+ """
417
+ return any(
418
+ line.strip().startswith("# doc-check: allow-empty")
419
+ for line in code.splitlines()
420
+ )
421
+
422
+
423
+ def apply_allow_empty(code: str, run_ok: bool, run_msg: str) -> tuple[bool, str]:
424
+ """Relax an empty-output failure for blocks marked `# doc-check: allow-empty`."""
425
+ if not run_ok and has_allow_empty_marker(code) and "no output" in run_msg.lower():
426
+ return True, "(no output - allowed by marker)"
427
+ return run_ok, run_msg
428
+
429
+
404
430
  async def execute_blocks(
405
431
  results: list[Result],
406
432
  timeout: float,
@@ -433,6 +459,11 @@ async def execute_blocks(
433
459
  else:
434
460
  run_ok, run_msg, dur = await exec_sync(code, timeout)
435
461
 
462
+ # Blocks marked `# doc-check: allow-empty` may legitimately print
463
+ # nothing (e.g. a filtered loop over live data that matched no rows).
464
+ # They still fail on exceptions and timeouts.
465
+ run_ok, run_msg = apply_allow_empty(code, run_ok, run_msg)
466
+
436
467
  if record_results:
437
468
  results[i] = Result(
438
469
  r.file, r.code, r.syntax_ok, r.syntax_error, r.imports_ok,
@@ -420,7 +420,7 @@ for _sid, _sslug in SERIES_FORFEIT_NOTES:
420
420
 
421
421
  def _parse_last_page(html: str) -> int | None:
422
422
  """Parse the highest page number from vlr.gg pagination controls."""
423
- from selectolax.parser import HTMLParser
423
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
424
424
 
425
425
  doc = HTMLParser(html)
426
426
  pagination = doc.css_first("div.action-container-pages")
@@ -54,7 +54,7 @@ if TYPE_CHECKING:
54
54
  news: NewsNamespace
55
55
  client: type[VLRClient]
56
56
 
57
- __version__ = "2.4.0"
57
+ __version__ = "2.4.1"
58
58
 
59
59
  _default_client: "VLRClient | None" = None
60
60
 
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
  from typing import Any
7
7
 
8
8
  import httpx
9
- from selectolax.parser import HTMLParser
9
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
10
10
 
11
11
  from vlrdevapi.fetcher import (
12
12
  BASE_URL,
@@ -4,7 +4,8 @@ from datetime import date, datetime
4
4
 
5
5
  from vlrdevapi.commons.datetime import date_to_utc_datetime
6
6
 
7
- from selectolax.parser import HTMLParser, Node
7
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
8
+ from selectolax.lexbor import LexborNode as Node
8
9
 
9
10
  from vlrdevapi._event.info.models import (
10
11
  EventInfo,
@@ -1,7 +1,8 @@
1
1
  import re
2
2
  from typing import Literal
3
3
 
4
- from selectolax.parser import HTMLParser, Node
4
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
5
+ from selectolax.lexbor import LexborNode as Node
5
6
 
6
7
  from vlrdevapi._event.info.models import EventPrize
7
8
  from vlrdevapi._event.list.models import (
@@ -2,7 +2,8 @@
2
2
  from datetime import date, tzinfo
3
3
  from zoneinfo import ZoneInfo
4
4
 
5
- from selectolax.parser import HTMLParser, Node
5
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
6
+ from selectolax.lexbor import LexborNode as Node
6
7
 
7
8
  from vlrdevapi._event.matches.models import (
8
9
  EventMatch,
@@ -4,7 +4,8 @@ import re
4
4
  from datetime import date, datetime
5
5
  from urllib.parse import parse_qs, urlparse
6
6
 
7
- from selectolax.parser import HTMLParser, Node
7
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
8
+ from selectolax.lexbor import LexborNode as Node
8
9
 
9
10
  from vlrdevapi._event.stages.models import EventStage, EventStages
10
11
  from vlrdevapi.exceptions import DataNotFoundError
@@ -1,6 +1,7 @@
1
1
  """Event standings parser."""
2
2
 
3
- from selectolax.parser import HTMLParser, Node
3
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
4
+ from selectolax.lexbor import LexborNode as Node
4
5
 
5
6
  from vlrdevapi._event.standings.models import StandingEntry, TeamStanding
6
7
  from vlrdevapi.commons.prizes import parse_prize_amount
@@ -1,4 +1,5 @@
1
- from selectolax.parser import HTMLParser, Node
1
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
2
+ from selectolax.lexbor import LexborNode as Node
2
3
 
3
4
  from vlrdevapi._event.teams.models import Team, TeamPlayer
4
5
 
@@ -9,7 +9,8 @@ from typing import Any, Protocol
9
9
  from zoneinfo import ZoneInfo
10
10
 
11
11
  import httpx
12
- from selectolax.parser import HTMLParser, Node
12
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
13
+ from selectolax.lexbor import LexborNode as Node
13
14
 
14
15
  from vlrdevapi._cache import LRUCache
15
16
  from vlrdevapi._series.info.namespace import SeriesInfoNamespace
@@ -5,7 +5,8 @@ from datetime import date, tzinfo
5
5
  from zoneinfo import ZoneInfo
6
6
 
7
7
  import httpx
8
- from selectolax.parser import HTMLParser, Node
8
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
9
+ from selectolax.lexbor import LexborNode as Node
9
10
 
10
11
  from vlrdevapi._cache import LRUCache
11
12
  from vlrdevapi._matches.common import (
@@ -5,7 +5,8 @@ from datetime import date, tzinfo
5
5
  from zoneinfo import ZoneInfo
6
6
 
7
7
  import httpx
8
- from selectolax.parser import HTMLParser, Node
8
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
9
+ from selectolax.lexbor import LexborNode as Node
9
10
 
10
11
  from vlrdevapi._cache import LRUCache
11
12
  from vlrdevapi._matches.common import (
@@ -5,7 +5,8 @@ from datetime import date, tzinfo
5
5
  from zoneinfo import ZoneInfo
6
6
 
7
7
  import httpx
8
- from selectolax.parser import HTMLParser, Node
8
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
9
+ from selectolax.lexbor import LexborNode as Node
9
10
 
10
11
  from vlrdevapi._cache import LRUCache
11
12
  from vlrdevapi._matches.common import (
@@ -11,7 +11,8 @@ from datetime import tzinfo
11
11
  from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
12
12
  from zoneinfo import ZoneInfo
13
13
 
14
- from selectolax.parser import HTMLParser, Node
14
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
15
+ from selectolax.lexbor import LexborNode as Node
15
16
 
16
17
  from vlrdevapi._news.article.models import NewsArticle
17
18
  from vlrdevapi.commons.datetime import parse_vlr_iso_datetime
@@ -1,6 +1,6 @@
1
1
  """Shared helpers for reading pagination state from vlr.gg/news pages."""
2
2
 
3
- from selectolax.parser import HTMLParser
3
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
4
4
 
5
5
 
6
6
  def get_page_number(html: HTMLParser) -> int:
@@ -8,7 +8,8 @@ controls.
8
8
  import logging
9
9
  from datetime import datetime
10
10
 
11
- from selectolax.parser import HTMLParser, Node
11
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
12
+ from selectolax.lexbor import LexborNode as Node
12
13
 
13
14
  from vlrdevapi._news.common import check_pagination, get_page_number
14
15
  from vlrdevapi._news.list.models import News, NewsPage
@@ -7,7 +7,7 @@ from datetime import date, datetime, timezone
7
7
 
8
8
  from typing import TYPE_CHECKING
9
9
 
10
- from selectolax.parser import Node
10
+ from selectolax.lexbor import LexborNode as Node
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from vlrdevapi._player.agents.models import AgentStats
@@ -1,4 +1,4 @@
1
- from selectolax.parser import HTMLParser
1
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
2
2
 
3
3
  from vlrdevapi._player._common_parsing import (
4
4
  _next_sibling_card,
@@ -1,4 +1,4 @@
1
- from selectolax.parser import HTMLParser
1
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
2
2
 
3
3
  from vlrdevapi._player.info.models import PlayerInfo
4
4
  from vlrdevapi.commons.countries import get_country_name
@@ -2,7 +2,8 @@
2
2
  from datetime import tzinfo
3
3
  from zoneinfo import ZoneInfo
4
4
 
5
- from selectolax.parser import HTMLParser, Node
5
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
6
+ from selectolax.lexbor import LexborNode as Node
6
7
 
7
8
  from vlrdevapi._player.matches.models import MatchEntry, MatchHistoryPage, TeamInMatch
8
9
  from vlrdevapi.commons.datetime import (
@@ -1,4 +1,4 @@
1
- from selectolax.parser import HTMLParser
1
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
2
2
 
3
3
  from vlrdevapi._player._common_parsing import (
4
4
  _next_sibling_card,
@@ -1,4 +1,4 @@
1
- from selectolax.parser import HTMLParser
1
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
2
2
 
3
3
  from vlrdevapi._player._common_parsing import _next_sibling_card, _parse_team_entry
4
4
  from vlrdevapi._player.teams.models import PlayerTeams
@@ -1,6 +1,6 @@
1
1
  """Shared helpers for series sub-namespaces."""
2
2
 
3
- from selectolax.parser import HTMLParser
3
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
4
4
 
5
5
  from vlrdevapi.exceptions import ParsingError
6
6
 
@@ -4,7 +4,7 @@ from datetime import tzinfo
4
4
  from zoneinfo import ZoneInfo
5
5
 
6
6
  import httpx
7
- from selectolax.parser import HTMLParser
7
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
8
8
 
9
9
  from vlrdevapi._base import SyncNamespace
10
10
  from vlrdevapi._series._utils import _extract_team_abbreviations
@@ -80,10 +80,11 @@ class SeriesEconomyNamespace:
80
80
  def _enrich_economy(result: EconomyData, html_series: HTMLParser) -> EconomyData:
81
81
  """Enrich economy data with team IDs and round winners from the series info.
82
82
 
83
- Team matching first tries abbreviation-level names extracted from
84
- ``.ovw-player-tag`` elements (e.g. "SEN"), then falls back to full
85
- team names from the series header. Round winners are resolved from
86
- the stored ``_temp_winner`` tag set during parsing.
83
+ Team matching compares the parsed economy team tag against both the
84
+ abbreviation-level names extracted from ``.ovw-player-tag`` elements
85
+ (e.g. "SEN") and the full team names from the series header, using a
86
+ case-insensitive match. Round winners are resolved from the stored
87
+ ``_temp_winner`` tag set during parsing.
87
88
 
88
89
  Args:
89
90
  result: The EconomyData to enrich.
@@ -96,14 +97,16 @@ def _enrich_economy(result: EconomyData, html_series: HTMLParser) -> EconomyData
96
97
  series_info = parse_series_info(html_series)
97
98
  team1_abbr, team2_abbr = _extract_team_abbreviations(html_series)
98
99
 
99
- if team1_abbr == result.team1:
100
- result.team1_id = series_info.team1.id
101
- elif result.team1 and series_info.team1.name == result.team1:
100
+ def _normalize(value: str) -> str:
101
+ return (value or "").strip().casefold()
102
+
103
+ team1_keys = {_normalize(team1_abbr), _normalize(series_info.team1.name)}
104
+ team2_keys = {_normalize(team2_abbr), _normalize(series_info.team2.name)}
105
+
106
+ if _normalize(result.team1) in team1_keys:
102
107
  result.team1_id = series_info.team1.id
103
108
 
104
- if team2_abbr == result.team2:
105
- result.team2_id = series_info.team2.id
106
- elif result.team2 and series_info.team2.name == result.team2:
109
+ if _normalize(result.team2) in team2_keys:
107
110
  result.team2_id = series_info.team2.id
108
111
 
109
112
  for round_data in result.rounds:
@@ -0,0 +1,169 @@
1
+ """Series economy parser — extracts per-round bank and spend data."""
2
+
3
+ from selectolax.lexbor import LexborHTMLParser as HTMLParser
4
+ from selectolax.lexbor import LexborNode as Node
5
+
6
+ from vlrdevapi._series._utils import resolve_game_id
7
+ from vlrdevapi._series.economy.models import (
8
+ BuyType,
9
+ EconomyData,
10
+ RoundEconomyData,
11
+ RoundWinner,
12
+ )
13
+
14
+ # The "ecr" round grid encodes the buy level as a class on ``.ecc-sq``.
15
+ _BUY_CLASS_MAP: dict[str, BuyType] = {
16
+ "mod-bp": BuyType.ECO, # pistol round
17
+ "mod-b1": BuyType.SEMI_ECO,
18
+ "mod-b2": BuyType.SEMI_BUY,
19
+ "mod-b3": BuyType.FULL_BUY,
20
+ }
21
+
22
+
23
+ def _get_ecr_buy_type(sq: Node | None) -> BuyType:
24
+ """Resolve the buy type from an ``.ecc-sq`` round square's classes.
25
+
26
+ Args:
27
+ sq: The ``.ecc-sq`` node, or ``None``.
28
+
29
+ Returns:
30
+ BuyType: Corresponding enum value (defaults to eco).
31
+
32
+ """
33
+ if sq is None:
34
+ return BuyType.ECO
35
+ classes = (sq.attributes.get("class") or "").split()
36
+ for cls in classes:
37
+ if cls in _BUY_CLASS_MAP:
38
+ return _BUY_CLASS_MAP[cls]
39
+ return BuyType.ECO
40
+
41
+
42
+ def _parse_spend_k(title: str | None) -> int:
43
+ """Parse a spend value from an ``.ecc-c`` title (e.g. ``'4.1k'``).
44
+
45
+ Args:
46
+ title: The title attribute text.
47
+
48
+ Returns:
49
+ int: Spend in credits, or ``0`` when absent/unparseable.
50
+
51
+ """
52
+ text = (title or "").strip().lower()
53
+ if not text:
54
+ return 0
55
+ multiplier = 1000 if text.endswith("k") else 1
56
+ if multiplier == 1000:
57
+ text = text[:-1]
58
+ try:
59
+ return round(float(text) * multiplier)
60
+ except ValueError:
61
+ return 0
62
+
63
+
64
+ def _parse_bank_fig(text: str) -> float:
65
+ """Parse a bank figure from an ``.ecr-fig`` (in thousands).
66
+
67
+ Args:
68
+ text: The figure text (e.g. ``'10.3'``).
69
+
70
+ Returns:
71
+ float: Bank in credits, or ``0.0`` when unparseable.
72
+
73
+ """
74
+ try:
75
+ return float(text.strip()) * 1000
76
+ except ValueError:
77
+ return 0.0
78
+
79
+
80
+ def _parse_ecr_economy(rounds_root: Node) -> EconomyData:
81
+ """Parse the VLR round-by-round ``.vlr-rounds.ecr`` grid.
82
+
83
+ Args:
84
+ rounds_root: The ``.vlr-rounds.ecr`` node.
85
+
86
+ Returns:
87
+ EconomyData: Parsed economy data with per-round bank, spend,
88
+ buy type, and winner information.
89
+
90
+ """
91
+ teams_col = rounds_root.css_first(".vlr-rounds-row-col.mod-teams")
92
+ tag_els = teams_col.css(".team .vlr-rounds-tag") if teams_col else []
93
+ team1 = tag_els[0].text(strip=True) if len(tag_els) > 0 else ""
94
+ team2 = tag_els[1].text(strip=True) if len(tag_els) > 1 else ""
95
+
96
+ economy_data = EconomyData(team1=team1, team2=team2)
97
+
98
+ round_number = 0
99
+ for col in rounds_root.css(".vlr-rounds-row-col"):
100
+ if "mod-teams" in (col.attributes.get("class") or ""):
101
+ continue
102
+
103
+ ecc1 = col.css_first(".ecc-c.mod-t1")
104
+ ecc2 = col.css_first(".ecc-c.mod-t2")
105
+ if ecc1 is None and ecc2 is None:
106
+ continue
107
+
108
+ # Unplayed rounds render empty cells (no title) — skip them.
109
+ if not (ecc1.attributes.get("title") if ecc1 else None) and not (
110
+ ecc2.attributes.get("title") if ecc2 else None
111
+ ):
112
+ continue
113
+
114
+ round_number += 1
115
+
116
+ bank1 = col.css_first(".ecr-bank.mod-t1 .ecr-fig")
117
+ bank2 = col.css_first(".ecr-bank.mod-t2 .ecr-fig")
118
+
119
+ sq1 = ecc1.css_first(".ecc-sq") if ecc1 else None
120
+ sq2 = ecc2.css_first(".ecc-sq") if ecc2 else None
121
+
122
+ winner_team = ""
123
+ if sq1 and "mod-win" in (sq1.attributes.get("class") or "").split():
124
+ winner_team = "team1"
125
+ elif sq2 and "mod-win" in (sq2.attributes.get("class") or "").split():
126
+ winner_team = "team2"
127
+
128
+ round_data = RoundEconomyData(
129
+ round_number=round_number,
130
+ bank_team1=_parse_bank_fig(bank1.text(strip=True)) if bank1 else 0.0,
131
+ bank_team2=_parse_bank_fig(bank2.text(strip=True)) if bank2 else 0.0,
132
+ spent_team1=_parse_spend_k(ecc1.attributes.get("title") if ecc1 else None),
133
+ spent_team2=_parse_spend_k(ecc2.attributes.get("title") if ecc2 else None),
134
+ winner=RoundWinner(),
135
+ buy_type_team1=_get_ecr_buy_type(sq1),
136
+ buy_type_team2=_get_ecr_buy_type(sq2),
137
+ is_pistol_round=round_number in (1, 13),
138
+ )
139
+ round_data._temp_winner = winner_team
140
+
141
+ economy_data.rounds.append(round_data)
142
+
143
+ return economy_data
144
+
145
+
146
+ def parse_economy_data(html: HTMLParser, game_id: int | str = "all") -> EconomyData:
147
+ """Parse economy data from the series page HTML.
148
+
149
+ Args:
150
+ html: The selectolax HTMLParser of the series page.
151
+ game_id: Game number within the series (1-based), or a real VLR
152
+ game ID ("all" for the combined overview).
153
+
154
+ Returns:
155
+ EconomyData: Parsed economy data with per-round team banks and
156
+ spends.
157
+
158
+ """
159
+ gid = resolve_game_id(html, game_id)
160
+ game_div = html.css_first(f'.vm-stats-game[data-game-id="{gid}"]')
161
+ if not game_div:
162
+ return EconomyData()
163
+
164
+ # VLR renders the round-by-round economy grid inside the game container.
165
+ rounds_root = game_div.css_first(".vlr-rounds.ecr")
166
+ if not rounds_root:
167
+ return EconomyData()
168
+
169
+ return _parse_ecr_economy(rounds_root)