Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Added

- `Match(match_id)` works without a series ID: the match page is found from
the match ID and `series_id` is read from it (#50).
- `Series.season(key)` finds a season by year or by ESPNcricinfo's two-year
label (`"2024/25"`), and `Season.label` gives that label. Multi-year seasons
live under their own series ID, now reachable as `season.series_id` (#76).
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A Python client for ESPNcricinfo match, series, season, team and player data.

## Description

Match pages are loaded with Playwright (WebKit), because ESPN's CDN refuses plain HTTP clients; player, series, season and team records come from ESPN's core JSON API. A `Match` needs both a `match_id` and a `series_id`, which `Match.get_recent_matches()` and `Series(...).matches` provide.
Match pages are loaded with Playwright (WebKit), because ESPN's CDN refuses plain HTTP clients; player, series, season and team records come from ESPN's core JSON API. A `Match` needs only a `match_id`; `Match.get_recent_matches()` and `Series(...).matches` list them.

Disclaimer: This library is not intended for commercial use and neither it nor its creator has any affiliation with ESPNCricInfo. The [LICENSE](https://github.com/outside-edge/python-espncricinfo/blob/master/LICENSE.txt) for this library applies only to the code, not to the data.

Expand Down Expand Up @@ -54,10 +54,10 @@ MatchRef(series_id=1523975, match_id=1523981)

## A single match

A match needs both its match ID and series ID: the two numbers in a match page URL, or a `MatchRef`. Loading a match starts a headless WebKit browser.
A match needs only its match ID, the number in a match page URL. Passing the series ID too (`Match(1478914, 1478874)`, or `MatchRef.to_match()`) skips a redirect. Loading a match starts a headless WebKit browser.

```python
>>> m = Match(1478914, 1478874) # same as MatchRef(1478874, 1478914).to_match()
>>> m = Match(1478914)
>>> m.description
'IND Women v AUS Women'
>>> m.match_class
Expand Down
42 changes: 30 additions & 12 deletions espncricinfo/match.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ def build_team(t):
"series": [
{
"series_name": series_data.get("name", ""),
"core_recreation_id": str(series_data.get("objectId", series_id)),
"core_recreation_id": str(
series_data.get("objectId") or series_id or ""
),
"slug": series_data.get("slug", ""),
}
],
Expand Down Expand Up @@ -424,21 +426,32 @@ class Match:
#: The parsed page, reshaped by ``_normalize``. Prefer the attributes.
json: dict[str, Any]

def __init__(self, match_id: int | str, series_id: int | str) -> None:
def __init__(self, match_id: int | str, series_id: int | str | None = None) -> None:
"""Fetch and parse the match.

Args:
match_id: ESPNcricinfo match (event) id.
series_id: ESPNcricinfo series (league) id the match belongs to.
series_id: ESPNcricinfo series id the match belongs to. Optional:
without it the match page is found from the match id alone,
and ``series_id`` is read from the page.

Raises:
MatchNotFoundError: If ESPNcricinfo has no such match.
"""
self.match_id = int(match_id)
self.series_id = int(series_id)
self.json = self._get_json()
given_series = int(series_id) if series_id is not None else None
self.json = self._get_json(given_series)
data = self.json
match = data["match"]
series = data["series"][-1]

self.series_id = int(series["core_recreation_id"])
page_series = series.get("core_recreation_id")
if page_series:
self.series_id = int(page_series)
elif given_series is not None:
self.series_id = given_series
else:
raise MatchNotFoundError(f"No series found for match {self.match_id}")
self.description = data["description"]
self.match_title = match["cms_match_title"]
self.match_class = (
Expand Down Expand Up @@ -506,13 +519,18 @@ def __repr__(self) -> str:
"""Return ``Match(match_id, series_id)``."""
return f"{self.__class__.__name__}({self.match_id!r}, {self.series_id!r})"

def _get_json(self) -> dict[str, Any]:
def _get_json(self, series_id: int | None) -> dict[str, Any]:
"""Fetch the match page and reshape its __NEXT_DATA__ blob."""
url = (
f"https://www.espncricinfo.com/series/"
f"x-{self.series_id}/x-{self.match_id}/full-scorecard"
)
return _normalize(_playwright_fetch(url), self.match_id, self.series_id)
if series_id is None:
# ESPNcricinfo redirects its legacy match URL to the full match
# page, whatever series the match belongs to.
url = f"https://www.espncricinfo.com/matches/engine/match/{self.match_id}.html"
else:
url = (
f"https://www.espncricinfo.com/series/"
f"x-{series_id}/x-{self.match_id}/full-scorecard"
)
return _normalize(_playwright_fetch(url), self.match_id, series_id)

def _first_innings_of(self, team_id: str) -> dict[str, Any] | None:
return next(
Expand Down
18 changes: 18 additions & 0 deletions tests/test_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,21 @@ async def notebook_cell():

with patch.object(match_module, "_async_playwright_fetch", fake_fetch):
assert asyncio.run(notebook_cell()) == {"url": "https://example.invalid/page"}


def test_match_without_series_id_uses_legacy_url_and_reads_series_from_page():
next_data = json.loads((FIXTURE_DIR / "match_1478914_next_data.json").read_text())
with patch("espncricinfo.match._playwright_fetch", return_value=next_data) as fetch:
match = Match(1478914)
fetch.assert_called_once_with(
"https://www.espncricinfo.com/matches/engine/match/1478914.html"
)
assert match.series_id == 1478874


@pytest.mark.integration
def test_live_match_from_id_alone():
"""#50: match 729303 (IPL 2014) loads without knowing its series."""
match = Match(729303)
assert match.series_id == 695871
assert match.batting_scorecard[0]
Loading