11import aiohttp
22import asyncio
3+ import requests
34
4- from box import Box , BoxKeyError
5+ import json
56
6- from .errors import BadRequest , InvalidTag , NotFoundError , Unauthorized , UnexpectedError , ServerError
7+ from box import Box , BoxList
8+
9+ from .errors import InvalidTag , Unauthorized , UnexpectedError , ServerError
710from .utils import API
811
912
10- class BaseBox (Box ):
11- def __init__ (self , * args , ** kwargs ):
12- kwargs ['camel_killer_box' ] = True
13- super ().__init__ (* args , ** kwargs )
13+ class BaseBox :
14+ def __init__ (self , client , data ):
15+ self .client = client
16+ self .from_data (data )
17+
18+ def from_data (self , data ):
19+ self .raw_data = data
20+ if isinstance (data , list ):
21+ self ._boxed_data = BoxList (
22+ data , camel_killer_box = True
23+ )
24+ else :
25+ self ._boxed_data = Box (
26+ data , camel_killer_box = True
27+ )
28+ return self
29+
30+ def __getattr__ (self , attr ):
31+ try :
32+ return getattr (self ._boxed_data , attr )
33+ except AttributeError :
34+ try :
35+ return super ().__getattr__ (attr )
36+ except AttributeError :
37+ return None # makes it easier on the user's end
38+
39+ def __getitem__ (self , item ):
40+ try :
41+ return getattr (self ._boxed_data , item )
42+ except AttributeError :
43+ raise KeyError ('No such key: {}' .format (item ))
1444
1545
1646class Client :
1747 """
18- This is an async client class that lets you access the API.
48+ This is a sync/ async client class that lets you access the API.
1949
2050 Parameters
2151 ------------
2252 token: str
2353 The API Key that you can get from https://discord.me/BrawlAPI
24- timeout: Optional[int] = 5
54+ timeout: Optional[int] = 10
2555 A timeout for requests to the API.
26- session: Optional[Session] = aiohttp.ClientSession()
27- Use a current aiohttp session or a new one.
28- loop : Optional[Loop ] = None
29- Use a current loop. Recommended to remove warnings when you run the program .
56+ session: Optional[Session] = None
57+ Use a current session or a make new one.
58+ is_async : Optional[bool ] = False
59+ Makes the client async .
3060 """
3161
3262 def __init__ (self , token , ** options ):
33- loop = options .get ('loop ' , asyncio . get_event_loop () )
34- self .session = options .get ('session' , aiohttp .ClientSession (loop = loop ))
35- self .timeout = options .get ('timeout' , 5 )
63+ self . is_async = options .get ('is_async ' , False )
64+ self .session = options .get ('session' , aiohttp .ClientSession () if self . is_async else requests . Session ( ))
65+ self .timeout = options .get ('timeout' , 10 )
3666 self .headers = {
3767 'Authorization' : token ,
3868 'User-Agent' : 'brawlstats | Python'
3969 }
4070
4171 def __repr__ (self ):
42- return '<BrawlStats-Client timeout={}>' .format (self .timeout )
72+ return '<BrawlStats-Client async={} timeout={}>' .format (self . is_async , self .timeout )
4373
44- async def close (self ):
45- return await self .session .close ()
74+ def close (self ):
75+ return self .session .close ()
4676
4777 def _check_tag (self , tag , endpoint ):
4878 tag = tag .upper ().replace ('#' , '' ).replace ('O' , '0' )
@@ -53,26 +83,44 @@ def _check_tag(self, tag, endpoint):
5383 raise InvalidTag (endpoint + '/' + tag , 404 )
5484 return tag
5585
86+ def _raise_for_status (self , resp , text , url ):
87+ try :
88+ data = json .loads (text )
89+ except json .JSONDecodeError :
90+ data = text
91+
92+ code = getattr (resp , 'status' , None ) or getattr (resp , 'status_code' )
93+
94+ if 300 > code >= 200 :
95+ return data
96+ if code == 401 :
97+ raise Unauthorized (url , code )
98+ if code in (400 , 404 ):
99+ raise InvalidTag (url , code )
100+ if code >= 500 :
101+ raise ServerError (url , code )
102+
103+ raise UnexpectedError (url , code )
104+
56105 async def _aget (self , url ):
57106 try :
58107 async with self .session .get (url , timeout = self .timeout , headers = self .headers ) as resp :
59- if resp .status == 200 :
60- raw_data = await resp .json ()
61- elif resp .status == 400 :
62- raise BadRequest (url , resp .status )
63- elif resp .status == 401 :
64- raise Unauthorized (url , resp .status )
65- elif resp .status == 404 :
66- raise InvalidTag (url , resp .status )
67- elif resp .status in (503 , 520 , 521 ):
68- raise ServerError (url , resp .status )
69- else :
70- raise UnexpectedError (url , resp .status )
108+ return self ._raise_for_status (resp , await resp .text (), url )
71109 except asyncio .TimeoutError :
72- raise NotFoundError (url , 400 )
73- return raw_data
110+ raise ServerError (url , 503 )
74111
75- async def get_profile (self , tag : str ):
112+ def _get (self , url ):
113+ try :
114+ with self .session .get (url , timeout = self .timeout , headers = self .headers ) as resp :
115+ return self ._raise_for_status (resp , resp .text , url )
116+ except requests .Timeout :
117+ raise ServerError (url , 503 )
118+
119+ async def _get_profile_async (self , tag : str ):
120+ response = await self ._aget (API .PROFILE + '/' + tag )
121+ return Profile (self , response )
122+
123+ def get_profile (self , tag : str ):
76124 """Get a player's stats.
77125
78126 Parameters
@@ -84,14 +132,19 @@ async def get_profile(self, tag: str):
84132 Returns Profile
85133 """
86134 tag = self ._check_tag (tag , API .PROFILE )
87- response = await self ._aget (API .PROFILE + '/' + tag )
88- response ['client' ] = self
135+ if self .is_async :
136+ return self ._get_profile_async (tag )
137+ response = self ._get (API .PROFILE + '/' + tag )
89138
90- return Profile (response )
139+ return Profile (self , response )
91140
92141 get_player = get_profile
93142
94- async def get_band (self , tag : str ):
143+ async def _get_band_async (self , tag : str ):
144+ response = await self ._aget (API .BAND + '/' + tag )
145+ return Band (self , response )
146+
147+ def get_band (self , tag : str ):
95148 """Get a band's stats.
96149
97150 Parameters
@@ -103,11 +156,17 @@ async def get_band(self, tag: str):
103156 Returns Band
104157 """
105158 tag = self ._check_tag (tag , API .BAND )
106- response = await self ._aget (API .BAND + '/' + tag )
159+ if self .is_async :
160+ return self ._get_band_async (tag )
161+ response = self ._get (API .BAND + '/' + tag )
162+
163+ return Band (self , response )
107164
108- return Band (response )
165+ async def _get_leaderboard_async (self , url ):
166+ response = await self ._aget (url )
167+ return Leaderboard (self , response )
109168
110- async def get_leaderboard (self , player_or_band : str , count : int = 200 ):
169+ def get_leaderboard (self , player_or_band : str , count : int = 200 ):
111170 """Get the top count players/bands.
112171
113172 Parameters
@@ -126,17 +185,25 @@ async def get_leaderboard(self, player_or_band: str, count: int=200):
126185 if player_or_band .lower () not in ('players' , 'bands' ) or count > 200 or count < 1 :
127186 raise ValueError ("Please enter 'players' or 'bands' or make sure 'count' is between 1 and 200." )
128187 url = API .LEADERBOARD + '/' + player_or_band + '/' + str (count )
129- response = await self ._aget (url )
188+ if self .is_async :
189+ return self ._get_leaderboard_async (url )
190+ response = self ._get (url )
191+
192+ return Leaderboard (self , response )
130193
131- return Leaderboard (response )
194+ async def _get_events_async (self ):
195+ response = await self ._aget (API .EVENTS )
196+ return Events (self , response )
132197
133- async def get_events (self ):
198+ def get_events (self ):
134199 """Get current and upcoming events.
135200
136201 Returns Events"""
137- response = await self ._aget (API .EVENTS )
202+ if self .is_async :
203+ return self ._get_events_async ()
204+ response = self ._get (API .EVENTS )
138205
139- return Events (response )
206+ return Events (self , response )
140207
141208class Profile (BaseBox ):
142209 """
@@ -149,7 +216,7 @@ def __repr__(self):
149216 def __str__ (self ):
150217 return '{0.name} (#{0.tag})' .format (self )
151218
152- async def get_band (self , full = False ):
219+ def get_band (self , full = False ):
153220 """
154221 Gets the player's band.
155222
@@ -163,10 +230,9 @@ async def get_band(self, full=False):
163230 if not self .band :
164231 return None
165232 if not full :
166- self .band ['client' ] = self .client
167- band = SimpleBand (self .band )
233+ band = SimpleBand (self , self .band )
168234 else :
169- band = await self .client .get_band (self .band .tag )
235+ band = self .client .get_band (self .band .tag )
170236 return band
171237
172238
@@ -181,13 +247,13 @@ def __repr__(self):
181247 def __str__ (self ):
182248 return '{0.name} (#{0.tag})' .format (self )
183249
184- async def get_full (self ):
250+ def get_full (self ):
185251 """
186252 Gets the full band statistics.
187253
188254 Returns Band
189255 """
190- return await self .client .get_band (self .tag )
256+ return self .client .get_band (self .tag )
191257
192258
193259class Band (BaseBox ):
@@ -208,16 +274,14 @@ class Leaderboard(BaseBox):
208274 """
209275
210276 def __repr__ (self ):
211- try :
212- return "<Leaderboard object type='players' count={}>" .format (len (self .players ))
213- except BoxKeyError :
214- return "<Leaderboard object type='bands' count={}>" .format (len (self .bands ))
277+ lb_type = 'player' if self .players else 'band'
278+ count = len (self .players ) if self .players else len (self .bands )
279+ return "<Leaderboard object type='{}' count={}>" .format (lb_type , count )
215280
216281 def __str__ (self ):
217- try :
218- return 'Player Leaderboard containing {} items' .format (len (self .players ))
219- except BoxKeyError :
220- return 'Band Leaderboard containing {} items' .format (len (self .bands ))
282+ lb_type = 'Player' if self .players else 'Band'
283+ count = len (self .players ) if self .players else len (self .bands )
284+ return '{} Leaderboard containing {} items' .format (lb_type , count )
221285
222286class Events (BaseBox ):
223287 """
0 commit comments