Source code for espncricinfo.player

"""Player records from the ESPN core API and the Statsguru pages."""

import csv
from datetime import date
from functools import cached_property
from pathlib import Path
from typing import Any

from bs4 import BeautifulSoup

from espncricinfo import _core
from espncricinfo.exceptions import PlayerNotFoundError

_ATHLETES = "http://core.espnuk.org/v2/sports/cricket/athletes"


[docs] class Player: """A player, loaded from the ESPN core API athlete record. Example values are for ``Player(277916)``, Ajinkya Rahane. """ #: ESPNcricinfo player id. player_id: int #: Name as printed on scorecards, ``'Ajinkya Rahane'``. name: str #: ``'Ajinkya'``. first_name: str #: ``'Ajinkya Madhukar Rahane'``. full_name: str #: Date of birth, or None if ESPN has none. date_of_birth: date | None #: Age in years as ESPN reports it, or None. age: int | None #: ``'Top-order batter'``, or None. playing_role: str | None #: ``'Right-hand bat'``, or None. batting_style: str | None #: ``'Right-arm medium'``, or None. bowling_style: str | None #: The core API athlete record. json: dict[str, Any] def __init__(self, player_id: int | str) -> None: """Load the player record. Args: player_id: ESPNcricinfo player id (the number in a profile URL). """ self.player_id = int(player_id) self.json = self._get_json() record = self.json self.name = record["name"] self.first_name = record.get("firstName", "") self.full_name = record.get("fullName", self.name) born = record.get("dateOfBirth") self.date_of_birth = date.fromisoformat(born[:10]) if born else None self.age = record.get("age") self.playing_role = (record.get("position") or {}).get("name") styles = { style.get("type"): style.get("description") for style in record.get("style") or [] } self.batting_style = styles.get("batting") self.bowling_style = styles.get("bowling") def __str__(self) -> str: """Return the player's name.""" return self.name def __repr__(self) -> str: """Return ``Player(player_id)``.""" return f"Player({self.player_id})" def _get_json(self) -> dict[str, Any]: response = _core.http_get(f"{_ATHLETES}/{self.player_id}") if response.status_code == 404: raise PlayerNotFoundError(f"Player not found: {self.player_id}") response.raise_for_status() return response.json() @cached_property def major_teams(self) -> list[str]: """Names of the player's major teams (one request per team, on first access).""" teams = [] for reference in self.json.get("majorTeams", []): response = _core.http_get(reference["$ref"]) response.raise_for_status() team = response.json() name = team.get("longName") or team.get("displayName") or team.get("name") if name: teams.append(name) return teams def _write_stats_table(self, query: str, table_index: int, file_name: str) -> None: """Write one table of the player's stats page to ``file_name`` as CSV. Each row's cells are on separate lines in ESPN's HTML, so a row's text split on newlines gives its cells (with an empty leading cell). """ url = ( "https://stats.espncricinfo.com/ci/engine/player/" f"{self.player_id}.html?{query}" ) response = _core.http_get(url) soup = BeautifulSoup(response.text, "html.parser") table = soup.find_all("table")[table_index] rows = [tr.text.splitlines() for tr in table.find_all("tr")] with Path(file_name).open("w", newline="") as csv_file: csv.writer(csv_file).writerows(rows)
[docs] def get_career_averages( self, file_name: str | None = None, match_format: int = 11, data_type: str = "allround", ) -> None: """Write career averages (overall and by span) to a CSV file. Args: file_name: Output path. Defaults to ``{player_id}_{match_format}_{data_type}_career_averages.csv``. match_format: 1 Test, 2 ODI, 3 T20I, 11 all international, 20 youth Test, 21 youth ODI. data_type: ``allround``, ``batting``, ``bowling`` or ``fielding``. """ if file_name is None: file_name = ( f"{self.player_id}_{match_format}_{data_type}_career_averages.csv" ) query = f"class={match_format};template=results;type={data_type}" self._write_stats_table(query, 2, file_name)
[docs] def get_career_summary( self, file_name: str | None = None, match_format: int = 11, data_type: str = "allround", ) -> None: """Write the career summary (one row per format, venue, opponent...) to CSV. Args: file_name: Output path. Defaults to ``{player_id}_{match_format}_{data_type}_career_summary.csv``. match_format: 1 Test, 2 ODI, 3 T20I, 11 all international, 20 youth Test, 21 youth ODI. data_type: ``allround``, ``batting``, ``bowling`` or ``fielding``. """ if file_name is None: file_name = ( f"{self.player_id}_{match_format}_{data_type}_career_summary.csv" ) query = f"class={match_format};template=results;type={data_type}" self._write_stats_table(query, 3, file_name)
[docs] def get_data( self, file_name: str | None = None, match_format: int = 11, data_type: str = "allround", view: str = "match", ) -> None: """Write the player's record match by match (or another view) to CSV. Args: file_name: Output path. Defaults to ``{player_id}_{match_format}_{data_type}_{view}.csv``. match_format: 1 Test, 2 ODI, 3 T20I, 11 all international, 20 youth Test, 21 youth ODI. data_type: ``allround``, ``batting``, ``bowling`` or ``fielding``. view: ``match``, ``innings``, ``cumulative``, ``reverse_cumulative``, ``series``, ``tour`` or ``ground``. """ if file_name is None: file_name = f"{self.player_id}_{match_format}_{data_type}_{view}.csv" query = f"class={match_format};template=results;type={data_type};view={view}" self._write_stats_table(query, 3, file_name)