Source code for espncricinfo.series

"""Series (ESPN "league") records from the ESPN core API."""

import re
from functools import cached_property
from typing import Any

from espncricinfo import _core
from espncricinfo._core import collection_refs, get_json
from espncricinfo.exceptions import NoSeasonError, NoSeriesError
from espncricinfo.match_ref import MatchRef
from espncricinfo.seasons import Season

_LEAGUES = "http://core.espnuk.org/v2/sports/cricket/leagues"
_SEASON_KEY = re.compile(r"(\d{4})(?:[/-](\d{2}|\d{4}))?")


[docs] class Series: """A series or league, with its seasons, teams and matches. Example values are for ``Series(1449924)``, the Indian Premier League. """ #: ESPNcricinfo series (league) id. series_id: int #: ``'Indian Premier League'``. name: str #: Short name, e.g. ``'IPL'``. short_name: str #: ESPN abbreviation. abbreviation: str #: URL slug. slug: str #: True for tournaments, False for bilateral series. is_tournament: bool #: ESPNcricinfo web page for the series. url: str | None #: Core API URLs of the series' seasons. seasons: list[str] #: Season years as strings, e.g. ``['2008', ..., '2025']``. years: list[str] #: The core API series record. json: dict[str, Any] def __init__(self, series_id: int | str) -> None: """Load the series record and its list of seasons. Args: series_id: ESPNcricinfo series (league) id. """ self.series_id = int(series_id) self.json = self._get_json(f"{_LEAGUES}/{self.series_id}/") self.name = self.json["name"] self.short_name = self.json["shortName"] self.abbreviation = self.json["abbreviation"] self.slug = self.json["slug"] self.is_tournament = bool(self.json.get("isTournament")) links = self.json.get("links") or [] self.url = links[0]["href"] if links else None self.seasons = collection_refs( f"{_LEAGUES}/{self.series_id}/seasons", NoSeriesError ) self.years = [ref.rstrip("/").rsplit("/", 1)[-1] for ref in self.seasons] def _get_json(self, url: str) -> dict[str, Any]: response = _core.http_get(url) if response.status_code == 404: raise NoSeriesError(f"Series not found: {self.series_id}") response.raise_for_status() return response.json() def __str__(self) -> str: """Return the series name.""" return self.name def __repr__(self) -> str: """Return ``Series(series_id)``.""" return f"Series({self.series_id})" @cached_property def events(self) -> list[dict[str, Any]]: """Core API event records for the series' first page of events. One request per event, made on first access. """ refs = self._get_json(f"{_LEAGUES}/{self.series_id}/events").get("items", []) return [self._get_json(item["$ref"]) for item in refs] @cached_property def season_objects(self) -> list[Season]: """Every season of the series, loaded on first access.""" return [Season.from_reference(url) for url in self.seasons]
[docs] def season(self, key: int | str) -> Season: """Return one season, by year or by ESPNcricinfo's two-year label. Multi-year seasons live under their own series id, which this exposes as ``season.series_id``: for the Big Bash League, ``Series(8044).season("2024/25").series_id`` is ``1443056``. Args: key: ``2024``, ``"2024"``, ``"2024/25"``, ``"2024-25"`` or ``"2024/2025"``. A bare year matches the season starting then. Returns: The matching season. Raises: NoSeasonError: If the series has no such season. """ match = _SEASON_KEY.fullmatch(str(key).strip()) if match is None: raise NoSeasonError(f"Not a season year or label: {key!r}") start, end = match.groups() ref = next( (r for r in self.seasons if r.rstrip("/").endswith(f"/{start}")), None ) if ref is None: raise NoSeasonError(f"{self.name} has no season starting in {start}") season = Season.from_reference(ref) if end is not None and season.label != f"{start}/{end[-2:]}": raise NoSeasonError( f"{self.name} {start} season is labeled {season.label!r}, not {key!r}" ) return season
@cached_property def current_season(self) -> Season | None: """The season ESPN marks as current, or None.""" reference = (self.json.get("season") or {}).get("$ref") return Season.from_reference(reference) if reference else None @cached_property def matches(self) -> list[MatchRef]: """MatchRefs for every match across all seasons, without duplicates.""" if self.seasons: return list( dict.fromkeys( ref for season in self.season_objects for ref in season.match_refs ) ) reference = (self.json.get("events") or {}).get("$ref") urls = collection_refs( reference or f"{_LEAGUES}/{self.series_id}/events", NoSeriesError ) return [MatchRef.from_core_event_url(url) for url in urls] @cached_property def current_matches(self) -> list[MatchRef]: """Matches whose ESPN status is in progress (two requests per match).""" active = [] for ref in self.matches: event = get_json( f"{_LEAGUES}/{ref.series_id}/events/{ref.match_id}", NoSeriesError ) for competition in event.get("competitions", []): status_url = (competition.get("status") or {}).get("$ref") if status_url and ( get_json(status_url, NoSeriesError).get("type", {}).get("state") == "in" ): active.append(ref) break return active