diff --git a/fpl/fpl.py b/fpl/fpl.py index c0e7f21..04befae 100644 --- a/fpl/fpl.py +++ b/fpl/fpl.py @@ -27,6 +27,8 @@ import itertools import os +import requests + from .constants import API_URLS from .models.classic_league import ClassicLeague from .models.fixture import Fixture @@ -44,6 +46,14 @@ class FPL: def __init__(self, session): self.session = session + static = requests.get(API_URLS["static"]).json() # use synchronous request + for k, v in static.items(): + try: + v = {w["id"]: w for w in v} + except (KeyError, TypeError): + pass + setattr(self, k, v) + setattr(self, "current_gameweek", next(event for event in static["events"] if event["is_current"])['id']) async def get_user(self, user_id=None, return_json=False): """Returns the user with the given ``user_id``. @@ -91,19 +101,19 @@ async def get_teams(self, team_ids=None, return_json=False): :type return_json: bool :rtype: list """ - url = API_URLS["static"] - teams = await fetch(self.session, url) - teams = teams["teams"] + teams = getattr(self, "teams") if team_ids: team_ids = set(team_ids) - teams = [team for team in teams if team["id"] in team_ids] + teams = [team for team in teams.values() if team["id"] in team_ids] + else: + teams = [team for team in teams.values()] if return_json: return teams - return [Team(team_information, self.session) - for team_information in teams] + return {team_information["id"]: Team(team_information, self.session) + for team_information in teams} async def get_team(self, team_id, return_json=False): """Returns the team with the given ``team_id``. @@ -145,9 +155,8 @@ async def get_team(self, team_id, return_json=False): """ assert 0 < int( team_id) < 21, "Team ID must be a number between 1 and 20." - url = API_URLS["static"] - teams = await fetch(self.session, url) - team = next(team for team in teams["teams"] + teams = getattr(self, "teams") + team = next(team for team in teams.values() if team["id"] == int(team_id)) if return_json: @@ -177,6 +186,7 @@ async def get_player_summary(self, player_id, return_json=False): return PlayerSummary(player_summary) + # not used async def get_player_summaries(self, player_ids, return_json=False): """Returns a list of summaries of players whose ID are in the ``player_ids`` list. @@ -206,7 +216,7 @@ async def get_player_summaries(self, player_ids, return_json=False): return [PlayerSummary(player_summary) for player_summary in player_summaries] - async def get_player(self, player_id, players=None, include_summary=False, + async def get_player(self, player_id, players=None, gameweek=None, include_summary=False, return_json=False): """Returns the player with the given ``player_id``. @@ -214,6 +224,7 @@ async def get_player(self, player_id, players=None, include_summary=False, https://fantasy.premierleague.com/api/bootstrap-static/ https://fantasy.premierleague.com/api/element-summary/1/ (optional) + :param gameweek: the current gameweek data (for applying live scores) :param player_id: A player's ID. :type player_id: string or int :param list players: (optional) A list of players. @@ -226,11 +237,10 @@ async def get_player(self, player_id, players=None, include_summary=False, :raises ValueError: Player with ``player_id`` not found """ if not players: - players = await fetch(self.session, API_URLS["static"]) - players = players["elements"] + players = getattr(self, "elements") try: - player = next(player for player in players + player = next(player for player in players.values() if player["id"] == player_id) except StopIteration: raise ValueError(f"Player with ID {player_id} not found") @@ -240,12 +250,19 @@ async def get_player(self, player_id, players=None, include_summary=False, player["id"], return_json=True) player.update(player_summary) + player["image_url"] = (f'https://platform-static-files.s3.amazonaws.com' + f'/premierleague/photos/players/110x140/p{player["code"]}.png') + + if gameweek: + player["live_score"] = gameweek.elements[player_id]["stats"]["total_points"] + player["did_not_play"] = gameweek.elements[player_id]["did_not_play"] + if return_json: return player return Player(player, self.session) - async def get_players(self, player_ids=None, include_summary=False, + async def get_players(self, player_ids=None, include_summary=False, include_live=False, return_json=False): """Returns either a list of *all* players, or a list of players whose IDs are in the given ``player_ids`` list. @@ -254,6 +271,7 @@ async def get_players(self, player_ids=None, include_summary=False, https://fantasy.premierleague.com/api/bootstrap-static/ https://fantasy.premierleague.com/api/element-summary/1/ (optional) + :param include_live: (optional) include a player's live score :param list player_ids: (optional) A list of player IDs :param boolean include_summary: (optional) Includes a player's summary if ``True``. @@ -263,19 +281,31 @@ async def get_players(self, player_ids=None, include_summary=False, :type return_json: bool :rtype: list """ - players = await fetch(self.session, API_URLS["static"]) - players = players["elements"] + players = getattr(self, "elements") + gameweek = None if not player_ids: - player_ids = [player["id"] for player in players] + player_ids = [player["id"] for player in players.values()] + + current_gameweek_id = getattr(self, "current_gameweek") + gameweeks = getattr(self, "events") + current_gameweek = gameweeks[current_gameweek_id] + current_gameweek_finished = current_gameweek["finished"] + include_live = include_live and not current_gameweek_finished + + if include_live: + gameweek = await self.get_gameweek(current_gameweek_id, include_live=include_live) tasks = [asyncio.ensure_future( self.get_player( - player_id, players, include_summary, return_json)) + player_id, players, gameweek, include_summary, return_json)) for player_id in player_ids] players = await asyncio.gather(*tasks) - return players + if return_json: + return [player for player in players if player["id"] in player_ids] + + return {player.id: player for player in players if player.id in player_ids} async def get_fixture(self, fixture_id, return_json=False): """Returns the fixture with the given ``fixture_id``. @@ -352,7 +382,7 @@ async def get_fixtures_by_id(self, fixture_ids, return_json=False): if return_json: return fixtures - return [Fixture(fixture) for fixture in fixtures] + return {fixture["id"]: Fixture(fixture) for fixture in fixtures} async def get_fixtures_by_gameweek(self, gameweek, return_json=False): """Returns a list of all fixtures of the given ``gameweek``. @@ -375,7 +405,7 @@ async def get_fixtures_by_gameweek(self, gameweek, return_json=False): if return_json: return fixtures - return [Fixture(fixture) for fixture in fixtures] + return {fixture["id"]: Fixture(fixture) for fixture in fixtures} async def get_fixtures(self, return_json=False): """Returns a list of *all* fixtures. @@ -402,10 +432,9 @@ async def get_fixtures(self, return_json=False): if return_json: return fixtures - return [Fixture(fixture) for fixture in fixtures] + return {fixture["id"]: Fixture(fixture) for fixture in fixtures} - async def get_gameweek(self, gameweek_id, include_live=False, - return_json=False): + async def get_gameweek(self, gameweek_id, include_live=False, return_json=False): """Returns the gameweek with the ID ``gameweek_id``. Information is taken from e.g.: @@ -413,7 +442,7 @@ async def get_gameweek(self, gameweek_id, include_live=False, https://fantasy.premierleague.com/api/event/1/live/ :param int gameweek_id: A gameweek's ID. - :param bool include_summary: (optional) Includes a gameweek's live data + :param bool include_live: (optional) Includes a gameweek's live data if ``True``. :param return_json: (optional) Boolean. If ``True`` returns a ``dict``, if ``False`` returns a :class:`Gameweek` object. Defaults to @@ -422,11 +451,10 @@ async def get_gameweek(self, gameweek_id, include_live=False, :rtype: :class:`Gameweek` or ``dict`` """ - static_gameweeks = await fetch(self.session, API_URLS["static"]) - static_gameweeks = static_gameweeks["events"] + static_gameweeks = getattr(self, "events") try: - static_gameweek = next(gameweek for gameweek in static_gameweeks if + static_gameweek = next(gameweek for gameweek in static_gameweeks.values() if gameweek["id"] == gameweek_id) except StopIteration: raise ValueError(f"Gameweek with ID {gameweek_id} not found") @@ -441,16 +469,24 @@ async def get_gameweek(self, gameweek_id, include_live=False, # include live bonus points if not static_gameweek['finished']: fixtures = await self.get_fixtures_by_gameweek(gameweek_id) - fixtures = filter(lambda f: not f.finished, fixtures) + fixtures_not_finished = [fixture for fixture in fixtures.values() if not fixture.finished] bonus_for_gameweek = [] - for fixture in fixtures: + for fixture in fixtures_not_finished: bonus = fixture.get_bonus(provisional=True) bonus_for_gameweek.extend(bonus['a'] + bonus['h']) bonus_for_gameweek = {b['element']: b['value'] for b in bonus_for_gameweek} - for player_id, bonus_points in bonus_for_gameweek: - if live_gameweek["elements"][player_id]["bonus"] == 0: - live_gameweek["elements"][player_id]["bonus"] += bonus_points - live_gameweek["elements"][player_id]["total_points"] += bonus_points + for player_id, bonus_points in bonus_for_gameweek.items(): + if live_gameweek["elements"][player_id]["stats"]["bonus"] == 0: + live_gameweek["elements"][player_id]["stats"]["bonus"] += bonus_points + live_gameweek["elements"][player_id]["stats"]["total_points"] += bonus_points + + # mark players that did not play + fixtures_started = [fixture.id for fixture in fixtures.values() if fixture.started] + for element in live_gameweek["elements"].values(): + player_id = element["id"] + no_minutes = element["stats"]["minutes"] == 0 + game_started = any([game["fixture"] in fixtures_started for game in element["explain"]]) + live_gameweek["elements"][player_id]["did_not_play"] = no_minutes and game_started static_gameweek.update(live_gameweek) @@ -459,8 +495,7 @@ async def get_gameweek(self, gameweek_id, include_live=False, return Gameweek(static_gameweek) - async def get_gameweeks(self, gameweek_ids=None, include_live=False, - return_json=False): + async def get_gameweeks(self, gameweek_ids=None, return_json=False): """Returns either a list of *all* gamweeks, or a list of gameweeks whose IDs are in the ``gameweek_ids`` list. @@ -480,11 +515,15 @@ async def get_gameweeks(self, gameweek_ids=None, include_live=False, gameweek_ids = range(1, 39) tasks = [asyncio.ensure_future( - self.get_gameweek(gameweek_id, include_live, return_json)) + self.get_gameweek(gameweek_id, False, return_json)) for gameweek_id in gameweek_ids] gameweeks = await asyncio.gather(*tasks) - return gameweeks + + if return_json: + return gameweeks + + return {gameweek.id: gameweek for gameweek in gameweeks} async def get_classic_league(self, league_id, return_json=False): """Returns the classic league with the given ``league_id``. Requires diff --git a/fpl/models/player.py b/fpl/models/player.py index 098e993..40167a4 100644 --- a/fpl/models/player.py +++ b/fpl/models/player.py @@ -25,6 +25,8 @@ def __init__(self, player_information, session): self._session = session for k, v in player_information.items(): setattr(self, k, v) + self.did_not_play = False + self.live_score = getattr(self, "event_points") @property async def games_played(self): diff --git a/fpl/models/user.py b/fpl/models/user.py index 25ed616..063b5ec 100644 --- a/fpl/models/user.py +++ b/fpl/models/user.py @@ -1,8 +1,7 @@ import asyncio import json -import aiohttp -from urllib3.util import response +from async_property import async_cached_property from ..constants import API_URLS from ..utils import fetch, logged_in, post, get_headers @@ -103,20 +102,46 @@ def _set_captain(lineup, captain, captain_type, player_ids): player[captain_type] = True -class User(): - """A class representing a user of the Fantasy Premier League. +def _valid_formation(players): + positions = [player.element_type for player in players] + g = positions.count(1) + d = positions.count(2) + m = positions.count(3) + f = positions.count(4) + return all([ + g == 1, + 3 <= d <= 5, + 2 <= m <= 5, + 1 <= f <= 3, + sum([g, d, m, f]) == 11 + ]) + + +def _get_first_xi(picks, players): + return [players[pick["element"]].id for pick in picks if pick["position"] <= 11] + - >>> from fpl import FPL - >>> import aiohttp - >>> import asyncio - >>> - >>> async def main(): - ... async with aiohttp.ClientSession() as session: - ... fpl = FPL(session) - ... user = await fpl.get_user(3808385) - ... print(user) - ... - >>> asyncio.run(main()) +def _get_subs(picks, players): + return [players[pick["element"]].id + for pick in picks + if pick["position"] > 11 + and + not players[pick["element"]].did_not_play] + + +class User: + """A class representing a user of the Fantasy Premier League. + # >>> from fpl import FPL + # >>> import aiohttp + # >>> import asyncio + # >>> + # >>> async def main(): + # ... async with aiohttp.ClientSession() as session: + # ... fpl = FPL(session) + # ... user = await fpl.get_user(3808385) + # ... print(user) + # ... + # >>> asyncio.run(main()) Amos Bastian - Netherlands """ @@ -125,6 +150,11 @@ def __init__(self, user_information, session): for k, v in user_information.items(): setattr(self, k, v) + @async_cached_property + async def history(self): + history = await fetch(self._session, API_URLS["user_history"].format(getattr(self, "id"))) + return history + async def get_gameweek_history(self, gameweek=None): """Returns a list containing the gameweek history of the user. @@ -134,20 +164,14 @@ async def get_gameweek_history(self, gameweek=None): :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: list if gameweek is ``None``, otherwise dict. """ - if hasattr(self, "_history"): - history = self._history - else: - history = await fetch( - self._session, API_URLS["user_history"].format(self.id)) - self._history = history + current = (await self.history)["current"] if gameweek is not None: valid_gameweek(gameweek) - return next(gw for gw in history["current"] - if gw["event"] == gameweek) + return next(gw for gw in current if gw["event"] == gameweek) - return history["current"] + return current async def get_season_history(self): """Returns a list containing the seasonal history of the user. @@ -157,14 +181,8 @@ async def get_season_history(self): :rtype: list """ - if hasattr(self, "_history"): - history = self._history - else: - history = await fetch( - self._session, API_URLS["user_history"].format(self.id)) - - self._history = history - return history["past"] + past = (await self.history)["past"] + return past async def get_chips_history(self, gameweek=None): """Returns a list containing the chip history of the user. @@ -175,25 +193,35 @@ async def get_chips_history(self, gameweek=None): :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: list """ - if hasattr(self, "_history"): - history = self._history - else: - history = await fetch( - self._session, API_URLS["user_history"].format(self.id)) - - self._history = history + chips = (await self.history)["chips"] if gameweek is not None: valid_gameweek(gameweek) try: - return next(chip for chip in history["chips"] - if chip["event"] == gameweek) + return next(chip for chip in chips if chip["event"] == gameweek) except StopIteration: return None - return history["chips"] + return chips + + @async_cached_property + async def picks(self): + """Returns a dict containing the user's picks each gameweek. + Key is the gameweek number, value contains picks of the gameweek. + Information is taken from e.g.: + https://fantasy.premierleague.com/api/entry/91928/event/1/picks/ + :rtype: dict + """ + + tasks = [asyncio.ensure_future( + fetch(self._session, API_URLS["user_picks"].format(getattr(self, "id"), gameweek)) + ) for gameweek in range(getattr(self, "started_event"), getattr(self, "current_event") + 1)] + picks = await asyncio.gather(*tasks) + picks = {p["entry_history"]["event"]: p for p in picks} + return picks - async def get_picks(self, gameweek=None): + @property + async def picks_for_current_gameweek(self): """Returns a dict containing the user's picks each gameweek. Key is the gameweek number, value contains picks of the gameweek. @@ -201,37 +229,96 @@ async def get_picks(self, gameweek=None): Information is taken from e.g.: https://fantasy.premierleague.com/api/entry/91928/event/1/picks/ - :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: dict """ - if hasattr(self, "_picks"): - picks = self._picks + + current_gameweek = getattr(self, "current_event") + picks = await self.picks + return picks[current_gameweek] + + async def get_formation(self, players): + """ + Get a user's formation for the current gameweek + :param players: (required) player dict from fpl.get_players() + :return: The user's formation + :rtype string + """ + picks = await self.picks_for_current_gameweek + picks = picks["picks"] + first_xi = [players[player_id].element_type for player_id in _get_first_xi(picks, players)] + d = first_xi.count(2) + m = first_xi.count(3) + f = first_xi.count(4) + return f"{d}-{m}-{f}" + + async def get_live_score(self, players): + """ + Get a user's live score for the current gameweek + :param players: (required) player dict from fpl.get_players() with live scores + :return: The user's live score + :rtype int + """ + picks = await self.picks_for_current_gameweek + active_chip = picks["active_chip"] + points_hit = picks["entry_history"]["event_transfers_cost"] + picks = picks["picks"] + + first_xi = set(_get_first_xi(picks, players)) + subs = _get_subs(picks, players) + subs_out = [player_id for player_id in first_xi if players[player_id].did_not_play] + + if active_chip == "bboost": # count scores for all 15 players if bench boost chip is active + first_xi.update(subs) else: - tasks = [asyncio.ensure_future( - fetch(self._session, - API_URLS["user_picks"].format(self.id, gameweek))) - for gameweek in range(self.started_event, - self.current_event + 1)] - picks = await asyncio.gather(*tasks) - self._picks = picks + # perform auto-subs if applicable + for sub_out in subs_out: + i = 0 + first_xi.remove(sub_out) # remove first sub out + first_xi.add(subs[0]) # add first sub in + valid_formation = _valid_formation([players[player_id] for player_id in first_xi]) + # check formation is valid + # if formation not valid, move on to the next sub + while not valid_formation and i <= 3: + i += 1 + first_xi.remove(subs[i - 1]) # remove previous player subbed in + first_xi.add(subs[i]) # add next sub in + # check formation + valid_formation = _valid_formation([players[player_id] for player_id in first_xi]) + subs.pop(i) # when complete, remove the subbed in player from the list of subs + + first_xi_live_scores = [players[player_id].live_score for player_id in first_xi] + + captain = next(pick["element"] for pick in picks if pick["is_captain"]) + try: + vice_captain = next(pick["element"] for pick in picks if pick["is_vice_captain"]) + except StopIteration: + vice_captain = None + + captain_points = players[captain].live_score + if captain in subs_out + subs: + captain_points = players[vice_captain].live_score + + if active_chip == "3xc": # for triple captain chip + captain_points *= 2 + + print(sum(first_xi_live_scores), captain_points, points_hit) + + return sum(first_xi_live_scores) + captain_points - points_hit + + async def get_live_total_points(self, players): + """ + Get a user's live total points + :param players: (required) player dict from fpl.get_players() with live scores + :return: The user's live overall points total + :rtype int + """ + history = await self.get_gameweek_history() + history = [x["total_points"] - x["event_transfers cost"] for x in history + if x["event"] < getattr(self, "current_event")] - if gameweek is not None: - valid_gameweek(gameweek) - try: - pick = next(pick for pick in picks - if pick["entry_history"]["event"] == gameweek) - except StopIteration: - return {} - else: - return {pick["entry_history"]["event"]: pick["picks"]} + live_score = + await self.get_live_score(players) - picks_out = {} - for pick in picks: - try: - picks_out[pick["entry_history"]["event"]] = pick["picks"] - except KeyError: - pass - return picks_out + return sum(history) + live_score async def get_active_chips(self, gameweek=None): """Returns a list containing the user's active chip for each gameweek, @@ -243,15 +330,7 @@ async def get_active_chips(self, gameweek=None): :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: list """ - if hasattr(self, "_picks"): - picks = self._picks - else: - tasks = [asyncio.ensure_future( - fetch(self._session, - API_URLS["user_picks"].format(self.id, gameweek))) - for gameweek in range(1, self.current_event + 1)] - picks = await asyncio.gather(*tasks) - self._picks = picks + picks = await self.picks if gameweek is not None: valid_gameweek(gameweek) @@ -273,15 +352,8 @@ async def get_automatic_substitutions(self, gameweek=None): :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: list """ - if hasattr(self, "_picks"): - picks = self._picks - else: - tasks = [asyncio.ensure_future( - fetch(self._session, - API_URLS["user_picks"].format(self.id, gameweek))) - for gameweek in range(1, self.current_event + 1)] - picks = await asyncio.gather(*tasks) - self._picks = picks + + picks = await self.picks if gameweek is not None: valid_gameweek(gameweek) @@ -306,13 +378,37 @@ async def get_team(self): raise Exception("User must be logged in.") response = await fetch( - self._session, API_URLS["user_team"].format(self.id)) + self._session, API_URLS["user_team"].format(getattr(self, "id"))) if response == {"details": "You cannot view this entry"}: raise ValueError("User ID does not match provided email address!") return response["picks"] + @async_cached_property + async def transfers(self): + """Returns either a list of all the user's transfers, or a list of + transfers made in the given gameweek. + + Information is taken from e.g.: + https://fantasy.premierleague.com/api/entry/91928/transfers/ + + :rtype: list + """ + return await fetch(self._session, API_URLS["user_transfers"].format(getattr(self, "id"))) + + @property + async def transfers_for_current_gameweek(self): + current_gameweek = getattr(self, "current_event") + picks = await self.picks_for_current_gameweek + transfers = await self.transfers + transfers = [transfer for transfer in transfers if transfer["event"] == current_gameweek] + return { + "transfers_made": picks["entry_history"]["event_transfers"], + "transfers_cost": picks["entry_history"]["event_transfers_cost"], + "transfers": transfers + } + async def get_transfers(self, gameweek=None): """Returns either a list of all the user's transfers, or a list of transfers made in the given gameweek. @@ -323,11 +419,8 @@ async def get_transfers(self, gameweek=None): :param gameweek: (optional): The gameweek. Defaults to ``None``. :rtype: list """ - transfers = getattr(self, "_transfers", None) - if not transfers: - transfers = await fetch( - self._session, API_URLS["user_transfers"].format(self.id)) - self._transfers = transfers + + transfers = await self.transfers if gameweek is not None: valid_gameweek(gameweek) @@ -349,27 +442,21 @@ async def get_latest_transfers(self): raise Exception("User must be logged in.") transfers = await fetch( - self._session, API_URLS["user_latest_transfers"].format(self.id)) + self._session, API_URLS["user_latest_transfers"].format(getattr(self, "id"))) return transfers - # async def get_wildcards(self): - # """Returns a list containing information about when (and if) the user - # has played their wildcard(s). - - # Information is taken from e.g.: - # https://fantasy.premierleague.com/drf/entry/3808385/transfers - - # :rtype: list - # """ - # if hasattr(self, "_transfers"): - # return self._transfers["wildcards"] + async def get_wildcards(self): + """Returns a list containing information about when (and if) the user + has played their wildcard(s). - # transfers = await fetch( - # self._session, API_URLS["user_transfers"].format(self.id)) + Information is taken from e.g.: + https://fantasy.premierleague.com/drf/entry/3808385/history - # self._transfers = transfers - # return transfers["wildcards"] + :rtype: list + """ + chips_played = await self.get_chips_history() + return [chip for chip in chips_played if chip["name"] == "wildcard"] async def get_watchlist(self): """Returns the user's watchlist. Requires the user to have logged in @@ -386,14 +473,12 @@ async def get_watchlist(self): me = await fetch(self._session, API_URLS["me"]) return me["watched"] - def _get_transfer_payload( - self, players_out, players_in, user_team, players, wildcard, - free_hit): + def _get_transfer_payload(self, players_out, players_in, user_team, players, wildcard, free_hit): """Returns the payload needed to make the desired transfers.""" payload = { "confirmed": False, - "entry": self.id, - "event": self.current_event + 1, + "entry": getattr(self, "id"), + "event": getattr(self, "current_event") + 1, "transfers": [], "wildcard": wildcard, "freehit": free_hit @@ -428,7 +513,7 @@ async def transfer(self, players_out, players_in, max_hit=60, :param wildcard: bool, optional :param free_hit: Boolean for playing free hit, defaults to False :param free_hit: bool, optional - :return: Returns the response given by a succesful transfer. + :return: Returns the response given by a successful transfer. :rtype: dict """ if wildcard and free_hit: @@ -537,8 +622,7 @@ async def _create_new_lineup(self, players_in, players_out, lineup): lineup[in_i][is_vc], lineup[out_i][is_vc]) starters, subs = lineup[:11], lineup[11:] - new_starters = sorted(starters, key=lambda x: ( - x["element_type"] - 1) * 100 + x["position"]) + new_starters = sorted(starters, key=lambda x: (x["element_type"] - 1) * 100 + x["position"]) lineup = new_starters + subs for position, player in enumerate(lineup): @@ -564,7 +648,7 @@ async def _post_substitutions(self, lineup): headers = get_headers("https://fantasy.premierleague.com/a/team/my") await post( - self._session, API_URLS["user_team"].format(self.id) + "/", + self._session, API_URLS["user_team"].format(getattr(self, "id")) + "/", payload=payload, headers=headers) async def _captain_helper(self, captain, captain_type): @@ -644,5 +728,5 @@ async def substitute(self, players_in, players_out, captain=None, await self._post_substitutions(lineup) def __str__(self): - return (f"{self.player_first_name} {self.player_last_name} - " - f"{self.player_region_name}") + return (f"{getattr(self, 'player_first_name')} {getattr(self, 'player_last_name')} - " + f"{getattr(self, 'player_region_name')}") diff --git a/fpl/utils.py b/fpl/utils.py index e39f290..a690622 100644 --- a/fpl/utils.py +++ b/fpl/utils.py @@ -6,18 +6,21 @@ headers = {"User-Agent": "https://github.com/amosbastian/fpl"} -async def fetch(session, url): +async def fetch(session, url, params=None): + if params is None: + params = {} while True: + # noinspection PyBroadException try: - async with session.get(url, headers=headers) as response: + async with session.get(url, headers=headers, params=params) as response: assert response.status == 200 return await response.json() except Exception: pass -async def post(session, url, payload, headers): - async with session.post(url, data=payload, headers=headers) as response: +async def post(session, url, payload, headers_): + async with session.post(url, data=payload, headers=headers_) as response: return await response.json() @@ -151,6 +154,7 @@ def logged_in(session): :return: True if user is logged in else False :rtype: bool """ + # noinspection PyTypeChecker return "csrftoken" in session.cookie_jar.filter_cookies( "https://users.premierleague.com/") diff --git a/setup.py b/setup.py index 8aa7157..36847b4 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ "Source": "https://github.com/amosbastian/fpl" }, install_requires=[ + "async_property", "Click", "colorama", "codecov", @@ -37,6 +38,7 @@ "pytest-cov", "pytest-mock", "pytest", + "requests" ], entry_points=""" [console_scripts] diff --git a/tests/test_fpl.py b/tests/test_fpl.py index 4390bfb..036fdb4 100644 --- a/tests/test_fpl.py +++ b/tests/test_fpl.py @@ -17,6 +17,21 @@ async def test_init(self, loop): session = aiohttp.ClientSession() fpl = FPL(session) assert fpl.session is session + keys = [ + "events", + "game_settings", + "phases", + "teams", + "elements", + "element_types", + "element_stats", + "total_players", + "current_gameweek", + ] + assert all([hasattr(fpl, key) for key in keys]) + assert all([isinstance(getattr(fpl, key), dict) for key in keys[:-3]]) + assert isinstance(getattr(fpl, keys[-3]), list) + assert all([isinstance(getattr(fpl, key), int) for key in keys[-2:]]) await session.close() async def test_user(self, loop, fpl): @@ -50,20 +65,20 @@ async def test_team(self, loop, fpl): async def test_teams(self, loop, fpl): teams = await fpl.get_teams() - assert isinstance(teams, list) - assert len(teams) == 20 - assert isinstance(teams[0], Team) + assert isinstance(teams, dict) + assert len(teams.values()) == 20 + assert isinstance(teams[10], Team) teams = await fpl.get_teams(return_json=True) assert isinstance(teams, list) assert len(teams) == 20 - assert isinstance(teams[0], dict) + assert isinstance(teams[1], dict) teams = await fpl.get_teams(team_ids=[1, 2, 3]) - assert isinstance(teams, list) - assert len(teams) == 3 - assert isinstance(teams[0], Team) - assert [team.id for team in teams] == [1, 2, 3] + assert isinstance(teams, dict) + assert len(teams.values()) == 3 + assert isinstance(teams[1], Team) + assert [team.id for team in teams.values()] == [1, 2, 3] async def test_player_summary(self, loop, fpl): # test non positive id @@ -107,19 +122,28 @@ async def test_player(self, loop, fpl): async def test_players(self, loop, fpl): players = await fpl.get_players() - assert isinstance(players, list) - assert isinstance(players[0], Player) + assert isinstance(players, dict) + assert isinstance(players[1], Player) players = await fpl.get_players(return_json=True) assert isinstance(players, list) assert isinstance(players[0], dict) players = await fpl.get_players([1, 2, 3]) - assert len(players) == 3 + assert len(players.values()) == 3 + + players = await fpl.get_players([1, 2, 3], include_summary=True) + assert len(players.values()) == 3 + summary_keys = ("history_past", "history", "fixtures") + assert all([isinstance(getattr(players[2], key), list) for key in summary_keys]) - players = await fpl.get_players([1, 2, 3], True) + players = await fpl.get_players([1, 2, 3], include_live=True) + assert len(players.values()) == 3 + assert isinstance(getattr(players[2], "live_score"), int) + + players = await fpl.get_players([1, 2, 3], include_summary=True, return_json=True) assert len(players) == 3 - assert isinstance(players[0].fixtures, list) + assert all([isinstance(players[2][key], list) for key in summary_keys]) async def test_fixture(self, loop, fpl): # test fixture with unknown id @@ -139,8 +163,8 @@ async def test_fixtures_by_id(self, loop, fpl): assert len(fixtures) == 0 fixtures = await fpl.get_fixtures_by_id([100, 200, 300]) - assert isinstance(fixtures, list) - assert isinstance(fixtures[0], Fixture) + assert isinstance(fixtures, dict) + assert isinstance(fixtures[100], Fixture) fixtures = await fpl.get_fixtures_by_id( [100, 200, 300], return_json=True) @@ -153,8 +177,8 @@ async def test_fixtures_by_id(self, loop, fpl): async def test_fixtures_by_gameweek(self, loop, fpl): for gameweek in range(1, 39): fixtures = await fpl.get_fixtures_by_gameweek(gameweek) - assert isinstance(fixtures, list) - assert isinstance(fixtures[0], Fixture) + assert isinstance(fixtures, dict) + assert all([isinstance(fixtures[fixture_id], Fixture) for fixture_id in fixtures.keys()]) fixtures = await fpl.get_fixtures_by_gameweek( gameweek, return_json=True) @@ -162,17 +186,17 @@ async def test_fixtures_by_gameweek(self, loop, fpl): async def test_fixtures(self, loop, fpl): fixtures = await fpl.get_fixtures() - assert isinstance(fixtures, list) - assert isinstance(fixtures[0], Fixture) + assert isinstance(fixtures, dict) + assert isinstance(fixtures[10], Fixture) fixtures = await fpl.get_fixtures(return_json=True) assert isinstance(fixtures[0], dict) async def test_gameweeks(self, loop, fpl): gameweeks = await fpl.get_gameweeks() - assert isinstance(gameweeks, list) - assert len(gameweeks) == 38 - assert isinstance(gameweeks[0], Gameweek) + assert isinstance(gameweeks, dict) + assert len(gameweeks.values()) == 38 + assert isinstance(gameweeks[10], Gameweek) gameweeks = await fpl.get_gameweeks([1, 2, 3], return_json=True) assert isinstance(gameweeks, list) @@ -182,6 +206,7 @@ async def test_gameweeks(self, loop, fpl): async def test_gameweek(self, loop, fpl): gameweek = await fpl.get_gameweek(20) assert isinstance(gameweek, Gameweek) + # noinspection PyUnresolvedReferences assert gameweek.id == 20 assert not hasattr(gameweek, "elements") @@ -193,6 +218,7 @@ async def test_gameweek(self, loop, fpl): gameweek = await fpl.get_gameweek(1, include_live=True) assert isinstance(gameweek, Gameweek) assert hasattr(gameweek, "elements") + # noinspection PyUnresolvedReferences assert isinstance(gameweek.elements, dict) gameweek = await fpl.get_gameweek(1, include_live=True, return_json=True) @@ -200,8 +226,6 @@ async def test_gameweek(self, loop, fpl): assert "elements" in gameweek.keys() assert isinstance(gameweek["elements"], dict) - - @pytest.mark.skip(reason="Cannot currently test it.") async def test_classic_league(self, loop, fpl): await fpl.login() classic_league = await fpl.get_classic_league(173226) @@ -253,12 +277,13 @@ async def test_points_against(self, loop, fpl): points_against = await fpl.get_points_against() assert isinstance(points_against, dict) + # noinspection PyPep8Naming async def test_FDR(self, loop, fpl): - def test_main(fdr): - assert isinstance(fdr, dict) + def test_main(fdr_): + assert isinstance(fdr_, dict) location_extrema = {"H": [], "A": []} - for _, positions in fdr.items(): + for _, positions in fdr_.items(): for location in positions.values(): location_extrema["H"].append(location["H"]) location_extrema["A"].append(location["A"])