Skip to content

Commit ce0a151

Browse files
committed
Release 0.3.0
- Rename pre-f2p seasons to "legacy" for clarity - Update and correct map lists - Fix rate limits counting as retries - Add f2p season 19 - Add legacy grand champion rank ID
1 parent ebbf397 commit ce0a151

File tree

4 files changed

+216
-117
lines changed

4 files changed

+216
-117
lines changed

ballchasing/api.py

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22
import time
33
from datetime import datetime
4-
from typing import Optional, Iterator, Union, List
4+
from pathlib import Path
5+
from typing import Optional, Iterator, Union, List, BinaryIO
56
from urllib.parse import parse_qs, urlparse
67

78
from requests import sessions, Response, ConnectionError
@@ -18,12 +19,14 @@ class BallchasingApi:
1819
Class for communication with ballchasing.com API (https://ballchasing.com/doc/api)
1920
"""
2021

21-
def __init__(self,
22-
auth_key: str,
23-
sleep_time_on_rate_limit: Optional[float] = None,
24-
print_on_rate_limit: bool = False,
25-
base_url=None,
26-
do_initial_ping=True):
22+
def __init__(
23+
self,
24+
auth_key: str,
25+
sleep_time_on_rate_limit: Optional[float] = None,
26+
print_on_rate_limit: bool = False,
27+
base_url=None,
28+
do_initial_ping=True
29+
):
2730
"""
2831
2932
:param auth_key: authentication key for API calls.
@@ -74,11 +77,12 @@ def quota(self):
7477
self.ping()
7578
return self._ping_result.get("quota")
7679

77-
def _request(self,
78-
url_or_endpoint: str,
79-
method: str,
80-
**params
81-
) -> Response:
80+
def _request(
81+
self,
82+
url_or_endpoint: str,
83+
method: str,
84+
**params
85+
) -> Response:
8286
"""
8387
Helper method for all requests.
8488
@@ -92,7 +96,8 @@ def _request(self,
9296
headers = {"Authorization": self.auth_key}
9397
url = f"{self.base_url}{url_or_endpoint}" if url_or_endpoint.startswith("/") else url_or_endpoint
9498
max_retries = 8
95-
for retries in range(max_retries):
99+
retries = 0
100+
while True:
96101
try:
97102
r: Response = self._session.request(method=method, url=url, headers=headers, **params)
98103
if 200 <= r.status_code < 300:
@@ -108,13 +113,14 @@ def _request(self,
108113
elif self.sleep_time_on_rate_limit:
109114
time.sleep(self.sleep_time_on_rate_limit)
110115
else:
111-
r.raise_for_status() # Raise an error for any other status code
116+
r.raise_for_status() # Raise an error for any other status code'
112117
except ConnectionError as e:
113118
if retries >= max_retries - 1:
114119
raise e
115120
s = 2 ** retries
116121
print(f"Connection error, trying again in {s} seconds...")
117122
time.sleep(s)
123+
retries += 1
118124

119125
def ping(self) -> dict:
120126
"""
@@ -130,28 +136,29 @@ def ping(self) -> dict:
130136
self._ping_result = result
131137
return result
132138

133-
def get_replays(self,
134-
title: Optional[str] = None,
135-
player_name: Optional[Union[str, List[str]]] = None,
136-
player_id: Optional[Union[str, List[str]]] = None,
137-
playlist: Optional[Union[AnyPlaylist, List[AnyPlaylist]]] = None,
138-
season: Optional[Union[AnySeason, List[AnySeason]]] = None,
139-
match_result: Optional[Union[AnyMatchResult, List[AnyMatchResult]]] = None,
140-
min_rank: Optional[AnyRank] = None,
141-
max_rank: Optional[AnyRank] = None,
142-
pro: Optional[bool] = None,
143-
uploader: Optional[str] = None,
144-
group_id: Optional[Union[str, List[str]]] = None,
145-
map_id: Optional[Union[AnyMap, List[AnyMap]]] = None,
146-
created_before: Optional[Union[str, datetime]] = None,
147-
created_after: Optional[Union[str, datetime]] = None,
148-
replay_after: Optional[Union[str, datetime]] = None,
149-
replay_before: Optional[Union[str, datetime]] = None,
150-
count: int = 150,
151-
sort_by: Optional[AnyReplaySortBy] = None,
152-
sort_dir: AnySortDir = SortDir.DESCENDING,
153-
deep: bool = False
154-
) -> Iterator[dict]:
139+
def get_replays(
140+
self,
141+
title: Optional[str] = None,
142+
player_name: Optional[Union[str, List[str]]] = None,
143+
player_id: Optional[Union[str, List[str]]] = None,
144+
playlist: Optional[Union[AnyPlaylist, List[AnyPlaylist]]] = None,
145+
season: Optional[Union[AnySeason, List[AnySeason]]] = None,
146+
match_result: Optional[Union[AnyMatchResult, List[AnyMatchResult]]] = None,
147+
min_rank: Optional[AnyRank] = None,
148+
max_rank: Optional[AnyRank] = None,
149+
pro: Optional[bool] = None,
150+
uploader: Optional[str] = None,
151+
group_id: Optional[Union[str, List[str]]] = None,
152+
map_id: Optional[Union[AnyMap, List[AnyMap]]] = None,
153+
created_before: Optional[Union[str, datetime]] = None,
154+
created_after: Optional[Union[str, datetime]] = None,
155+
replay_after: Optional[Union[str, datetime]] = None,
156+
replay_before: Optional[Union[str, datetime]] = None,
157+
count: int = 150,
158+
sort_by: Optional[AnyReplaySortBy] = None,
159+
sort_dir: AnySortDir = SortDir.DESCENDING,
160+
deep: bool = False
161+
) -> Iterator[dict]:
155162
"""
156163
This endpoint lets you filter and retrieve replays. The implementation returns an iterator.
157164
@@ -230,18 +237,23 @@ def patch_replay(self, replay_id: str, **params) -> None:
230237
"""
231238
self._request(f"/replays/{replay_id}", "PATCH", json=params)
232239

233-
def upload_replay(self,
234-
replay_file,
235-
visibility: Optional[AnyVisibility] = None,
236-
group: Optional[str] = None) -> dict:
240+
def upload_replay(
241+
self,
242+
replay_file: Union[str, Path, BinaryIO],
243+
visibility: Optional[AnyVisibility] = None,
244+
group: Optional[str] = None
245+
) -> dict:
237246
"""
238247
Use this API to upload a replay file to ballchasing.com.
239248
240-
:param replay_file: replay file to upload.
249+
:param replay_file: replay file to upload. Can be a file path (str or Path) or a file-like object.
241250
:param visibility: to set the visibility of the uploaded replay.
242251
:param group: to upload the replay to an existing group.
243252
:return: the result of the POST request.
244253
"""
254+
if isinstance(replay_file, (str, Path)):
255+
with open(replay_file, "rb") as f:
256+
return self.upload_replay(f, visibility, group)
245257
return self._request(f"/v2/upload", "POST", files={"file": replay_file},
246258
params={"group": group, "visibility": visibility}).json()
247259

@@ -254,16 +266,17 @@ def delete_replay(self, replay_id: str) -> None:
254266
"""
255267
self._request(f"/replays/{replay_id}", "DELETE")
256268

257-
def get_groups(self,
258-
name: Optional[str] = None,
259-
creator: Optional[str] = None,
260-
group: Optional[str] = None,
261-
created_before: Optional[Union[str, datetime]] = None,
262-
created_after: Optional[Union[str, datetime]] = None,
263-
count: int = 200,
264-
sort_by: AnyGroupSortBy = GroupSortBy.CREATED,
265-
sort_dir: AnySortDir = SortDir.DESCENDING
266-
) -> Iterator[dict]:
269+
def get_groups(
270+
self,
271+
name: Optional[str] = None,
272+
creator: Optional[str] = None,
273+
group: Optional[str] = None,
274+
created_before: Optional[Union[str, datetime]] = None,
275+
created_after: Optional[Union[str, datetime]] = None,
276+
count: int = 200,
277+
sort_by: AnyGroupSortBy = GroupSortBy.CREATED,
278+
sort_dir: AnySortDir = SortDir.DESCENDING
279+
) -> Iterator[dict]:
267280
"""
268281
This endpoint lets you filter and retrieve replay groups.
269282
@@ -301,12 +314,13 @@ def get_groups(self,
301314
left -= len(batch)
302315
params["after"] = parse_qs(urlparse(next_url).query)["after"][0]
303316

304-
def create_group(self,
305-
name: str,
306-
player_identification: AnyPlayerIdentification,
307-
team_identification: AnyTeamIdentification,
308-
parent: Optional[str] = None
309-
) -> dict:
317+
def create_group(
318+
self,
319+
name: str,
320+
player_identification: AnyPlayerIdentification,
321+
team_identification: AnyTeamIdentification,
322+
parent: Optional[str] = None
323+
) -> dict:
310324
"""
311325
Use this API to create a new replay group.
312326

0 commit comments

Comments
 (0)