vlrdevapi 2.0.0__tar.gz → 2.1.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 (197) hide show
  1. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/CHANGELOG.md +61 -0
  2. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/PKG-INFO +1 -1
  3. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/pyproject.toml +1 -1
  4. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/__init__.py +1 -1
  5. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/info/parser.py +2 -3
  6. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/list/models.py +10 -2
  7. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/namespace.py +6 -2
  8. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/stages/parser.py +4 -6
  9. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/teams/__init__.py +2 -1
  10. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/teams/models.py +22 -1
  11. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/teams/namespace.py +4 -2
  12. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/teams/parser.py +30 -3
  13. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/_common_parsing.py +6 -8
  14. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/agents/models.py +0 -2
  15. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/agents/parser.py +1 -1
  16. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/profile/parser.py +1 -1
  17. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/players/parser.py +112 -94
  18. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/commons/datetime.py +20 -11
  19. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/commons/timezone.py +9 -2
  20. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/uv.lock +241 -229
  21. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.gitattributes +0 -0
  22. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  23. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  24. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.github/workflows/docs-check.yml +0 -0
  25. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.github/workflows/publish.yml +0 -0
  26. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.github/workflows/test.yml +0 -0
  27. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.gitignore +0 -0
  28. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.python-version +0 -0
  29. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/.readthedocs.yaml +0 -0
  30. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/CODE_OF_CONDUCT.md +0 -0
  31. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/CONTRIBUTING.md +0 -0
  32. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/LICENCE +0 -0
  33. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/README.md +0 -0
  34. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/SECURITY.md +0 -0
  35. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/SUPPORT.md +0 -0
  36. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/scripts/check_mdx_examples.py +0 -0
  37. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/scripts/download_fixtures.py +0 -0
  38. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/__init__.pyi +0 -0
  39. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_base.py +0 -0
  40. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_cache.py +0 -0
  41. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_client.py +0 -0
  42. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/__init__.py +0 -0
  43. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/_common.py +0 -0
  44. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/info/__init__.py +0 -0
  45. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/info/models.py +0 -0
  46. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/info/namespace.py +0 -0
  47. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
  48. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/list/__init__.py +0 -0
  49. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/list/namespace.py +0 -0
  50. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
  51. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/list/parser.py +0 -0
  52. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/matches/__init__.py +0 -0
  53. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/matches/models.py +0 -0
  54. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/matches/namespace.py +0 -0
  55. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
  56. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/matches/parser.py +0 -0
  57. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/namespace.pyi +0 -0
  58. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/stages/__init__.py +0 -0
  59. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/stages/models.py +0 -0
  60. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/stages/namespace.py +0 -0
  61. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
  62. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/standings/__init__.py +0 -0
  63. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/standings/models.py +0 -0
  64. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/standings/namespace.py +0 -0
  65. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
  66. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/standings/parser.py +0 -0
  67. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
  68. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/__init__.py +0 -0
  69. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/common.py +0 -0
  70. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
  71. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/completed/models.py +0 -0
  72. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
  73. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
  74. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/completed/parser.py +0 -0
  75. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/live/__init__.py +0 -0
  76. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/live/models.py +0 -0
  77. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/live/namespace.py +0 -0
  78. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
  79. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/live/parser.py +0 -0
  80. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/namespace.py +0 -0
  81. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/namespace.pyi +0 -0
  82. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
  83. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
  84. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
  85. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
  86. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_matches/upcoming/parser.py +0 -0
  87. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/__init__.py +0 -0
  88. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/agents/__init__.py +0 -0
  89. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/agents/namespace.py +0 -0
  90. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
  91. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/info/__init__.py +0 -0
  92. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/info/models.py +0 -0
  93. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/info/namespace.py +0 -0
  94. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
  95. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/info/parser.py +0 -0
  96. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/matches/__init__.py +0 -0
  97. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/matches/models.py +0 -0
  98. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/matches/namespace.py +0 -0
  99. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
  100. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/matches/parser.py +0 -0
  101. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/namespace.py +0 -0
  102. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/namespace.pyi +0 -0
  103. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/profile/__init__.py +0 -0
  104. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/profile/models.py +0 -0
  105. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/profile/namespace.py +0 -0
  106. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
  107. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/teams/__init__.py +0 -0
  108. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/teams/models.py +0 -0
  109. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/teams/namespace.py +0 -0
  110. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
  111. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_player/teams/parser.py +0 -0
  112. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/__init__.py +0 -0
  113. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/economy/__init__.py +0 -0
  114. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/economy/models.py +0 -0
  115. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/economy/namespace.py +0 -0
  116. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
  117. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/economy/parser.py +0 -0
  118. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/info/__init__.py +0 -0
  119. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/info/models.py +0 -0
  120. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/info/namespace.py +0 -0
  121. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
  122. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/info/parser.py +0 -0
  123. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/match_namespace.py +0 -0
  124. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
  125. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/namespace.py +0 -0
  126. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/namespace.pyi +0 -0
  127. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/performance/__init__.py +0 -0
  128. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/performance/models.py +0 -0
  129. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/performance/namespace.py +0 -0
  130. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
  131. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/performance/parser.py +0 -0
  132. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/players/__init__.py +0 -0
  133. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/players/models.py +0 -0
  134. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/players/namespace.py +0 -0
  135. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
  136. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
  137. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/rounds/models.py +0 -0
  138. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/rounds/namespace.py +0 -0
  139. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
  140. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/rounds/parser.py +0 -0
  141. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/vods/__init__.py +0 -0
  142. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/vods/models.py +0 -0
  143. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/vods/namespace.py +0 -0
  144. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
  145. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_series/vods/parser.py +0 -0
  146. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/__init__.py +0 -0
  147. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
  148. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
  149. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
  150. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
  151. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/completed_matches/parser.py +0 -0
  152. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/info/__init__.py +0 -0
  153. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/info/models.py +0 -0
  154. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/info/namespace.py +0 -0
  155. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
  156. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/info/parser.py +0 -0
  157. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/namespace.py +0 -0
  158. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/namespace.pyi +0 -0
  159. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/placements/__init__.py +0 -0
  160. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/placements/models.py +0 -0
  161. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/placements/namespace.py +0 -0
  162. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
  163. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/placements/parser.py +0 -0
  164. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/roster/__init__.py +0 -0
  165. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/roster/models.py +0 -0
  166. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/roster/namespace.py +0 -0
  167. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
  168. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/roster/parser.py +0 -0
  169. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/stats/__init__.py +0 -0
  170. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/stats/models.py +0 -0
  171. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/stats/namespace.py +0 -0
  172. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
  173. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/stats/parser.py +0 -0
  174. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
  175. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/transactions/models.py +0 -0
  176. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
  177. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
  178. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/transactions/parser.py +0 -0
  179. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
  180. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
  181. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
  182. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
  183. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_team/upcoming_matches/parser.py +0 -0
  184. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_utils/__init__.py +0 -0
  185. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_utils/pagination.py +0 -0
  186. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_utils/paths.py +0 -0
  187. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_utils/team_enrichment.py +0 -0
  188. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/_utils/team_parsing.py +0 -0
  189. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/commons/__init__.py +0 -0
  190. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/commons/countries.py +0 -0
  191. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/commons/mappings.py +0 -0
  192. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/commons/prizes.py +0 -0
  193. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/exceptions.py +0 -0
  194. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/fetcher.py +0 -0
  195. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/py.typed +0 -0
  196. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/src/vlrdevapi/validators.py +0 -0
  197. {vlrdevapi-2.0.0 → vlrdevapi-2.1.0}/wrangler.jsonc +0 -0
@@ -5,6 +5,65 @@ 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
+ ## [Unreleased]
9
+
10
+ ### Changed
11
+
12
+ - **Events list dates without a year** — `event.list()` dates are rendered
13
+ on vlr.gg without a year (e.g. `"Jul 9 – Aug 23"`). These now use a
14
+ sentinel year of `2019` instead of the current year. Any parsed date with
15
+ `year < 2020` means the source omitted the year, so guard with
16
+ `event.start_date.year < 2020` before relying on it.
17
+
18
+ ### Fixed
19
+
20
+ - **Python 3.13/3.15 date parsing deprecation** — `datetime.strptime` emits
21
+ a `DeprecationWarning` when parsing a day-of-month without a year (and will
22
+ change behavior in Python 3.15). All such parses now supply an explicit
23
+ year derived from context (reference date, page-header year, or the
24
+ sentinel above).
25
+
26
+ ## [2.1.0] - 2026-08-07
27
+
28
+ ### Added
29
+
30
+ - **Event teams player rosters** — `event.teams()` now includes a `players`
31
+ list on each `Team`, containing each player's `name` and `id` parsed from
32
+ the event page. The `players` field is always present and defaults to `[]`
33
+ when roster data is unavailable. A new `TeamPlayer` model was added and exposed
34
+ from the `_event.teams` submodule.
35
+
36
+ ### Changed
37
+
38
+ - **Documentation** — event teams reference updated with the new `players`
39
+ field, a `TeamPlayer` fields table, and a roster iteration example.
40
+
41
+ ## [2.0.1] - 2026-07-29
42
+
43
+ ### Fixed
44
+
45
+ - **Series player stats** — overview tab HTML changed from `<table>` to
46
+ `<div>` on vlr.gg; updated CSS selectors to match the new structure.
47
+ - **Player agent stats** — agent table class changed from `wf-table` to
48
+ `st-table.mod-agent-rows`; column count reduced from 17 to 16 (FKPR and
49
+ FDPR columns consolidated into a single FK:FD ratio column).
50
+ - **Player profile** — same agent table selector fix; the profile fallback
51
+ to `timespan=all` now works correctly.
52
+ - **Team fixtures** — updated stale test assertions for NRG Haven map
53
+ stats (games played, wins, losses, etc.) and total winnings to match
54
+ current vlr.gg data.
55
+
56
+ ### Removed
57
+
58
+ - `fkpr` and `fdpr` fields from `AgentStats` model — vlr.gg no longer
59
+ exposes separate First Kills Per Round / First Deaths Per Round columns.
60
+ The `first_kills` and `first_deaths` total fields remain available.
61
+
62
+ ### Changed
63
+
64
+ - **Documentation** — agents schema reference updated to reflect the
65
+ removal of `fkpr` and `fdpr`.
66
+
8
67
  ## [2.0.0] - 2026-07-07
9
68
 
10
69
  ### Major Rewrite
@@ -66,4 +125,6 @@ The API surface, module structure, and type system are all new.
66
125
  - `aiohttp` and `asyncio` dependencies.
67
126
  - v1.x match/event/team parsing modules.
68
127
 
128
+ [2.1.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.0.1...v2.1.0
129
+ [2.0.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.0.0...v2.0.1
69
130
  [2.0.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v1.6.2...v2.0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vlrdevapi
3
- Version: 2.0.0
3
+ Version: 2.1.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vlrdevapi"
7
- version = "2.0.0"
7
+ version = "2.1.0"
8
8
  description = "A Python library to scrape data from vlr.gg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -45,7 +45,7 @@ if TYPE_CHECKING:
45
45
  matches: MatchesNamespace
46
46
  client: type[VLRClient]
47
47
 
48
- __version__ = "2.0.0"
48
+ __version__ = "2.1.0"
49
49
 
50
50
  _default_client: "VLRClient | None" = None
51
51
 
@@ -288,10 +288,9 @@ def _parse_partial_date(text: str, reference_end: date) -> date | None:
288
288
  if full_attempt is not None:
289
289
  return full_attempt
290
290
 
291
- for fmt in ("%b %d", "%B %d"):
291
+ for fmt in ("%b %d %Y", "%B %d %Y"):
292
292
  try:
293
- dt = datetime.strptime(text, fmt)
294
- return dt.replace(year=reference_end.year).date()
293
+ return datetime.strptime(f"{text} {reference_end.year}", fmt).date()
295
294
  except ValueError:
296
295
  continue
297
296
 
@@ -22,8 +22,16 @@ class EventListItem(BaseModel):
22
22
  default="upcoming", description="Event status",
23
23
  )
24
24
  prize: EventPrize | None = Field(default=None, description="Prize information")
25
- start_date: date | None = Field(default=None, description="Event start date")
26
- end_date: date | None = Field(default=None, description="Event end date")
25
+ start_date: date | None = Field(
26
+ default=None,
27
+ description="Event start date. Year is 2019 when the listing omitted it "
28
+ "(year < 2020 means the year is not present).",
29
+ )
30
+ end_date: date | None = Field(
31
+ default=None,
32
+ description="Event end date. Year is 2019 when the listing omitted it "
33
+ "(year < 2020 means the year is not present).",
34
+ )
27
35
  region: str = Field(default="", description="Country or region name from flag")
28
36
  image_url: str = Field(default="", description="URL to the event thumbnail image")
29
37
  url: str = Field(default="", description="Full URL to the event page on vlr.gg")
@@ -99,8 +99,10 @@ class EventMatchNamespace:
99
99
  If ``None``, returns teams for all stages.
100
100
 
101
101
  Returns:
102
- EventTeams: Teams grouped by stage, with team ``name``, ``tag``,
103
- ``logo_url``, and roster info where available.
102
+ EventTeams: Teams grouped by stage, with team ``name``, ``id``,
103
+ optional ``note``, and a ``players`` list (each with ``name``
104
+ and ``id``). ``players`` is always present and may be an empty
105
+ list when roster data is unavailable.
104
106
 
105
107
  Raises:
106
108
  NotFoundError: If the event page does not exist (HTTP 404).
@@ -241,6 +243,8 @@ class EventNamespace:
241
243
  Returns:
242
244
  An ``EventTeamsNamespace`` instance. Call with an ``event_id``
243
245
  and optional ``stage`` to return an ``EventTeams`` model.
246
+ Each ``Team`` includes a ``players`` roster list that is always
247
+ present and may be empty when roster data is unavailable.
244
248
 
245
249
  """
246
250
  return self._teams
@@ -189,10 +189,9 @@ def _parse_subnav_single_date(text: str, year: int | None) -> date | None:
189
189
  return None
190
190
 
191
191
  if year is not None:
192
- for fmt in ("%b %d", "%B %d"):
192
+ for fmt in ("%b %d %Y", "%B %d %Y"):
193
193
  try:
194
- dt = datetime.strptime(text, fmt)
195
- return dt.replace(year=year).date()
194
+ return datetime.strptime(f"{text} {year}", fmt).date()
196
195
  except ValueError:
197
196
  continue
198
197
 
@@ -220,10 +219,9 @@ def _parse_subnav_partial_date(text: str, year: int) -> date | None:
220
219
  if full is not None:
221
220
  return full
222
221
 
223
- for fmt in ("%b %d", "%B %d"):
222
+ for fmt in ("%b %d %Y", "%B %d %Y"):
224
223
  try:
225
- dt = datetime.strptime(text, fmt)
226
- return dt.replace(year=year).date()
224
+ return datetime.strptime(f"{text} {year}", fmt).date()
227
225
  except ValueError:
228
226
  continue
229
227
 
@@ -1,8 +1,9 @@
1
- from vlrdevapi._event.teams.models import EventStageTeams, EventTeams
1
+ from vlrdevapi._event.teams.models import EventStageTeams, EventTeams, TeamPlayer
2
2
  from vlrdevapi._event.teams.namespace import EventTeamsNamespace
3
3
 
4
4
  __all__ = [
5
5
  "EventStageTeams",
6
6
  "EventTeams",
7
+ "TeamPlayer",
7
8
  "EventTeamsNamespace",
8
9
  ]
@@ -1,6 +1,23 @@
1
1
  from pydantic import BaseModel, ConfigDict, Field
2
2
 
3
3
 
4
+ class TeamPlayer(BaseModel):
5
+ """Player information for a team at an event."""
6
+
7
+ model_config = ConfigDict(
8
+ json_schema_extra={"description": "Player information for a team at an event."},
9
+ )
10
+
11
+ id: int = Field(
12
+ default=0,
13
+ description="Player identifier from vlr.gg (e.g., 1311)",
14
+ )
15
+ name: str = Field(
16
+ default="",
17
+ description="Player in-game name (e.g., 'Melser')",
18
+ )
19
+
20
+
4
21
  class Team(BaseModel):
5
22
  """Team information for an event from vlr.gg."""
6
23
 
@@ -20,6 +37,10 @@ class Team(BaseModel):
20
37
  default=None,
21
38
  description="Optional note (e.g., 'Partner Team')",
22
39
  )
40
+ players: list[TeamPlayer] = Field(
41
+ default_factory=list,
42
+ description="List of players on the team (name and id). Always present; empty when roster data is unavailable.",
43
+ )
23
44
 
24
45
 
25
46
  class EventStageTeams(BaseModel):
@@ -60,4 +81,4 @@ class EventTeams(BaseModel):
60
81
  return iter(self.stages)
61
82
 
62
83
  def __getitem__(self, index):
63
- return self.stages[index]
84
+ return self.stages[index]
@@ -43,8 +43,10 @@ class EventTeamsNamespace:
43
43
  stage: Optional stage name or path to filter by (e.g. 'playoffs').
44
44
 
45
45
  Returns:
46
- EventTeams: Teams grouped by stage, with team ``name``, ``tag``,
47
- ``logo_url``, and roster info where available.
46
+ EventTeams: Teams grouped by stage, with team ``name``, ``id``,
47
+ optional ``note``, and a ``players`` list (each with ``name``
48
+ and ``id``). ``players`` is always present and may be an empty
49
+ list when roster data is unavailable.
48
50
 
49
51
  Raises:
50
52
  ValidationError: If ``event_id`` is not a valid positive integer.
@@ -1,7 +1,6 @@
1
-
2
1
  from selectolax.parser import HTMLParser, Node
3
2
 
4
- from vlrdevapi._event.teams.models import Team
3
+ from vlrdevapi._event.teams.models import Team, TeamPlayer
5
4
 
6
5
 
7
6
  def parse_subnav(html: HTMLParser) -> list[tuple[str, str]]:
@@ -70,4 +69,32 @@ def _parse_team_card(card: Node) -> Team | None:
70
69
  note_el = card.css_first(".event-team-note")
71
70
  note = note_el.text(strip=True) if note_el else None
72
71
 
73
- return Team(name=name, id=team_id, note=note)
72
+ players = _parse_players(card)
73
+
74
+ return Team(name=name, id=team_id, note=note, players=players)
75
+
76
+
77
+ def _parse_players(card: Node) -> list[TeamPlayer]:
78
+ """Parse the players listed on a team card."""
79
+ players = []
80
+ for item in card.css(".event-team-players-item"):
81
+ player = _parse_player_item(item)
82
+ if player:
83
+ players.append(player)
84
+ return players
85
+
86
+
87
+ def _parse_player_item(item: Node) -> TeamPlayer | None:
88
+ """Parse a single player anchor (e.g. /player/1311/melser)."""
89
+ href = item.attributes.get("href", "")
90
+ if not href or "/player/" not in href:
91
+ return None
92
+
93
+ try:
94
+ id_str = href.split("/player/")[1].split("/")[0]
95
+ player_id = int(id_str)
96
+ except (ValueError, IndexError):
97
+ return None
98
+
99
+ name = item.text(strip=True)
100
+ return TeamPlayer(id=player_id, name=name)
@@ -139,7 +139,7 @@ def _parse_agent_row(tr: Node) -> AgentStats:
139
139
  stats = AgentStats()
140
140
  tds = tr.css("td")
141
141
 
142
- if len(tds) < 17:
142
+ if len(tds) < 16:
143
143
  return stats
144
144
 
145
145
  img_el = tds[0].css_first("img")
@@ -160,12 +160,10 @@ def _parse_agent_row(tr: Node) -> AgentStats:
160
160
  stats.kast = tds[7].text(strip=True)
161
161
  stats.kpr = _parse_float(tds[8].text(strip=True))
162
162
  stats.apr = _parse_float(tds[9].text(strip=True))
163
- stats.fkpr = _parse_float(tds[10].text(strip=True))
164
- stats.fdpr = _parse_float(tds[11].text(strip=True))
165
- stats.kills = _parse_int(tds[12].text(strip=True))
166
- stats.deaths = _parse_int(tds[13].text(strip=True))
167
- stats.assists = _parse_int(tds[14].text(strip=True))
168
- stats.first_kills = _parse_int(tds[15].text(strip=True))
169
- stats.first_deaths = _parse_int(tds[16].text(strip=True))
163
+ stats.kills = _parse_int(tds[11].text(strip=True))
164
+ stats.deaths = _parse_int(tds[12].text(strip=True))
165
+ stats.assists = _parse_int(tds[13].text(strip=True))
166
+ stats.first_kills = _parse_int(tds[14].text(strip=True))
167
+ stats.first_deaths = _parse_int(tds[15].text(strip=True))
170
168
 
171
169
  return stats
@@ -18,8 +18,6 @@ class AgentStats(BaseModel):
18
18
  kast: str = Field(default="", description="Kill, Assist, Survive, Trade %")
19
19
  kpr: float | None = Field(default=None, description="Kills Per Round")
20
20
  apr: float | None = Field(default=None, description="Assists Per Round")
21
- fkpr: float | None = Field(default=None, description="First Kills Per Round")
22
- fdpr: float | None = Field(default=None, description="First Deaths Per Round")
23
21
  kills: int | None = Field(default=None, description="Total kills")
24
22
  deaths: int | None = Field(default=None, description="Total deaths")
25
23
  assists: int | None = Field(default=None, description="Total assists")
@@ -16,7 +16,7 @@ def parse_agent_stats(html: HTMLParser, timespan: str = "all") -> AgentStatsPage
16
16
  card = _next_sibling_card(search_from)
17
17
  if card is None:
18
18
  break
19
- table = card.css_first("table.wf-table")
19
+ table = card.css_first("table.st-table.mod-agent-rows")
20
20
  if table is None:
21
21
  break
22
22
  tbody = table.css_first("tbody")
@@ -69,7 +69,7 @@ def _parse_agents(html: HTMLParser, profile: PlayerProfile) -> None:
69
69
  card = _next_sibling_card(search_from)
70
70
  if card is None:
71
71
  break
72
- table = card.css_first("table.wf-table")
72
+ table = card.css_first("table.st-table.mod-agent-rows")
73
73
  if table is None:
74
74
  break
75
75
  tbody = table.css_first("tbody")
@@ -138,119 +138,137 @@ def _parse_stat_cell(td: Node) -> tuple:
138
138
  return overall, attack, defend
139
139
 
140
140
 
141
- def _parse_player_row(tr: Node) -> PlayerGameStats:
142
- """Parse a player row from the stats table.
141
+ def _parse_player_row(cell: Node) -> PlayerGameStats:
142
+ player = PlayerGameStats()
143
143
 
144
- Args:
145
- tr: The table row Node containing player data.
144
+ a = cell.css_first("a")
145
+ if a:
146
+ href = a.attributes.get("href", "") or ""
147
+ parts = href.strip("/").split("/")
148
+ if len(parts) >= 2:
149
+ with contextlib.suppress(ValueError):
150
+ player.player_id = int(parts[1])
146
151
 
147
- Returns:
148
- PlayerGameStats: Parsed player info including name, ID, team,
149
- and agents.
150
-
151
- """
152
- player = PlayerGameStats()
153
- tds = tr.css("td")
154
-
155
- td_player = tds[0] if len(tds) > 0 else None
156
- td_agents = tds[1] if len(tds) > 1 else None
157
-
158
- if td_player:
159
- a = td_player.css_first("a")
160
- if a:
161
- href = a.attributes.get("href", "") or ""
162
- parts = href.strip("/").split("/")
163
- if len(parts) >= 2:
164
- with contextlib.suppress(ValueError):
165
- player.player_id = int(parts[1])
166
-
167
- name_el = td_player.css_first(".text-of")
168
- if name_el:
169
- player.name = name_el.text(strip=True)
152
+ name_el = cell.css_first(".ovw-player-name")
153
+ if name_el:
154
+ player.name = name_el.text(strip=True)
155
+ else:
156
+ bold_el = cell.css_first("div[style*='font-weight: 700']")
157
+ if bold_el:
158
+ player.name = bold_el.text(strip=True)
170
159
  else:
171
- bold_el = td_player.css_first("div[style*='font-weight: 700']")
172
- if bold_el:
173
- player.name = bold_el.text(strip=True)
174
- else:
175
- for div in td_player.css("div"):
176
- text = div.text(strip=True)
177
- if text and len(text) <= 20 and not div.css("div"):
178
- player.name = text
179
- break
180
-
181
- team_el = td_player.css_first(".ge-text-light")
182
- if team_el:
183
- player.team_short = team_el.text(strip=True)
184
-
185
- flag_el = td_player.css_first("i.flag")
186
- if flag_el:
187
- for cls in (flag_el.attributes.get("class") or "").split():
188
- if cls.startswith("mod-") and cls != "mod-none":
189
- player.country_code = cls[4:]
190
- player.country = get_country_name(player.country_code)
160
+ for div in cell.css("div"):
161
+ text = div.text(strip=True)
162
+ if text and len(text) <= 20 and not div.css("div"):
163
+ player.name = text
191
164
  break
192
165
 
193
- if td_agents:
194
- for img in td_agents.css("img"):
195
- alt = img.attributes.get("alt", "") or ""
196
- if alt:
197
- player.agents.append(alt.title())
166
+ team_el = cell.css_first(".ovw-player-tag")
167
+ if team_el:
168
+ player.team_short = team_el.text(strip=True)
198
169
 
199
- return player
170
+ flag_el = cell.css_first("i.flag")
171
+ if flag_el:
172
+ for cls in (flag_el.attributes.get("class") or "").split():
173
+ if cls.startswith("mod-") and cls != "mod-none":
174
+ player.country_code = cls[4:]
175
+ player.country = get_country_name(player.country_code)
176
+ break
200
177
 
178
+ for img in cell.css(".ovw-agents img"):
179
+ alt = img.attributes.get("alt", "") or ""
180
+ if alt:
181
+ player.agents.append(alt.title())
201
182
 
202
- def _parse_table(table: Node) -> list[PlayerGameStats]:
203
- """Parse an entire player stats table into PlayerGameStats list.
183
+ return player
204
184
 
205
- Args:
206
- table: The table Node containing player stat rows.
207
185
 
208
- Returns:
209
- list[PlayerGameStats]: Parsed player stats with side-specific
210
- breakdowns.
186
+ def _parse_kda_cell(cell: Node) -> tuple:
187
+ kills_o = kills_a = kills_d = None
188
+ deaths_o = deaths_a = deaths_d = None
189
+ assists_o = assists_a = assists_d = None
211
190
 
212
- """
213
- players: list[PlayerGameStats] = []
214
- rows = table.css("tbody tr")
215
- for tr in rows:
216
- tds = tr.css("td")
217
- if len(tds) < 14:
218
- continue
191
+ for stat_span in cell.css("span.ovw-kda-stat"):
192
+ col = stat_span.attributes.get("data-col", "")
193
+ both_el = stat_span.css_first("span.mod-both")
194
+ t_el = stat_span.css_first("span.mod-t")
195
+ ct_el = stat_span.css_first("span.mod-ct")
196
+ both = _parse_int(both_el.text(strip=True)) if both_el is not None else None
197
+ t = _parse_int(t_el.text(strip=True)) if t_el is not None else None
198
+ ct = _parse_int(ct_el.text(strip=True)) if ct_el is not None else None
199
+ if col == "kills":
200
+ kills_o, kills_a, kills_d = both, t, ct
201
+ elif col == "deaths":
202
+ deaths_o, deaths_a, deaths_d = both, t, ct
203
+ elif col == "assists":
204
+ assists_o, assists_a, assists_d = both, t, ct
219
205
 
220
- player = _parse_player_row(tr)
206
+ return (kills_o, kills_a, kills_d, deaths_o, deaths_a, deaths_d, assists_o, assists_a, assists_d)
221
207
 
222
- stat_tds = tds[2:14]
223
208
 
224
- overall_vals = []
225
- attack_vals = []
226
- defend_vals = []
209
+ def _parse_table(table: Node) -> list[PlayerGameStats]:
210
+ players: list[PlayerGameStats] = []
211
+ rows = table.css("div.ovw-row")
212
+ for row in rows:
213
+ cls = row.attributes.get("class", "") or ""
214
+ if "mod-head" in cls:
215
+ continue
227
216
 
228
- for td in stat_tds:
229
- o, a, d = _parse_stat_cell(td)
230
- overall_vals.append(o)
231
- attack_vals.append(a)
232
- defend_vals.append(d)
217
+ cells = row.css("div.ovw-cell")
218
+ if len(cells) < 11:
219
+ continue
233
220
 
234
- def _build_side(vals) -> SideStats:
221
+ player = _parse_player_row(cells[0])
222
+
223
+ rating_vals = _parse_stat_cell(cells[1])
224
+ acs_vals = _parse_stat_cell(cells[2])
225
+ kills_vals, deaths_vals, assists_vals = None, None, None
226
+ kda_vals = _parse_kda_cell(cells[3])
227
+ kills_vals = (kda_vals[0], kda_vals[1], kda_vals[2])
228
+ deaths_vals = (kda_vals[3], kda_vals[4], kda_vals[5])
229
+ assists_vals = (kda_vals[6], kda_vals[7], kda_vals[8])
230
+ kd_diff_vals = _parse_stat_cell(cells[4])
231
+ kast_vals = _parse_stat_cell(cells[5])
232
+ adr_vals = _parse_stat_cell(cells[6])
233
+ hs_percent_vals = _parse_stat_cell(cells[7])
234
+ first_kills_vals = _parse_stat_cell(cells[8])
235
+ first_deaths_vals = _parse_stat_cell(cells[9])
236
+ fk_fd_diff_vals = _parse_stat_cell(cells[10])
237
+
238
+ def _build_side(
239
+ rating_t, acs_t, kills_t, deaths_t, assists_t, kd_diff_t, kast_t, adr_t, hs_percent_t, first_kills_t, first_deaths_t, fk_fd_diff_t,
240
+ ) -> SideStats:
235
241
  return SideStats(
236
- rating=vals[0],
237
- acs=vals[1],
238
- kills=vals[2],
239
- deaths=vals[3],
240
- assists=vals[4],
241
- kd_diff=vals[5],
242
- kast=vals[6],
243
- adr=vals[7],
244
- hs_percent=vals[8],
245
- first_kills=vals[9],
246
- first_deaths=vals[10],
247
- fk_fd_diff=vals[11],
242
+ rating=rating_t,
243
+ acs=acs_t,
244
+ kills=kills_t,
245
+ deaths=deaths_t,
246
+ assists=assists_t,
247
+ kd_diff=kd_diff_t,
248
+ kast=kast_t,
249
+ adr=adr_t,
250
+ hs_percent=hs_percent_t,
251
+ first_kills=first_kills_t,
252
+ first_deaths=first_deaths_t,
253
+ fk_fd_diff=fk_fd_diff_t,
248
254
  )
249
255
 
250
256
  player.stats = PlayerStats(
251
- overall=_build_side(overall_vals),
252
- attack=_build_side(attack_vals),
253
- defend=_build_side(defend_vals),
257
+ overall=_build_side(
258
+ rating_vals[0], acs_vals[0], kills_vals[0], deaths_vals[0], assists_vals[0],
259
+ kd_diff_vals[0], kast_vals[0], adr_vals[0], hs_percent_vals[0],
260
+ first_kills_vals[0], first_deaths_vals[0], fk_fd_diff_vals[0],
261
+ ),
262
+ attack=_build_side(
263
+ rating_vals[1], acs_vals[1], kills_vals[1], deaths_vals[1], assists_vals[1],
264
+ kd_diff_vals[1], kast_vals[1], adr_vals[1], hs_percent_vals[1],
265
+ first_kills_vals[1], first_deaths_vals[1], fk_fd_diff_vals[1],
266
+ ),
267
+ defend=_build_side(
268
+ rating_vals[2], acs_vals[2], kills_vals[2], deaths_vals[2], assists_vals[2],
269
+ kd_diff_vals[2], kast_vals[2], adr_vals[2], hs_percent_vals[2],
270
+ first_kills_vals[2], first_deaths_vals[2], fk_fd_diff_vals[2],
271
+ ),
254
272
  )
255
273
 
256
274
  players.append(player)
@@ -319,7 +337,7 @@ def parse_players_stats(html: HTMLParser, game_id: str = "all") -> PlayersStats:
319
337
  if not result.map_name:
320
338
  result.map_name = map_div.text(strip=True)
321
339
 
322
- tables = game_div.css("table.wf-table-inset.mod-overview")
340
+ tables = game_div.css("div.ovw-table")
323
341
  if len(tables) < 2:
324
342
  return result
325
343