vlrdevapi 3.1.0__tar.gz → 3.1.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-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/docs-check.yml +7 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/publish.yml +26 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/CHANGELOG.md +18 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/PKG-INFO +5 -2
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/README.md +3 -1
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/pyproject.toml +3 -2
- vlrdevapi-3.1.1/scripts/check_docs_schema.py +478 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/scripts/download_fixtures.py +14 -2
- vlrdevapi-3.1.1/server.json +32 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/__init__.py +1 -1
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/uv.lock +573 -518
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.gitattributes +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/cleanup-caches.yml +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.github/workflows/test.yml +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.gitignore +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.nvmrc +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.python-version +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/.readthedocs.yaml +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/CODE_OF_CONDUCT.md +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/CONTRIBUTING.md +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/LICENCE +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/SECURITY.md +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/SUPPORT.md +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/scripts/check_mdx_examples.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/__init__.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_base.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_cache.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_client.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/_common.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/info/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/list/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/matches/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/stages/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/standings/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_event/teams/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/common.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/completed/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/live/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_matches/upcoming/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/article/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/common.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/list/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_news/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/_common_parsing.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/agents/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/info/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/matches/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/placements/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/profile/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_player/teams/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/_utils.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/economy/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/info/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/match_namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/match_namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/performance/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/players/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/replay/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/rounds/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_series/vods/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/completed_matches/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/info/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/placements/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/roster/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/stats/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/transactions/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/models.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/namespace.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/namespace.pyi +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_team/upcoming_matches/parser.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/pagination.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/paths.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/team_enrichment.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/team_parsing.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/_utils/text.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/countries.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/datetime.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/export.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/mappings.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/prizes.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/regions.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/commons/timezone.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/exceptions.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/fetcher.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/_common.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/server.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/__init__.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/composite.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/events.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/matches.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/news.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/players.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/series.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/mcp/tools/teams.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/py.typed +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/src/vlrdevapi/validators.py +0 -0
- {vlrdevapi-3.1.0 → vlrdevapi-3.1.1}/wrangler.jsonc +0 -0
|
@@ -5,11 +5,15 @@ on:
|
|
|
5
5
|
paths:
|
|
6
6
|
- 'official-docs/**'
|
|
7
7
|
- 'docs-py/**'
|
|
8
|
+
- 'src/**'
|
|
9
|
+
- 'scripts/**'
|
|
8
10
|
push:
|
|
9
11
|
branches: [main]
|
|
10
12
|
paths:
|
|
11
13
|
- 'official-docs/**'
|
|
12
14
|
- 'docs-py/**'
|
|
15
|
+
- 'src/**'
|
|
16
|
+
- 'scripts/**'
|
|
13
17
|
|
|
14
18
|
jobs:
|
|
15
19
|
check:
|
|
@@ -24,6 +28,9 @@ jobs:
|
|
|
24
28
|
- name: Install package
|
|
25
29
|
run: uv sync --group dev --all-extras
|
|
26
30
|
|
|
31
|
+
- name: Validate documentation schemas against Python library
|
|
32
|
+
run: uv run scripts/check_docs_schema.py
|
|
33
|
+
|
|
27
34
|
# Restore fixture cache scoped to the current ref (branch or main) and version prefix (v3).
|
|
28
35
|
# This ensures caches are never shared across unrelated branches or older versions.
|
|
29
36
|
- name: Restore fixture cache
|
|
@@ -75,3 +75,29 @@ jobs:
|
|
|
75
75
|
|
|
76
76
|
- name: Publish to PyPI
|
|
77
77
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
78
|
+
|
|
79
|
+
mcp-publish:
|
|
80
|
+
name: Publish to MCP Registry
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
needs:
|
|
83
|
+
- pypi-publish
|
|
84
|
+
|
|
85
|
+
permissions:
|
|
86
|
+
id-token: write
|
|
87
|
+
contents: read
|
|
88
|
+
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v7
|
|
91
|
+
|
|
92
|
+
- name: Install mcp-publisher
|
|
93
|
+
run: |
|
|
94
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
95
|
+
|
|
96
|
+
- name: Wait for PyPI propagation
|
|
97
|
+
run: sleep 15
|
|
98
|
+
|
|
99
|
+
- name: Authenticate to MCP Registry
|
|
100
|
+
run: ./mcp-publisher login github-oidc
|
|
101
|
+
|
|
102
|
+
- name: Publish to MCP Registry
|
|
103
|
+
run: ./mcp-publisher publish
|
|
@@ -5,6 +5,23 @@ 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
|
+
## [3.1.1] - 2026-11-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Official MCP Registry Manifest** — Added `server.json` compliant with the Model Context Protocol registry schema (`registry.modelcontextprotocol.io`), registering `io.github.vanshbordia/vlrdevapi` with stdio transport and `uvx` runtime arguments.
|
|
13
|
+
- **Automated MCP Registry Publishing Workflow** — Extended GitHub Actions release pipeline (`publish.yml`) with automated OIDC authentication (`id-token: write`) via `mcp-publisher`.
|
|
14
|
+
- **PyPI MCP Ownership Verification** — Added package ownership verification marker to `README.md`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **MCP Registry CLI Entrypoint** — Expose `vlrdevapi` console script alias to `vlrdevapi.mcp.server:main` alongside `vlrdevapi-mcp` so MCP registry clients launch the server using the package identifier.
|
|
19
|
+
- **Windows Timezone Support** — Added `tzdata>=2025.1; sys_platform == 'win32'` as a core dependency to resolve `ZoneInfoNotFoundError` when running `vlrdevapi` or `uvx` on Windows.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Dependency Upgrades** — Bumped core Python dependencies (Pydantic, Pandas, Polars, PyArrow, Pytest, Ruff) and documentation website dependencies (Next.js 16.4.6, Fumadocs 16.19, Tailwind CSS 4.3.5).
|
|
24
|
+
|
|
8
25
|
## [3.1.0] - 2026-10-25
|
|
9
26
|
|
|
10
27
|
### Added
|
|
@@ -485,6 +502,7 @@ The API surface, module structure, and type system are all new.
|
|
|
485
502
|
- `aiohttp` and `asyncio` dependencies.
|
|
486
503
|
- v1.x match/event/team parsing modules.
|
|
487
504
|
|
|
505
|
+
[3.1.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v3.1.0...v3.1.1
|
|
488
506
|
[3.1.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v3.0.0...v3.1.0
|
|
489
507
|
[3.0.0]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.4.1...v3.0.0
|
|
490
508
|
[2.4.1]: https://github.com/Vanshbordia/vlrdevapi/compare/v2.4.0...v2.4.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vlrdevapi
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.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
|
|
@@ -42,6 +42,7 @@ Requires-Python: >=3.11
|
|
|
42
42
|
Requires-Dist: httpx>=0.28.1
|
|
43
43
|
Requires-Dist: pydantic>=2.14.1
|
|
44
44
|
Requires-Dist: selectolax>=1.0.0
|
|
45
|
+
Requires-Dist: tzdata>=2025.1; sys_platform == 'win32'
|
|
45
46
|
Provides-Extra: all
|
|
46
47
|
Requires-Dist: mcp>=1.2.0; extra == 'all'
|
|
47
48
|
Requires-Dist: openpyxl>=3.1.0; extra == 'all'
|
|
@@ -88,6 +89,8 @@ Description-Content-Type: text/markdown
|
|
|
88
89
|
|
|
89
90
|
</div>
|
|
90
91
|
|
|
92
|
+
<!-- mcp-name: io.github.vanshbordia/vlrdevapi -->
|
|
93
|
+
|
|
91
94
|
---
|
|
92
95
|
|
|
93
96
|
**vlrdevapi** is a high-performance, type-safe Python client for extracting and analyzing Valorant esports data from [VLR.gg](https://www.vlr.gg/). Built for competitive analysts, data scientists, Discord bot creators, and esports engineers, it delivers structured [Pydantic](https://docs.pydantic.dev/) models with full type hinting, built-in rate limiting, automatic retries, and comprehensive coverage across all VLR.gg entities.
|
|
@@ -373,7 +376,7 @@ print(players.to_markdown())
|
|
|
373
376
|
|
|
374
377
|
## Model Context Protocol (MCP) Server
|
|
375
378
|
|
|
376
|
-
VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents:
|
|
379
|
+
VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents (`io.github.vanshbordia/vlrdevapi` on the official MCP Registry):
|
|
377
380
|
|
|
378
381
|
```bash
|
|
379
382
|
# Run server over stdio
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
20
|
+
<!-- mcp-name: io.github.vanshbordia/vlrdevapi -->
|
|
21
|
+
|
|
20
22
|
---
|
|
21
23
|
|
|
22
24
|
**vlrdevapi** is a high-performance, type-safe Python client for extracting and analyzing Valorant esports data from [VLR.gg](https://www.vlr.gg/). Built for competitive analysts, data scientists, Discord bot creators, and esports engineers, it delivers structured [Pydantic](https://docs.pydantic.dev/) models with full type hinting, built-in rate limiting, automatic retries, and comprehensive coverage across all VLR.gg entities.
|
|
@@ -302,7 +304,7 @@ print(players.to_markdown())
|
|
|
302
304
|
|
|
303
305
|
## Model Context Protocol (MCP) Server
|
|
304
306
|
|
|
305
|
-
VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents:
|
|
307
|
+
VLRdevAPI includes a native MCP server for AI assistants and autonomous coding agents (`io.github.vanshbordia/vlrdevapi` on the official MCP Registry):
|
|
306
308
|
|
|
307
309
|
```bash
|
|
308
310
|
# Run server over stdio
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vlrdevapi"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.1"
|
|
8
8
|
description = "A Python library to scrape data from vlr.gg"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -37,6 +37,7 @@ dependencies = [
|
|
|
37
37
|
"httpx>=0.28.1",
|
|
38
38
|
"pydantic>=2.14.1",
|
|
39
39
|
"selectolax>=1.0.0",
|
|
40
|
+
"tzdata>=2025.1; sys_platform == 'win32'",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.urls]
|
|
@@ -80,6 +81,7 @@ all = [
|
|
|
80
81
|
]
|
|
81
82
|
|
|
82
83
|
[project.scripts]
|
|
84
|
+
vlrdevapi = "vlrdevapi.mcp.server:main"
|
|
83
85
|
vlrdevapi-mcp = "vlrdevapi.mcp.server:main"
|
|
84
86
|
|
|
85
87
|
[dependency-groups]
|
|
@@ -123,4 +125,3 @@ exclude = [
|
|
|
123
125
|
"/.pytest_cache",
|
|
124
126
|
"/.ruff_cache",
|
|
125
127
|
]
|
|
126
|
-
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"""Validate documentation TypeTable schemas against live Python library models.
|
|
2
|
+
|
|
3
|
+
Performs static and dynamic introspection of vlrdevapi against the official
|
|
4
|
+
documentation (English and all translations: es, pt-br, zh-cn).
|
|
5
|
+
|
|
6
|
+
Checks:
|
|
7
|
+
1. Python Signatures vs English Docs:
|
|
8
|
+
- Ensures all function parameters in vlrdevapi match the '## Parameters' table.
|
|
9
|
+
2. Live Python Models vs English Docs:
|
|
10
|
+
- Introspects Pydantic models and Enums from vlrdevapi.
|
|
11
|
+
- Verifies that every documented field exists on the live Python model.
|
|
12
|
+
- Catches phantom / outdated fields (e.g. 'dates', 'tag', 'score' on wrong models).
|
|
13
|
+
3. Cross-Language Parity (English vs es, pt-br, zh-cn):
|
|
14
|
+
- Verifies that all translations have the exact same number of tables.
|
|
15
|
+
- Verifies that tables appear in the exact same sequence.
|
|
16
|
+
- Verifies that fields in every table match the exact same order and types.
|
|
17
|
+
|
|
18
|
+
CI/CD usage:
|
|
19
|
+
uv run scripts/check_docs_schema.py
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import inspect
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, NamedTuple
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel
|
|
33
|
+
|
|
34
|
+
import vlrdevapi
|
|
35
|
+
|
|
36
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
37
|
+
DOCS_DIR = REPO_ROOT / "official-docs" / "content" / "docs" / "reference"
|
|
38
|
+
LANGUAGES = ("es", "pt-br", "zh-cn")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FieldDef(NamedTuple):
|
|
42
|
+
name: str
|
|
43
|
+
type_str: str
|
|
44
|
+
default_str: str | None
|
|
45
|
+
required: bool
|
|
46
|
+
line: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TableDef(NamedTuple):
|
|
50
|
+
heading: str
|
|
51
|
+
model_name: str | None
|
|
52
|
+
fields: dict[str, FieldDef]
|
|
53
|
+
start_line: int
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DocDef(NamedTuple):
|
|
57
|
+
path: Path
|
|
58
|
+
sig: str | None
|
|
59
|
+
ret_name: str | None
|
|
60
|
+
tables: list[TableDef]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Issue(NamedTuple):
|
|
64
|
+
file_path: Path
|
|
65
|
+
line: int
|
|
66
|
+
category: str
|
|
67
|
+
table_name: str
|
|
68
|
+
message: str
|
|
69
|
+
expected: str | None = None
|
|
70
|
+
found: str | None = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_all_subclasses(cls: type) -> set[type]:
|
|
74
|
+
"""Recursively collect all subclasses defined under vlrdevapi."""
|
|
75
|
+
subclasses = set()
|
|
76
|
+
for sub in cls.__subclasses__():
|
|
77
|
+
if sub.__module__.startswith("vlrdevapi"):
|
|
78
|
+
subclasses.add(sub)
|
|
79
|
+
subclasses.update(get_all_subclasses(sub))
|
|
80
|
+
return subclasses
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_type_catalog() -> tuple[dict[str, type[BaseModel]], dict[str, type[Enum]]]:
|
|
84
|
+
"""Dynamically discover all Pydantic models and Enums from vlrdevapi."""
|
|
85
|
+
models: dict[str, type[BaseModel]] = {
|
|
86
|
+
cls.__name__: cls for cls in get_all_subclasses(BaseModel)
|
|
87
|
+
}
|
|
88
|
+
enums: dict[str, type[Enum]] = {
|
|
89
|
+
cls.__name__: cls for cls in get_all_subclasses(Enum)
|
|
90
|
+
}
|
|
91
|
+
return models, enums
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def parse_mdx(file_path: Path) -> DocDef:
|
|
95
|
+
"""Parse an MDX file into structured table definitions with line numbers."""
|
|
96
|
+
content = file_path.read_text(encoding="utf-8")
|
|
97
|
+
lines = content.splitlines()
|
|
98
|
+
|
|
99
|
+
# Extract signature e.g. matches.completed(
|
|
100
|
+
sig_m = re.search(r"##\s+Signature\s+```python\s*([a-zA-Z0-9_\.]+)\(", content)
|
|
101
|
+
sig = sig_m.group(1) if sig_m else None
|
|
102
|
+
|
|
103
|
+
# Extract return type name if present in ## Returns - Model
|
|
104
|
+
ret_m = re.search(r"##\s+(?:Returns|Retorno|Retorno -|返回类型)\s+-\s+`?([a-zA-Z0-9_]+)`?", content)
|
|
105
|
+
ret_name = ret_m.group(1) if ret_m else None
|
|
106
|
+
|
|
107
|
+
# Locate each <TypeTable ... />
|
|
108
|
+
table_matches = list(re.finditer(r"<TypeTable\s+type=\{\{(.*?)\}\}\s*/>", content, re.DOTALL))
|
|
109
|
+
tables: list[TableDef] = []
|
|
110
|
+
|
|
111
|
+
for m in table_matches:
|
|
112
|
+
start_char = m.start()
|
|
113
|
+
start_line = content[:start_char].count("\n") + 1
|
|
114
|
+
|
|
115
|
+
# Look backward for closest markdown heading
|
|
116
|
+
pre_text = content[:start_char]
|
|
117
|
+
headings = list(re.finditer(r"(#{2,3}\s+[^\n]+)", pre_text))
|
|
118
|
+
last_heading = headings[-1].group(1).strip() if headings else "Unknown Section"
|
|
119
|
+
|
|
120
|
+
# Check for model name in heading: e.g. `CompletedMatchEntry` Fields
|
|
121
|
+
name_matches = re.findall(r"`([a-zA-Z0-9_]+)`", last_heading)
|
|
122
|
+
inferred_model = name_matches[0] if name_matches else None
|
|
123
|
+
|
|
124
|
+
# Parse fields
|
|
125
|
+
body = m.group(1)
|
|
126
|
+
body_start_line = start_line
|
|
127
|
+
field_matches = re.finditer(r"[\"']?([a-zA-Z0-9_]+)[\"']?\s*:\s*\{([^}]+)\}", body)
|
|
128
|
+
fields: dict[str, FieldDef] = {}
|
|
129
|
+
|
|
130
|
+
for fm in field_matches:
|
|
131
|
+
fname = fm.group(1)
|
|
132
|
+
fbody = fm.group(2)
|
|
133
|
+
fline = body_start_line + body[:fm.start()].count("\n")
|
|
134
|
+
|
|
135
|
+
tm = re.search(r"type\s*:\s*[\"']([^\"']+)[\"']", fbody)
|
|
136
|
+
type_str = tm.group(1) if tm else ""
|
|
137
|
+
|
|
138
|
+
dm = re.search(r"default\s*:\s*([^\n,]+)", fbody)
|
|
139
|
+
default_str = dm.group(1).strip() if dm else None
|
|
140
|
+
|
|
141
|
+
req_m = re.search(r"required\s*:\s*(true|false)", fbody, re.IGNORECASE)
|
|
142
|
+
required = req_m.group(1).lower() == "true" if req_m else False
|
|
143
|
+
|
|
144
|
+
fields[fname] = FieldDef(
|
|
145
|
+
name=fname,
|
|
146
|
+
type_str=type_str,
|
|
147
|
+
default_str=default_str,
|
|
148
|
+
required=required,
|
|
149
|
+
line=fline,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
tables.append(TableDef(
|
|
153
|
+
heading=last_heading,
|
|
154
|
+
model_name=inferred_model,
|
|
155
|
+
fields=fields,
|
|
156
|
+
start_line=start_line,
|
|
157
|
+
))
|
|
158
|
+
|
|
159
|
+
return DocDef(
|
|
160
|
+
path=file_path,
|
|
161
|
+
sig=sig,
|
|
162
|
+
ret_name=ret_name,
|
|
163
|
+
tables=tables,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def resolve_callable(sig: str) -> Any:
|
|
168
|
+
"""Dynamically resolve a Python callable from vlrdevapi using dot path."""
|
|
169
|
+
obj: Any = vlrdevapi
|
|
170
|
+
for part in sig.split("."):
|
|
171
|
+
if not hasattr(obj, part):
|
|
172
|
+
return None
|
|
173
|
+
obj = getattr(obj, part)
|
|
174
|
+
|
|
175
|
+
# If it is a namespace class with __call__, get __call__
|
|
176
|
+
if hasattr(obj, "__call__") and not inspect.isfunction(obj) and not inspect.ismethod(obj):
|
|
177
|
+
return obj.__call__
|
|
178
|
+
return obj
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def validate_doc_against_python(
|
|
182
|
+
doc: DocDef,
|
|
183
|
+
model_catalog: dict[str, type[BaseModel]],
|
|
184
|
+
enum_catalog: dict[str, type[Enum]],
|
|
185
|
+
) -> list[Issue]:
|
|
186
|
+
"""Validate canonical English MDX against live Python signatures and models."""
|
|
187
|
+
issues: list[Issue] = []
|
|
188
|
+
if not doc.sig:
|
|
189
|
+
return issues
|
|
190
|
+
|
|
191
|
+
fn = resolve_callable(doc.sig)
|
|
192
|
+
if fn is None:
|
|
193
|
+
issues.append(Issue(
|
|
194
|
+
file_path=doc.path,
|
|
195
|
+
line=1,
|
|
196
|
+
category="UNRESOLVED_CALLABLE",
|
|
197
|
+
table_name="Signature",
|
|
198
|
+
message=f"Callable '{doc.sig}' could not be resolved from vlrdevapi",
|
|
199
|
+
))
|
|
200
|
+
return issues
|
|
201
|
+
|
|
202
|
+
# 1. Parameter Table vs Signature
|
|
203
|
+
try:
|
|
204
|
+
sig_obj = inspect.signature(fn)
|
|
205
|
+
py_params = [p for p in sig_obj.parameters if p != "self"]
|
|
206
|
+
except (ValueError, TypeError):
|
|
207
|
+
py_params = []
|
|
208
|
+
|
|
209
|
+
param_tables = [t for t in doc.tables if "Parameters" in t.heading]
|
|
210
|
+
if param_tables:
|
|
211
|
+
ptable = param_tables[0]
|
|
212
|
+
doc_params = list(ptable.fields.keys())
|
|
213
|
+
missing_params = set(py_params) - set(doc_params)
|
|
214
|
+
extra_params = set(doc_params) - set(py_params)
|
|
215
|
+
|
|
216
|
+
for mp in sorted(missing_params):
|
|
217
|
+
param_obj = sig_obj.parameters.get(mp)
|
|
218
|
+
default_val = param_obj.default if param_obj else None
|
|
219
|
+
issues.append(Issue(
|
|
220
|
+
file_path=doc.path,
|
|
221
|
+
line=ptable.start_line,
|
|
222
|
+
category="MISSING_PARAMETER",
|
|
223
|
+
table_name=ptable.heading,
|
|
224
|
+
message=f"Parameter '{mp}' exists in Python signature {doc.sig}() but is missing from Parameters table",
|
|
225
|
+
expected=f"{mp} (default: {default_val})",
|
|
226
|
+
))
|
|
227
|
+
|
|
228
|
+
for ep in sorted(extra_params):
|
|
229
|
+
fdef = ptable.fields[ep]
|
|
230
|
+
issues.append(Issue(
|
|
231
|
+
file_path=doc.path,
|
|
232
|
+
line=fdef.line,
|
|
233
|
+
category="EXTRA_PARAMETER",
|
|
234
|
+
table_name=ptable.heading,
|
|
235
|
+
message=f"Parameter '{ep}' documented in Parameters table does not exist in Python signature {doc.sig}()",
|
|
236
|
+
found=ep,
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
if not missing_params and not extra_params and py_params != doc_params:
|
|
240
|
+
issues.append(Issue(
|
|
241
|
+
file_path=doc.path,
|
|
242
|
+
line=ptable.start_line,
|
|
243
|
+
category="PARAMETER_ORDER_MISMATCH",
|
|
244
|
+
table_name=ptable.heading,
|
|
245
|
+
message="Parameter order mismatch between Python signature and documentation",
|
|
246
|
+
expected=str(py_params),
|
|
247
|
+
found=str(doc_params),
|
|
248
|
+
))
|
|
249
|
+
|
|
250
|
+
# 2. Documented Models vs Live Python Models / Enums
|
|
251
|
+
for table in doc.tables:
|
|
252
|
+
if "Parameters" in table.heading:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
# Extract all candidate names from heading
|
|
256
|
+
names = re.findall(r"`([a-zA-Z0-9_]+)`", table.heading)
|
|
257
|
+
if not names and doc.ret_name and "Returns" in table.heading:
|
|
258
|
+
names = [doc.ret_name]
|
|
259
|
+
|
|
260
|
+
matched_model: type[BaseModel] | None = None
|
|
261
|
+
matched_enum: type[Enum] | None = None
|
|
262
|
+
|
|
263
|
+
# Check candidate names (try in order, preferring more specific e.g. SideStats over PlayerStats)
|
|
264
|
+
for n in reversed(names):
|
|
265
|
+
if n in model_catalog:
|
|
266
|
+
matched_model = model_catalog[n]
|
|
267
|
+
break
|
|
268
|
+
if n in enum_catalog:
|
|
269
|
+
matched_enum = enum_catalog[n]
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
if matched_model is not None:
|
|
273
|
+
py_fields = matched_model.model_fields
|
|
274
|
+
doc_fields = table.fields
|
|
275
|
+
|
|
276
|
+
# Check for extra fields documented that do NOT exist in the Python model
|
|
277
|
+
for doc_fname, fdef in doc_fields.items():
|
|
278
|
+
if doc_fname not in py_fields:
|
|
279
|
+
issues.append(Issue(
|
|
280
|
+
file_path=doc.path,
|
|
281
|
+
line=fdef.line,
|
|
282
|
+
category="PHANTOM_FIELD",
|
|
283
|
+
table_name=matched_model.__name__,
|
|
284
|
+
message=f"Field '{doc_fname}' is documented in table '{matched_model.__name__}', but does NOT exist on the Python model '{matched_model.__module__}.{matched_model.__name__}'",
|
|
285
|
+
found=doc_fname,
|
|
286
|
+
expected=f"One of: {sorted(py_fields.keys())}",
|
|
287
|
+
))
|
|
288
|
+
|
|
289
|
+
elif matched_enum is not None:
|
|
290
|
+
enum_members = set(matched_enum.__members__.keys())
|
|
291
|
+
doc_fields = table.fields
|
|
292
|
+
|
|
293
|
+
for doc_mname, fdef in doc_fields.items():
|
|
294
|
+
if doc_mname not in enum_members:
|
|
295
|
+
issues.append(Issue(
|
|
296
|
+
file_path=doc.path,
|
|
297
|
+
line=fdef.line,
|
|
298
|
+
category="PHANTOM_ENUM_MEMBER",
|
|
299
|
+
table_name=matched_enum.__name__,
|
|
300
|
+
message=f"Enum member '{doc_mname}' is documented in '{matched_enum.__name__}', but does not exist in Python enum '{matched_enum.__module__}.{matched_enum.__name__}'",
|
|
301
|
+
found=doc_mname,
|
|
302
|
+
expected=f"One of: {sorted(enum_members)}",
|
|
303
|
+
))
|
|
304
|
+
|
|
305
|
+
return issues
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def validate_translations_against_en(en_doc: DocDef, tr_doc: DocDef, lang: str) -> list[Issue]:
|
|
309
|
+
"""Validate a translation MDX against the canonical English MDX."""
|
|
310
|
+
issues: list[Issue] = []
|
|
311
|
+
|
|
312
|
+
# 1. Table count parity
|
|
313
|
+
if len(en_doc.tables) != len(tr_doc.tables):
|
|
314
|
+
issues.append(Issue(
|
|
315
|
+
file_path=tr_doc.path,
|
|
316
|
+
line=1,
|
|
317
|
+
category="TABLE_COUNT_MISMATCH",
|
|
318
|
+
table_name="Document",
|
|
319
|
+
message=f"Table count mismatch: English document has {len(en_doc.tables)} tables, but {lang} translation has {len(tr_doc.tables)} tables",
|
|
320
|
+
expected=f"{len(en_doc.tables)} tables",
|
|
321
|
+
found=f"{len(tr_doc.tables)} tables",
|
|
322
|
+
))
|
|
323
|
+
return issues
|
|
324
|
+
|
|
325
|
+
# 2. Table-by-table structure, keys, order, and types
|
|
326
|
+
for idx, (en_table, tr_table) in enumerate(zip(en_doc.tables, tr_doc.tables)):
|
|
327
|
+
t_name = en_table.model_name or en_table.heading
|
|
328
|
+
en_keys = list(en_table.fields.keys())
|
|
329
|
+
tr_keys = list(tr_table.fields.keys())
|
|
330
|
+
|
|
331
|
+
missing_in_tr = set(en_keys) - set(tr_keys)
|
|
332
|
+
extra_in_tr = set(tr_keys) - set(en_keys)
|
|
333
|
+
|
|
334
|
+
for m_key in sorted(missing_in_tr):
|
|
335
|
+
en_fdef = en_table.fields[m_key]
|
|
336
|
+
issues.append(Issue(
|
|
337
|
+
file_path=tr_doc.path,
|
|
338
|
+
line=tr_table.start_line,
|
|
339
|
+
category="MISSING_TRANSLATION_FIELD",
|
|
340
|
+
table_name=t_name,
|
|
341
|
+
message=f"Table {idx} ({t_name}): Missing field '{m_key}' in {lang} translation",
|
|
342
|
+
expected=f"Field '{m_key}' with type '{en_fdef.type_str}'",
|
|
343
|
+
))
|
|
344
|
+
|
|
345
|
+
for e_key in sorted(extra_in_tr):
|
|
346
|
+
tr_fdef = tr_table.fields[e_key]
|
|
347
|
+
issues.append(Issue(
|
|
348
|
+
file_path=tr_doc.path,
|
|
349
|
+
line=tr_fdef.line,
|
|
350
|
+
category="EXTRA_TRANSLATION_FIELD",
|
|
351
|
+
table_name=t_name,
|
|
352
|
+
message=f"Table {idx} ({t_name}): Extra field '{e_key}' found in {lang} translation that does not exist in English document",
|
|
353
|
+
found=e_key,
|
|
354
|
+
))
|
|
355
|
+
|
|
356
|
+
# Check exact field ordering
|
|
357
|
+
if not missing_in_tr and not extra_in_tr and en_keys != tr_keys:
|
|
358
|
+
# Pinpoint the first index where order diverges
|
|
359
|
+
mismatch_idx = next(i for i, (ek, tk) in enumerate(zip(en_keys, tr_keys)) if ek != tk)
|
|
360
|
+
issues.append(Issue(
|
|
361
|
+
file_path=tr_doc.path,
|
|
362
|
+
line=tr_table.fields[tr_keys[mismatch_idx]].line,
|
|
363
|
+
category="FIELD_ORDER_MISMATCH",
|
|
364
|
+
table_name=t_name,
|
|
365
|
+
message=f"Table {idx} ({t_name}): Field ordering differs from English at position {mismatch_idx}",
|
|
366
|
+
expected=f"Field '{en_keys[mismatch_idx]}'",
|
|
367
|
+
found=f"Field '{tr_keys[mismatch_idx]}'",
|
|
368
|
+
))
|
|
369
|
+
|
|
370
|
+
# Check field types
|
|
371
|
+
for k in en_keys:
|
|
372
|
+
if k in tr_table.fields:
|
|
373
|
+
en_f = en_table.fields[k]
|
|
374
|
+
tr_f = tr_table.fields[k]
|
|
375
|
+
if en_f.type_str != tr_f.type_str:
|
|
376
|
+
issues.append(Issue(
|
|
377
|
+
file_path=tr_doc.path,
|
|
378
|
+
line=tr_f.line,
|
|
379
|
+
category="TYPE_MISMATCH",
|
|
380
|
+
table_name=t_name,
|
|
381
|
+
message=f"Table {idx} ({t_name}): Field '{k}' type mismatch",
|
|
382
|
+
expected=en_f.type_str,
|
|
383
|
+
found=tr_f.type_str,
|
|
384
|
+
))
|
|
385
|
+
|
|
386
|
+
return issues
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def main() -> int:
|
|
390
|
+
parser = argparse.ArgumentParser(description="Validate documentation schemas against live Python library.")
|
|
391
|
+
parser.add_argument("--strict", action="store_true", default=True, help="Exit with code 1 if any issues are found.")
|
|
392
|
+
args = parser.parse_args()
|
|
393
|
+
|
|
394
|
+
model_catalog, enum_catalog = build_type_catalog()
|
|
395
|
+
|
|
396
|
+
en_files = sorted(
|
|
397
|
+
p for p in DOCS_DIR.rglob("*.mdx")
|
|
398
|
+
if not any(p.name.endswith(f".{lang}.mdx") for lang in LANGUAGES)
|
|
399
|
+
and p.name != "index.mdx"
|
|
400
|
+
and p.parent.name != "mcp"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
all_issues: list[Issue] = []
|
|
404
|
+
total_tables = 0
|
|
405
|
+
total_translations = 0
|
|
406
|
+
|
|
407
|
+
print("============================================================")
|
|
408
|
+
print("VLRDEVAPI DOCUMENTATION SCHEMA VALIDATOR")
|
|
409
|
+
print(f"Loaded {len(model_catalog)} Pydantic models & {len(enum_catalog)} Enums from live library")
|
|
410
|
+
print(f"Auditing {len(en_files)} canonical reference docs across {len(LANGUAGES)} languages...")
|
|
411
|
+
print("============================================================\n")
|
|
412
|
+
|
|
413
|
+
for en_path in en_files:
|
|
414
|
+
en_doc = parse_mdx(en_path)
|
|
415
|
+
total_tables += len(en_doc.tables)
|
|
416
|
+
|
|
417
|
+
# 1. Validate English against live Python library
|
|
418
|
+
py_issues = validate_doc_against_python(en_doc, model_catalog, enum_catalog)
|
|
419
|
+
all_issues.extend(py_issues)
|
|
420
|
+
|
|
421
|
+
# 2. Validate each language translation against English
|
|
422
|
+
for lang in LANGUAGES:
|
|
423
|
+
tr_path = en_path.parent / f"{en_path.stem}.{lang}.mdx"
|
|
424
|
+
if not tr_path.exists():
|
|
425
|
+
all_issues.append(Issue(
|
|
426
|
+
file_path=en_path.parent / f"{en_path.stem}.{lang}.mdx",
|
|
427
|
+
line=1,
|
|
428
|
+
category="MISSING_FILE",
|
|
429
|
+
table_name="File",
|
|
430
|
+
message=f"Missing translation file: {tr_path.name}",
|
|
431
|
+
))
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
total_translations += 1
|
|
435
|
+
tr_doc = parse_mdx(tr_path)
|
|
436
|
+
total_tables += len(tr_doc.tables)
|
|
437
|
+
tr_issues = validate_translations_against_en(en_doc, tr_doc, lang)
|
|
438
|
+
all_issues.extend(tr_issues)
|
|
439
|
+
|
|
440
|
+
# Report results
|
|
441
|
+
if all_issues:
|
|
442
|
+
print(f"FAIL: Found {len(all_issues)} schema issue(s) across documentation:\n")
|
|
443
|
+
# Group issues by file
|
|
444
|
+
by_file: dict[Path, list[Issue]] = {}
|
|
445
|
+
for iss in all_issues:
|
|
446
|
+
by_file.setdefault(iss.file_path, []).append(iss)
|
|
447
|
+
|
|
448
|
+
for fpath, issues_list in sorted(by_file.items(), key=lambda x: str(x[0])):
|
|
449
|
+
rel_path = fpath.relative_to(REPO_ROOT) if fpath.is_relative_to(REPO_ROOT) else fpath
|
|
450
|
+
print(f"--- {rel_path} ({len(issues_list)} issue{'s' if len(issues_list) > 1 else ''}) ---")
|
|
451
|
+
for iss in issues_list:
|
|
452
|
+
print(f" Line {iss.line} [{iss.category}] in {iss.table_name}:")
|
|
453
|
+
print(f" Issue: {iss.message}")
|
|
454
|
+
if iss.expected is not None:
|
|
455
|
+
print(f" Expected: {iss.expected}")
|
|
456
|
+
if iss.found is not None:
|
|
457
|
+
print(f" Found: {iss.found}")
|
|
458
|
+
print()
|
|
459
|
+
|
|
460
|
+
print("============================================================")
|
|
461
|
+
print("VALIDATION FAILED")
|
|
462
|
+
print(f" Total issues: {len(all_issues)} in {len(by_file)} files")
|
|
463
|
+
print("============================================================")
|
|
464
|
+
return 1
|
|
465
|
+
|
|
466
|
+
print("SUCCESS: All documentation tables and translation schemas are 100% in sync with the live library!")
|
|
467
|
+
print("============================================================")
|
|
468
|
+
print("VALIDATION SUMMARY")
|
|
469
|
+
print(f" Canonical English docs checked: {len(en_files)}")
|
|
470
|
+
print(f" Translation files verified: {total_translations}")
|
|
471
|
+
print(f" Total tables validated: {total_tables}")
|
|
472
|
+
print(" Schema mismatches: 0")
|
|
473
|
+
print("============================================================")
|
|
474
|
+
return 0
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
if __name__ == "__main__":
|
|
478
|
+
sys.exit(main())
|
|
@@ -279,6 +279,11 @@ ROUTES += [
|
|
|
279
279
|
"team/8050/overview_light.html",
|
|
280
280
|
{},
|
|
281
281
|
),
|
|
282
|
+
(
|
|
283
|
+
"/team/4330/fancy-united-esports",
|
|
284
|
+
"team/4330/overview_light.html",
|
|
285
|
+
{},
|
|
286
|
+
),
|
|
282
287
|
(
|
|
283
288
|
"/team/matches/120/?core_id=all&group=completed&page=2",
|
|
284
289
|
"team/120/completed_matches_page2.html",
|
|
@@ -491,8 +496,8 @@ for _gid in GAMES_BO5_2026:
|
|
|
491
496
|
]
|
|
492
497
|
|
|
493
498
|
# ── Upcoming series (best_of + TBD map tabs) ─────────────────
|
|
494
|
-
SERIES_ID_UPCOMING =
|
|
495
|
-
SERIES_SLUG_UPCOMING = "
|
|
499
|
+
SERIES_ID_UPCOMING = 784392
|
|
500
|
+
SERIES_SLUG_UPCOMING = "fancy-united-esports-vs-original-gaming-predator-league-2027-vietnam-gf"
|
|
496
501
|
|
|
497
502
|
ROUTES.append(
|
|
498
503
|
(
|
|
@@ -501,6 +506,13 @@ ROUTES.append(
|
|
|
501
506
|
{},
|
|
502
507
|
),
|
|
503
508
|
)
|
|
509
|
+
ROUTES.append(
|
|
510
|
+
(
|
|
511
|
+
"/773433/mibr-gc-vs-gentle-mates-gc-game-changers-2026-championship-s-o-paulo-ubsf",
|
|
512
|
+
"series/773433/overview.html",
|
|
513
|
+
{},
|
|
514
|
+
),
|
|
515
|
+
)
|
|
504
516
|
|
|
505
517
|
# ── Replay/logs tab fixtures (series.replay) ──────────────────
|
|
506
518
|
SERIES_LOG_SAMPLES = [
|