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