11import os
22import time
33from 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
56from urllib .parse import parse_qs , urlparse
67
78from 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