overfast-client 0.1.0__py3-none-any.whl
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.
- overfast_client-0.1.0.dist-info/METADATA +160 -0
- overfast_client-0.1.0.dist-info/RECORD +34 -0
- overfast_client-0.1.0.dist-info/WHEEL +4 -0
- overfast_client-0.1.0.dist-info/licenses/LICENSE +21 -0
- overwatch_py/__init__.py +4 -0
- overwatch_py/client.py +96 -0
- overwatch_py/config.py +11 -0
- overwatch_py/enums/__init__.py +28 -0
- overwatch_py/enums/background_image_size.py +10 -0
- overwatch_py/enums/career_stat_category.py +12 -0
- overwatch_py/enums/gamemode.py +13 -0
- overwatch_py/enums/hero.py +55 -0
- overwatch_py/enums/locale.py +17 -0
- overwatch_py/enums/map.py +61 -0
- overwatch_py/enums/map_gamemode.py +18 -0
- overwatch_py/enums/platform.py +6 -0
- overwatch_py/enums/rank.py +23 -0
- overwatch_py/enums/region.py +7 -0
- overwatch_py/enums/role.py +7 -0
- overwatch_py/exceptions.py +33 -0
- overwatch_py/models/__init__.py +61 -0
- overwatch_py/models/gamemode_details.py +11 -0
- overwatch_py/models/hero.py +72 -0
- overwatch_py/models/hero_short.py +11 -0
- overwatch_py/models/hero_stats.py +9 -0
- overwatch_py/models/map_details.py +13 -0
- overwatch_py/models/player.py +64 -0
- overwatch_py/models/player_stats.py +115 -0
- overwatch_py/py.typed +0 -0
- overwatch_py/services/__init__.py +6 -0
- overwatch_py/services/heros.py +110 -0
- overwatch_py/services/maps.py +21 -0
- overwatch_py/services/players.py +135 -0
- overwatch_py/session.py +64 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: overfast-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async Python client for the OverFast API (Overwatch data).
|
|
5
|
+
Project-URL: Homepage, https://github.com/Leo890728/overwatch_py
|
|
6
|
+
Project-URL: Repository, https://github.com/Leo890728/overwatch_py
|
|
7
|
+
Project-URL: Issues, https://github.com/Leo890728/overwatch_py/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Leo890728/overwatch_py/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Leo890728 <Leo890728@users.noreply.github.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,async,httpx,overfast,overwatch,pydantic
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Framework :: Pydantic :: 2
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Games/Entertainment
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: hishel<0.2,>=0.0.30
|
|
29
|
+
Requires-Dist: httpx-retries>=0.3
|
|
30
|
+
Requires-Dist: httpx>=0.27
|
|
31
|
+
Requires-Dist: pydantic>=2.5
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# overfast-client
|
|
39
|
+
|
|
40
|
+
Async Python client for the [OverFast API](https://overfast-api.tekrop.fr) — comprehensive Overwatch data (heroes, maps, gamemodes, player stats) via a typed, pydantic-backed interface.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install overfast-client
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> The PyPI distribution is `overfast-client`; the import name is `overwatch_py`.
|
|
49
|
+
|
|
50
|
+
Or from source (editable):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import asyncio
|
|
60
|
+
from overwatch_py import Client
|
|
61
|
+
from overwatch_py.enums import Hero, Locale, Role, HeroGamemode, PlayerGamemode, Platform
|
|
62
|
+
|
|
63
|
+
async def main():
|
|
64
|
+
client = Client()
|
|
65
|
+
|
|
66
|
+
# List heroes
|
|
67
|
+
heroes = await client.get_heroes(role=Role.SUPPORT, locale=Locale.EN_US)
|
|
68
|
+
for h in heroes:
|
|
69
|
+
print(h.key, h.name)
|
|
70
|
+
|
|
71
|
+
# Full hero data (abilities, story, hitpoints, ...)
|
|
72
|
+
ana = await client.get_hero_data(Hero.ANA)
|
|
73
|
+
print(ana.hitpoints, len(ana.abilities))
|
|
74
|
+
|
|
75
|
+
# Maps & gamemodes
|
|
76
|
+
maps = await client.get_maps()
|
|
77
|
+
gamemodes = await client.get_gamemode_details()
|
|
78
|
+
|
|
79
|
+
# Hero usage stats (pickrate / winrate)
|
|
80
|
+
stats = await client.get_heroes_stats(
|
|
81
|
+
platform=Platform.PC,
|
|
82
|
+
gamemode=PlayerGamemode.COMPETITIVE,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Players
|
|
86
|
+
result = await client.search_players("TeKrop", limit=10)
|
|
87
|
+
player = await client.get_player(result.results[0].player_id)
|
|
88
|
+
summary = await client.get_player_summary("TeKrop-2217")
|
|
89
|
+
stats_summary = await client.get_player_stats("TeKrop-2217", gamemode=PlayerGamemode.COMPETITIVE)
|
|
90
|
+
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Configuration
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from overwatch_py import Client
|
|
98
|
+
from overwatch_py.config import Config
|
|
99
|
+
from overwatch_py.session import HTTPSession
|
|
100
|
+
|
|
101
|
+
config = Config(
|
|
102
|
+
timeout=10, # seconds
|
|
103
|
+
retries=3, # retries on 5xx
|
|
104
|
+
cache=True, # HTTP-level cache (hishel), respects server Cache-Control
|
|
105
|
+
cache_backend="sqlite", # "memory" | "sqlite" | "file"
|
|
106
|
+
)
|
|
107
|
+
client = Client(HTTPSession(config))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Caching
|
|
111
|
+
|
|
112
|
+
Caching is disabled by default. When enabled, [hishel](https://hishel.com) sits in the httpx transport stack and honors each endpoint's `Cache-Control` / `Age` headers (e.g. `/heroes` is cached ~1 day, `/heroes/stats` ~1 hour by the upstream API).
|
|
113
|
+
|
|
114
|
+
## Exceptions
|
|
115
|
+
|
|
116
|
+
All non-2xx responses map to subclasses of `APIError`:
|
|
117
|
+
|
|
118
|
+
| Status | Exception |
|
|
119
|
+
|--------|-----------|
|
|
120
|
+
| 400 | `BadRequestError` |
|
|
121
|
+
| 404 | `NotFoundError` |
|
|
122
|
+
| 422 | `ValidationError` |
|
|
123
|
+
| 429 | `APIRateLimitError` |
|
|
124
|
+
| 500 | `InternalServerError` |
|
|
125
|
+
| 503 | `BlizzardRateLimitError` |
|
|
126
|
+
| 504 | `BlizzardServerError` |
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from overwatch_py.exceptions import NotFoundError, APIRateLimitError
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
await client.get_player_summary("does-not-exist-1234")
|
|
133
|
+
except NotFoundError:
|
|
134
|
+
...
|
|
135
|
+
except APIRateLimitError as e:
|
|
136
|
+
print("rate limited:", e.response.headers.get("retry-after"))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## API surface
|
|
140
|
+
|
|
141
|
+
Exposed on `Client`:
|
|
142
|
+
|
|
143
|
+
- **Heroes** — `get_heroes`, `get_hero_data`, `get_heroes_stats`
|
|
144
|
+
- **Maps / Gamemodes** — `get_maps`, `get_gamemode_details`
|
|
145
|
+
- **Players** — `search_players`, `get_player`, `get_player_summary`, `get_player_stats`, `get_player_career_stats`, `get_player_career_stats_with_labels`, `get_player_full_stats`
|
|
146
|
+
|
|
147
|
+
The underlying services (`client.heros`, `client.maps`, `client.players`) are also available if you prefer service-level access.
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install -e ".[dev]"
|
|
153
|
+
pytest # unit + mocked HTTP tests
|
|
154
|
+
pytest -m live # hits the real API (rate-limited)
|
|
155
|
+
pytest -m 'live or not live' # run everything
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
overwatch_py/__init__.py,sha256=h8jkjJd_ZWwpwwRJ5zMXBF-8oV3xKQdpYYIp-G5UOws,85
|
|
2
|
+
overwatch_py/client.py,sha256=Svn_fsXl6Gk22KCFBuxi0-s79EgMrXxcCnzsUj0yEIs,4902
|
|
3
|
+
overwatch_py/config.py,sha256=v21ZAqzGpuBkiwpSkh8yPJLQUz_Q-UJBZoI2i4PR2ts,284
|
|
4
|
+
overwatch_py/exceptions.py,sha256=SDiXFwMGTX34fOR4uaRjOWySUVQMYDlpxObAU8XQlhQ,714
|
|
5
|
+
overwatch_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
overwatch_py/session.py,sha256=acnkIDmvBV668hB48kGvcI26j5ED80FO9WiZmEgK-KM,2148
|
|
7
|
+
overwatch_py/enums/__init__.py,sha256=qUKvvqaxRInTkIzAepfVMNl4WsmgzB8vHH16ikqixgk,652
|
|
8
|
+
overwatch_py/enums/background_image_size.py,sha256=rHJL6s4YAbUAUU4gred2nWFbfJQHaeemG1Ync-KWN7k,154
|
|
9
|
+
overwatch_py/enums/career_stat_category.py,sha256=s1h4olzLj4r0ZfjhoFskH2_5lKPfiLbMHFSwNOMALUc,272
|
|
10
|
+
overwatch_py/enums/gamemode.py,sha256=YbNU4nI9azUkN2pfd12pgo5n8zGhTWhwHdffLkYTt9I,348
|
|
11
|
+
overwatch_py/enums/hero.py,sha256=-8ZVCuBUpXazr4N_SCF-AhyJwbB_cLagO5VhYekVQGc,1225
|
|
12
|
+
overwatch_py/enums/locale.py,sha256=7cESDJy_LYkvdfr-zVB-S8f4FTKvvIkdVF-0NTVv4VM,308
|
|
13
|
+
overwatch_py/enums/map.py,sha256=3Hkm4zBs1flqWUpwwOaXe-BQBYpyB3gQnjBayK9W18c,1825
|
|
14
|
+
overwatch_py/enums/map_gamemode.py,sha256=ny9h0NtrtBvmpXutZfioQOxwv3MLBjhXoD6XIyAy3Rg,455
|
|
15
|
+
overwatch_py/enums/platform.py,sha256=U__Ga6Ma_JlvfQ3tlR14R2t5OTJQ03ILFJhb2WtRu20,88
|
|
16
|
+
overwatch_py/enums/rank.py,sha256=vZoyW9XcyPg1lSmOLTdfB6vGdXsTYKfUdmYLKoXO4Kk,556
|
|
17
|
+
overwatch_py/enums/region.py,sha256=eiVnirCz3DiaQmAlHNXEjlFoKi8pC2OxQuSLWQnSRSk,115
|
|
18
|
+
overwatch_py/enums/role.py,sha256=6KrifIbH6BW9D_BsyFTxT6s46B025YEfyGZr7Pj4EM8,110
|
|
19
|
+
overwatch_py/models/__init__.py,sha256=x3Hg2J-UnEYTL7RS559E_HLLTgUfLSa9E72SiMptC5Q,1382
|
|
20
|
+
overwatch_py/models/gamemode_details.py,sha256=Pd-mujYqsKsOpRhCsZUu5jTyr_reUHMwXt69SiucqQ0,206
|
|
21
|
+
overwatch_py/models/hero.py,sha256=l7EqTbpdHQJCtoXEGFieVUobRCkBdtceoyQYbSkEcsY,1269
|
|
22
|
+
overwatch_py/models/hero_short.py,sha256=e7Yi6lrRHbzkEGVmNCNDhX9Y9nuandznjF61yHFGER8,215
|
|
23
|
+
overwatch_py/models/hero_stats.py,sha256=tbQVxSwPnUIbxi-7nnwN0bIrvjFviBJuTwUAY4TvY0k,140
|
|
24
|
+
overwatch_py/models/map_details.py,sha256=I5wTkKFO3xAq6-6yizqKg7wRnLPpcqQaXRtpvad9MX8,270
|
|
25
|
+
overwatch_py/models/player.py,sha256=7uZ9eG_GmHqTqonFc7hKNVof5rJN_DnNfD7aIcxvZw8,1608
|
|
26
|
+
overwatch_py/models/player_stats.py,sha256=lPPe47O_oDPaMUnnXXUPT-hhX3eJopnB-feq5AdNSuU,3247
|
|
27
|
+
overwatch_py/services/__init__.py,sha256=a2drwxI_bz_a9eRjLlQaEgLvnE5iDqHbbZgHlVxSPUE,159
|
|
28
|
+
overwatch_py/services/heros.py,sha256=sAZNu1VOxkf_UFdrwGvfJDAHzp-o4VPxWmWKNvm9588,4660
|
|
29
|
+
overwatch_py/services/maps.py,sha256=B6n-Ao2yP99UE42ngxfAnSbwdfAzd8ieX2Al4ZDCNqI,892
|
|
30
|
+
overwatch_py/services/players.py,sha256=5J8jG0NPB6x_zPNvV75XxgknNyZsgQRKfvQyRRRC-Ag,4759
|
|
31
|
+
overfast_client-0.1.0.dist-info/METADATA,sha256=KQaMBQZgU_BeGN1nlBU8owUbwN7Brk5FM979DgMa77M,5106
|
|
32
|
+
overfast_client-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
33
|
+
overfast_client-0.1.0.dist-info/licenses/LICENSE,sha256=1OjTzFo2zh8donxQs_QiBTv6e4fp9NqYLvRKooqWyWk,1065
|
|
34
|
+
overfast_client-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leo890728
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
overwatch_py/__init__.py
ADDED
overwatch_py/client.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from .services import HerosService, MapsService, PlayersService
|
|
4
|
+
from .services.players import HeroFilter
|
|
5
|
+
from .session import HTTPSession
|
|
6
|
+
from .models import (
|
|
7
|
+
GamemodeDetails,
|
|
8
|
+
HeroData,
|
|
9
|
+
HeroPlayerCareerStats,
|
|
10
|
+
HeroShort,
|
|
11
|
+
HeroStats,
|
|
12
|
+
MapDetails,
|
|
13
|
+
Player,
|
|
14
|
+
PlayerSearchResult,
|
|
15
|
+
PlayerStats,
|
|
16
|
+
PlayerStatsSummary,
|
|
17
|
+
PlayerSummary,
|
|
18
|
+
)
|
|
19
|
+
from .enums import (
|
|
20
|
+
CompetitiveDivisionFilter,
|
|
21
|
+
Hero,
|
|
22
|
+
HeroGamemode,
|
|
23
|
+
Locale,
|
|
24
|
+
Map,
|
|
25
|
+
MapGamemode,
|
|
26
|
+
Platform,
|
|
27
|
+
PlayerGamemode,
|
|
28
|
+
Region,
|
|
29
|
+
Role,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Client:
|
|
34
|
+
def __init__(self, session: HTTPSession = None):
|
|
35
|
+
self.session = session or HTTPSession()
|
|
36
|
+
self.heros = HerosService(self.session)
|
|
37
|
+
self.maps = MapsService(self.session)
|
|
38
|
+
self.players = PlayersService(self.session)
|
|
39
|
+
|
|
40
|
+
async def get_heroes(self, role: Role = None, locale: Locale = Locale.EN_US, gamemode: Optional[HeroGamemode] = None) -> list[HeroShort]:
|
|
41
|
+
"""Get a list of Overwatch heroes, which can be filtered using roles or gamemodes."""
|
|
42
|
+
return await self.heros.get_heroes(role, locale, gamemode)
|
|
43
|
+
|
|
44
|
+
async def get_hero_data(self, hero: Hero, locale: Locale = Locale.EN_US) -> HeroData:
|
|
45
|
+
"""Get data about an Overwatch hero : description, abilities, stadium powers, story, etc."""
|
|
46
|
+
return await self.heros.get_hero_data(hero, locale)
|
|
47
|
+
|
|
48
|
+
async def get_maps(self, map_gamemode: Optional[MapGamemode] = None) -> list[MapDetails]:
|
|
49
|
+
"""Get a list of Overwatch maps : Hanamura, King's Row, Dorado, etc."""
|
|
50
|
+
return await self.maps.get_maps(map_gamemode)
|
|
51
|
+
|
|
52
|
+
async def get_gamemode_details(self) -> list[GamemodeDetails]:
|
|
53
|
+
"""Get a list of Overwatch gamemodes : Assault, Escort, Flashpoint, Hybrid, etc."""
|
|
54
|
+
return await self.maps.get_gamemode_details()
|
|
55
|
+
|
|
56
|
+
async def get_heroes_stats(self, platform: Platform = Platform.PC, gamemode: PlayerGamemode = PlayerGamemode.COMPETITIVE,
|
|
57
|
+
region: Region = Region.ASIA, role: Optional[Role] = None, map: Optional[Map] = None,
|
|
58
|
+
competitive_division: Optional[CompetitiveDivisionFilter] = None, order_by: Optional[str] = "hero:asc") -> list[HeroStats]:
|
|
59
|
+
"""Get hero statistics usage, filtered by platform, region, role, etc. Only Role Queue gamemodes are concerned."""
|
|
60
|
+
return await self.heros.get_heroes_stats(platform, gamemode, region, role, map, competitive_division, order_by)
|
|
61
|
+
|
|
62
|
+
async def search_players(self, name: str, order_by: Optional[str] = None,
|
|
63
|
+
offset: Optional[int] = None, limit: Optional[int] = None) -> PlayerSearchResult:
|
|
64
|
+
"""Search for players by BattleTag name."""
|
|
65
|
+
return await self.players.search_players(name, order_by, offset, limit)
|
|
66
|
+
|
|
67
|
+
async def get_player(self, player_id: str, gamemode: Optional[PlayerGamemode] = None,
|
|
68
|
+
platform: Optional[Platform] = None) -> Player:
|
|
69
|
+
"""Get all player data: summary and statistics."""
|
|
70
|
+
return await self.players.get_player(player_id, gamemode, platform)
|
|
71
|
+
|
|
72
|
+
async def get_player_summary(self, player_id: str) -> PlayerSummary:
|
|
73
|
+
"""Get player summary: name, avatar, endorsement, competitive ranks."""
|
|
74
|
+
return await self.players.get_player_summary(player_id)
|
|
75
|
+
|
|
76
|
+
async def get_player_stats(self, player_id: str, gamemode: Optional[PlayerGamemode] = None,
|
|
77
|
+
platform: Optional[Platform] = None) -> PlayerStatsSummary:
|
|
78
|
+
"""Get player stats summary (general / roles / heroes)."""
|
|
79
|
+
return await self.players.get_player_stats(player_id, gamemode, platform)
|
|
80
|
+
|
|
81
|
+
async def get_player_career_stats(self, player_id: str, gamemode: PlayerGamemode,
|
|
82
|
+
platform: Optional[Platform] = None,
|
|
83
|
+
hero: Optional[HeroFilter] = None) -> dict[str, Optional[HeroPlayerCareerStats]]:
|
|
84
|
+
"""Get player career stats as flat {label: value} maps per hero."""
|
|
85
|
+
return await self.players.get_player_career_stats(player_id, gamemode, platform, hero)
|
|
86
|
+
|
|
87
|
+
async def get_player_career_stats_with_labels(self, player_id: str, gamemode: PlayerGamemode,
|
|
88
|
+
platform: Optional[Platform] = None,
|
|
89
|
+
hero: Optional[HeroFilter] = None):
|
|
90
|
+
"""Get player career stats with labels and categories per hero."""
|
|
91
|
+
return await self.players.get_player_career_stats_with_labels(player_id, gamemode, platform, hero)
|
|
92
|
+
|
|
93
|
+
async def get_player_full_stats(self, player_id: str, gamemode: Optional[PlayerGamemode] = None,
|
|
94
|
+
platform: Optional[Platform] = None) -> PlayerStats:
|
|
95
|
+
"""Get a player's full per-platform stats (pc/console)."""
|
|
96
|
+
return await self.players.get_player_full_stats(player_id, gamemode, platform)
|
overwatch_py/config.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, HttpUrl
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Config(BaseModel):
|
|
7
|
+
base_url: HttpUrl = "https://overfast-api.tekrop.fr"
|
|
8
|
+
timeout: int = 10
|
|
9
|
+
retries: int = 3
|
|
10
|
+
cache: bool = False
|
|
11
|
+
cache_backend: Literal["memory", "sqlite", "file"] = "memory"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .background_image_size import BackgroundImageSize
|
|
2
|
+
from .career_stat_category import CareerStatCategory
|
|
3
|
+
from .map_gamemode import MapGamemode
|
|
4
|
+
from .hero import Hero
|
|
5
|
+
from .locale import Locale
|
|
6
|
+
from .platform import Platform
|
|
7
|
+
from .region import Region
|
|
8
|
+
from .role import Role
|
|
9
|
+
from .map import Map
|
|
10
|
+
from .gamemode import HeroGamemode, PlayerGamemode
|
|
11
|
+
from .rank import Rank, CompetitiveDivisionFilter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BackgroundImageSize",
|
|
16
|
+
"CareerStatCategory",
|
|
17
|
+
"CompetitiveDivisionFilter",
|
|
18
|
+
"Hero",
|
|
19
|
+
"HeroGamemode",
|
|
20
|
+
"Locale",
|
|
21
|
+
"Map",
|
|
22
|
+
"MapGamemode",
|
|
23
|
+
"Platform",
|
|
24
|
+
"PlayerGamemode",
|
|
25
|
+
"Rank",
|
|
26
|
+
"Region",
|
|
27
|
+
"Role",
|
|
28
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CareerStatCategory(str, Enum):
|
|
5
|
+
ASSISTS = "assists"
|
|
6
|
+
AVERAGE = "average"
|
|
7
|
+
BEST = "best"
|
|
8
|
+
COMBAT = "combat"
|
|
9
|
+
GAME = "game"
|
|
10
|
+
HERO_SPECIFIC = "hero_specific"
|
|
11
|
+
MATCH_AWARDS = "match_awards"
|
|
12
|
+
MISCELLANEOUS = "miscellaneous"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HeroGamemode(str, Enum):
|
|
5
|
+
"""Gamemodes a hero can be played in (used by /heroes filter and HeroShort.gamemodes)."""
|
|
6
|
+
QUICKPLAY = "quickplay"
|
|
7
|
+
STADIUM = "stadium"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PlayerGamemode(str, Enum):
|
|
11
|
+
"""Gamemodes used by player-stats endpoints."""
|
|
12
|
+
QUICKPLAY = "quickplay"
|
|
13
|
+
COMPETITIVE = "competitive"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Hero(str, Enum):
|
|
5
|
+
ANA = "ana"
|
|
6
|
+
ANRAN = "anran"
|
|
7
|
+
ASHE = "ashe"
|
|
8
|
+
BAPTISTE = "baptiste"
|
|
9
|
+
BASTION = "bastion"
|
|
10
|
+
BRIGITTE = "brigitte"
|
|
11
|
+
CASSIDY = "cassidy"
|
|
12
|
+
DVA = "dva"
|
|
13
|
+
DOMINA = "domina"
|
|
14
|
+
DOOMFIST = "doomfist"
|
|
15
|
+
ECHO = "echo"
|
|
16
|
+
EMRE = "emre"
|
|
17
|
+
FREJA = "freja"
|
|
18
|
+
GENJI = "genji"
|
|
19
|
+
HAZARD = "hazard"
|
|
20
|
+
HANZO = "hanzo"
|
|
21
|
+
ILLARI = "illari"
|
|
22
|
+
JETPACK_CAT = "jetpack-cat"
|
|
23
|
+
JUNKER_QUEEN = "junker-queen"
|
|
24
|
+
JUNKRAT = "junkrat"
|
|
25
|
+
JUNO = "juno"
|
|
26
|
+
KIRIKO = "kiriko"
|
|
27
|
+
LIFEWEAVER = "lifeweaver"
|
|
28
|
+
LUCIO = "lucio"
|
|
29
|
+
MAUGA = "mauga"
|
|
30
|
+
MEI = "mei"
|
|
31
|
+
MERCY = "mercy"
|
|
32
|
+
MIZUKI = "mizuki"
|
|
33
|
+
MOIRA = "moira"
|
|
34
|
+
ORISA = "orisa"
|
|
35
|
+
PHARAH = "pharah"
|
|
36
|
+
RAMATTRA = "ramattra"
|
|
37
|
+
REAPER = "reaper"
|
|
38
|
+
REINHARDT = "reinhardt"
|
|
39
|
+
ROADHOG = "roadhog"
|
|
40
|
+
SIGMA = "sigma"
|
|
41
|
+
SIERRA = "sierra"
|
|
42
|
+
SOJOURN = "sojourn"
|
|
43
|
+
SOLDIER_76 = "soldier-76"
|
|
44
|
+
SOMBRA = "sombra"
|
|
45
|
+
SYMMETRA = "symmetra"
|
|
46
|
+
TORBJORN = "torbjorn"
|
|
47
|
+
TRACER = "tracer"
|
|
48
|
+
VENDETTA = "vendetta"
|
|
49
|
+
VENTURE = "venture"
|
|
50
|
+
WIDOWMAKER = "widowmaker"
|
|
51
|
+
WINSTON = "winston"
|
|
52
|
+
WRECKING_BALL = "wrecking-ball"
|
|
53
|
+
WUYANG = "wuyang"
|
|
54
|
+
ZARYA = "zarya"
|
|
55
|
+
ZENYATTA = "zenyatta"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Locale(str, Enum):
|
|
5
|
+
DE_DE = "de-de"
|
|
6
|
+
EN_GB = "en-gb"
|
|
7
|
+
EN_US = "en-us"
|
|
8
|
+
ES_ES = "es-es"
|
|
9
|
+
ES_MX = "es-mx"
|
|
10
|
+
FR_FR = "fr-fr"
|
|
11
|
+
IT_IT = "it-it"
|
|
12
|
+
JA_JP = "ja-jp"
|
|
13
|
+
KO_KR = "ko-kr"
|
|
14
|
+
PL_PL = "pl-pl"
|
|
15
|
+
PT_BR = "pt-br"
|
|
16
|
+
RU_RU = "ru-ru"
|
|
17
|
+
ZH_TW = "zh-tw"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Map(str, Enum):
|
|
5
|
+
AATLIS = "aatlis"
|
|
6
|
+
ANTARCTIC_PENINSULA = "antarctic-peninsula"
|
|
7
|
+
ANUBIS = "anubis"
|
|
8
|
+
ARENA_VICTORIAE = "arena-victoriae"
|
|
9
|
+
AYUTTHAYA = "ayutthaya"
|
|
10
|
+
BLACK_FOREST = "black-forest"
|
|
11
|
+
BLIZZARD_WORLD = "blizzard-world"
|
|
12
|
+
BUSAN = "busan"
|
|
13
|
+
CASTILLO = "castillo"
|
|
14
|
+
CHATEAU_GUILLARD = "chateau-guillard"
|
|
15
|
+
CIRCUIT_ROYAL = "circuit-royal"
|
|
16
|
+
COLOSSEO = "colosseo"
|
|
17
|
+
DORADO = "dorado"
|
|
18
|
+
ECOPOINT_ANTARCTICA = "ecopoint-antarctica"
|
|
19
|
+
EICHENWALDE = "eichenwalde"
|
|
20
|
+
ESPERANCA = "esperanca"
|
|
21
|
+
GOGADORO = "gogadoro"
|
|
22
|
+
HANAMURA = "hanamura"
|
|
23
|
+
HANAOKA = "hanaoka"
|
|
24
|
+
HAVANA = "havana"
|
|
25
|
+
HOLLYWOOD = "hollywood"
|
|
26
|
+
HORIZON = "horizon"
|
|
27
|
+
ILIOS = "ilios"
|
|
28
|
+
JUNKERTOWN = "junkertown"
|
|
29
|
+
LIJIANG_TOWER = "lijiang-tower"
|
|
30
|
+
KANEZAKA = "kanezaka"
|
|
31
|
+
KINGS_ROW = "kings-row"
|
|
32
|
+
MALEVENTO = "malevento"
|
|
33
|
+
MIDTOWN = "midtown"
|
|
34
|
+
NECROPOLIS = "necropolis"
|
|
35
|
+
NEPAL = "nepal"
|
|
36
|
+
NEW_JUNK_CITY = "new-junk-city"
|
|
37
|
+
NEW_QUEEN_STREET = "new-queen-street"
|
|
38
|
+
NUMBANI = "numbani"
|
|
39
|
+
OASIS = "oasis"
|
|
40
|
+
PARAISO = "paraiso"
|
|
41
|
+
PARIS = "paris"
|
|
42
|
+
PETRA = "petra"
|
|
43
|
+
PLACE_LACROIX = "place-lacroix"
|
|
44
|
+
POWDER_KEG_MINE = "powder-keg-mine"
|
|
45
|
+
PRACTICE_RANGE = "practice-range"
|
|
46
|
+
REDWOOD_DAM = "redwood-dam"
|
|
47
|
+
RIALTO = "rialto"
|
|
48
|
+
ROUTE_66 = "route-66"
|
|
49
|
+
RUNASAPI = "runasapi"
|
|
50
|
+
SAMOA = "samoa"
|
|
51
|
+
SHAMBALI_MONASTERY = "shambali-monastery"
|
|
52
|
+
SURAVASA = "suravasa"
|
|
53
|
+
THAMES_DISTRICT = "thames-district"
|
|
54
|
+
THRONE_OF_ANUBIS = "throne-of-anubis"
|
|
55
|
+
VOLSKAYA = "volskaya"
|
|
56
|
+
WATCHPOINT_GIBRALTAR = "watchpoint-gibraltar"
|
|
57
|
+
WORKSHOP_CHAMBER = "workshop-chamber"
|
|
58
|
+
WORKSHOP_EXPANSE = "workshop-expanse"
|
|
59
|
+
WORKSHOP_GREEN_SCREEN = "workshop-green-screen"
|
|
60
|
+
WORKSHOP_ISLAND = "workshop-island"
|
|
61
|
+
WUXING_UNIVERSITY = "wuxing-university"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MapGamemode(str, Enum):
|
|
5
|
+
ASSAULT = "assault"
|
|
6
|
+
CAPTURE_THE_FLAG = "capture-the-flag"
|
|
7
|
+
CLASH = "clash"
|
|
8
|
+
CONTROL = "control"
|
|
9
|
+
DEATHMATCH = "deathmatch"
|
|
10
|
+
ELIMINATION = "elimination"
|
|
11
|
+
ESCORT = "escort"
|
|
12
|
+
FLASHPOINT = "flashpoint"
|
|
13
|
+
HYBRID = "hybrid"
|
|
14
|
+
PAYLOAD_RACE = "payload-race"
|
|
15
|
+
PRACTICE_RANGE = "practice-range"
|
|
16
|
+
PUSH = "push"
|
|
17
|
+
TEAM_DEATHMATCH = "team-deathmatch"
|
|
18
|
+
WORKSHOP = "workshop"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Rank(str, Enum):
|
|
5
|
+
BRONZE = "bronze"
|
|
6
|
+
SILVER = "silver"
|
|
7
|
+
GOLD = "gold"
|
|
8
|
+
PLATINUM = "platinum"
|
|
9
|
+
DIAMOND = "diamond"
|
|
10
|
+
MASTER = "master"
|
|
11
|
+
GRANDMASTER = "grandmaster"
|
|
12
|
+
ULTIMATE = "ultimate"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CompetitiveDivisionFilter(str, Enum):
|
|
16
|
+
"""Same as Rank, but without ULTIMATE — accepted by /heroes/stats `competitive_division` query."""
|
|
17
|
+
BRONZE = "bronze"
|
|
18
|
+
SILVER = "silver"
|
|
19
|
+
GOLD = "gold"
|
|
20
|
+
PLATINUM = "platinum"
|
|
21
|
+
DIAMOND = "diamond"
|
|
22
|
+
MASTER = "master"
|
|
23
|
+
GRANDMASTER = "grandmaster"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class APIError(Exception):
|
|
2
|
+
def __init__(self, message: str, status_code: int, response=None):
|
|
3
|
+
super().__init__(message)
|
|
4
|
+
self.status_code = status_code
|
|
5
|
+
self.response = response
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BadRequestError(APIError):
|
|
9
|
+
"""400 Bad Request Error"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NotFoundError(APIError):
|
|
13
|
+
"""404 Not Found (hero or player)"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ValidationError(APIError):
|
|
17
|
+
"""422 Validation Error"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class APIRateLimitError(APIError):
|
|
21
|
+
"""429 API Rate Limit Error"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InternalServerError(APIError):
|
|
25
|
+
"""500 Internal Server Error"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BlizzardRateLimitError(APIError):
|
|
29
|
+
"""503 Blizzard Rate Limit Error"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BlizzardServerError(APIError):
|
|
33
|
+
"""504 Blizzard Server Error"""
|