Skip to content

Commit 3135300

Browse files
authored
Merge pull request #69 from SharpBit/development
Version 4.0.4
2 parents e95c6ec + f32a84f commit 3135300

File tree

13 files changed

+128
-62
lines changed

13 files changed

+128
-62
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4+
5+
## [4.0.4] - 7/22/20
6+
### Added
7+
- `get_brawlers` function to get available brawlers
8+
### Changed
9+
- Split `BaseBox` into `BaseBox` and `BaseBoxList` for convenience
10+
411
## [4.0.3] - 4/17/20
512
### Fixed
613
- Brawler leaderboards for Python 3.5

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Contributing
7373
Special thanks to this project's contributors ❤️
7474

7575
- `4JR`_
76+
- `golbu`_
7677
- `kawaii banana`_
7778
- `kjkui`_
7879
- `Kyber`_
@@ -97,3 +98,4 @@ If you want to contribute, whether it be a bug fix or new feature, make sure to
9798
.. _Papiersnipper: https://github.com/robinmahieu
9899
.. _Pollen: https://github.com/pollen5
99100
.. _kawaii banana: https://github.com/bananaboy21
101+
.. _golbu: https://github.com/0dminnimda

brawlstats/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
from .models import *
33
from .errors import *
44

5-
65
############
76
# METADATA #
87
############
98

109

11-
__version__ = 'v4.0.3'
10+
__version__ = 'v4.0.4'
1211
__title__ = 'brawlstats'
1312
__license__ = 'MIT'
1413
__author__ = 'SharpBit'

brawlstats/core.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from cachetools import TTLCache
1010

1111
from .errors import Forbidden, NotFoundError, RateLimitError, ServerError, UnexpectedError
12-
from .models import BattleLog, Club, Constants, Members, Player, Ranking
12+
from .models import BattleLog, Brawlers, Club, Constants, Members, Player, Ranking
1313
from .utils import API, bstag, typecasted
1414

1515
log = logging.getLogger(__name__)
@@ -50,16 +50,16 @@ def __init__(self, token, session=None, timeout=30, is_async=False, **options):
5050
self.loop = options.get('loop', asyncio.get_event_loop()) if self.is_async else None
5151
self.connector = options.get('connector')
5252

53+
self.debug = options.get('debug', False)
54+
self.cache = TTLCache(3200 * 3, 60 * 3) # 3200 requests per minute
55+
5356
# Session and request options
5457
self.session = options.get('session') or (
5558
aiohttp.ClientSession(loop=self.loop, connector=self.connector) if self.is_async else requests.Session()
5659
)
5760
self.timeout = timeout
5861
self.prevent_ratelimit = options.get('prevent_ratelimit', False)
59-
self.api = API(options.get('base_url'), version=1)
60-
61-
self.debug = options.get('debug', False)
62-
self.cache = TTLCache(3200 * 3, 60 * 3) # 3200 requests per minute
62+
self.api = API(base_url=options.get('base_url'), version=1)
6363

6464
# Request/response headers
6565
self.headers = {
@@ -68,6 +68,17 @@ def __init__(self, token, session=None, timeout=30, is_async=False, **options):
6868
'Accept-Encoding': 'gzip'
6969
}
7070

71+
# Load brawlers for get_rankings
72+
if self.is_async:
73+
self.loop.create_task(self.__ainit__())
74+
else:
75+
brawlers_info = self.get_brawlers()
76+
self.api.set_brawlers(brawlers_info)
77+
78+
async def __ainit__(self):
79+
"""Task created to run `get_brawlers` asynchronously"""
80+
self.api.set_brawlers(await self.get_brawlers())
81+
7182
def __repr__(self):
7283
return '<Client async={} timeout={} debug={}>'.format(self.is_async, self.timeout, self.debug)
7384

@@ -272,19 +283,20 @@ def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=Non
272283
273284
Returns Ranking
274285
"""
275-
if region is None:
276-
region = 'global'
277-
278286
if brawler is not None:
279287
if isinstance(brawler, str):
280288
brawler = brawler.lower()
289+
281290
# Replace brawler name with ID
282-
if brawler in self.api.BRAWLERS.keys():
283-
brawler = self.api.BRAWLERS[brawler]
291+
if brawler in self.api.CURRENT_BRAWLERS.keys():
292+
brawler = self.api.CURRENT_BRAWLERS[brawler]
284293

285-
if brawler not in self.api.BRAWLERS.values():
294+
if brawler not in self.api.CURRENT_BRAWLERS.values():
286295
raise ValueError('Invalid brawler.')
287296

297+
if region is None:
298+
region = 'global'
299+
288300
# Check for invalid parameters
289301
if ranking not in ('players', 'clubs', 'brawlers'):
290302
raise ValueError("'ranking' must be 'players', 'clubs' or 'brawlers'.")
@@ -310,3 +322,13 @@ def get_constants(self, key=None):
310322
Returns Constants
311323
"""
312324
return self._get_model(self.api.CONSTANTS, model=Constants, key=key)
325+
326+
def get_brawlers(self):
327+
"""
328+
Get available brawlers and information about them.
329+
330+
No parameters
331+
332+
Returns Brawlers
333+
"""
334+
return self._get_model(self.api.BRAWLERS, model=Brawlers)

brawlstats/models.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from .utils import bstag
33

44

5-
__all__ = ['Player', 'Club', 'Members', 'Ranking', 'BattleLog', 'Constants']
5+
__all__ = ['Player', 'Club', 'Members', 'Ranking', 'BattleLog', 'Constants', 'Brawlers']
6+
67

78
class BaseBox:
89
def __init__(self, client, data):
@@ -11,14 +12,7 @@ def __init__(self, client, data):
1112

1213
def from_data(self, data):
1314
self.raw_data = data
14-
if isinstance(data, list):
15-
self._boxed_data = BoxList(
16-
data, camel_killer_box=True
17-
)
18-
else:
19-
self._boxed_data = Box(
20-
data, camel_killer_box=True
21-
)
15+
self._boxed_data = Box(data, camel_killer_box=True)
2216
return self
2317

2418
def __getattr__(self, attr):
@@ -37,6 +31,16 @@ def __getitem__(self, item):
3731
raise IndexError('No such index: {}'.format(item))
3832

3933

34+
class BaseBoxList(BaseBox):
35+
def from_data(self, data):
36+
self.raw_data = data
37+
self._boxed_data = BoxList(data, camel_killer_box=True)
38+
return self
39+
40+
def __len__(self):
41+
return sum(1 for i in self)
42+
43+
4044
class Player(BaseBox):
4145
"""
4246
Returns a full player object with all of its attributes.
@@ -85,37 +89,31 @@ def get_members(self):
8589
return self.client._get_model(url, model=Members)
8690

8791

88-
class Members(BaseBox):
92+
class Members(BaseBoxList):
8993
"""
9094
Returns the members in a club.
9195
"""
9296

9397
def __init__(self, client, data):
9498
super().__init__(client, data['items'])
9599

96-
def __len__(self):
97-
return sum(1 for i in self)
98-
99100
def __repr__(self):
100101
return '<Members object count={}>'.format(len(self))
101102

102103

103-
class Ranking(BaseBox):
104+
class Ranking(BaseBoxList):
104105
"""
105106
Returns a player or club ranking that contains a list of players or clubs.
106107
"""
107108

108109
def __init__(self, client, data):
109110
super().__init__(client, data['items'])
110111

111-
def __len__(self):
112-
return sum(1 for i in self)
113-
114112
def __repr__(self):
115113
return '<Ranking object count={}>'.format(len(self))
116114

117115

118-
class BattleLog(BaseBox):
116+
class BattleLog(BaseBoxList):
119117
"""
120118
Returns a full player battle object with all of its attributes.
121119
"""
@@ -129,3 +127,12 @@ class Constants(BaseBox):
129127
Returns some Brawl Stars constants.
130128
"""
131129
pass
130+
131+
132+
class Brawlers(BaseBoxList):
133+
"""
134+
Returns list of available brawlers and information about them.
135+
"""
136+
137+
def __init__(self, client, data):
138+
super().__init__(client, data['items'])

brawlstats/utils.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import inspect
2-
import json
2+
# import json
33
import os
44
import re
5-
import urllib.request
5+
# import urllib.request
66
from datetime import datetime
77
from functools import wraps
88

@@ -16,28 +16,18 @@ def __init__(self, base_url, version=1):
1616
self.CLUB = self.BASE + '/clubs'
1717
self.RANKINGS = self.BASE + '/rankings'
1818
self.CONSTANTS = 'https://fourjr.herokuapp.com/bs/constants'
19+
self.BRAWLERS = self.BASE + '/brawlers'
1920

2021
# Get package version from __init__.py
2122
path = os.path.dirname(__file__)
2223
with open(os.path.join(path, '__init__.py')) as f:
2324
self.VERSION = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1)
2425

25-
# Get current brawlers and their IDs
26-
try:
27-
resp = urllib.request.urlopen(self.CONSTANTS + '/characters').read()
28-
if isinstance(resp, bytes):
29-
resp = resp.decode('utf-8')
30-
data = json.loads(resp)
31-
except (TypeError, urllib.error.HTTPError, urllib.error.URLError):
32-
self.BRAWLERS = {}
33-
else:
34-
if data:
35-
self.BRAWLERS = {
36-
b['tID'].lower(): int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:])
37-
for b in data if b['tID']
38-
}
39-
else:
40-
self.BRAWLERS = {}
26+
self.CURRENT_BRAWLERS = {}
27+
28+
def set_brawlers(self, brawlers):
29+
self.CURRENT_BRAWLERS = {b['name'].lower(): int(b['id']) for b in brawlers}
30+
print(self.CURRENT_BRAWLERS)
4131

4232

4333
def bstag(tag):
@@ -55,6 +45,7 @@ def bstag(tag):
5545

5646
return tag
5747

48+
5849
def get_datetime(timestamp: str, unix=True):
5950
"""
6051
Converts a %Y%m%dT%H%M%S.%fZ to a UNIX timestamp
@@ -76,6 +67,12 @@ def get_datetime(timestamp: str, unix=True):
7667
else:
7768
return time
7869

70+
71+
def nothing(value):
72+
"""Function that returns the argument"""
73+
return value
74+
75+
7976
def typecasted(func):
8077
"""Decorator that converts arguments via annotations.
8178
Source: https://github.com/cgrok/clashroyale/blob/master/clashroyale/official_api/utils.py#L11"""
@@ -89,7 +86,7 @@ def wrapper(*args, **kwargs):
8986
for _, param in signature:
9087
converter = param.annotation
9188
if converter is inspect._empty:
92-
converter = lambda a: a # do nothing
89+
converter = nothing
9390
if param.kind is param.POSITIONAL_OR_KEYWORD:
9491
if args:
9592
to_conv = args.pop(0)

docs/api.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Data Models
2525
.. autoclass:: brawlstats.models.Constants
2626
:members:
2727

28+
.. autoclass:: brawlstats.models.Brawlers
29+
:members:
30+
2831

2932
Attributes of Data Models
3033
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -264,3 +267,16 @@ Attributes:
264267
]
265268
}
266269
}
270+
271+
Brawlers
272+
~~~~~~~~
273+
274+
Returns list of available brawlers and information about them with this structure:
275+
276+
Attributes:
277+
278+
::
279+
280+
[
281+
Brawler
282+
]

docs/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ Features
2424
- Get a player profile and battlelog.
2525
- Get a club and its members.
2626
- Get the top 200 rankings for players, clubs, or a specific brawler.
27-
- Get information about maps, brawlers, and more!
27+
- Get information about maps and more!
28+
- Get information about current available brawlers.
2829

2930
Installation
3031
~~~~~~~~~~~~

examples/async.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
import brawlstats
22
import asyncio
33

4+
# Do not post your token on a public github!
45
client = brawlstats.Client('token', is_async=True)
5-
# Do not post your token on a public github
6+
67

78
# await only works in an async loop
89
async def main():
910
player = await client.get_profile('GGJVJLU2')
1011
print(player.trophies) # access attributes using dot.notation
11-
print(player.solo_victories) # access using snake_case instead of camelCase
12+
print(player.solo_victories) # use snake_case instead of camelCase
1213

1314
club = await player.get_club()
1415
print(club.tag)
15-
members = await club.get_members()
16-
best_players = members[:5] # members sorted by trophies, gets best 5 players
16+
members = await club.get_members() # members sorted by trophies
17+
best_players = members[:5] # gets best 5 players
1718
for player in best_players:
1819
print(player.name, player.trophies)
1920

20-
ranking = await client.get_rankings(ranking='players', limit=5) # gets top 5 players
21+
# get top 5 players in the world
22+
ranking = await client.get_rankings(ranking='players', limit=5)
2123
for player in ranking:
2224
print(player.name, player.rank)
2325

24-
# Get top 5 mortis players in the US
26+
# get top 5 mortis players in the US
2527
ranking = await client.get_rankings(
2628
ranking='brawlers',
2729
region='us',

examples/discord_cog.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from discord.ext import commands
33
import brawlstats
44

5+
56
class BrawlStars(commands.Cog, name='Brawl Stars'):
67
"""A simple cog for Brawl Stars commands using discord.py"""
78

@@ -17,6 +18,7 @@ async def profile(self, ctx, tag: str):
1718
except brawlstats.RequestError as e: # catches all exceptions
1819
return await ctx.send('```\n{}: {}\n```'.format(e.code, e.message)) # sends code and error message
1920
em = discord.Embed(title='{0.name} ({0.tag})'.format(player))
21+
2022
em.description = 'Trophies: {}'.format(player.trophies) # you could make this better by using embed fields
2123
await ctx.send(embed=em)
2224

0 commit comments

Comments
 (0)