rivalsdata-api 1.2.0__tar.gz → 1.2.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.
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/PKG-INFO +12 -3
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/README.md +11 -2
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/docs/API.md +5 -1
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/docs/PROJECT_CONTEXT.md +6 -2
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/pyproject.toml +1 -1
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/__init__.py +1 -1
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/mcp_server.py +7 -3
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/resources.py +15 -3
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/tests/test_class_stats.py +45 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/.github/workflows/publish-pypi.yml +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/.gitignore +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/CONTRIBUTING.md +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/LICENSE +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/client.py +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/exceptions.py +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/hero_ids.py +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/src/rivalsdata/models.py +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/tests/test_hero_names.py +0 -0
- {rivalsdata_api-1.2.0 → rivalsdata_api-1.2.1}/tests/test_typed_responses.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rivalsdata-api
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Python client and MCP server for public Marvel Rivals stats from RivalsData
|
|
5
5
|
Project-URL: Homepage, https://github.com/GS-Rionnag/rivalsdata-api
|
|
6
6
|
Project-URL: Repository, https://github.com/GS-Rionnag/rivalsdata-api
|
|
@@ -87,18 +87,27 @@ print(hero_season[0].win_rate) # integer percent when wins/losses are present
|
|
|
87
87
|
|
|
88
88
|
Calculated player class statistics are available through
|
|
89
89
|
`player.stats.classes(season=20)` and the MCP `get_player_stats` tool with
|
|
90
|
-
`category="classes"`.
|
|
90
|
+
`category="classes"`. Pass a numeric season ID for that season, or
|
|
91
|
+
`season="all"` for combined all-seasons data, matching `player.heroes.fetch`.
|
|
92
|
+
Omitting the season uses the endpoint default. `player.stats.heroes` also
|
|
93
|
+
accepts `season="all"`. Each row contains `player_class` (`tank`, `support`,
|
|
91
94
|
`dps`), the official role, hero IDs, and separate `competitive` and `quickplay`
|
|
92
95
|
totals for games, wins, losses, and available MVP/SVP counts.
|
|
93
96
|
|
|
94
97
|
```python
|
|
95
98
|
with RivalsDataClient() as rd:
|
|
96
|
-
|
|
99
|
+
player = rd.get_player("GS-")
|
|
100
|
+
stats = player.stats.classes(season=20)
|
|
101
|
+
all_seasons = player.stats.classes(season="all")
|
|
97
102
|
for row in stats.classes:
|
|
98
103
|
print(row.player_class, row.competitive.win_rate)
|
|
99
104
|
print(stats.excluded) # Unknown roles or incomplete win/loss records
|
|
100
105
|
```
|
|
101
106
|
|
|
107
|
+
For MCP, use `get_player_stats(uid_or_name="GS-", category="classes", season=20)`
|
|
108
|
+
for one season, or `season="all"` for combined all-seasons stats. Both return
|
|
109
|
+
the same class response structure.
|
|
110
|
+
|
|
102
111
|
Win rates are `total wins / (total wins + total losses)`, rounded to an integer
|
|
103
112
|
percent. They are weighted by hero records, rather than averaging hero win
|
|
104
113
|
rates. Switching heroes can make one match contribute to multiple records;
|
|
@@ -51,18 +51,27 @@ print(hero_season[0].win_rate) # integer percent when wins/losses are present
|
|
|
51
51
|
|
|
52
52
|
Calculated player class statistics are available through
|
|
53
53
|
`player.stats.classes(season=20)` and the MCP `get_player_stats` tool with
|
|
54
|
-
`category="classes"`.
|
|
54
|
+
`category="classes"`. Pass a numeric season ID for that season, or
|
|
55
|
+
`season="all"` for combined all-seasons data, matching `player.heroes.fetch`.
|
|
56
|
+
Omitting the season uses the endpoint default. `player.stats.heroes` also
|
|
57
|
+
accepts `season="all"`. Each row contains `player_class` (`tank`, `support`,
|
|
55
58
|
`dps`), the official role, hero IDs, and separate `competitive` and `quickplay`
|
|
56
59
|
totals for games, wins, losses, and available MVP/SVP counts.
|
|
57
60
|
|
|
58
61
|
```python
|
|
59
62
|
with RivalsDataClient() as rd:
|
|
60
|
-
|
|
63
|
+
player = rd.get_player("GS-")
|
|
64
|
+
stats = player.stats.classes(season=20)
|
|
65
|
+
all_seasons = player.stats.classes(season="all")
|
|
61
66
|
for row in stats.classes:
|
|
62
67
|
print(row.player_class, row.competitive.win_rate)
|
|
63
68
|
print(stats.excluded) # Unknown roles or incomplete win/loss records
|
|
64
69
|
```
|
|
65
70
|
|
|
71
|
+
For MCP, use `get_player_stats(uid_or_name="GS-", category="classes", season=20)`
|
|
72
|
+
for one season, or `season="all"` for combined all-seasons stats. Both return
|
|
73
|
+
the same class response structure.
|
|
74
|
+
|
|
66
75
|
Win rates are `total wins / (total wins + total losses)`, rounded to an integer
|
|
67
76
|
percent. They are weighted by hero records, rather than averaging hero win
|
|
68
77
|
rates. Switching heroes can make one match contribute to multiple records;
|
|
@@ -53,7 +53,11 @@ none of these, it returns `None`.
|
|
|
53
53
|
`player.stats.classes(season=...)` derives tank (Vanguard), support (Strategist),
|
|
54
54
|
and DPS (Duelist) totals from `/player/stats/heroes`; it does not call a class
|
|
55
55
|
endpoint. It sums games/wins/losses separately for competitive and quickplay,
|
|
56
|
-
|
|
56
|
+
accepting a numeric season ID or `season="all"` (sent to the API as `-1`). The
|
|
57
|
+
all-seasons selection combines the returned hero records across seasons;
|
|
58
|
+
omitting the season keeps the endpoint default. `player.stats.heroes` and MCP
|
|
59
|
+
`get_player_stats` support the same selector. The method also sums
|
|
60
|
+
MVP/SVP counts when every included row supplies them. Win rate is calculated
|
|
57
61
|
from summed wins and losses. Unknown classes and incomplete win/loss rows appear
|
|
58
62
|
in `excluded`. Hero switching can count a single match in multiple hero records,
|
|
59
63
|
so class totals are participation counts rather than distinct matches.
|
|
@@ -15,7 +15,7 @@ as public, stable methods. Keep requests respectful and conservative.
|
|
|
15
15
|
## Current package
|
|
16
16
|
|
|
17
17
|
- Distribution: `rivalsdata-api`; import: `rivalsdata`.
|
|
18
|
-
- Version: `1.2.
|
|
18
|
+
- Version: `1.2.1`.
|
|
19
19
|
- Python `>=3.10`, Hatchling build, `src/` layout.
|
|
20
20
|
- Runtime HTTP dependency: `curl-cffi`; optional browser fallback: Camoufox.
|
|
21
21
|
- Public entry point: `RivalsDataClient`.
|
|
@@ -55,7 +55,11 @@ Rows offer `.win_rate` and `.winrate` integer-percent access when data supports
|
|
|
55
55
|
it; all original data remains in mapping access.
|
|
56
56
|
|
|
57
57
|
`player.stats.classes(season=...)` groups observed hero IDs into tank, support,
|
|
58
|
-
and DPS.
|
|
58
|
+
and DPS. Supply a numeric season ID for one season or `season="all"` for
|
|
59
|
+
combined all-seasons stats; the latter sends `season=-1` to the upstream API.
|
|
60
|
+
The detailed `player.stats.heroes` method and MCP stats tool accept the same
|
|
61
|
+
selector. Omitting the season keeps the endpoint default.
|
|
62
|
+
It sums games, wins, losses, and available MVP/SVP counts separately
|
|
59
63
|
for competitive and quickplay, with win rates calculated from summed wins and
|
|
60
64
|
losses. The response has typed `classes` rows and an `excluded` list for unknown
|
|
61
65
|
roles or incomplete win/loss data. MCP exposes it through `get_player_stats`
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rivalsdata-api"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.1"
|
|
8
8
|
description = "Python client and MCP server for public Marvel Rivals stats from RivalsData"
|
|
9
9
|
keywords = ["marvel-rivals", "rivalsdata", "game-stats", "api-client", "mcp"]
|
|
10
10
|
readme = "README.md"
|
|
@@ -408,19 +408,23 @@ def get_player_matches(
|
|
|
408
408
|
|
|
409
409
|
@mcp.tool()
|
|
410
410
|
def get_player_stats(
|
|
411
|
-
uid_or_name: str, category: str = "heroes",
|
|
411
|
+
uid_or_name: str, category: str = "heroes",
|
|
412
|
+
season: int | Literal["all"] | None = None,
|
|
412
413
|
) -> Any:
|
|
413
414
|
"""Get player stats: heroes, maps, bans, or calculated classes.
|
|
414
415
|
|
|
415
416
|
Classes sum hero wins/losses by tank/support/dps and game mode; these are
|
|
416
417
|
hero participation totals, which can count a match more than once.
|
|
418
|
+
Supply a season ID or "all" for combined all-seasons totals. Omitting the
|
|
419
|
+
season uses the endpoint default.
|
|
417
420
|
"""
|
|
418
421
|
methods = {"heroes": "heroes", "maps": "maps", "bans": "bans", "classes": "classes"}
|
|
419
422
|
if category not in methods:
|
|
420
423
|
raise ValueError("category must be one of: heroes, maps, bans, classes")
|
|
421
424
|
def fetch(client: RivalsDataClient, value: str, category: str,
|
|
422
|
-
season: int | None) -> Any:
|
|
423
|
-
|
|
425
|
+
season: int | Literal["all"] | None) -> Any:
|
|
426
|
+
season_id = -1 if season == "all" else season
|
|
427
|
+
return getattr(client.get_player(value).stats, methods[category])(season=season_id)
|
|
424
428
|
return _call(fetch, uid_or_name, category, season)
|
|
425
429
|
|
|
426
430
|
|
|
@@ -205,13 +205,25 @@ class PlayerHeroes(PlayerResource):
|
|
|
205
205
|
class PlayerStats(PlayerResource):
|
|
206
206
|
"""Detailed per-player statistics tabs."""
|
|
207
207
|
|
|
208
|
-
def heroes(
|
|
209
|
-
|
|
208
|
+
def heroes(
|
|
209
|
+
self, *, season: int | Literal["all"] | None = None
|
|
210
|
+
) -> list[HeroStatsRecord]:
|
|
211
|
+
"""Fetch mode-specific hero stats for a season or all seasons.
|
|
212
|
+
|
|
213
|
+
``season="all"`` sends the API's all-seasons selector (-1). Omitting
|
|
214
|
+
``season`` preserves the endpoint default.
|
|
215
|
+
"""
|
|
216
|
+
season_id = -1 if season == "all" else season
|
|
217
|
+
payload = {"season": season_id} if season_id is not None else {}
|
|
210
218
|
return _many(self._post("/player/stats/heroes", **payload), HeroStatsRecord)
|
|
211
219
|
|
|
212
|
-
def classes(
|
|
220
|
+
def classes(
|
|
221
|
+
self, *, season: int | Literal["all"] | None = None
|
|
222
|
+
) -> ClassStatsResponse:
|
|
213
223
|
"""Sum hero records by tank/support/dps, separately for each mode.
|
|
214
224
|
|
|
225
|
+
Supply a season ID or ``season="all"`` for combined all-seasons totals.
|
|
226
|
+
Omitting ``season`` preserves the endpoint default.
|
|
215
227
|
Win rate uses total wins / (wins + losses), not the average of hero
|
|
216
228
|
percentages. Counts describe hero participation: switching heroes can
|
|
217
229
|
cause one match to contribute to multiple hero or class records.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
1
3
|
from rivalsdata import (
|
|
2
4
|
ClassModeStats,
|
|
3
5
|
ClassStatsRecord,
|
|
@@ -55,3 +57,46 @@ def test_one_percent_is_not_treated_as_a_fraction():
|
|
|
55
57
|
row = ClassModeStats({"wins": 1, "losses": 99, "win_rate": 1})
|
|
56
58
|
assert row.win_rate == 1
|
|
57
59
|
assert row.winrate == 1
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SeasonClient:
|
|
63
|
+
def _post_json(self, path, payload):
|
|
64
|
+
self.request = path, payload
|
|
65
|
+
wins = {None: 2, 20: 3, -1: 12}[payload.get("season")]
|
|
66
|
+
return [{"hero_id": 1016, "competitive": {"wins": wins, "losses": 1}}]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.parametrize("season,payload,wins", [
|
|
70
|
+
(None, {"uid": 123}, 2),
|
|
71
|
+
(20, {"uid": 123, "season": 20}, 3),
|
|
72
|
+
("all", {"uid": 123, "season": -1}, 12),
|
|
73
|
+
(-1, {"uid": 123, "season": -1}, 12),
|
|
74
|
+
])
|
|
75
|
+
def test_class_season_selector_uses_the_selected_hero_data(season, payload, wins):
|
|
76
|
+
client = SeasonClient()
|
|
77
|
+
result = PlayerStats(client, 123).classes(season=season)
|
|
78
|
+
assert client.request == ("/player/stats/heroes", payload)
|
|
79
|
+
assert result.classes[1].competitive.wins == wins
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_detailed_hero_stats_accept_the_same_all_seasons_selector():
|
|
83
|
+
client = SeasonClient()
|
|
84
|
+
rows = PlayerStats(client, 123).heroes(season="all")
|
|
85
|
+
assert client.request == ("/player/stats/heroes", {"uid": 123, "season": -1})
|
|
86
|
+
assert rows[0].competitive.wins == 12
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_mcp_stats_exposes_all_seasons_and_forwards_the_selector(monkeypatch):
|
|
90
|
+
import asyncio
|
|
91
|
+
from types import SimpleNamespace
|
|
92
|
+
|
|
93
|
+
server = pytest.importorskip("rivalsdata.mcp_server", exc_type=ImportError)
|
|
94
|
+
client = SeasonClient()
|
|
95
|
+
client.get_player = lambda value: SimpleNamespace(stats=PlayerStats(client, 123))
|
|
96
|
+
monkeypatch.setattr(server, "_call", lambda fn, *a, **kw: fn(client, *a, **kw))
|
|
97
|
+
result = server.get_player_stats("123", category="classes", season="all")
|
|
98
|
+
assert client.request == ("/player/stats/heroes", {"uid": 123, "season": -1})
|
|
99
|
+
assert result.classes[1].competitive.wins == 12
|
|
100
|
+
tools = asyncio.run(server.mcp.list_tools())
|
|
101
|
+
schema = next(tool.inputSchema for tool in tools if tool.name == "get_player_stats")
|
|
102
|
+
assert {"const": "all", "type": "string"} in schema["properties"]["season"]["anyOf"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|