Skip to content

Commit

Permalink
Merge pull request #69 from SharpBit/development
Browse files Browse the repository at this point in the history
Version 4.0.4
  • Loading branch information
SharpBit committed Jul 22, 2020
2 parents e95c6ec + f32a84f commit 3135300
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 62 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Change Log
All notable changes to this project will be documented in this file.


## [4.0.4] - 7/22/20
### Added
- `get_brawlers` function to get available brawlers
### Changed
- Split `BaseBox` into `BaseBox` and `BaseBoxList` for convenience

## [4.0.3] - 4/17/20
### Fixed
- Brawler leaderboards for Python 3.5
Expand Down
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Contributing
Special thanks to this project's contributors ❤️

- `4JR`_
- `golbu`_
- `kawaii banana`_
- `kjkui`_
- `Kyber`_
Expand All @@ -97,3 +98,4 @@ If you want to contribute, whether it be a bug fix or new feature, make sure to
.. _Papiersnipper: https://github.com/robinmahieu
.. _Pollen: https://github.com/pollen5
.. _kawaii banana: https://github.com/bananaboy21
.. _golbu: https://github.com/0dminnimda
3 changes: 1 addition & 2 deletions brawlstats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
from .models import *
from .errors import *


############
# METADATA #
############


__version__ = 'v4.0.3'
__version__ = 'v4.0.4'
__title__ = 'brawlstats'
__license__ = 'MIT'
__author__ = 'SharpBit'
Expand Down
44 changes: 33 additions & 11 deletions brawlstats/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from cachetools import TTLCache

from .errors import Forbidden, NotFoundError, RateLimitError, ServerError, UnexpectedError
from .models import BattleLog, Club, Constants, Members, Player, Ranking
from .models import BattleLog, Brawlers, Club, Constants, Members, Player, Ranking
from .utils import API, bstag, typecasted

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

self.debug = options.get('debug', False)
self.cache = TTLCache(3200 * 3, 60 * 3) # 3200 requests per minute

# Session and request options
self.session = options.get('session') or (
aiohttp.ClientSession(loop=self.loop, connector=self.connector) if self.is_async else requests.Session()
)
self.timeout = timeout
self.prevent_ratelimit = options.get('prevent_ratelimit', False)
self.api = API(options.get('base_url'), version=1)

self.debug = options.get('debug', False)
self.cache = TTLCache(3200 * 3, 60 * 3) # 3200 requests per minute
self.api = API(base_url=options.get('base_url'), version=1)

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

# Load brawlers for get_rankings
if self.is_async:
self.loop.create_task(self.__ainit__())
else:
brawlers_info = self.get_brawlers()
self.api.set_brawlers(brawlers_info)

async def __ainit__(self):
"""Task created to run `get_brawlers` asynchronously"""
self.api.set_brawlers(await self.get_brawlers())

def __repr__(self):
return '<Client async={} timeout={} debug={}>'.format(self.is_async, self.timeout, self.debug)

Expand Down Expand Up @@ -272,19 +283,20 @@ def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=Non
Returns Ranking
"""
if region is None:
region = 'global'

if brawler is not None:
if isinstance(brawler, str):
brawler = brawler.lower()

# Replace brawler name with ID
if brawler in self.api.BRAWLERS.keys():
brawler = self.api.BRAWLERS[brawler]
if brawler in self.api.CURRENT_BRAWLERS.keys():
brawler = self.api.CURRENT_BRAWLERS[brawler]

if brawler not in self.api.BRAWLERS.values():
if brawler not in self.api.CURRENT_BRAWLERS.values():
raise ValueError('Invalid brawler.')

if region is None:
region = 'global'

# Check for invalid parameters
if ranking not in ('players', 'clubs', 'brawlers'):
raise ValueError("'ranking' must be 'players', 'clubs' or 'brawlers'.")
Expand All @@ -310,3 +322,13 @@ def get_constants(self, key=None):
Returns Constants
"""
return self._get_model(self.api.CONSTANTS, model=Constants, key=key)

def get_brawlers(self):
"""
Get available brawlers and information about them.
No parameters
Returns Brawlers
"""
return self._get_model(self.api.BRAWLERS, model=Brawlers)
43 changes: 25 additions & 18 deletions brawlstats/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from .utils import bstag


__all__ = ['Player', 'Club', 'Members', 'Ranking', 'BattleLog', 'Constants']
__all__ = ['Player', 'Club', 'Members', 'Ranking', 'BattleLog', 'Constants', 'Brawlers']


class BaseBox:
def __init__(self, client, data):
Expand All @@ -11,14 +12,7 @@ def __init__(self, client, data):

def from_data(self, data):
self.raw_data = data
if isinstance(data, list):
self._boxed_data = BoxList(
data, camel_killer_box=True
)
else:
self._boxed_data = Box(
data, camel_killer_box=True
)
self._boxed_data = Box(data, camel_killer_box=True)
return self

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


class BaseBoxList(BaseBox):
def from_data(self, data):
self.raw_data = data
self._boxed_data = BoxList(data, camel_killer_box=True)
return self

def __len__(self):
return sum(1 for i in self)


class Player(BaseBox):
"""
Returns a full player object with all of its attributes.
Expand Down Expand Up @@ -85,37 +89,31 @@ def get_members(self):
return self.client._get_model(url, model=Members)


class Members(BaseBox):
class Members(BaseBoxList):
"""
Returns the members in a club.
"""

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

def __len__(self):
return sum(1 for i in self)

def __repr__(self):
return '<Members object count={}>'.format(len(self))


class Ranking(BaseBox):
class Ranking(BaseBoxList):
"""
Returns a player or club ranking that contains a list of players or clubs.
"""

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

def __len__(self):
return sum(1 for i in self)

def __repr__(self):
return '<Ranking object count={}>'.format(len(self))


class BattleLog(BaseBox):
class BattleLog(BaseBoxList):
"""
Returns a full player battle object with all of its attributes.
"""
Expand All @@ -129,3 +127,12 @@ class Constants(BaseBox):
Returns some Brawl Stars constants.
"""
pass


class Brawlers(BaseBoxList):
"""
Returns list of available brawlers and information about them.
"""

def __init__(self, client, data):
super().__init__(client, data['items'])
35 changes: 16 additions & 19 deletions brawlstats/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import inspect
import json
# import json
import os
import re
import urllib.request
# import urllib.request
from datetime import datetime
from functools import wraps

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

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

# Get current brawlers and their IDs
try:
resp = urllib.request.urlopen(self.CONSTANTS + '/characters').read()
if isinstance(resp, bytes):
resp = resp.decode('utf-8')
data = json.loads(resp)
except (TypeError, urllib.error.HTTPError, urllib.error.URLError):
self.BRAWLERS = {}
else:
if data:
self.BRAWLERS = {
b['tID'].lower(): int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:])
for b in data if b['tID']
}
else:
self.BRAWLERS = {}
self.CURRENT_BRAWLERS = {}

def set_brawlers(self, brawlers):
self.CURRENT_BRAWLERS = {b['name'].lower(): int(b['id']) for b in brawlers}
print(self.CURRENT_BRAWLERS)


def bstag(tag):
Expand All @@ -55,6 +45,7 @@ def bstag(tag):

return tag


def get_datetime(timestamp: str, unix=True):
"""
Converts a %Y%m%dT%H%M%S.%fZ to a UNIX timestamp
Expand All @@ -76,6 +67,12 @@ def get_datetime(timestamp: str, unix=True):
else:
return time


def nothing(value):
"""Function that returns the argument"""
return value


def typecasted(func):
"""Decorator that converts arguments via annotations.
Source: https://github.com/cgrok/clashroyale/blob/master/clashroyale/official_api/utils.py#L11"""
Expand All @@ -89,7 +86,7 @@ def wrapper(*args, **kwargs):
for _, param in signature:
converter = param.annotation
if converter is inspect._empty:
converter = lambda a: a # do nothing
converter = nothing
if param.kind is param.POSITIONAL_OR_KEYWORD:
if args:
to_conv = args.pop(0)
Expand Down
16 changes: 16 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Data Models
.. autoclass:: brawlstats.models.Constants
:members:

.. autoclass:: brawlstats.models.Brawlers
:members:


Attributes of Data Models
~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -264,3 +267,16 @@ Attributes:
]
}
}

Brawlers
~~~~~~~~

Returns list of available brawlers and information about them with this structure:

Attributes:

::

[
Brawler
]
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Features
- Get a player profile and battlelog.
- Get a club and its members.
- Get the top 200 rankings for players, clubs, or a specific brawler.
- Get information about maps, brawlers, and more!
- Get information about maps and more!
- Get information about current available brawlers.

Installation
~~~~~~~~~~~~
Expand Down
14 changes: 8 additions & 6 deletions examples/async.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import brawlstats
import asyncio

# Do not post your token on a public github!
client = brawlstats.Client('token', is_async=True)
# Do not post your token on a public github


# await only works in an async loop
async def main():
player = await client.get_profile('GGJVJLU2')
print(player.trophies) # access attributes using dot.notation
print(player.solo_victories) # access using snake_case instead of camelCase
print(player.solo_victories) # use snake_case instead of camelCase

club = await player.get_club()
print(club.tag)
members = await club.get_members()
best_players = members[:5] # members sorted by trophies, gets best 5 players
members = await club.get_members() # members sorted by trophies
best_players = members[:5] # gets best 5 players
for player in best_players:
print(player.name, player.trophies)

ranking = await client.get_rankings(ranking='players', limit=5) # gets top 5 players
# get top 5 players in the world
ranking = await client.get_rankings(ranking='players', limit=5)
for player in ranking:
print(player.name, player.rank)

# Get top 5 mortis players in the US
# get top 5 mortis players in the US
ranking = await client.get_rankings(
ranking='brawlers',
region='us',
Expand Down
2 changes: 2 additions & 0 deletions examples/discord_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from discord.ext import commands
import brawlstats


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

Expand All @@ -17,6 +18,7 @@ async def profile(self, ctx, tag: str):
except brawlstats.RequestError as e: # catches all exceptions
return await ctx.send('```\n{}: {}\n```'.format(e.code, e.message)) # sends code and error message
em = discord.Embed(title='{0.name} ({0.tag})'.format(player))

em.description = 'Trophies: {}'.format(player.trophies) # you could make this better by using embed fields
await ctx.send(embed=em)

Expand Down
Loading

0 comments on commit 3135300

Please sign in to comment.