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.
- vlrdevapi-2.4.1/.nvmrc +1 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.readthedocs.yaml +3 -2
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/CHANGELOG.md +38 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/PKG-INFO +4 -4
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/pyproject.toml +11 -11
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/scripts/check_mdx_examples.py +31 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/scripts/download_fixtures.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/__init__.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_base.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/common.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/common.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/_common_parsing.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/parser.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/parser.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/parser.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/parser.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/_utils.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/namespace.py +14 -11
- vlrdevapi-2.4.1/src/vlrdevapi/_series/economy/parser.py +169 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/parser.py +10 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/parser.py +54 -16
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/namespace.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/parser.py +1 -1
- vlrdevapi-2.4.1/src/vlrdevapi/_series/vods/parser.py +50 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/namespace.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/parser.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/parser.py +2 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/pagination.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/team_enrichment.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/team_parsing.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/timezone.py +1 -1
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/fetcher.py +1 -1
- vlrdevapi-2.4.1/uv.lock +1165 -0
- vlrdevapi-2.4.0/src/vlrdevapi/_series/economy/parser.py +0 -144
- vlrdevapi-2.4.0/src/vlrdevapi/_series/vods/parser.py +0 -30
- vlrdevapi-2.4.0/uv.lock +0 -1049
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.gitattributes +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/cleanup-caches.yml +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/docs-check.yml +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/publish.yml +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.github/workflows/test.yml +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.gitignore +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/.python-version +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/CODE_OF_CONDUCT.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/CONTRIBUTING.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/LICENCE +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/README.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/SECURITY.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/SUPPORT.md +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/__init__.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_cache.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_client.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/_common.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/article/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/list/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_news/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/match_namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/_utils/paths.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/__init__.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/countries.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/datetime.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/mappings.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/prizes.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/commons/regions.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/exceptions.py +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/py.typed +0 -0
- {vlrdevapi-2.4.0 → vlrdevapi-2.4.1}/src/vlrdevapi/validators.py +0 -0
- {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
|
|
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.
|
|
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.
|
|
42
|
-
Requires-Dist: selectolax>=0.
|
|
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>=
|
|
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.
|
|
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.
|
|
26
|
-
"selectolax>=0.
|
|
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>=
|
|
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.
|
|
44
|
-
"pytest>=
|
|
45
|
-
"tzdata>=2026.
|
|
46
|
-
"ty>=0.0.
|
|
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.
|
|
50
|
-
"mkdocstrings>=0.
|
|
51
|
-
"mkdocstrings-python>=
|
|
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.
|
|
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")
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -9,7 +9,8 @@ from typing import Any, Protocol
|
|
|
9
9
|
from zoneinfo import ZoneInfo
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
|
-
from selectolax.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -8,7 +8,8 @@ controls.
|
|
|
8
8
|
import logging
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
|
|
11
|
-
from selectolax.
|
|
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
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
from datetime import tzinfo
|
|
3
3
|
from zoneinfo import ZoneInfo
|
|
4
4
|
|
|
5
|
-
from selectolax.
|
|
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 (
|
|
@@ -4,7 +4,7 @@ from datetime import tzinfo
|
|
|
4
4
|
from zoneinfo import ZoneInfo
|
|
5
5
|
|
|
6
6
|
import httpx
|
|
7
|
-
from selectolax.
|
|
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
|
|
84
|
-
``.ovw-player-tag`` elements
|
|
85
|
-
team names from the series header
|
|
86
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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)
|