From 17e7066d69514be9c85c8121e10a95e6b53c72a5 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 05:33:55 +0300 Subject: [PATCH 01/14] many interesting things (see description) major changes: 1) all py files were refactored in pep8 style 2) splitting BaseBox into BaseBox and BaseBoxList for convenience (with the corresponding estates of both the models themselves and the models inheriting these models) 3) added: - get_brawlers function - model Brawlers - BRAWLERS_URL in API - nothing function to replace lambda expressions in typecasted 4) changed async.py and sync.py (now they are completely identical, in terms of meaning) --- .gitignore | 2 ++ brawlstats/core.py | 70 +++++++++++++++++++++++++++++++---------- brawlstats/errors.py | 6 ++-- brawlstats/models.py | 68 +++++++++++++++++++++------------------ brawlstats/utils.py | 32 ++++++++++++++----- docs/conf.py | 5 ++- examples/async.py | 14 +++++---- examples/discord_cog.py | 8 +++-- examples/sync.py | 13 +++++--- setup.py | 5 ++- tests/test_async.py | 21 ++++++++----- tests/test_blocking.py | 43 ++++++++++++++++--------- 12 files changed, 194 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index 4ebe54c..80870d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +some.py + # VSCode settings .vscode/ diff --git a/brawlstats/core.py b/brawlstats/core.py index a54f9ec..e242290 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -8,8 +8,14 @@ import requests from cachetools import TTLCache -from .errors import Forbidden, NotFoundError, RateLimitError, ServerError, UnexpectedError -from .models import BattleLog, Club, Constants, Members, Player, Ranking +from .errors import ( + Forbidden, NotFoundError, RateLimitError, + ServerError, UnexpectedError +) +from .models import ( + BattleLog, Brawlers, Club, + Constants, Members, Player, Ranking +) from .utils import API, bstag, typecasted log = logging.getLogger(__name__) @@ -30,7 +36,7 @@ class Client: session: Optional[Union[requests.Session, aiohttp.ClientSession]] = None Use a current session or a make new one. loop: Optional[asyncio.window_events._WindowsSelectorEventLoop] - The event loop to use for asynchronous operations. Defaults to ``None``, + The event loop to use for asynchronous operations. Defaults = ``None``, in which case the default event loop is ``asyncio.get_event_loop()``. connector: Optional[aiohttp.TCPConnector] Pass a TCPConnector into the client (aiohttp). Defaults to ``None``. @@ -44,15 +50,20 @@ class Client: REQUEST_LOG = '{method} {url} recieved {text} has returned {status}' - def __init__(self, token, session=None, timeout=30, is_async=False, **options): + def __init__( + self, token, session=None, timeout=30, is_async=False, **options + ): # Async options self.is_async = is_async - self.loop = options.get('loop', asyncio.get_event_loop()) if self.is_async else None + self.loop = ( + options.get('loop', asyncio.get_event_loop()) + if self.is_async else None) self.connector = options.get('connector') # 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() + 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) @@ -64,12 +75,16 @@ def __init__(self, token, session=None, timeout=30, is_async=False, **options): # Request/response headers self.headers = { 'Authorization': 'Bearer {}'.format(token), - 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format(self.api.VERSION, sys.version_info), + 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format( + self.api.VERSION, sys.version_info + ), 'Accept-Encoding': 'gzip' } def __repr__(self): - return ''.format(self.is_async, self.timeout, self.debug) + return ''.format( + self.is_async, self.timeout, self.debug + ) def close(self): return self.session.close() @@ -87,7 +102,9 @@ def _raise_for_status(self, resp, text): url = resp.url if self.debug: - log.debug(self.REQUEST_LOG.format(method='GET', url=url, text=text, status=code)) + log.debug(self.REQUEST_LOG.format( + method='GET', url=url, text=text, status=code + )) if 300 > code >= 200: return data @@ -119,7 +136,9 @@ async def _arequest(self, url): return cache try: - async with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: + async with self.session.get( + url, timeout=self.timeout, headers=self.headers + ) as resp: data = self._raise_for_status(resp, await resp.text()) except asyncio.TimeoutError: raise ServerError(503, url) @@ -140,7 +159,9 @@ def _request(self, url): return cache try: - with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: + with self.session.get( + url, timeout=self.timeout, headers=self.headers + ) as resp: data = self._raise_for_status(resp, resp.text) except requests.Timeout: raise ServerError(503, url) @@ -151,7 +172,8 @@ def _request(self, url): return data async def _aget_model(self, url, model, key=None): - """Method to turn the response data into a Model class for the async client.""" + """Method to turn the response data into a + Model class for the async client.""" if self.prevent_ratelimit: # Use asyncio.Lock() if prevent_ratelimit=True async with asyncio.Lock(): @@ -170,7 +192,8 @@ async def _aget_model(self, url, model, key=None): return model(self, data) def _get_model(self, url, model, key=None): - """Method to turn the response data into a Model class for the sync client.""" + """Method to turn the response data into a + Model class for the sync client.""" if self.is_async: # Calls the async function return self._aget_model(url, model=model, key=key) @@ -253,7 +276,9 @@ def get_club_members(self, tag: bstag): url = '{}/{}/members'.format(self.api.CLUB, tag) return self._get_model(url, model=Members) - def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=None): + def get_rankings( + self, *, ranking: str, region=None, limit: int = 200, brawler=None + ): """ Get the top count players/clubs/brawlers. @@ -287,14 +312,15 @@ def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=Non # Check for invalid parameters if ranking not in ('players', 'clubs', 'brawlers'): - raise ValueError("'ranking' must be 'players', 'clubs' or 'brawlers'.") + raise ValueError( + "'ranking' must be 'players', 'clubs' or 'brawlers'.") if not 0 < limit <= 200: raise ValueError('Make sure limit is between 1 and 200.') # Construct URL - url = '{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, limit) + url = f'{self.api.RANKINGS}/{region}/{ranking}?{limit=}' if ranking == 'brawlers': - url = '{}/{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, brawler, limit) + url = f'{self.api.RANKINGS}/{region}/{ranking}/{brawler}?{limit=}' return self._get_model(url, model=Ranking) @@ -310,3 +336,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. + + No parameters + + Returns Brawlers + """ + return self._get_model(self.api.BRAWLERS_URL, model=Brawlers) diff --git a/brawlstats/errors.py b/brawlstats/errors.py index 932fea6..481ec31 100644 --- a/brawlstats/errors.py +++ b/brawlstats/errors.py @@ -29,7 +29,8 @@ def __init__(self, code, **kwargs): if self.reason: self.message += '\nReason: {}'.format(self.reason) elif self.invalid_chars: - self.message += 'Invalid characters: {}'.format(', '.join(self.invalid_chars)) + self.message += 'Invalid characters: {}'.format( + ', '.join(self.invalid_chars)) super().__init__(self.code, self.message) @@ -58,5 +59,6 @@ class ServerError(RequestError): def __init__(self, code, url): self.code = code self.url = url - self.message = 'The API is down. Please be patient and try again later.' + self.message = 'The API is down. Please be patient '\ + 'and try again later.' super().__init__(self.code, self.message) diff --git a/brawlstats/models.py b/brawlstats/models.py index df7f0be..1eb850b 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -2,7 +2,11 @@ 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): @@ -11,14 +15,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): @@ -28,7 +25,9 @@ def __getattr__(self, attr): try: return super().__getattr__(attr) except AttributeError: - return None # users can use an if statement rather than try/except to find a missing attribute + return None + # users can use an if statement rather than + # try/except to find a missing attribute def __getitem__(self, item): try: @@ -37,6 +36,17 @@ def __getitem__(self, item): raise IndexError('No such index: {}'.format(item)) +class BaseBoxList(BaseBox): + def from_data(self, data): + data = data['items'] + 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. @@ -85,43 +95,29 @@ 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 ''.format(len(self)) + return f'' -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 ''.format(len(self)) + return f'' -class BattleLog(BaseBox): +class BattleLog(BaseBoxList): """ Returns a full player battle object with all of its attributes. """ - - def __init__(self, client, data): - super().__init__(client, data['items']) + pass class Constants(BaseBox): @@ -129,3 +125,15 @@ class Constants(BaseBox): Returns some Brawl Stars constants. """ pass + + +class Brawlers(BaseBoxList): + """ + Returns list of available brawlers and information about every brawler. + """ + + def __repr__(self): + return f'' + + def __str__(self): + return f'Here {self.__len__()} brawlers' diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 16824ea..fce1bec 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -11,20 +11,26 @@ class API: def __init__(self, base_url, version=1): - self.BASE = base_url or 'https://api.brawlstars.com/v{}'.format(version) + self.BASE = base_url or f'https://api.brawlstars.com/v{version}' self.PROFILE = self.BASE + '/players' self.CLUB = self.BASE + '/clubs' self.RANKINGS = self.BASE + '/rankings' self.CONSTANTS = 'https://fourjr.herokuapp.com/bs/constants' + self.BRAWLERS_URL = 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) + 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() + resp = urllib.request.urlopen( + self.CONSTANTS + '/characters' + ).read() if isinstance(resp, bytes): resp = resp.decode('utf-8') data = json.loads(resp) @@ -33,7 +39,8 @@ def __init__(self, base_url, version=1): else: if data: self.BRAWLERS = { - b['tID'].lower(): int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) + b['tID'].lower(): + int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) for b in data if b['tID'] } else: @@ -55,6 +62,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 @@ -63,8 +71,10 @@ def get_datetime(timestamp: str, unix=True): Parameters ---------- timestamp: str - A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, usually returned by the API - in the ``created_time`` field for example (eg. 2018-07-18T14:59:06.000Z) + A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, + usually returned by the API + in the ``created_time`` field + for example (eg. 2018-07-18T14:59:06.000Z) unix: Optional[bool] = True Whether to return a POSIX timestamp (seconds since epoch) or not @@ -76,8 +86,14 @@ def get_datetime(timestamp: str, unix=True): else: return time + +# do nothing +def nothing(value): + return value + + def typecasted(func): - """Decorator that converts arguments via annotations. + f"""Decorator that converts arguments via annotations. Source: https://github.com/cgrok/clashroyale/blob/master/clashroyale/official_api/utils.py#L11""" signature = inspect.signature(func).parameters.items() @@ -89,7 +105,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) diff --git a/docs/conf.py b/docs/conf.py index 0d149b5..a605d56 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,10 @@ # sys.path.insert(0, os.path.abspath('.')) with open('../brawlstats/__init__.py') as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + f.read(), re.MULTILINE + ).group(1) # -- Project information ----------------------------------------------------- diff --git a/examples/async.py b/examples/async.py index ee39f18..4db1a96 100644 --- a/examples/async.py +++ b/examples/async.py @@ -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', diff --git a/examples/discord_cog.py b/examples/discord_cog.py index 2dedb9f..8952883 100644 --- a/examples/discord_cog.py +++ b/examples/discord_cog.py @@ -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""" @@ -15,9 +16,12 @@ async def profile(self, ctx, tag: str): try: player = await self.client.get_profile(tag) except brawlstats.RequestError as e: # catches all exceptions - return await ctx.send('```\n{}: {}\n```'.format(e.code, e.message)) # sends code and error message + # sends code and error message + return await ctx.send('```\n{}: {}\n```'.format(e.code, e.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 + + # you could make this better by using embed fields + em.description = 'Trophies: {}'.format(player.trophies) await ctx.send(embed=em) diff --git a/examples/sync.py b/examples/sync.py index ac37ceb..4862db0 100644 --- a/examples/sync.py +++ b/examples/sync.py @@ -1,23 +1,26 @@ import brawlstats +# Do not post your token on a public github! client = brawlstats.Client('token') -# Do not post your token on a public github + player = 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 = player.get_club() print(club.tag) -best_players = club.get_members()[:5] # members sorted by trophies, gets best 5 players +members = 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) # prints name and trophies -ranking = client.get_rankings(ranking='players', limit=5) # gets top 5 players +# gets top 5 players in the world +ranking = 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 = client.get_rankings( ranking='brawlers', region='us', diff --git a/setup.py b/setup.py index f7b62c8..61059a3 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,10 @@ long_description = f.read() with open('brawlstats/__init__.py') as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + f.read(), re.MULTILINE + ).group(1) requirements = [] with open('requirements.txt') as f: diff --git a/tests/test_async.py b/tests/test_async.py index a6569b0..542371e 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -71,27 +71,32 @@ async def test_get_club_members(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - await self.assertAsyncRaises(brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) + await self.assertAsyncRaises( + brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) async def test_get_rankings(self): player_ranking = await self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = await self.client.get_rankings(ranking='players', region='US', limit=1) + us_player_ranking = await self.client.get_rankings( + ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) club_ranking = await self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = await self.client.get_rankings(ranking='clubs', region='US', limit=1) + us_club_ranking = await self.client.get_rankings( + ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler='Shelly') + brawler_ranking = await self.client.get_rankings( + ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = await self.client.get_rankings( + ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) @@ -102,7 +107,8 @@ async def test_get_rankings(self): await self.client.get_rankings(ranking='people', limit=0) with self.assertRaises(ValueError): - await self.client.get_rankings(ranking='brawlers', brawler='SharpBit') + await self.client.get_rankings( + ranking='brawlers', brawler='SharpBit') async def test_get_constants(self): constants = await self.client.get_constants() @@ -111,7 +117,8 @@ async def test_get_constants(self): maps = await self.client.get_constants('maps') self.assertIsInstance(maps, brawlstats.Constants) - await self.assertAsyncRaises(KeyError, self.client.get_constants('invalid')) + await self.assertAsyncRaises( + KeyError, self.client.get_constants('invalid')) async def asyncTearDown(self): await self.client.close() diff --git a/tests/test_blocking.py b/tests/test_blocking.py index 70b4fdf..f6cf2b6 100644 --- a/tests/test_blocking.py +++ b/tests/test_blocking.py @@ -28,9 +28,12 @@ def test_get_player(self): self.assertIsInstance(club, brawlstats.Club) self.assertEqual(club.tag, self.CLUB_TAG) - self.assertRaises(brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') - self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'P') - self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'AAA') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_player, 'P') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_player, 'AAA') def test_get_battle_logs(self): battle_logs = self.client.get_battle_logs(self.PLAYER_TAG) @@ -45,40 +48,52 @@ def test_get_club(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises(brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') - self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'P') - self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'AAA') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club, 'P') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club, 'AAA') def test_get_club_members(self): club_members = self.client.get_club_members(self.CLUB_TAG) self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises(brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') def test_get_rankings(self): player_ranking = self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = self.client.get_rankings(ranking='players', region='US', limit=1) + us_player_ranking = self.client.get_rankings( + ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) - self.assertRaises(ValueError, self.client.get_rankings, ranking='people') - self.assertRaises(ValueError, self.client.get_rankings, ranking='people', limit=0) - self.assertRaises(ValueError, self.client.get_rankings, ranking='brawlers', brawler='SharpBit') + self.assertRaises( + ValueError, self.client.get_rankings, ranking='people') + self.assertRaises( + ValueError, self.client.get_rankings, ranking='people', limit=0) + self.assertRaises( + ValueError, self.client.get_rankings, + ranking='brawlers', brawler='SharpBit') club_ranking = self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = self.client.get_rankings(ranking='clubs', region='US', limit=1) + us_club_ranking = self.client.get_rankings( + ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler='Shelly') + brawler_ranking = self.client.get_rankings( + ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = self.client.get_rankings( + ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) From 84a235dc310ccd1085aafc5381aa86dedac78946 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 05:53:09 +0300 Subject: [PATCH 02/14] Revert "many interesting things (see description)" This reverts commit 17e7066d69514be9c85c8121e10a95e6b53c72a5. --- .gitignore | 2 -- brawlstats/core.py | 70 ++++++++++------------------------------- brawlstats/errors.py | 6 ++-- brawlstats/models.py | 68 ++++++++++++++++++--------------------- brawlstats/utils.py | 32 +++++-------------- docs/conf.py | 5 +-- examples/async.py | 14 ++++----- examples/discord_cog.py | 8 ++--- examples/sync.py | 13 +++----- setup.py | 5 +-- tests/test_async.py | 21 +++++-------- tests/test_blocking.py | 43 +++++++++---------------- 12 files changed, 93 insertions(+), 194 deletions(-) diff --git a/.gitignore b/.gitignore index 80870d1..4ebe54c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -some.py - # VSCode settings .vscode/ diff --git a/brawlstats/core.py b/brawlstats/core.py index e242290..a54f9ec 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -8,14 +8,8 @@ import requests from cachetools import TTLCache -from .errors import ( - Forbidden, NotFoundError, RateLimitError, - ServerError, UnexpectedError -) -from .models import ( - BattleLog, Brawlers, Club, - Constants, Members, Player, Ranking -) +from .errors import Forbidden, NotFoundError, RateLimitError, ServerError, UnexpectedError +from .models import BattleLog, Club, Constants, Members, Player, Ranking from .utils import API, bstag, typecasted log = logging.getLogger(__name__) @@ -36,7 +30,7 @@ class Client: session: Optional[Union[requests.Session, aiohttp.ClientSession]] = None Use a current session or a make new one. loop: Optional[asyncio.window_events._WindowsSelectorEventLoop] - The event loop to use for asynchronous operations. Defaults = ``None``, + The event loop to use for asynchronous operations. Defaults to ``None``, in which case the default event loop is ``asyncio.get_event_loop()``. connector: Optional[aiohttp.TCPConnector] Pass a TCPConnector into the client (aiohttp). Defaults to ``None``. @@ -50,20 +44,15 @@ class Client: REQUEST_LOG = '{method} {url} recieved {text} has returned {status}' - def __init__( - self, token, session=None, timeout=30, is_async=False, **options - ): + def __init__(self, token, session=None, timeout=30, is_async=False, **options): # Async options self.is_async = is_async - self.loop = ( - options.get('loop', asyncio.get_event_loop()) - if self.is_async else None) + self.loop = options.get('loop', asyncio.get_event_loop()) if self.is_async else None self.connector = options.get('connector') # 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() + 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) @@ -75,16 +64,12 @@ def __init__( # Request/response headers self.headers = { 'Authorization': 'Bearer {}'.format(token), - 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format( - self.api.VERSION, sys.version_info - ), + 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format(self.api.VERSION, sys.version_info), 'Accept-Encoding': 'gzip' } def __repr__(self): - return ''.format( - self.is_async, self.timeout, self.debug - ) + return ''.format(self.is_async, self.timeout, self.debug) def close(self): return self.session.close() @@ -102,9 +87,7 @@ def _raise_for_status(self, resp, text): url = resp.url if self.debug: - log.debug(self.REQUEST_LOG.format( - method='GET', url=url, text=text, status=code - )) + log.debug(self.REQUEST_LOG.format(method='GET', url=url, text=text, status=code)) if 300 > code >= 200: return data @@ -136,9 +119,7 @@ async def _arequest(self, url): return cache try: - async with self.session.get( - url, timeout=self.timeout, headers=self.headers - ) as resp: + async with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: data = self._raise_for_status(resp, await resp.text()) except asyncio.TimeoutError: raise ServerError(503, url) @@ -159,9 +140,7 @@ def _request(self, url): return cache try: - with self.session.get( - url, timeout=self.timeout, headers=self.headers - ) as resp: + with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: data = self._raise_for_status(resp, resp.text) except requests.Timeout: raise ServerError(503, url) @@ -172,8 +151,7 @@ def _request(self, url): return data async def _aget_model(self, url, model, key=None): - """Method to turn the response data into a - Model class for the async client.""" + """Method to turn the response data into a Model class for the async client.""" if self.prevent_ratelimit: # Use asyncio.Lock() if prevent_ratelimit=True async with asyncio.Lock(): @@ -192,8 +170,7 @@ async def _aget_model(self, url, model, key=None): return model(self, data) def _get_model(self, url, model, key=None): - """Method to turn the response data into a - Model class for the sync client.""" + """Method to turn the response data into a Model class for the sync client.""" if self.is_async: # Calls the async function return self._aget_model(url, model=model, key=key) @@ -276,9 +253,7 @@ def get_club_members(self, tag: bstag): url = '{}/{}/members'.format(self.api.CLUB, tag) return self._get_model(url, model=Members) - def get_rankings( - self, *, ranking: str, region=None, limit: int = 200, brawler=None - ): + def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=None): """ Get the top count players/clubs/brawlers. @@ -312,15 +287,14 @@ def get_rankings( # Check for invalid parameters if ranking not in ('players', 'clubs', 'brawlers'): - raise ValueError( - "'ranking' must be 'players', 'clubs' or 'brawlers'.") + raise ValueError("'ranking' must be 'players', 'clubs' or 'brawlers'.") if not 0 < limit <= 200: raise ValueError('Make sure limit is between 1 and 200.') # Construct URL - url = f'{self.api.RANKINGS}/{region}/{ranking}?{limit=}' + url = '{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, limit) if ranking == 'brawlers': - url = f'{self.api.RANKINGS}/{region}/{ranking}/{brawler}?{limit=}' + url = '{}/{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, brawler, limit) return self._get_model(url, model=Ranking) @@ -336,13 +310,3 @@ 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. - - No parameters - - Returns Brawlers - """ - return self._get_model(self.api.BRAWLERS_URL, model=Brawlers) diff --git a/brawlstats/errors.py b/brawlstats/errors.py index 481ec31..932fea6 100644 --- a/brawlstats/errors.py +++ b/brawlstats/errors.py @@ -29,8 +29,7 @@ def __init__(self, code, **kwargs): if self.reason: self.message += '\nReason: {}'.format(self.reason) elif self.invalid_chars: - self.message += 'Invalid characters: {}'.format( - ', '.join(self.invalid_chars)) + self.message += 'Invalid characters: {}'.format(', '.join(self.invalid_chars)) super().__init__(self.code, self.message) @@ -59,6 +58,5 @@ class ServerError(RequestError): def __init__(self, code, url): self.code = code self.url = url - self.message = 'The API is down. Please be patient '\ - 'and try again later.' + self.message = 'The API is down. Please be patient and try again later.' super().__init__(self.code, self.message) diff --git a/brawlstats/models.py b/brawlstats/models.py index 1eb850b..df7f0be 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -2,11 +2,7 @@ from .utils import bstag -__all__ = [ - 'Player', 'Club', 'Members', 'Ranking', - 'BattleLog', 'Constants', 'Brawlers' -] - +__all__ = ['Player', 'Club', 'Members', 'Ranking', 'BattleLog', 'Constants'] class BaseBox: def __init__(self, client, data): @@ -15,7 +11,14 @@ def __init__(self, client, data): def from_data(self, data): self.raw_data = data - self._boxed_data = Box(data, camel_killer_box=True) + if isinstance(data, list): + self._boxed_data = BoxList( + data, camel_killer_box=True + ) + else: + self._boxed_data = Box( + data, camel_killer_box=True + ) return self def __getattr__(self, attr): @@ -25,9 +28,7 @@ def __getattr__(self, attr): try: return super().__getattr__(attr) except AttributeError: - return None - # users can use an if statement rather than - # try/except to find a missing attribute + return None # users can use an if statement rather than try/except to find a missing attribute def __getitem__(self, item): try: @@ -36,17 +37,6 @@ def __getitem__(self, item): raise IndexError('No such index: {}'.format(item)) -class BaseBoxList(BaseBox): - def from_data(self, data): - data = data['items'] - 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. @@ -95,29 +85,43 @@ def get_members(self): return self.client._get_model(url, model=Members) -class Members(BaseBoxList): +class Members(BaseBox): """ 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 f'' + return ''.format(len(self)) -class Ranking(BaseBoxList): +class Ranking(BaseBox): """ 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 f'' + return ''.format(len(self)) -class BattleLog(BaseBoxList): +class BattleLog(BaseBox): """ Returns a full player battle object with all of its attributes. """ - pass + + def __init__(self, client, data): + super().__init__(client, data['items']) class Constants(BaseBox): @@ -125,15 +129,3 @@ class Constants(BaseBox): Returns some Brawl Stars constants. """ pass - - -class Brawlers(BaseBoxList): - """ - Returns list of available brawlers and information about every brawler. - """ - - def __repr__(self): - return f'' - - def __str__(self): - return f'Here {self.__len__()} brawlers' diff --git a/brawlstats/utils.py b/brawlstats/utils.py index fce1bec..16824ea 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -11,26 +11,20 @@ class API: def __init__(self, base_url, version=1): - self.BASE = base_url or f'https://api.brawlstars.com/v{version}' + self.BASE = base_url or 'https://api.brawlstars.com/v{}'.format(version) self.PROFILE = self.BASE + '/players' self.CLUB = self.BASE + '/clubs' self.RANKINGS = self.BASE + '/rankings' self.CONSTANTS = 'https://fourjr.herokuapp.com/bs/constants' - self.BRAWLERS_URL = 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) + 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() + resp = urllib.request.urlopen(self.CONSTANTS + '/characters').read() if isinstance(resp, bytes): resp = resp.decode('utf-8') data = json.loads(resp) @@ -39,8 +33,7 @@ def __init__(self, base_url, version=1): else: if data: self.BRAWLERS = { - b['tID'].lower(): - int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) + b['tID'].lower(): int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) for b in data if b['tID'] } else: @@ -62,7 +55,6 @@ 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 @@ -71,10 +63,8 @@ def get_datetime(timestamp: str, unix=True): Parameters ---------- timestamp: str - A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, - usually returned by the API - in the ``created_time`` field - for example (eg. 2018-07-18T14:59:06.000Z) + A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, usually returned by the API + in the ``created_time`` field for example (eg. 2018-07-18T14:59:06.000Z) unix: Optional[bool] = True Whether to return a POSIX timestamp (seconds since epoch) or not @@ -86,14 +76,8 @@ def get_datetime(timestamp: str, unix=True): else: return time - -# do nothing -def nothing(value): - return value - - def typecasted(func): - f"""Decorator that converts arguments via annotations. + """Decorator that converts arguments via annotations. Source: https://github.com/cgrok/clashroyale/blob/master/clashroyale/official_api/utils.py#L11""" signature = inspect.signature(func).parameters.items() @@ -105,7 +89,7 @@ def wrapper(*args, **kwargs): for _, param in signature: converter = param.annotation if converter is inspect._empty: - converter = nothing + converter = lambda a: a # do nothing if param.kind is param.POSITIONAL_OR_KEYWORD: if args: to_conv = args.pop(0) diff --git a/docs/conf.py b/docs/conf.py index a605d56..0d149b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,10 +19,7 @@ # sys.path.insert(0, os.path.abspath('.')) with open('../brawlstats/__init__.py') as f: - version = re.search( - r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - f.read(), re.MULTILINE - ).group(1) + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) # -- Project information ----------------------------------------------------- diff --git a/examples/async.py b/examples/async.py index 4db1a96..ee39f18 100644 --- a/examples/async.py +++ b/examples/async.py @@ -1,29 +1,27 @@ 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) # use snake_case instead of camelCase + print(player.solo_victories) # access using snake_case instead of camelCase club = await player.get_club() print(club.tag) - members = await club.get_members() # members sorted by trophies - best_players = members[:5] # gets best 5 players + members = await club.get_members() + best_players = members[:5] # members sorted by trophies, gets best 5 players for player in best_players: print(player.name, player.trophies) - # get top 5 players in the world - ranking = await client.get_rankings(ranking='players', limit=5) + ranking = await client.get_rankings(ranking='players', limit=5) # gets top 5 players 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', diff --git a/examples/discord_cog.py b/examples/discord_cog.py index 8952883..2dedb9f 100644 --- a/examples/discord_cog.py +++ b/examples/discord_cog.py @@ -2,7 +2,6 @@ from discord.ext import commands import brawlstats - class BrawlStars(commands.Cog, name='Brawl Stars'): """A simple cog for Brawl Stars commands using discord.py""" @@ -16,12 +15,9 @@ async def profile(self, ctx, tag: str): try: player = await self.client.get_profile(tag) except brawlstats.RequestError as e: # catches all exceptions - # sends code and error message - return await ctx.send('```\n{}: {}\n```'.format(e.code, e.message)) + 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)) - - # you could make this better by using embed fields - em.description = 'Trophies: {}'.format(player.trophies) + em.description = 'Trophies: {}'.format(player.trophies) # you could make this better by using embed fields await ctx.send(embed=em) diff --git a/examples/sync.py b/examples/sync.py index 4862db0..ac37ceb 100644 --- a/examples/sync.py +++ b/examples/sync.py @@ -1,26 +1,23 @@ import brawlstats -# Do not post your token on a public github! client = brawlstats.Client('token') - +# Do not post your token on a public github player = client.get_profile('GGJVJLU2') print(player.trophies) # access attributes using dot.notation -print(player.solo_victories) # use snake_case instead of camelCase +print(player.solo_victories) # access using snake_case instead of camelCase club = player.get_club() print(club.tag) -members = club.get_members() # members sorted by trophies -best_players = members[:5] # gets best 5 players +best_players = club.get_members()[:5] # members sorted by trophies, gets best 5 players for player in best_players: print(player.name, player.trophies) # prints name and trophies -# gets top 5 players in the world -ranking = client.get_rankings(ranking='players', limit=5) +ranking = client.get_rankings(ranking='players', limit=5) # gets top 5 players 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 = client.get_rankings( ranking='brawlers', region='us', diff --git a/setup.py b/setup.py index 61059a3..f7b62c8 100644 --- a/setup.py +++ b/setup.py @@ -6,10 +6,7 @@ long_description = f.read() with open('brawlstats/__init__.py') as f: - version = re.search( - r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - f.read(), re.MULTILINE - ).group(1) + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) requirements = [] with open('requirements.txt') as f: diff --git a/tests/test_async.py b/tests/test_async.py index 542371e..a6569b0 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -71,32 +71,27 @@ async def test_get_club_members(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - await self.assertAsyncRaises( - brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) + await self.assertAsyncRaises(brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) async def test_get_rankings(self): player_ranking = await self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = await self.client.get_rankings( - ranking='players', region='US', limit=1) + us_player_ranking = await self.client.get_rankings(ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) club_ranking = await self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = await self.client.get_rankings( - ranking='clubs', region='US', limit=1) + us_club_ranking = await self.client.get_rankings(ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = await self.client.get_rankings( - ranking='brawlers', brawler='Shelly') + brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = await self.client.get_rankings( - ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) @@ -107,8 +102,7 @@ async def test_get_rankings(self): await self.client.get_rankings(ranking='people', limit=0) with self.assertRaises(ValueError): - await self.client.get_rankings( - ranking='brawlers', brawler='SharpBit') + await self.client.get_rankings(ranking='brawlers', brawler='SharpBit') async def test_get_constants(self): constants = await self.client.get_constants() @@ -117,8 +111,7 @@ async def test_get_constants(self): maps = await self.client.get_constants('maps') self.assertIsInstance(maps, brawlstats.Constants) - await self.assertAsyncRaises( - KeyError, self.client.get_constants('invalid')) + await self.assertAsyncRaises(KeyError, self.client.get_constants('invalid')) async def asyncTearDown(self): await self.client.close() diff --git a/tests/test_blocking.py b/tests/test_blocking.py index f6cf2b6..70b4fdf 100644 --- a/tests/test_blocking.py +++ b/tests/test_blocking.py @@ -28,12 +28,9 @@ def test_get_player(self): self.assertIsInstance(club, brawlstats.Club) self.assertEqual(club.tag, self.CLUB_TAG) - self.assertRaises( - brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_player, 'P') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_player, 'AAA') + self.assertRaises(brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') + self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'P') + self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'AAA') def test_get_battle_logs(self): battle_logs = self.client.get_battle_logs(self.PLAYER_TAG) @@ -48,52 +45,40 @@ def test_get_club(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club, 'P') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club, 'AAA') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'P') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'AAA') def test_get_club_members(self): club_members = self.client.get_club_members(self.CLUB_TAG) self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') def test_get_rankings(self): player_ranking = self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = self.client.get_rankings( - ranking='players', region='US', limit=1) + us_player_ranking = self.client.get_rankings(ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) - self.assertRaises( - ValueError, self.client.get_rankings, ranking='people') - self.assertRaises( - ValueError, self.client.get_rankings, ranking='people', limit=0) - self.assertRaises( - ValueError, self.client.get_rankings, - ranking='brawlers', brawler='SharpBit') + self.assertRaises(ValueError, self.client.get_rankings, ranking='people') + self.assertRaises(ValueError, self.client.get_rankings, ranking='people', limit=0) + self.assertRaises(ValueError, self.client.get_rankings, ranking='brawlers', brawler='SharpBit') club_ranking = self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = self.client.get_rankings( - ranking='clubs', region='US', limit=1) + us_club_ranking = self.client.get_rankings(ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = self.client.get_rankings( - ranking='brawlers', brawler='Shelly') + brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = self.client.get_rankings( - ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) From 4562725bb0f3ad58cb61140cf77a72ed8910b98e Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 06:06:24 +0300 Subject: [PATCH 03/14] many interesting things (see description) major changes: 1) all py files were refactored in pep8 style (except for 97 lines in utils.py) 2) splitting BaseBox into BaseBox and BaseBoxList for convenience (with the corresponding estates of both the models themselves and the models inheriting these models) 3) added: - get_brawlers function - model Brawlers - BRAWLERS_URL in API - nothing function to replace lambda expressions in typecasted 4) changed async.py and sync.py (now they are completely identical, in terms of meaning) .. add some of my files to gitignore --- .gitignore | 2 ++ brawlstats/core.py | 70 +++++++++++++++++++++++++++++++---------- brawlstats/errors.py | 6 ++-- brawlstats/models.py | 68 +++++++++++++++++++++------------------ brawlstats/utils.py | 32 ++++++++++++++----- docs/conf.py | 5 ++- examples/async.py | 14 +++++---- examples/discord_cog.py | 8 +++-- examples/sync.py | 13 +++++--- setup.py | 5 ++- tests/test_async.py | 21 ++++++++----- tests/test_blocking.py | 43 ++++++++++++++++--------- 12 files changed, 194 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index 4ebe54c..80870d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +some.py + # VSCode settings .vscode/ diff --git a/brawlstats/core.py b/brawlstats/core.py index a54f9ec..e242290 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -8,8 +8,14 @@ import requests from cachetools import TTLCache -from .errors import Forbidden, NotFoundError, RateLimitError, ServerError, UnexpectedError -from .models import BattleLog, Club, Constants, Members, Player, Ranking +from .errors import ( + Forbidden, NotFoundError, RateLimitError, + ServerError, UnexpectedError +) +from .models import ( + BattleLog, Brawlers, Club, + Constants, Members, Player, Ranking +) from .utils import API, bstag, typecasted log = logging.getLogger(__name__) @@ -30,7 +36,7 @@ class Client: session: Optional[Union[requests.Session, aiohttp.ClientSession]] = None Use a current session or a make new one. loop: Optional[asyncio.window_events._WindowsSelectorEventLoop] - The event loop to use for asynchronous operations. Defaults to ``None``, + The event loop to use for asynchronous operations. Defaults = ``None``, in which case the default event loop is ``asyncio.get_event_loop()``. connector: Optional[aiohttp.TCPConnector] Pass a TCPConnector into the client (aiohttp). Defaults to ``None``. @@ -44,15 +50,20 @@ class Client: REQUEST_LOG = '{method} {url} recieved {text} has returned {status}' - def __init__(self, token, session=None, timeout=30, is_async=False, **options): + def __init__( + self, token, session=None, timeout=30, is_async=False, **options + ): # Async options self.is_async = is_async - self.loop = options.get('loop', asyncio.get_event_loop()) if self.is_async else None + self.loop = ( + options.get('loop', asyncio.get_event_loop()) + if self.is_async else None) self.connector = options.get('connector') # 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() + 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) @@ -64,12 +75,16 @@ def __init__(self, token, session=None, timeout=30, is_async=False, **options): # Request/response headers self.headers = { 'Authorization': 'Bearer {}'.format(token), - 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format(self.api.VERSION, sys.version_info), + 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format( + self.api.VERSION, sys.version_info + ), 'Accept-Encoding': 'gzip' } def __repr__(self): - return ''.format(self.is_async, self.timeout, self.debug) + return ''.format( + self.is_async, self.timeout, self.debug + ) def close(self): return self.session.close() @@ -87,7 +102,9 @@ def _raise_for_status(self, resp, text): url = resp.url if self.debug: - log.debug(self.REQUEST_LOG.format(method='GET', url=url, text=text, status=code)) + log.debug(self.REQUEST_LOG.format( + method='GET', url=url, text=text, status=code + )) if 300 > code >= 200: return data @@ -119,7 +136,9 @@ async def _arequest(self, url): return cache try: - async with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: + async with self.session.get( + url, timeout=self.timeout, headers=self.headers + ) as resp: data = self._raise_for_status(resp, await resp.text()) except asyncio.TimeoutError: raise ServerError(503, url) @@ -140,7 +159,9 @@ def _request(self, url): return cache try: - with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: + with self.session.get( + url, timeout=self.timeout, headers=self.headers + ) as resp: data = self._raise_for_status(resp, resp.text) except requests.Timeout: raise ServerError(503, url) @@ -151,7 +172,8 @@ def _request(self, url): return data async def _aget_model(self, url, model, key=None): - """Method to turn the response data into a Model class for the async client.""" + """Method to turn the response data into a + Model class for the async client.""" if self.prevent_ratelimit: # Use asyncio.Lock() if prevent_ratelimit=True async with asyncio.Lock(): @@ -170,7 +192,8 @@ async def _aget_model(self, url, model, key=None): return model(self, data) def _get_model(self, url, model, key=None): - """Method to turn the response data into a Model class for the sync client.""" + """Method to turn the response data into a + Model class for the sync client.""" if self.is_async: # Calls the async function return self._aget_model(url, model=model, key=key) @@ -253,7 +276,9 @@ def get_club_members(self, tag: bstag): url = '{}/{}/members'.format(self.api.CLUB, tag) return self._get_model(url, model=Members) - def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=None): + def get_rankings( + self, *, ranking: str, region=None, limit: int = 200, brawler=None + ): """ Get the top count players/clubs/brawlers. @@ -287,14 +312,15 @@ def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=Non # Check for invalid parameters if ranking not in ('players', 'clubs', 'brawlers'): - raise ValueError("'ranking' must be 'players', 'clubs' or 'brawlers'.") + raise ValueError( + "'ranking' must be 'players', 'clubs' or 'brawlers'.") if not 0 < limit <= 200: raise ValueError('Make sure limit is between 1 and 200.') # Construct URL - url = '{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, limit) + url = f'{self.api.RANKINGS}/{region}/{ranking}?{limit=}' if ranking == 'brawlers': - url = '{}/{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, brawler, limit) + url = f'{self.api.RANKINGS}/{region}/{ranking}/{brawler}?{limit=}' return self._get_model(url, model=Ranking) @@ -310,3 +336,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. + + No parameters + + Returns Brawlers + """ + return self._get_model(self.api.BRAWLERS_URL, model=Brawlers) diff --git a/brawlstats/errors.py b/brawlstats/errors.py index 932fea6..481ec31 100644 --- a/brawlstats/errors.py +++ b/brawlstats/errors.py @@ -29,7 +29,8 @@ def __init__(self, code, **kwargs): if self.reason: self.message += '\nReason: {}'.format(self.reason) elif self.invalid_chars: - self.message += 'Invalid characters: {}'.format(', '.join(self.invalid_chars)) + self.message += 'Invalid characters: {}'.format( + ', '.join(self.invalid_chars)) super().__init__(self.code, self.message) @@ -58,5 +59,6 @@ class ServerError(RequestError): def __init__(self, code, url): self.code = code self.url = url - self.message = 'The API is down. Please be patient and try again later.' + self.message = 'The API is down. Please be patient '\ + 'and try again later.' super().__init__(self.code, self.message) diff --git a/brawlstats/models.py b/brawlstats/models.py index df7f0be..1eb850b 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -2,7 +2,11 @@ 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): @@ -11,14 +15,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): @@ -28,7 +25,9 @@ def __getattr__(self, attr): try: return super().__getattr__(attr) except AttributeError: - return None # users can use an if statement rather than try/except to find a missing attribute + return None + # users can use an if statement rather than + # try/except to find a missing attribute def __getitem__(self, item): try: @@ -37,6 +36,17 @@ def __getitem__(self, item): raise IndexError('No such index: {}'.format(item)) +class BaseBoxList(BaseBox): + def from_data(self, data): + data = data['items'] + 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. @@ -85,43 +95,29 @@ 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 ''.format(len(self)) + return f'' -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 ''.format(len(self)) + return f'' -class BattleLog(BaseBox): +class BattleLog(BaseBoxList): """ Returns a full player battle object with all of its attributes. """ - - def __init__(self, client, data): - super().__init__(client, data['items']) + pass class Constants(BaseBox): @@ -129,3 +125,15 @@ class Constants(BaseBox): Returns some Brawl Stars constants. """ pass + + +class Brawlers(BaseBoxList): + """ + Returns list of available brawlers and information about every brawler. + """ + + def __repr__(self): + return f'' + + def __str__(self): + return f'Here {self.__len__()} brawlers' diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 16824ea..fce1bec 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -11,20 +11,26 @@ class API: def __init__(self, base_url, version=1): - self.BASE = base_url or 'https://api.brawlstars.com/v{}'.format(version) + self.BASE = base_url or f'https://api.brawlstars.com/v{version}' self.PROFILE = self.BASE + '/players' self.CLUB = self.BASE + '/clubs' self.RANKINGS = self.BASE + '/rankings' self.CONSTANTS = 'https://fourjr.herokuapp.com/bs/constants' + self.BRAWLERS_URL = 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) + 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() + resp = urllib.request.urlopen( + self.CONSTANTS + '/characters' + ).read() if isinstance(resp, bytes): resp = resp.decode('utf-8') data = json.loads(resp) @@ -33,7 +39,8 @@ def __init__(self, base_url, version=1): else: if data: self.BRAWLERS = { - b['tID'].lower(): int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) + b['tID'].lower(): + int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) for b in data if b['tID'] } else: @@ -55,6 +62,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 @@ -63,8 +71,10 @@ def get_datetime(timestamp: str, unix=True): Parameters ---------- timestamp: str - A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, usually returned by the API - in the ``created_time`` field for example (eg. 2018-07-18T14:59:06.000Z) + A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, + usually returned by the API + in the ``created_time`` field + for example (eg. 2018-07-18T14:59:06.000Z) unix: Optional[bool] = True Whether to return a POSIX timestamp (seconds since epoch) or not @@ -76,8 +86,14 @@ def get_datetime(timestamp: str, unix=True): else: return time + +# do nothing +def nothing(value): + return value + + def typecasted(func): - """Decorator that converts arguments via annotations. + f"""Decorator that converts arguments via annotations. Source: https://github.com/cgrok/clashroyale/blob/master/clashroyale/official_api/utils.py#L11""" signature = inspect.signature(func).parameters.items() @@ -89,7 +105,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) diff --git a/docs/conf.py b/docs/conf.py index 0d149b5..a605d56 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,10 @@ # sys.path.insert(0, os.path.abspath('.')) with open('../brawlstats/__init__.py') as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + f.read(), re.MULTILINE + ).group(1) # -- Project information ----------------------------------------------------- diff --git a/examples/async.py b/examples/async.py index ee39f18..4db1a96 100644 --- a/examples/async.py +++ b/examples/async.py @@ -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', diff --git a/examples/discord_cog.py b/examples/discord_cog.py index 2dedb9f..8952883 100644 --- a/examples/discord_cog.py +++ b/examples/discord_cog.py @@ -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""" @@ -15,9 +16,12 @@ async def profile(self, ctx, tag: str): try: player = await self.client.get_profile(tag) except brawlstats.RequestError as e: # catches all exceptions - return await ctx.send('```\n{}: {}\n```'.format(e.code, e.message)) # sends code and error message + # sends code and error message + return await ctx.send('```\n{}: {}\n```'.format(e.code, e.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 + + # you could make this better by using embed fields + em.description = 'Trophies: {}'.format(player.trophies) await ctx.send(embed=em) diff --git a/examples/sync.py b/examples/sync.py index ac37ceb..4862db0 100644 --- a/examples/sync.py +++ b/examples/sync.py @@ -1,23 +1,26 @@ import brawlstats +# Do not post your token on a public github! client = brawlstats.Client('token') -# Do not post your token on a public github + player = 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 = player.get_club() print(club.tag) -best_players = club.get_members()[:5] # members sorted by trophies, gets best 5 players +members = 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) # prints name and trophies -ranking = client.get_rankings(ranking='players', limit=5) # gets top 5 players +# gets top 5 players in the world +ranking = 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 = client.get_rankings( ranking='brawlers', region='us', diff --git a/setup.py b/setup.py index f7b62c8..61059a3 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,10 @@ long_description = f.read() with open('brawlstats/__init__.py') as f: - version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) + version = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + f.read(), re.MULTILINE + ).group(1) requirements = [] with open('requirements.txt') as f: diff --git a/tests/test_async.py b/tests/test_async.py index a6569b0..542371e 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -71,27 +71,32 @@ async def test_get_club_members(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - await self.assertAsyncRaises(brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) + await self.assertAsyncRaises( + brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) async def test_get_rankings(self): player_ranking = await self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = await self.client.get_rankings(ranking='players', region='US', limit=1) + us_player_ranking = await self.client.get_rankings( + ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) club_ranking = await self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = await self.client.get_rankings(ranking='clubs', region='US', limit=1) + us_club_ranking = await self.client.get_rankings( + ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler='Shelly') + brawler_ranking = await self.client.get_rankings( + ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = await self.client.get_rankings( + ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) @@ -102,7 +107,8 @@ async def test_get_rankings(self): await self.client.get_rankings(ranking='people', limit=0) with self.assertRaises(ValueError): - await self.client.get_rankings(ranking='brawlers', brawler='SharpBit') + await self.client.get_rankings( + ranking='brawlers', brawler='SharpBit') async def test_get_constants(self): constants = await self.client.get_constants() @@ -111,7 +117,8 @@ async def test_get_constants(self): maps = await self.client.get_constants('maps') self.assertIsInstance(maps, brawlstats.Constants) - await self.assertAsyncRaises(KeyError, self.client.get_constants('invalid')) + await self.assertAsyncRaises( + KeyError, self.client.get_constants('invalid')) async def asyncTearDown(self): await self.client.close() diff --git a/tests/test_blocking.py b/tests/test_blocking.py index 70b4fdf..f6cf2b6 100644 --- a/tests/test_blocking.py +++ b/tests/test_blocking.py @@ -28,9 +28,12 @@ def test_get_player(self): self.assertIsInstance(club, brawlstats.Club) self.assertEqual(club.tag, self.CLUB_TAG) - self.assertRaises(brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') - self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'P') - self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'AAA') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_player, 'P') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_player, 'AAA') def test_get_battle_logs(self): battle_logs = self.client.get_battle_logs(self.PLAYER_TAG) @@ -45,40 +48,52 @@ def test_get_club(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises(brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') - self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'P') - self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'AAA') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club, 'P') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club, 'AAA') def test_get_club_members(self): club_members = self.client.get_club_members(self.CLUB_TAG) self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises(brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') + self.assertRaises( + brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') def test_get_rankings(self): player_ranking = self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = self.client.get_rankings(ranking='players', region='US', limit=1) + us_player_ranking = self.client.get_rankings( + ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) - self.assertRaises(ValueError, self.client.get_rankings, ranking='people') - self.assertRaises(ValueError, self.client.get_rankings, ranking='people', limit=0) - self.assertRaises(ValueError, self.client.get_rankings, ranking='brawlers', brawler='SharpBit') + self.assertRaises( + ValueError, self.client.get_rankings, ranking='people') + self.assertRaises( + ValueError, self.client.get_rankings, ranking='people', limit=0) + self.assertRaises( + ValueError, self.client.get_rankings, + ranking='brawlers', brawler='SharpBit') club_ranking = self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = self.client.get_rankings(ranking='clubs', region='US', limit=1) + us_club_ranking = self.client.get_rankings( + ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler='Shelly') + brawler_ranking = self.client.get_rankings( + ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = self.client.get_rankings( + ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) From c87181f35ca7a4b6db822432429c8575629b0281 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 07:29:18 +0300 Subject: [PATCH 04/14] test creation, redo tox for py38 --- brawlstats/core.py | 2 +- tests/test_async.py | 8 ++++++-- tests/test_blocking.py | 8 ++++++-- tox.ini | 6 +++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/brawlstats/core.py b/brawlstats/core.py index e242290..53e6321 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -339,7 +339,7 @@ def get_constants(self, key=None): def get_brawlers(self): """ - Get available brawlers. + Get available brawlers and information about them. No parameters diff --git a/tests/test_async.py b/tests/test_async.py index 542371e..eae60e9 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -26,6 +26,9 @@ async def setUp(self): session=session ) + async def asyncTearDown(self): + await self.client.close() + async def test_get_player(self): player = await self.client.get_player(self.PLAYER_TAG) self.assertIsInstance(player, brawlstats.Player) @@ -120,8 +123,9 @@ async def test_get_constants(self): await self.assertAsyncRaises( KeyError, self.client.get_constants('invalid')) - async def asyncTearDown(self): - await self.client.close() + async def test_get_brawlers(self): + brawlers = await self.client.get_brawlers() + self.assertIsInstance(brawlers, brawlstats.Brawlers) if __name__ == '__main__': diff --git a/tests/test_blocking.py b/tests/test_blocking.py index f6cf2b6..3ec57d2 100644 --- a/tests/test_blocking.py +++ b/tests/test_blocking.py @@ -19,6 +19,9 @@ def setUp(self): base_url=os.getenv('base_url') ) + def tearDown(self): + self.client.close() + def test_get_player(self): player = self.client.get_player(self.PLAYER_TAG) self.assertIsInstance(player, brawlstats.Player) @@ -106,8 +109,9 @@ def test_get_constants(self): self.assertRaises(KeyError, self.client.get_constants, 'invalid') - def tearDown(self): - self.client.close() + def test_get_brawlers(self): + brawlers = self.client.get_brawlers() + self.assertIsInstance(brawlers, brawlstats.Brawlers) if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index b189141..e47e3db 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,13 @@ exclude = __init__.py,.tox ignore = E252,E302,E731,W605 [tox] -envlist = py35,py36,py37 +envlist = py35,py36,py37,py38 -[testenv] +[env] changedir = tests deps = -r{toxinidir}/requirements-dev.txt sitepackages = true -whitelist_externals = pytest +whitelist_externals = flake8,pytest commands = flake8 pytest From ba8ededf79be47408d9afff1857466df25962b74 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 08:12:35 +0300 Subject: [PATCH 05/14] documentation creation --- CHANGELOG.md | 9 +++++++++ brawlstats/models.py | 2 +- docs/api.rst | 16 ++++++++++++++++ docs/index.rst | 3 ++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaab76a..39bb739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. + +## [4.0.4] - 6/27/20 +### Added +- `get_brawlers` function to get available brawlers +### Changed +- splitting `BaseBox` into `BaseBox` and `BaseBoxList` for convenience +### Fixed +- tox for py38 works (not completely sure) + ## [4.0.3] - 4/17/20 ### Fixed - Brawler leaderboards for Python 3.5 diff --git a/brawlstats/models.py b/brawlstats/models.py index 1eb850b..76d3530 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -129,7 +129,7 @@ class Constants(BaseBox): class Brawlers(BaseBoxList): """ - Returns list of available brawlers and information about every brawler. + Returns list of available brawlers and information about them. """ def __repr__(self): diff --git a/docs/api.rst b/docs/api.rst index ac1f5e2..f00b21e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -25,6 +25,9 @@ Data Models .. autoclass:: brawlstats.models.Constants :members: +.. autoclass:: brawlstats.models.Brawlers + :members: + Attributes of Data Models ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -264,3 +267,16 @@ Attributes: ] } } + +Brawlers +~~~~~~~~ + +Returns list of available brawlers and information about them with this structure: + +Attributes: + +:: + +[ + Brawler +] \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index acfef74..26f7121 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 ~~~~~~~~~~~~ From ad440d672d4fece0fa0482d09395e1cb04fb8463 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 09:04:02 +0300 Subject: [PATCH 06/14] this is solving (issue #60) also resolving compatibility issues --- brawlstats/core.py | 6 ++++-- brawlstats/models.py | 8 ++++---- brawlstats/utils.py | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/brawlstats/core.py b/brawlstats/core.py index 53e6321..0cde730 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -318,9 +318,11 @@ def get_rankings( raise ValueError('Make sure limit is between 1 and 200.') # Construct URL - url = f'{self.api.RANKINGS}/{region}/{ranking}?{limit=}' + url = '{}/{}/{}?limit={}'.format( + self.api.RANKINGS, region, ranking, limit) if ranking == 'brawlers': - url = f'{self.api.RANKINGS}/{region}/{ranking}/{brawler}?{limit=}' + url = '{}/{}/{}/{}?limit={}'.format( + self.api.RANKINGS, region, ranking, brawler, limit) return self._get_model(url, model=Ranking) diff --git a/brawlstats/models.py b/brawlstats/models.py index 76d3530..175d1eb 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -101,7 +101,7 @@ class Members(BaseBoxList): """ def __repr__(self): - return f'' + return ''.format(len(self)) class Ranking(BaseBoxList): @@ -110,7 +110,7 @@ class Ranking(BaseBoxList): """ def __repr__(self): - return f'' + return ''.format(len(self)) class BattleLog(BaseBoxList): @@ -133,7 +133,7 @@ class Brawlers(BaseBoxList): """ def __repr__(self): - return f'' + return ''.format(len(self)) def __str__(self): - return f'Here {self.__len__()} brawlers' + return 'Here {} brawlers'.format(len(self)) diff --git a/brawlstats/utils.py b/brawlstats/utils.py index fce1bec..2bfccb3 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -11,7 +11,8 @@ class API: def __init__(self, base_url, version=1): - self.BASE = base_url or f'https://api.brawlstars.com/v{version}' + self.BASE = base_url or 'https://api.brawlstars.com/v'\ + '{}'.format(version) self.PROFILE = self.BASE + '/players' self.CLUB = self.BASE + '/clubs' self.RANKINGS = self.BASE + '/rankings' From d6b8fb96818477acbf8d209dd247d1cc9a990582 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 09:06:37 +0300 Subject: [PATCH 07/14] Update utils.py --- brawlstats/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 2bfccb3..2d04533 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -94,7 +94,7 @@ def nothing(value): def typecasted(func): - f"""Decorator that converts arguments via annotations. + """Decorator that converts arguments via annotations. Source: https://github.com/cgrok/clashroyale/blob/master/clashroyale/official_api/utils.py#L11""" signature = inspect.signature(func).parameters.items() From 7dcc3e5abb258d5821b390d8772d97e23fed9be5 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 10:02:42 +0300 Subject: [PATCH 08/14] little change --- brawlstats/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 2d04533..560dce9 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -12,7 +12,7 @@ class API: def __init__(self, base_url, version=1): self.BASE = base_url or 'https://api.brawlstars.com/v'\ - '{}'.format(version) + + str(version) self.PROFILE = self.BASE + '/players' self.CLUB = self.BASE + '/clubs' self.RANKINGS = self.BASE + '/rankings' From d2bc53200e1d1e255c225d1e1d696dd02679e465 Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 16:58:28 +0300 Subject: [PATCH 09/14] revert some of the code, as requested by sharpBit --- CHANGELOG.md | 2 +- brawlstats/core.py | 62 +++++++++++------------------------------ brawlstats/errors.py | 6 ++-- brawlstats/models.py | 9 ++---- brawlstats/utils.py | 21 ++++---------- docs/conf.py | 5 +--- examples/discord_cog.py | 6 ++-- setup.py | 5 +--- tests/test_async.py | 27 +++++++----------- tests/test_blocking.py | 49 +++++++++++--------------------- 10 files changed, 59 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39bb739..5282e64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## [4.0.4] - 6/27/20 +## [Unnoun yet] - 6/27/20 ### Added - `get_brawlers` function to get available brawlers ### Changed diff --git a/brawlstats/core.py b/brawlstats/core.py index 0cde730..5114b36 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -8,14 +8,8 @@ import requests from cachetools import TTLCache -from .errors import ( - Forbidden, NotFoundError, RateLimitError, - ServerError, UnexpectedError -) -from .models import ( - BattleLog, Brawlers, Club, - Constants, Members, Player, Ranking -) +from .errors import Forbidden, NotFoundError, RateLimitError, ServerError, UnexpectedError +from .models import BattleLog, Brawlers, Club, Constants, Members, Player, Ranking from .utils import API, bstag, typecasted log = logging.getLogger(__name__) @@ -36,7 +30,7 @@ class Client: session: Optional[Union[requests.Session, aiohttp.ClientSession]] = None Use a current session or a make new one. loop: Optional[asyncio.window_events._WindowsSelectorEventLoop] - The event loop to use for asynchronous operations. Defaults = ``None``, + The event loop to use for asynchronous operations. Defaults to ``None``, in which case the default event loop is ``asyncio.get_event_loop()``. connector: Optional[aiohttp.TCPConnector] Pass a TCPConnector into the client (aiohttp). Defaults to ``None``. @@ -50,20 +44,15 @@ class Client: REQUEST_LOG = '{method} {url} recieved {text} has returned {status}' - def __init__( - self, token, session=None, timeout=30, is_async=False, **options - ): + def __init__(self, token, session=None, timeout=30, is_async=False, **options): # Async options self.is_async = is_async - self.loop = ( - options.get('loop', asyncio.get_event_loop()) - if self.is_async else None) + self.loop = options.get('loop', asyncio.get_event_loop()) if self.is_async else None self.connector = options.get('connector') # 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() + 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) @@ -75,16 +64,12 @@ def __init__( # Request/response headers self.headers = { 'Authorization': 'Bearer {}'.format(token), - 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format( - self.api.VERSION, sys.version_info - ), + 'User-Agent': 'brawlstats/{0} (Python {1[0]}.{1[1]})'.format(self.api.VERSION, sys.version_info), 'Accept-Encoding': 'gzip' } def __repr__(self): - return ''.format( - self.is_async, self.timeout, self.debug - ) + return ''.format(self.is_async, self.timeout, self.debug) def close(self): return self.session.close() @@ -102,9 +87,7 @@ def _raise_for_status(self, resp, text): url = resp.url if self.debug: - log.debug(self.REQUEST_LOG.format( - method='GET', url=url, text=text, status=code - )) + log.debug(self.REQUEST_LOG.format(method='GET', url=url, text=text, status=code)) if 300 > code >= 200: return data @@ -136,9 +119,7 @@ async def _arequest(self, url): return cache try: - async with self.session.get( - url, timeout=self.timeout, headers=self.headers - ) as resp: + async with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: data = self._raise_for_status(resp, await resp.text()) except asyncio.TimeoutError: raise ServerError(503, url) @@ -159,9 +140,7 @@ def _request(self, url): return cache try: - with self.session.get( - url, timeout=self.timeout, headers=self.headers - ) as resp: + with self.session.get(url, timeout=self.timeout, headers=self.headers) as resp: data = self._raise_for_status(resp, resp.text) except requests.Timeout: raise ServerError(503, url) @@ -172,8 +151,7 @@ def _request(self, url): return data async def _aget_model(self, url, model, key=None): - """Method to turn the response data into a - Model class for the async client.""" + """Method to turn the response data into a Model class for the async client.""" if self.prevent_ratelimit: # Use asyncio.Lock() if prevent_ratelimit=True async with asyncio.Lock(): @@ -192,8 +170,7 @@ async def _aget_model(self, url, model, key=None): return model(self, data) def _get_model(self, url, model, key=None): - """Method to turn the response data into a - Model class for the sync client.""" + """Method to turn the response data into a Model class for the sync client.""" if self.is_async: # Calls the async function return self._aget_model(url, model=model, key=key) @@ -276,9 +253,7 @@ def get_club_members(self, tag: bstag): url = '{}/{}/members'.format(self.api.CLUB, tag) return self._get_model(url, model=Members) - def get_rankings( - self, *, ranking: str, region=None, limit: int = 200, brawler=None - ): + def get_rankings(self, *, ranking: str, region=None, limit: int=200, brawler=None): """ Get the top count players/clubs/brawlers. @@ -312,17 +287,14 @@ def get_rankings( # Check for invalid parameters if ranking not in ('players', 'clubs', 'brawlers'): - raise ValueError( - "'ranking' must be 'players', 'clubs' or 'brawlers'.") + raise ValueError("'ranking' must be 'players', 'clubs' or 'brawlers'.") if not 0 < limit <= 200: raise ValueError('Make sure limit is between 1 and 200.') # Construct URL - url = '{}/{}/{}?limit={}'.format( - self.api.RANKINGS, region, ranking, limit) + url = '{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, limit) if ranking == 'brawlers': - url = '{}/{}/{}/{}?limit={}'.format( - self.api.RANKINGS, region, ranking, brawler, limit) + url = '{}/{}/{}/{}?limit={}'.format(self.api.RANKINGS, region, ranking, brawler, limit) return self._get_model(url, model=Ranking) diff --git a/brawlstats/errors.py b/brawlstats/errors.py index 481ec31..932fea6 100644 --- a/brawlstats/errors.py +++ b/brawlstats/errors.py @@ -29,8 +29,7 @@ def __init__(self, code, **kwargs): if self.reason: self.message += '\nReason: {}'.format(self.reason) elif self.invalid_chars: - self.message += 'Invalid characters: {}'.format( - ', '.join(self.invalid_chars)) + self.message += 'Invalid characters: {}'.format(', '.join(self.invalid_chars)) super().__init__(self.code, self.message) @@ -59,6 +58,5 @@ class ServerError(RequestError): def __init__(self, code, url): self.code = code self.url = url - self.message = 'The API is down. Please be patient '\ - 'and try again later.' + self.message = 'The API is down. Please be patient and try again later.' super().__init__(self.code, self.message) diff --git a/brawlstats/models.py b/brawlstats/models.py index 175d1eb..0658993 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -2,10 +2,7 @@ from .utils import bstag -__all__ = [ - 'Player', 'Club', 'Members', 'Ranking', - 'BattleLog', 'Constants', 'Brawlers' -] +__all__ = ['Player', 'Club', 'Members', 'Ranking', 'BattleLog', 'Constants', 'Brawlers'] class BaseBox: @@ -25,9 +22,7 @@ def __getattr__(self, attr): try: return super().__getattr__(attr) except AttributeError: - return None - # users can use an if statement rather than - # try/except to find a missing attribute + return None # users can use an if statement rather than try/except to find a missing attribute def __getitem__(self, item): try: diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 560dce9..66472ad 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -11,8 +11,7 @@ class API: def __init__(self, base_url, version=1): - self.BASE = base_url or 'https://api.brawlstars.com/v'\ - + str(version) + self.BASE = base_url or 'https://api.brawlstars.com/v{}'.format(version) self.PROFILE = self.BASE + '/players' self.CLUB = self.BASE + '/clubs' self.RANKINGS = self.BASE + '/rankings' @@ -22,16 +21,11 @@ def __init__(self, base_url, version=1): # 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) + 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() + resp = urllib.request.urlopen(self.CONSTANTS + '/characters').read() if isinstance(resp, bytes): resp = resp.decode('utf-8') data = json.loads(resp) @@ -40,8 +34,7 @@ def __init__(self, base_url, version=1): else: if data: self.BRAWLERS = { - b['tID'].lower(): - int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) + b['tID'].lower(): int(str(b['scId'])[:2] + '0' + str(b['scId'])[2:]) for b in data if b['tID'] } else: @@ -72,10 +65,8 @@ def get_datetime(timestamp: str, unix=True): Parameters ---------- timestamp: str - A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, - usually returned by the API - in the ``created_time`` field - for example (eg. 2018-07-18T14:59:06.000Z) + A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, usually returned by the API A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, + in the ``created_time`` field for example (eg. 2018-07-18T14:59:06.000Z) unix: Optional[bool] = True Whether to return a POSIX timestamp (seconds since epoch) or not diff --git a/docs/conf.py b/docs/conf.py index a605d56..0d149b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,10 +19,7 @@ # sys.path.insert(0, os.path.abspath('.')) with open('../brawlstats/__init__.py') as f: - version = re.search( - r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - f.read(), re.MULTILINE - ).group(1) + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) # -- Project information ----------------------------------------------------- diff --git a/examples/discord_cog.py b/examples/discord_cog.py index 8952883..a1cceef 100644 --- a/examples/discord_cog.py +++ b/examples/discord_cog.py @@ -16,12 +16,10 @@ async def profile(self, ctx, tag: str): try: player = await self.client.get_profile(tag) except brawlstats.RequestError as e: # catches all exceptions - # sends code and error message - return await ctx.send('```\n{}: {}\n```'.format(e.code, e.message)) + 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)) - # you could make this better by using embed fields - em.description = 'Trophies: {}'.format(player.trophies) + em.description = 'Trophies: {}'.format(player.trophies) # you could make this better by using embed fields await ctx.send(embed=em) diff --git a/setup.py b/setup.py index 61059a3..f7b62c8 100644 --- a/setup.py +++ b/setup.py @@ -6,10 +6,7 @@ long_description = f.read() with open('brawlstats/__init__.py') as f: - version = re.search( - r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - f.read(), re.MULTILINE - ).group(1) + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) requirements = [] with open('requirements.txt') as f: diff --git a/tests/test_async.py b/tests/test_async.py index eae60e9..ec1c83c 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -26,9 +26,6 @@ async def setUp(self): session=session ) - async def asyncTearDown(self): - await self.client.close() - async def test_get_player(self): player = await self.client.get_player(self.PLAYER_TAG) self.assertIsInstance(player, brawlstats.Player) @@ -74,32 +71,27 @@ async def test_get_club_members(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - await self.assertAsyncRaises( - brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) + await self.assertAsyncRaises(brawlstats.NotFoundError, self.client.get_club_members('8GGGGGGG')) async def test_get_rankings(self): player_ranking = await self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = await self.client.get_rankings( - ranking='players', region='US', limit=1) + us_player_ranking = await self.client.get_rankings(ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) club_ranking = await self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = await self.client.get_rankings( - ranking='clubs', region='US', limit=1) + us_club_ranking = await self.client.get_rankings(ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = await self.client.get_rankings( - ranking='brawlers', brawler='Shelly') + brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = await self.client.get_rankings( - ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = await self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) @@ -110,8 +102,7 @@ async def test_get_rankings(self): await self.client.get_rankings(ranking='people', limit=0) with self.assertRaises(ValueError): - await self.client.get_rankings( - ranking='brawlers', brawler='SharpBit') + await self.client.get_rankings(ranking='brawlers', brawler='SharpBit') async def test_get_constants(self): constants = await self.client.get_constants() @@ -120,13 +111,15 @@ async def test_get_constants(self): maps = await self.client.get_constants('maps') self.assertIsInstance(maps, brawlstats.Constants) - await self.assertAsyncRaises( - KeyError, self.client.get_constants('invalid')) + await self.assertAsyncRaises(KeyError, self.client.get_constants('invalid')) async def test_get_brawlers(self): brawlers = await self.client.get_brawlers() self.assertIsInstance(brawlers, brawlstats.Brawlers) + async def asyncTearDown(self): + await self.client.close() + if __name__ == '__main__': asynctest.main() diff --git a/tests/test_blocking.py b/tests/test_blocking.py index 3ec57d2..53cbddc 100644 --- a/tests/test_blocking.py +++ b/tests/test_blocking.py @@ -19,9 +19,6 @@ def setUp(self): base_url=os.getenv('base_url') ) - def tearDown(self): - self.client.close() - def test_get_player(self): player = self.client.get_player(self.PLAYER_TAG) self.assertIsInstance(player, brawlstats.Player) @@ -31,12 +28,9 @@ def test_get_player(self): self.assertIsInstance(club, brawlstats.Club) self.assertEqual(club.tag, self.CLUB_TAG) - self.assertRaises( - brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_player, 'P') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_player, 'AAA') + self.assertRaises(brawlstats.NotFoundError, self.client.get_player, '2PPPPPPP') + self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'P') + self.assertRaises(brawlstats.NotFoundError, self.client.get_player, 'AAA') def test_get_battle_logs(self): battle_logs = self.client.get_battle_logs(self.PLAYER_TAG) @@ -51,52 +45,40 @@ def test_get_club(self): self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club, 'P') - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club, 'AAA') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club, '8GGGGGGG') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'P') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club, 'AAA') def test_get_club_members(self): club_members = self.client.get_club_members(self.CLUB_TAG) self.assertIsInstance(club_members, brawlstats.Members) self.assertIn(self.PLAYER_TAG, [x.tag for x in club_members]) - self.assertRaises( - brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') + self.assertRaises(brawlstats.NotFoundError, self.client.get_club_members, '8GGGGGGG') def test_get_rankings(self): player_ranking = self.client.get_rankings(ranking='players') self.assertIsInstance(player_ranking, brawlstats.Ranking) - us_player_ranking = self.client.get_rankings( - ranking='players', region='US', limit=1) + us_player_ranking = self.client.get_rankings(ranking='players', region='US', limit=1) self.assertIsInstance(us_player_ranking, brawlstats.Ranking) self.assertTrue(len(us_player_ranking) == 1) - self.assertRaises( - ValueError, self.client.get_rankings, ranking='people') - self.assertRaises( - ValueError, self.client.get_rankings, ranking='people', limit=0) - self.assertRaises( - ValueError, self.client.get_rankings, - ranking='brawlers', brawler='SharpBit') + self.assertRaises(ValueError, self.client.get_rankings, ranking='people') + self.assertRaises(ValueError, self.client.get_rankings, ranking='people', limit=0) + self.assertRaises(ValueError, self.client.get_rankings, ranking='brawlers', brawler='SharpBit') club_ranking = self.client.get_rankings(ranking='clubs') self.assertIsInstance(club_ranking, brawlstats.Ranking) - us_club_ranking = self.client.get_rankings( - ranking='clubs', region='US', limit=1) + us_club_ranking = self.client.get_rankings(ranking='clubs', region='US', limit=1) self.assertIsInstance(us_club_ranking, brawlstats.Ranking) self.assertTrue(len(us_club_ranking) == 1) - brawler_ranking = self.client.get_rankings( - ranking='brawlers', brawler='Shelly') + brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler='Shelly') self.assertIsInstance(brawler_ranking, brawlstats.Ranking) - us_brawler_ranking = self.client.get_rankings( - ranking='brawlers', brawler=16000000, region='US', limit=1) + us_brawler_ranking = self.client.get_rankings(ranking='brawlers', brawler=16000000, region='US', limit=1) self.assertIsInstance(us_brawler_ranking, brawlstats.Ranking) self.assertTrue(len(us_brawler_ranking) == 1) @@ -113,6 +95,9 @@ def test_get_brawlers(self): brawlers = self.client.get_brawlers() self.assertIsInstance(brawlers, brawlstats.Brawlers) + def tearDown(self): + self.client.close() + if __name__ == '__main__': unittest.main() From 4897138300d86986d55d3a33f86294d79e24450a Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 17:00:56 +0300 Subject: [PATCH 10/14] little change --- brawlstats/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 66472ad..56c847d 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -65,7 +65,7 @@ def get_datetime(timestamp: str, unix=True): Parameters ---------- timestamp: str - A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, usually returned by the API A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, + A timestamp in the %Y-%m-%dT%H:%M:%S.%fZ format, usually returned by the API in the ``created_time`` field for example (eg. 2018-07-18T14:59:06.000Z) unix: Optional[bool] = True Whether to return a POSIX timestamp (seconds since epoch) or not From 3f863cef195c5121f57d91a948d3f67a2ae3593b Mon Sep 17 00:00:00 2001 From: 0dminnimda <52697657+0dminnimda@users.noreply.github.com> Date: Sat, 27 Jun 2020 20:31:31 +0300 Subject: [PATCH 11/14] edit gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 80870d1..4ebe54c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -some.py - # VSCode settings .vscode/ From 3f4714c40205aabcd845994a80c19abf0a6d003d Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Thu, 2 Jul 2020 19:25:32 -0400 Subject: [PATCH 12/14] Some changes (read desc) - change back to testenv - fix to load brawlers for async clients (tox doesnt work currently but should work for users) --- brawlstats/__init__.py | 1 - brawlstats/core.py | 30 +++++++++++++++++++----------- brawlstats/models.py | 18 +++++++++++------- brawlstats/utils.py | 29 +++++++++-------------------- tox.ini | 2 +- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/brawlstats/__init__.py b/brawlstats/__init__.py index 7c381d4..770df41 100644 --- a/brawlstats/__init__.py +++ b/brawlstats/__init__.py @@ -2,7 +2,6 @@ from .models import * from .errors import * - ############ # METADATA # ############ diff --git a/brawlstats/core.py b/brawlstats/core.py index 5114b36..9503a4f 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -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 = { @@ -68,6 +68,13 @@ def __init__(self, token, session=None, timeout=30, is_async=False, **options): 'Accept-Encoding': 'gzip' } + # Load brawlers for get_rankings + if self.is_async: + brawlers_info = self.loop.run_until_complete(self.get_brawlers()) + else: + brawlers_info = self.get_brawlers() + self.api.set_brawlers(brawlers_info) + def __repr__(self): return ''.format(self.is_async, self.timeout, self.debug) @@ -272,19 +279,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'.") @@ -319,4 +327,4 @@ def get_brawlers(self): Returns Brawlers """ - return self._get_model(self.api.BRAWLERS_URL, model=Brawlers) + return self._get_model(self.api.BRAWLERS, model=Brawlers) diff --git a/brawlstats/models.py b/brawlstats/models.py index 0658993..67a862f 100644 --- a/brawlstats/models.py +++ b/brawlstats/models.py @@ -33,7 +33,6 @@ def __getitem__(self, item): class BaseBoxList(BaseBox): def from_data(self, data): - data = data['items'] self.raw_data = data self._boxed_data = BoxList(data, camel_killer_box=True) return self @@ -95,6 +94,9 @@ class Members(BaseBoxList): Returns the members in a club. """ + def __init__(self, client, data): + super().__init__(client, data['items']) + def __repr__(self): return ''.format(len(self)) @@ -104,6 +106,9 @@ 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 __repr__(self): return ''.format(len(self)) @@ -112,7 +117,9 @@ class BattleLog(BaseBoxList): """ Returns a full player battle object with all of its attributes. """ - pass + + def __init__(self, client, data): + super().__init__(client, data['items']) class Constants(BaseBox): @@ -127,8 +134,5 @@ class Brawlers(BaseBoxList): Returns list of available brawlers and information about them. """ - def __repr__(self): - return ''.format(len(self)) - - def __str__(self): - return 'Here {} brawlers'.format(len(self)) + def __init__(self, client, data): + super().__init__(client, data['items']) diff --git a/brawlstats/utils.py b/brawlstats/utils.py index 56c847d..baca4c6 100644 --- a/brawlstats/utils.py +++ b/brawlstats/utils.py @@ -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 @@ -16,29 +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_URL = self.BASE + "/brawlers" + 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): @@ -79,8 +68,8 @@ def get_datetime(timestamp: str, unix=True): return time -# do nothing def nothing(value): + """Function that returns the argument""" return value diff --git a/tox.ini b/tox.ini index e47e3db..35f0470 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ ignore = E252,E302,E731,W605 [tox] envlist = py35,py36,py37,py38 -[env] +[testenv] changedir = tests deps = -r{toxinidir}/requirements-dev.txt sitepackages = true From 9238c471235e36b00b0fbd33a3bce510bedc9fdb Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Tue, 21 Jul 2020 22:36:25 -0400 Subject: [PATCH 13/14] finally fix the event loop problem (thanks 4JR) --- brawlstats/core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/brawlstats/core.py b/brawlstats/core.py index 9503a4f..4f53e07 100644 --- a/brawlstats/core.py +++ b/brawlstats/core.py @@ -70,10 +70,14 @@ def __init__(self, token, session=None, timeout=30, is_async=False, **options): # Load brawlers for get_rankings if self.is_async: - brawlers_info = self.loop.run_until_complete(self.get_brawlers()) + self.loop.create_task(self.__ainit__()) else: brawlers_info = self.get_brawlers() - self.api.set_brawlers(brawlers_info) + 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 ''.format(self.is_async, self.timeout, self.debug) From 087d929ba11f4ac95e61cbd7beb44247d3f5f027 Mon Sep 17 00:00:00 2001 From: SharpBit <31069084+SharpBit@users.noreply.github.com> Date: Wed, 22 Jul 2020 09:19:24 -0400 Subject: [PATCH 14/14] changelog and readme and version bump --- CHANGELOG.md | 6 ++---- README.rst | 2 ++ brawlstats/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5282e64..2f7051f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,11 @@ All notable changes to this project will be documented in this file. -## [Unnoun yet] - 6/27/20 +## [4.0.4] - 7/22/20 ### Added - `get_brawlers` function to get available brawlers ### Changed -- splitting `BaseBox` into `BaseBox` and `BaseBoxList` for convenience -### Fixed -- tox for py38 works (not completely sure) +- Split `BaseBox` into `BaseBox` and `BaseBoxList` for convenience ## [4.0.3] - 4/17/20 ### Fixed diff --git a/README.rst b/README.rst index 8b3d15a..b7a8f7f 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,7 @@ Contributing Special thanks to this project's contributors ❤️ - `4JR`_ +- `golbu`_ - `kawaii banana`_ - `kjkui`_ - `Kyber`_ @@ -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 diff --git a/brawlstats/__init__.py b/brawlstats/__init__.py index 770df41..650d86b 100644 --- a/brawlstats/__init__.py +++ b/brawlstats/__init__.py @@ -7,7 +7,7 @@ ############ -__version__ = 'v4.0.3' +__version__ = 'v4.0.4' __title__ = 'brawlstats' __license__ = 'MIT' __author__ = 'SharpBit'