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.
- vlrdevapi-2.4.0/CHANGELOG.md +287 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/PKG-INFO +14 -3
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/README.md +12 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/pyproject.toml +1 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/scripts/check_mdx_examples.py +1 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/scripts/download_fixtures.py +157 -2
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/__init__.py +17 -7
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/__init__.pyi +15 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_client.py +2 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/models.py +4 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/parser.py +47 -3
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/common.py +30 -6
- vlrdevapi-2.4.0/src/vlrdevapi/_news/__init__.py +5 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/article/__init__.py +4 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/article/models.py +41 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/article/namespace.py +86 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/article/namespace.pyi +19 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/article/parser.py +457 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/common.py +48 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/list/__init__.py +4 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/list/models.py +55 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/list/namespace.py +85 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/list/namespace.pyi +19 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/list/parser.py +109 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/namespace.py +127 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_news/namespace.pyi +21 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/_utils.py +56 -2
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/namespace.py +20 -7
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/parser.py +7 -2
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/models.py +29 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/namespace.py +3 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/parser.py +120 -46
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/namespace.py +17 -5
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/parser.py +6 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/paths.py +13 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/__init__.py +23 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/datetime.py +32 -0
- vlrdevapi-2.4.0/src/vlrdevapi/commons/regions.py +242 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/validators.py +1 -1
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/uv.lock +1 -1
- vlrdevapi-2.2.0/CHANGELOG.md +0 -148
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.gitattributes +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/cleanup-caches.yml +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/docs-check.yml +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/publish.yml +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.github/workflows/test.yml +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.gitignore +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.python-version +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/.readthedocs.yaml +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/CODE_OF_CONDUCT.md +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/CONTRIBUTING.md +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/LICENCE +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/SECURITY.md +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/SUPPORT.md +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_base.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_cache.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/_common.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/list/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/matches/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/stages/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/standings/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_event/teams/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/completed/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/live/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_matches/upcoming/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/_common_parsing.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/agents/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/info/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/matches/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/profile/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_player/teams/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/match_namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/performance/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/players/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_series/vods/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/completed_matches/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/info/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/placements/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/roster/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/stats/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/transactions/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_team/upcoming_matches/parser.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/__init__.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/pagination.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/team_enrichment.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/_utils/team_parsing.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/countries.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/mappings.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/prizes.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/commons/timezone.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/exceptions.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/fetcher.py +0 -0
- {vlrdevapi-2.2.0 → vlrdevapi-2.4.0}/src/vlrdevapi/py.typed +0 -0
- {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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: vlrdevapi
|
|
3
|
-
Version: 2.
|
|
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
|
|
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
|
|
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)
|
|
@@ -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/
|
|
122
|
-
("/event/2682", "event/
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|