Add Python examples for FPL teams, league standings and transfers
This commit is contained in:
4 files changed
+304
No files matched your search
@@ -0,0 +1,74 @@
|
||||
# Fantasy Premier League API: three Python examples
|
||||
|
||||
These standalone examples use Python 3's standard library. No packages, API key,
|
||||
or login are needed for these public GET requests.
|
||||
|
||||
## Run the examples
|
||||
|
||||
Open a terminal in this folder, then run:
|
||||
|
||||
```sh
|
||||
python3 team.py
|
||||
python3 league.py
|
||||
python3 transfers.py
|
||||
```
|
||||
|
||||
`team.py` defaults to fantasy team **2134**. It prints the manager, overall points
|
||||
and rank, then the latest published gameweek's squad, including captain and bench.
|
||||
It joins player IDs from the picks response with names, clubs and positions from
|
||||
the bootstrap response.
|
||||
|
||||
`league.py` defaults to classic league **37803**, **Special Teams, Special Players**.
|
||||
It prints the first page of standings with team names, managers, gameweek points,
|
||||
total points and entry IDs. You can pass an entry ID to the team script.
|
||||
|
||||
`transfers.py` defaults to fantasy team **2134**. It prints public transfer history,
|
||||
newest first, with UTC timestamps, gameweeks, players sold and bought, and their
|
||||
transfer prices in millions of pounds. Player IDs are joined to bootstrap data
|
||||
to display readable names.
|
||||
|
||||
```sh
|
||||
python3 team.py --team-id 2134 --gameweek 7
|
||||
python3 league.py --league-id 37803 --page 2
|
||||
python3 league.py --league-id 37803 --all-pages
|
||||
python3 transfers.py --team-id 2134 --gameweek 7
|
||||
```
|
||||
|
||||
`--all-pages` starts at `--page` (page 1 by default). Run any script with
|
||||
`--help` for its options.
|
||||
|
||||
## What the API calls demonstrate
|
||||
|
||||
Each script builds a URL, sends an HTTP GET with `urllib.request.urlopen`, parses
|
||||
the JSON using `json.load`, and reads fields from Python dictionaries and lists.
|
||||
The small request helper is repeated to keep each example self-contained.
|
||||
|
||||
| Endpoint, relative to `https://fantasy.premierleague.com/api/` | Purpose |
|
||||
| --- | --- |
|
||||
| `entry/2134/` | Fantasy team and manager summary |
|
||||
| `entry/2134/event/7/picks/` | Squad selection for gameweek 7 |
|
||||
| `entry/2134/transfers/` | Public transfer history for the current season |
|
||||
| `bootstrap-static/` | Player, club, position and gameweek reference data |
|
||||
| `leagues-classic/37803/standings/?page_standings=1` | One page of classic league standings |
|
||||
|
||||
Try opening the [team JSON](https://fantasy.premierleague.com/api/entry/2134/)
|
||||
or [league JSON](https://fantasy.premierleague.com/api/leagues-classic/37803/standings/)
|
||||
in your browser to compare the raw response with the printed output.
|
||||
|
||||
## Useful details
|
||||
|
||||
- A fantasy team ID is an **entry ID**, not a Premier League club ID or player ID.
|
||||
- Gameweek selections are public after the deadline. This does not show a
|
||||
manager's unpublished changes for the next deadline. The role column describes
|
||||
the selected lineup; it does not apply automatic substitutions or chip scoring.
|
||||
- The league example is for **classic** leagues, not head-to-head leagues.
|
||||
- The transfers example shows only the history published by the public API.
|
||||
Transfers for an upcoming deadline may not be public yet. Prices are the sale
|
||||
and purchase prices recorded for each transfer, not current player prices;
|
||||
they do not indicate transfer points deductions.
|
||||
- Standings are paginated; `has_next` tells the script whether another page exists.
|
||||
Newly joined entries may not appear in the ranked standings yet.
|
||||
- Data and IDs are season-specific. These public website endpoints can change;
|
||||
use the response's league name to confirm the intended league after a reset.
|
||||
- Requests have a 20-second timeout. HTTP/network errors produce a message and a
|
||||
nonzero exit status. Avoid repeatedly polling the service in a tight loop.
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Print classic mini-league standings, including optional pagination."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from urllib.error import URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
BASE_URL = "https://fantasy.premierleague.com/api/"
|
||||
|
||||
|
||||
def get_json(path):
|
||||
"""Send an HTTP GET request and turn the JSON response into a Python dict."""
|
||||
request = Request(
|
||||
BASE_URL + path,
|
||||
headers={"User-Agent": "FPL-API-Demo/1.0", "Accept": "application/json"},
|
||||
)
|
||||
with urlopen(request, timeout=20) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def positive_int(value):
|
||||
number = int(value)
|
||||
if number < 1:
|
||||
raise argparse.ArgumentTypeError("must be a positive integer")
|
||||
return number
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--league-id", type=positive_int, default=37803)
|
||||
parser.add_argument("--page", type=positive_int, default=1)
|
||||
parser.add_argument(
|
||||
"--all-pages", action="store_true", help="fetch all pages from --page onwards"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
page = args.page
|
||||
while True:
|
||||
# This example uses classic scoring; head-to-head leagues use other endpoints.
|
||||
data = get_json(
|
||||
f"leagues-classic/{args.league_id}/standings/?page_standings={page}"
|
||||
)
|
||||
if page == args.page:
|
||||
print(f"League: {data['league']['name']} (ID {args.league_id})")
|
||||
print(f"\nPage {page}")
|
||||
print(f"{'Rank':<6} {'Team':<30} {'Manager':<26} {'GW':>5} {'Total':>7} {'Entry ID':>10}")
|
||||
standings = data["standings"]
|
||||
for entry in standings["results"]:
|
||||
print(
|
||||
f"{entry['rank']:<6} {entry['entry_name']:<30} "
|
||||
f"{entry['player_name']:<26} {entry['event_total']:>5} "
|
||||
f"{entry['total']:>7} {entry['entry']:>10}"
|
||||
)
|
||||
if not standings["results"]:
|
||||
print("No ranked entries on this page.")
|
||||
if not standings["has_next"]:
|
||||
break
|
||||
if not args.all_pages:
|
||||
print(f"\nMore results available: use --page {page + 1} or --all-pages.")
|
||||
break
|
||||
page += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (URLError, TimeoutError, OSError, ValueError, KeyError) as exc:
|
||||
print(f"Unable to load league data: {exc}", file=sys.stderr)
|
||||
print("Check the classic league ID and connection. The API may also be unavailable.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Show an FPL team's summary and published gameweek squad (no login needed)."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from urllib.error import URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
BASE_URL = "https://fantasy.premierleague.com/api/"
|
||||
|
||||
|
||||
def get_json(path):
|
||||
"""Send an HTTP GET request and turn the JSON response into a Python dict."""
|
||||
request = Request(
|
||||
BASE_URL + path,
|
||||
headers={"User-Agent": "FPL-API-Demo/1.0", "Accept": "application/json"},
|
||||
)
|
||||
with urlopen(request, timeout=20) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def positive_int(value):
|
||||
number = int(value)
|
||||
if number < 1:
|
||||
raise argparse.ArgumentTypeError("must be a positive integer")
|
||||
return number
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--team-id", type=positive_int, default=2134)
|
||||
parser.add_argument("--gameweek", type=positive_int, choices=range(1, 39))
|
||||
args = parser.parse_args()
|
||||
|
||||
# An entry is a manager's fantasy team, rather than a Premier League club.
|
||||
team = get_json(f"entry/{args.team_id}/")
|
||||
print(f"Team: {team['name']} (entry {args.team_id})")
|
||||
print(f"Manager: {team['player_first_name']} {team['player_last_name']}")
|
||||
print(f"Overall points: {team['summary_overall_points']}")
|
||||
print(f"Overall rank: {team['summary_overall_rank']}")
|
||||
|
||||
gameweek = args.gameweek or team.get("current_event")
|
||||
if not gameweek:
|
||||
print("No gameweek squad is available yet.")
|
||||
return
|
||||
|
||||
# Picks contain player IDs. Join them to bootstrap data to get readable names.
|
||||
picks = get_json(f"entry/{args.team_id}/event/{gameweek}/picks/")
|
||||
bootstrap = get_json("bootstrap-static/")
|
||||
players = {player["id"]: player for player in bootstrap["elements"]}
|
||||
clubs = {club["id"]: club["short_name"] for club in bootstrap["teams"]}
|
||||
positions = {
|
||||
position["id"]: position["singular_name_short"]
|
||||
for position in bootstrap["element_types"]
|
||||
}
|
||||
|
||||
print(f"\nGameweek {gameweek} squad (published at the deadline):")
|
||||
print(f"{'Slot':<6} {'Player':<24} {'Club':<6} {'Pos':<5} Role")
|
||||
for pick in sorted(picks["picks"], key=lambda item: item["position"]):
|
||||
player = players[pick["element"]]
|
||||
role = "Starter" if pick["position"] <= 11 else "Bench"
|
||||
if pick["is_captain"]:
|
||||
role += " (captain)"
|
||||
elif pick["is_vice_captain"]:
|
||||
role += " (vice captain)"
|
||||
print(
|
||||
f"{pick['position']:<6} {player['web_name']:<24} "
|
||||
f"{clubs[player['team']]:<6} {positions[player['element_type']]:<5} {role}"
|
||||
)
|
||||
if picks.get("active_chip"):
|
||||
print(f"Active chip: {picks['active_chip']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (URLError, TimeoutError, OSError, ValueError, KeyError) as exc:
|
||||
print(f"Unable to load team data: {exc}", file=sys.stderr)
|
||||
print(
|
||||
"Check the team ID and connection. Gameweek picks are available only "
|
||||
"after that gameweek's deadline. The API may also be unavailable.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Show an FPL team's public transfer history, newest first (no login needed)."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from urllib.error import URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
BASE_URL = "https://fantasy.premierleague.com/api/"
|
||||
|
||||
|
||||
def get_json(path):
|
||||
"""Send an HTTP GET request and parse the JSON into Python objects."""
|
||||
request = Request(
|
||||
BASE_URL + path,
|
||||
headers={"User-Agent": "FPL-API-Demo/1.0", "Accept": "application/json"},
|
||||
)
|
||||
with urlopen(request, timeout=20) as response:
|
||||
return json.load(response)
|
||||
|
||||
|
||||
def positive_int(value):
|
||||
number = int(value)
|
||||
if number < 1:
|
||||
raise argparse.ArgumentTypeError("must be a positive integer")
|
||||
return number
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--team-id", type=positive_int, default=2134)
|
||||
parser.add_argument("--gameweek", type=positive_int, choices=range(1, 39))
|
||||
args = parser.parse_args()
|
||||
|
||||
# Public history contains completed transfers released by the API.
|
||||
transfers = get_json(f"entry/{args.team_id}/transfers/")
|
||||
if args.gameweek is not None:
|
||||
transfers = [item for item in transfers if item["event"] == args.gameweek]
|
||||
transfers.sort(key=lambda item: item["time"], reverse=True)
|
||||
|
||||
print(f"Transfer history for team {args.team_id}")
|
||||
if args.gameweek is not None:
|
||||
print(f"Gameweek: {args.gameweek}")
|
||||
print(f"Transfers: {len(transfers)}")
|
||||
if not transfers:
|
||||
print("No public transfers found for this selection.")
|
||||
return
|
||||
|
||||
# Transfer records contain IDs; bootstrap data supplies readable player names.
|
||||
bootstrap = get_json("bootstrap-static/")
|
||||
players = {player["id"]: player["web_name"] for player in bootstrap["elements"]}
|
||||
|
||||
print(f"\n{'Date/time (UTC)':<20} {'GW':>3} {'Player out':<24} {'Sold':>8} {'Player in':<24} {'Bought':>8}")
|
||||
for transfer in transfers:
|
||||
timestamp = datetime.fromisoformat(transfer["time"].replace("Z", "+00:00"))
|
||||
date = timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
player_out = players.get(transfer["element_out"], f"ID {transfer['element_out']}")
|
||||
player_in = players.get(transfer["element_in"], f"ID {transfer['element_in']}")
|
||||
# API prices are in tenths of a million pounds: 61 means £6.1m.
|
||||
sold = f"£{transfer['element_out_cost'] / 10:.1f}m"
|
||||
bought = f"£{transfer['element_in_cost'] / 10:.1f}m"
|
||||
print(
|
||||
f"{date:<20} {transfer['event']:>3} {player_out:<24} {sold:>8} "
|
||||
f"{player_in:<24} {bought:>8}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (URLError, TimeoutError, OSError, ValueError, KeyError) as exc:
|
||||
print(f"Unable to load transfer data: {exc}", file=sys.stderr)
|
||||
print("Check the team ID and connection. The API may also be unavailable.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in new issue
Block a user