datafc 2.1.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {datafc-2.1.0 → datafc-2.3.0}/PKG-INFO +41 -8
- {datafc-2.1.0 → datafc-2.3.0}/README.md +40 -7
- {datafc-2.1.0 → datafc-2.3.0}/datafc/__init__.py +1 -1
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/aio.py +30 -5
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_match_data.py +25 -3
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_past_matches_data.py +2 -2
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_standings_data.py +6 -3
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_team_match_history_data.py +108 -108
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_config.py +20 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_validate.py +8 -7
- {datafc-2.1.0 → datafc-2.3.0}/datafc.egg-info/PKG-INFO +41 -8
- {datafc-2.1.0 → datafc-2.3.0}/pyproject.toml +1 -1
- {datafc-2.1.0 → datafc-2.3.0}/LICENSE +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/exceptions.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/__init__.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/_parsers.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_average_positions_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_coordinates_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_goal_networks_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_incidents_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_league_player_stats_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_lineups_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_match_details_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_match_h2h_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_match_odds_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_match_stats_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_momentum_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_player_career_stats_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_player_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_player_match_log_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_player_national_team_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_player_stats_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_player_transfers_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_pregame_form_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_referee_stats_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_search_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_season_rounds_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_seasons_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_shots_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_squad_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_substitutions_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_team_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_team_stats_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_team_transfers_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/sofascore/fetch_upcoming_matches_data.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/__init__.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_async_client.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_cache.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_client.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_helpers.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_save_files.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc/utils/_tournament_info.py +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc.egg-info/SOURCES.txt +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc.egg-info/dependency_links.txt +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc.egg-info/requires.txt +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/datafc.egg-info/top_level.txt +0 -0
- {datafc-2.1.0 → datafc-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datafc
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Fetch, process, and export structured football data.
|
|
5
5
|
Author-email: Uraz Akgül <urazdev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,7 +24,7 @@ Requires-Dist: pytest>=8.0; extra == "dev"
|
|
|
24
24
|
Requires-Dist: pytest-mock>=3.12; extra == "dev"
|
|
25
25
|
Dynamic: license-file
|
|
26
26
|
|
|
27
|
-
# datafc v2.
|
|
27
|
+
# datafc v2.3.0
|
|
28
28
|
|
|
29
29
|
## Overview
|
|
30
30
|
|
|
@@ -530,15 +530,34 @@ ucl_df = match_data(
|
|
|
530
530
|
tournament_type="uefa",
|
|
531
531
|
tournament_stage="round_of_16",
|
|
532
532
|
)
|
|
533
|
+
|
|
534
|
+
# World Cup knockout stages — week_number not needed:
|
|
535
|
+
wc_df = match_data(
|
|
536
|
+
tournament_id=16,
|
|
537
|
+
season_id=58210,
|
|
538
|
+
tournament_type="world_cup",
|
|
539
|
+
tournament_stage="round_of_16",
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# World Cup group stage — week_number required:
|
|
543
|
+
wc_group_df = match_data(
|
|
544
|
+
tournament_id=16,
|
|
545
|
+
season_id=58210,
|
|
546
|
+
week_number=1,
|
|
547
|
+
tournament_type="world_cup",
|
|
548
|
+
tournament_stage="group_stage_week",
|
|
549
|
+
)
|
|
533
550
|
```
|
|
534
551
|
|
|
535
552
|
Parameters:
|
|
536
553
|
|
|
537
554
|
- `tournament_id` (int)
|
|
538
555
|
- `season_id` (int)
|
|
539
|
-
- `week_number` (int)
|
|
540
|
-
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions. `None` assumes a domestic league.
|
|
541
|
-
- `tournament_stage` (str, optional): Required when `tournament_type
|
|
556
|
+
- `week_number` (int, optional): Required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Not needed for other `world_cup` stages.
|
|
557
|
+
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions, `"world_cup"` for FIFA World Cup. `None` assumes a domestic league.
|
|
558
|
+
- `tournament_stage` (str, optional): Required when `tournament_type` is set.
|
|
559
|
+
- `"uefa"` options: `preliminary_semifinals`, `preliminary_final`, `qualification_round`, `qualification_playoff`, `group_stage_week`, `playoff_round`, `round_of_16`, `quarterfinals`, `semifinals`, `match_for_3rd_place`, `final`.
|
|
560
|
+
- `"world_cup"` options: `group_stage_week`, `round_of_32`, `round_of_16`, `quarterfinals`, `semifinals`, `match_for_3rd_place`, `final`.
|
|
542
561
|
|
|
543
562
|
Columns: `country`, `tournament`, `season`, `week`, `game_id`, `home_team`, `home_team_id`, `away_team`, `away_team_id`, `injury_time_1`, `injury_time_2`, `start_timestamp`, `status`, `home_score_current`, `home_score_display`, `home_score_period1`, `home_score_period2`, `home_score_normaltime`, `away_score_current`, `away_score_display`, `away_score_period1`, `away_score_period2`, `away_score_normaltime`.
|
|
544
563
|
|
|
@@ -775,9 +794,9 @@ Parameters:
|
|
|
775
794
|
|
|
776
795
|
- `tournament_id` (int)
|
|
777
796
|
- `season_id` (int)
|
|
778
|
-
- `week_number` (int)
|
|
779
|
-
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions.
|
|
780
|
-
- `tournament_stage` (str, optional): Required when `tournament_type
|
|
797
|
+
- `week_number` (int, optional): Required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Not needed for other `world_cup` stages.
|
|
798
|
+
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions, `"world_cup"` for FIFA World Cup.
|
|
799
|
+
- `tournament_stage` (str, optional): Required when `tournament_type` is set. Same options as `match_data`.
|
|
781
800
|
|
|
782
801
|
Same columns as `match_data`.
|
|
783
802
|
|
|
@@ -897,6 +916,20 @@ Columns: `referee_id`, `referee_name`, `tournament_id`, `tournament_name`, `stat
|
|
|
897
916
|
|
|
898
917
|
## Changelog
|
|
899
918
|
|
|
919
|
+
### v2.3.0
|
|
920
|
+
|
|
921
|
+
- **Fixed `match_data` for World Cup knockout stages across all seasons.** Round numbers are now resolved automatically from the API instead of being hardcoded, so older seasons work correctly.
|
|
922
|
+
- **Fixed `standings_data` for tournament-format competitions.** Calling this function for World Cup, Euro, or similar tournaments no longer raises an error. Only the available categories are returned.
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
### v2.2.0
|
|
927
|
+
|
|
928
|
+
- Added `tournament_type="world_cup"` support to `match_data` and `past_matches_data` for FIFA World Cup competitions. Knockout stage rounds are fixed internally; only `group_stage_week` requires `week_number`.
|
|
929
|
+
- `week_number` is now optional (`None` by default). It is required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Omitting it when required raises `InvalidParameterError`.
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
900
933
|
### v2.1.0
|
|
901
934
|
|
|
902
935
|
- Added `team_match_history_data`: fetches the complete match history for a single team across all competitions using `team_id` directly (no standings dependency).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# datafc v2.
|
|
1
|
+
# datafc v2.3.0
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
@@ -504,15 +504,34 @@ ucl_df = match_data(
|
|
|
504
504
|
tournament_type="uefa",
|
|
505
505
|
tournament_stage="round_of_16",
|
|
506
506
|
)
|
|
507
|
+
|
|
508
|
+
# World Cup knockout stages — week_number not needed:
|
|
509
|
+
wc_df = match_data(
|
|
510
|
+
tournament_id=16,
|
|
511
|
+
season_id=58210,
|
|
512
|
+
tournament_type="world_cup",
|
|
513
|
+
tournament_stage="round_of_16",
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# World Cup group stage — week_number required:
|
|
517
|
+
wc_group_df = match_data(
|
|
518
|
+
tournament_id=16,
|
|
519
|
+
season_id=58210,
|
|
520
|
+
week_number=1,
|
|
521
|
+
tournament_type="world_cup",
|
|
522
|
+
tournament_stage="group_stage_week",
|
|
523
|
+
)
|
|
507
524
|
```
|
|
508
525
|
|
|
509
526
|
Parameters:
|
|
510
527
|
|
|
511
528
|
- `tournament_id` (int)
|
|
512
529
|
- `season_id` (int)
|
|
513
|
-
- `week_number` (int)
|
|
514
|
-
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions. `None` assumes a domestic league.
|
|
515
|
-
- `tournament_stage` (str, optional): Required when `tournament_type
|
|
530
|
+
- `week_number` (int, optional): Required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Not needed for other `world_cup` stages.
|
|
531
|
+
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions, `"world_cup"` for FIFA World Cup. `None` assumes a domestic league.
|
|
532
|
+
- `tournament_stage` (str, optional): Required when `tournament_type` is set.
|
|
533
|
+
- `"uefa"` options: `preliminary_semifinals`, `preliminary_final`, `qualification_round`, `qualification_playoff`, `group_stage_week`, `playoff_round`, `round_of_16`, `quarterfinals`, `semifinals`, `match_for_3rd_place`, `final`.
|
|
534
|
+
- `"world_cup"` options: `group_stage_week`, `round_of_32`, `round_of_16`, `quarterfinals`, `semifinals`, `match_for_3rd_place`, `final`.
|
|
516
535
|
|
|
517
536
|
Columns: `country`, `tournament`, `season`, `week`, `game_id`, `home_team`, `home_team_id`, `away_team`, `away_team_id`, `injury_time_1`, `injury_time_2`, `start_timestamp`, `status`, `home_score_current`, `home_score_display`, `home_score_period1`, `home_score_period2`, `home_score_normaltime`, `away_score_current`, `away_score_display`, `away_score_period1`, `away_score_period2`, `away_score_normaltime`.
|
|
518
537
|
|
|
@@ -749,9 +768,9 @@ Parameters:
|
|
|
749
768
|
|
|
750
769
|
- `tournament_id` (int)
|
|
751
770
|
- `season_id` (int)
|
|
752
|
-
- `week_number` (int)
|
|
753
|
-
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions.
|
|
754
|
-
- `tournament_stage` (str, optional): Required when `tournament_type
|
|
771
|
+
- `week_number` (int, optional): Required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Not needed for other `world_cup` stages.
|
|
772
|
+
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions, `"world_cup"` for FIFA World Cup.
|
|
773
|
+
- `tournament_stage` (str, optional): Required when `tournament_type` is set. Same options as `match_data`.
|
|
755
774
|
|
|
756
775
|
Same columns as `match_data`.
|
|
757
776
|
|
|
@@ -871,6 +890,20 @@ Columns: `referee_id`, `referee_name`, `tournament_id`, `tournament_name`, `stat
|
|
|
871
890
|
|
|
872
891
|
## Changelog
|
|
873
892
|
|
|
893
|
+
### v2.3.0
|
|
894
|
+
|
|
895
|
+
- **Fixed `match_data` for World Cup knockout stages across all seasons.** Round numbers are now resolved automatically from the API instead of being hardcoded, so older seasons work correctly.
|
|
896
|
+
- **Fixed `standings_data` for tournament-format competitions.** Calling this function for World Cup, Euro, or similar tournaments no longer raises an error. Only the available categories are returned.
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
### v2.2.0
|
|
901
|
+
|
|
902
|
+
- Added `tournament_type="world_cup"` support to `match_data` and `past_matches_data` for FIFA World Cup competitions. Knockout stage rounds are fixed internally; only `group_stage_week` requires `week_number`.
|
|
903
|
+
- `week_number` is now optional (`None` by default). It is required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Omitting it when required raises `InvalidParameterError`.
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
874
907
|
### v2.1.0
|
|
875
908
|
|
|
876
909
|
- Added `team_match_history_data`: fetches the complete match history for a single team across all competitions using `team_id` directly (no standings dependency).
|
|
@@ -35,7 +35,7 @@ import pandas as pd
|
|
|
35
35
|
|
|
36
36
|
from datafc.utils._async_client import AsyncSofascoreClient
|
|
37
37
|
from datafc.utils._cache import DiskCache
|
|
38
|
-
from datafc.utils._config import API_URLS, WWW_URLS
|
|
38
|
+
from datafc.utils._config import API_URLS, WWW_URLS, WORLD_CUP_KNOCKOUT_SLUGS
|
|
39
39
|
from datafc.utils._validate import validate_source, validate_df, build_tournament_url
|
|
40
40
|
from datafc.utils._save_files import save_json, save_excel
|
|
41
41
|
from datafc.utils._tournament_info import resolve_tournament_season
|
|
@@ -77,7 +77,7 @@ logger = logging.getLogger(__name__)
|
|
|
77
77
|
async def match_data(
|
|
78
78
|
tournament_id: int,
|
|
79
79
|
season_id: int,
|
|
80
|
-
week_number: int,
|
|
80
|
+
week_number: Optional[int] = None,
|
|
81
81
|
tournament_type: Optional[str] = None,
|
|
82
82
|
tournament_stage: Optional[str] = None,
|
|
83
83
|
data_source: str = "sofascore",
|
|
@@ -89,6 +89,28 @@ async def match_data(
|
|
|
89
89
|
) -> pd.DataFrame:
|
|
90
90
|
"""Async version of match_data(). See sync docstring for full parameter docs."""
|
|
91
91
|
validate_source(data_source)
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
tournament_type == "world_cup"
|
|
95
|
+
and tournament_stage in WORLD_CUP_KNOCKOUT_SLUGS
|
|
96
|
+
and week_number is None
|
|
97
|
+
):
|
|
98
|
+
target_slug = WORLD_CUP_KNOCKOUT_SLUGS[tournament_stage]
|
|
99
|
+
rounds_url = (
|
|
100
|
+
f"{API_URLS[data_source]}/api/v1/unique-tournament/{tournament_id}"
|
|
101
|
+
f"/season/{season_id}/rounds"
|
|
102
|
+
)
|
|
103
|
+
async with AsyncSofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
104
|
+
rounds_data = await client.get(rounds_url)
|
|
105
|
+
rounds = rounds_data.get("rounds") or rounds_data.get("currentRounds") or []
|
|
106
|
+
matched = next((r for r in rounds if r.get("slug") == target_slug), None)
|
|
107
|
+
if matched is None:
|
|
108
|
+
raise DataNotAvailableError(
|
|
109
|
+
f"Could not find round with slug '{target_slug}' for "
|
|
110
|
+
f"tournament_id={tournament_id}, season_id={season_id}."
|
|
111
|
+
)
|
|
112
|
+
week_number = matched["round"]
|
|
113
|
+
|
|
92
114
|
url = build_tournament_url(
|
|
93
115
|
API_URLS[data_source], tournament_id, season_id, week_number,
|
|
94
116
|
tournament_type, tournament_stage,
|
|
@@ -649,8 +671,11 @@ async def standings_data(
|
|
|
649
671
|
f"{API_URLS[data_source]}/api/v1/unique-tournament/{tournament_id}"
|
|
650
672
|
f"/season/{season_id}/standings/{category}"
|
|
651
673
|
)
|
|
652
|
-
|
|
653
|
-
|
|
674
|
+
try:
|
|
675
|
+
data = await client.get(url)
|
|
676
|
+
return parse_standings_rows(data, category, tournament_id, season_id)
|
|
677
|
+
except APIError:
|
|
678
|
+
return []
|
|
654
679
|
|
|
655
680
|
async with AsyncSofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
656
681
|
batches = await asyncio.gather(*[_fetch(client, cat) for cat in ("total", "home", "away")])
|
|
@@ -1173,7 +1198,7 @@ async def goal_networks_data(
|
|
|
1173
1198
|
async def past_matches_data(
|
|
1174
1199
|
tournament_id: int,
|
|
1175
1200
|
season_id: int,
|
|
1176
|
-
week_number: int,
|
|
1201
|
+
week_number: Optional[int] = None,
|
|
1177
1202
|
tournament_type: Optional[str] = None,
|
|
1178
1203
|
tournament_stage: Optional[str] = None,
|
|
1179
1204
|
data_source: str = "sofascore",
|
|
@@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Optional
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from datafc.utils._client import SofascoreClient
|
|
4
4
|
from datafc.utils._save_files import save_json, save_excel
|
|
5
|
-
from datafc.utils._config import API_URLS
|
|
5
|
+
from datafc.utils._config import API_URLS, WORLD_CUP_KNOCKOUT_SLUGS
|
|
6
6
|
from datafc.utils._validate import validate_source, build_tournament_url
|
|
7
7
|
from datafc.sofascore._parsers import parse_match_events
|
|
8
8
|
from datafc.exceptions import DataNotAvailableError
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
def match_data(
|
|
15
15
|
tournament_id: int,
|
|
16
16
|
season_id: int,
|
|
17
|
-
week_number: int,
|
|
17
|
+
week_number: Optional[int] = None,
|
|
18
18
|
tournament_type: Optional[str] = None,
|
|
19
19
|
tournament_stage: Optional[str] = None,
|
|
20
20
|
data_source: str = "sofascore",
|
|
@@ -31,7 +31,7 @@ def match_data(
|
|
|
31
31
|
tournament_id: The unique identifier for the tournament.
|
|
32
32
|
season_id: The unique identifier for the season.
|
|
33
33
|
week_number: The matchweek number within the season.
|
|
34
|
-
tournament_type: The tournament type ('uefa'). If None, assumes league format.
|
|
34
|
+
tournament_type: The tournament type ('uefa', 'world_cup'). If None, assumes league format.
|
|
35
35
|
tournament_stage: The specific stage of the tournament (e.g., 'group_stage_week', 'round_of_16').
|
|
36
36
|
data_source: The data source ('sofavpn' or 'sofascore'). Defaults to 'sofascore'.
|
|
37
37
|
rate_limit: Maximum requests per second. Defaults to 2.0.
|
|
@@ -49,6 +49,28 @@ def match_data(
|
|
|
49
49
|
APIError: On HTTP errors from the Sofascore API.
|
|
50
50
|
"""
|
|
51
51
|
validate_source(data_source)
|
|
52
|
+
|
|
53
|
+
if (
|
|
54
|
+
tournament_type == "world_cup"
|
|
55
|
+
and tournament_stage in WORLD_CUP_KNOCKOUT_SLUGS
|
|
56
|
+
and week_number is None
|
|
57
|
+
):
|
|
58
|
+
target_slug = WORLD_CUP_KNOCKOUT_SLUGS[tournament_stage]
|
|
59
|
+
rounds_url = (
|
|
60
|
+
f"{API_URLS[data_source]}/api/v1/unique-tournament/{tournament_id}"
|
|
61
|
+
f"/season/{season_id}/rounds"
|
|
62
|
+
)
|
|
63
|
+
with SofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
64
|
+
rounds_data = client.get(rounds_url)
|
|
65
|
+
rounds = rounds_data.get("rounds") or rounds_data.get("currentRounds") or []
|
|
66
|
+
matched = next((r for r in rounds if r.get("slug") == target_slug), None)
|
|
67
|
+
if matched is None:
|
|
68
|
+
raise DataNotAvailableError(
|
|
69
|
+
f"Could not find round with slug '{target_slug}' for "
|
|
70
|
+
f"tournament_id={tournament_id}, season_id={season_id}."
|
|
71
|
+
)
|
|
72
|
+
week_number = matched["round"]
|
|
73
|
+
|
|
52
74
|
url = build_tournament_url(
|
|
53
75
|
API_URLS[data_source], tournament_id, season_id, week_number,
|
|
54
76
|
tournament_type, tournament_stage,
|
|
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
|
|
13
13
|
def past_matches_data(
|
|
14
14
|
tournament_id: int,
|
|
15
15
|
season_id: int,
|
|
16
|
-
week_number: int,
|
|
16
|
+
week_number: Optional[int] = None,
|
|
17
17
|
tournament_type: Optional[str] = None,
|
|
18
18
|
tournament_stage: Optional[str] = None,
|
|
19
19
|
data_source: str = "sofascore",
|
|
@@ -30,7 +30,7 @@ def past_matches_data(
|
|
|
30
30
|
tournament_id: The unique identifier for the tournament.
|
|
31
31
|
season_id: The unique identifier for the season.
|
|
32
32
|
week_number: The matchweek number within the season.
|
|
33
|
-
tournament_type: The tournament type ('uefa'). If None, assumes league format.
|
|
33
|
+
tournament_type: The tournament type ('uefa', 'world_cup'). If None, assumes league format.
|
|
34
34
|
tournament_stage: The specific stage of the tournament (e.g., 'group_stage_week', 'round_of_16').
|
|
35
35
|
data_source: The data source ('sofavpn' or 'sofascore'). Defaults to 'sofascore'.
|
|
36
36
|
rate_limit: Maximum requests per second. Defaults to 2.0.
|
|
@@ -6,7 +6,7 @@ from datafc.utils._config import API_URLS
|
|
|
6
6
|
from datafc.utils._validate import validate_source
|
|
7
7
|
from datafc.utils._tournament_info import resolve_tournament_season
|
|
8
8
|
from datafc.sofascore._parsers import parse_standings_rows
|
|
9
|
-
from datafc.exceptions import DataNotAvailableError
|
|
9
|
+
from datafc.exceptions import APIError, DataNotAvailableError
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from datafc.utils._cache import DiskCache
|
|
@@ -51,8 +51,11 @@ def standings_data(
|
|
|
51
51
|
f"{API_URLS[data_source]}/api/v1/unique-tournament/{tournament_id}"
|
|
52
52
|
f"/season/{season_id}/standings/{category}"
|
|
53
53
|
)
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
try:
|
|
55
|
+
data = client.get(url)
|
|
56
|
+
rows.extend(parse_standings_rows(data, category, tournament_id, season_id))
|
|
57
|
+
except APIError:
|
|
58
|
+
pass
|
|
56
59
|
|
|
57
60
|
result_df = pd.DataFrame(rows)
|
|
58
61
|
if result_df.empty:
|
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import TYPE_CHECKING, Optional
|
|
3
|
-
import pandas as pd
|
|
4
|
-
from datafc.utils._client import SofascoreClient
|
|
5
|
-
from datafc.utils._save_files import save_json, save_excel
|
|
6
|
-
from datafc.utils._config import API_URLS
|
|
7
|
-
from datafc.utils._validate import validate_source
|
|
8
|
-
from datafc.utils._helpers import _cast_int_cols
|
|
9
|
-
from datafc.sofascore._parsers import parse_team_match_history_records
|
|
10
|
-
from datafc.exceptions import APIError, DataNotAvailableError
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from datafc.utils._cache import DiskCache
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def team_match_history_data(
|
|
19
|
-
team_id: int,
|
|
20
|
-
data_source: str = "sofascore",
|
|
21
|
-
rate_limit: float = 2.0,
|
|
22
|
-
cache: Optional["DiskCache"] = None,
|
|
23
|
-
enable_json_export: bool = False,
|
|
24
|
-
enable_excel_export: bool = False,
|
|
25
|
-
output_dir: str = ".",
|
|
26
|
-
) -> pd.DataFrame:
|
|
27
|
-
"""
|
|
28
|
-
Fetches the complete match history for a single team across all competitions.
|
|
29
|
-
|
|
30
|
-
Paginates through all available history pages until no further pages exist.
|
|
31
|
-
The team_id can be obtained from standings_data(), squad_data(), or search_data().
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
team_id: The unique Sofascore identifier for the team.
|
|
35
|
-
data_source: The data source ('sofavpn' or 'sofascore'). Defaults to 'sofascore'.
|
|
36
|
-
rate_limit: Maximum requests per second. Defaults to 2.0.
|
|
37
|
-
cache: Optional DiskCache instance. Cached responses skip the API call.
|
|
38
|
-
enable_json_export: If True, saves output as JSON. Defaults to False.
|
|
39
|
-
enable_excel_export: If True, saves output as Excel. Defaults to False.
|
|
40
|
-
output_dir: Directory for exported files. Defaults to current directory.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
Past matches with country, tournament, season, week, home/away team names,
|
|
44
|
-
IDs, scores, start timestamp and status; sorted by start_timestamp ascending.
|
|
45
|
-
|
|
46
|
-
Raises:
|
|
47
|
-
InvalidParameterError: If an invalid data_source is given.
|
|
48
|
-
DataNotAvailableError: If no historical match data is found for the team.
|
|
49
|
-
APIError: On HTTP errors from the Sofascore API.
|
|
50
|
-
"""
|
|
51
|
-
validate_source(data_source)
|
|
52
|
-
|
|
53
|
-
seen_game_ids: set = set()
|
|
54
|
-
records = []
|
|
55
|
-
page = 0
|
|
56
|
-
|
|
57
|
-
with SofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
58
|
-
while True:
|
|
59
|
-
url = f"{API_URLS[data_source]}/api/v1/team/{team_id}/events/last/{page}"
|
|
60
|
-
try:
|
|
61
|
-
data = client.get(url)
|
|
62
|
-
except APIError as exc:
|
|
63
|
-
logger.warning(
|
|
64
|
-
"Failed to fetch match history for team_id=%s page=%s: %s",
|
|
65
|
-
team_id, page, exc,
|
|
66
|
-
)
|
|
67
|
-
break
|
|
68
|
-
|
|
69
|
-
batch = parse_team_match_history_records(data, seen_game_ids)
|
|
70
|
-
records.extend(batch)
|
|
71
|
-
|
|
72
|
-
if not data.get("hasNextPage", False):
|
|
73
|
-
break
|
|
74
|
-
page += 1
|
|
75
|
-
|
|
76
|
-
result_df = pd.DataFrame(records)
|
|
77
|
-
if result_df.empty:
|
|
78
|
-
raise DataNotAvailableError(
|
|
79
|
-
f"No historical match data found for team_id={team_id}."
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
result_df = result_df.sort_values("start_timestamp").reset_index(drop=True)
|
|
83
|
-
result_df = _cast_int_cols(
|
|
84
|
-
result_df,
|
|
85
|
-
"week",
|
|
86
|
-
"home_team_id", "away_team_id",
|
|
87
|
-
"home_score_period1", "home_score_period2", "home_score_normaltime",
|
|
88
|
-
"home_score_display", "home_score_current",
|
|
89
|
-
"away_score_period1", "away_score_period2", "away_score_normaltime",
|
|
90
|
-
"away_score_display", "away_score_current",
|
|
91
|
-
"start_timestamp",
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
if enable_json_export or enable_excel_export:
|
|
95
|
-
first = result_df.iloc[0]
|
|
96
|
-
kwargs = dict(
|
|
97
|
-
fn_name="team_match_history_data",
|
|
98
|
-
data_source=data_source,
|
|
99
|
-
country=first.get("country", ""),
|
|
100
|
-
tournament=first.get("tournament", ""),
|
|
101
|
-
season=first.get("season"),
|
|
102
|
-
)
|
|
103
|
-
if enable_json_export:
|
|
104
|
-
save_json(data=result_df, **kwargs, output_dir=output_dir)
|
|
105
|
-
if enable_excel_export:
|
|
106
|
-
save_excel(data=result_df, **kwargs, output_dir=output_dir)
|
|
107
|
-
|
|
108
|
-
return result_df
|
|
1
|
+
import logging
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from datafc.utils._client import SofascoreClient
|
|
5
|
+
from datafc.utils._save_files import save_json, save_excel
|
|
6
|
+
from datafc.utils._config import API_URLS
|
|
7
|
+
from datafc.utils._validate import validate_source
|
|
8
|
+
from datafc.utils._helpers import _cast_int_cols
|
|
9
|
+
from datafc.sofascore._parsers import parse_team_match_history_records
|
|
10
|
+
from datafc.exceptions import APIError, DataNotAvailableError
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from datafc.utils._cache import DiskCache
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def team_match_history_data(
|
|
19
|
+
team_id: int,
|
|
20
|
+
data_source: str = "sofascore",
|
|
21
|
+
rate_limit: float = 2.0,
|
|
22
|
+
cache: Optional["DiskCache"] = None,
|
|
23
|
+
enable_json_export: bool = False,
|
|
24
|
+
enable_excel_export: bool = False,
|
|
25
|
+
output_dir: str = ".",
|
|
26
|
+
) -> pd.DataFrame:
|
|
27
|
+
"""
|
|
28
|
+
Fetches the complete match history for a single team across all competitions.
|
|
29
|
+
|
|
30
|
+
Paginates through all available history pages until no further pages exist.
|
|
31
|
+
The team_id can be obtained from standings_data(), squad_data(), or search_data().
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
team_id: The unique Sofascore identifier for the team.
|
|
35
|
+
data_source: The data source ('sofavpn' or 'sofascore'). Defaults to 'sofascore'.
|
|
36
|
+
rate_limit: Maximum requests per second. Defaults to 2.0.
|
|
37
|
+
cache: Optional DiskCache instance. Cached responses skip the API call.
|
|
38
|
+
enable_json_export: If True, saves output as JSON. Defaults to False.
|
|
39
|
+
enable_excel_export: If True, saves output as Excel. Defaults to False.
|
|
40
|
+
output_dir: Directory for exported files. Defaults to current directory.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Past matches with country, tournament, season, week, home/away team names,
|
|
44
|
+
IDs, scores, start timestamp and status; sorted by start_timestamp ascending.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
InvalidParameterError: If an invalid data_source is given.
|
|
48
|
+
DataNotAvailableError: If no historical match data is found for the team.
|
|
49
|
+
APIError: On HTTP errors from the Sofascore API.
|
|
50
|
+
"""
|
|
51
|
+
validate_source(data_source)
|
|
52
|
+
|
|
53
|
+
seen_game_ids: set = set()
|
|
54
|
+
records = []
|
|
55
|
+
page = 0
|
|
56
|
+
|
|
57
|
+
with SofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
58
|
+
while True:
|
|
59
|
+
url = f"{API_URLS[data_source]}/api/v1/team/{team_id}/events/last/{page}"
|
|
60
|
+
try:
|
|
61
|
+
data = client.get(url)
|
|
62
|
+
except APIError as exc:
|
|
63
|
+
logger.warning(
|
|
64
|
+
"Failed to fetch match history for team_id=%s page=%s: %s",
|
|
65
|
+
team_id, page, exc,
|
|
66
|
+
)
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
batch = parse_team_match_history_records(data, seen_game_ids)
|
|
70
|
+
records.extend(batch)
|
|
71
|
+
|
|
72
|
+
if not data.get("hasNextPage", False):
|
|
73
|
+
break
|
|
74
|
+
page += 1
|
|
75
|
+
|
|
76
|
+
result_df = pd.DataFrame(records)
|
|
77
|
+
if result_df.empty:
|
|
78
|
+
raise DataNotAvailableError(
|
|
79
|
+
f"No historical match data found for team_id={team_id}."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
result_df = result_df.sort_values("start_timestamp").reset_index(drop=True)
|
|
83
|
+
result_df = _cast_int_cols(
|
|
84
|
+
result_df,
|
|
85
|
+
"week",
|
|
86
|
+
"home_team_id", "away_team_id",
|
|
87
|
+
"home_score_period1", "home_score_period2", "home_score_normaltime",
|
|
88
|
+
"home_score_display", "home_score_current",
|
|
89
|
+
"away_score_period1", "away_score_period2", "away_score_normaltime",
|
|
90
|
+
"away_score_display", "away_score_current",
|
|
91
|
+
"start_timestamp",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if enable_json_export or enable_excel_export:
|
|
95
|
+
first = result_df.iloc[0]
|
|
96
|
+
kwargs = dict(
|
|
97
|
+
fn_name="team_match_history_data",
|
|
98
|
+
data_source=data_source,
|
|
99
|
+
country=first.get("country", ""),
|
|
100
|
+
tournament=first.get("tournament", ""),
|
|
101
|
+
season=first.get("season"),
|
|
102
|
+
)
|
|
103
|
+
if enable_json_export:
|
|
104
|
+
save_json(data=result_df, **kwargs, output_dir=output_dir)
|
|
105
|
+
if enable_excel_export:
|
|
106
|
+
save_excel(data=result_df, **kwargs, output_dir=output_dir)
|
|
107
|
+
|
|
108
|
+
return result_df
|
|
@@ -40,6 +40,26 @@ TOURNAMENT_URL_PATTERNS = {
|
|
|
40
40
|
"match_for_3rd_place": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/match-for-3rd-place",
|
|
41
41
|
"final": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/final",
|
|
42
42
|
},
|
|
43
|
+
"world_cup": {
|
|
44
|
+
"group_stage_week": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}",
|
|
45
|
+
"round_of_32": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/round-of-32",
|
|
46
|
+
"round_of_16": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/round-of-16",
|
|
47
|
+
"quarterfinals": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/quarterfinals",
|
|
48
|
+
"semifinals": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/semifinals",
|
|
49
|
+
"match_for_3rd_place": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/match-for-3rd-place",
|
|
50
|
+
"final": "{base_url}/api/v1/unique-tournament/{tournament_id}/season/{season_id}/events/round/{week_number}/slug/final",
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Slug used in the API URL for each world_cup knockout stage.
|
|
55
|
+
# Used to auto-resolve the season-specific round number via the /rounds endpoint.
|
|
56
|
+
WORLD_CUP_KNOCKOUT_SLUGS: dict[str, str] = {
|
|
57
|
+
"round_of_32": "round-of-32",
|
|
58
|
+
"round_of_16": "round-of-16",
|
|
59
|
+
"quarterfinals": "quarterfinals",
|
|
60
|
+
"semifinals": "semifinals",
|
|
61
|
+
"match_for_3rd_place": "match-for-3rd-place",
|
|
62
|
+
"final": "final",
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
# ---------------------------------------------------------------------------
|
|
@@ -75,7 +75,7 @@ def build_tournament_url(
|
|
|
75
75
|
base_url: str,
|
|
76
76
|
tournament_id: int,
|
|
77
77
|
season_id: int,
|
|
78
|
-
week_number: int,
|
|
78
|
+
week_number: Optional[int],
|
|
79
79
|
tournament_type: Optional[str],
|
|
80
80
|
tournament_stage: Optional[str],
|
|
81
81
|
) -> str:
|
|
@@ -84,13 +84,14 @@ def build_tournament_url(
|
|
|
84
84
|
if tournament_type is not None:
|
|
85
85
|
validate_tournament_type(tournament_type)
|
|
86
86
|
validate_tournament_stage(tournament_type, tournament_stage)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
template = patterns[tournament_type][tournament_stage]
|
|
88
|
+
else:
|
|
89
|
+
template = patterns["default"]
|
|
90
|
+
if "{week_number}" in template and week_number is None:
|
|
91
|
+
raise InvalidParameterError(
|
|
92
|
+
"week_number is required for this tournament_type/tournament_stage combination."
|
|
92
93
|
)
|
|
93
|
-
return
|
|
94
|
+
return template.format(
|
|
94
95
|
base_url=base_url,
|
|
95
96
|
tournament_id=tournament_id,
|
|
96
97
|
season_id=season_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datafc
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Fetch, process, and export structured football data.
|
|
5
5
|
Author-email: Uraz Akgül <urazdev@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,7 +24,7 @@ Requires-Dist: pytest>=8.0; extra == "dev"
|
|
|
24
24
|
Requires-Dist: pytest-mock>=3.12; extra == "dev"
|
|
25
25
|
Dynamic: license-file
|
|
26
26
|
|
|
27
|
-
# datafc v2.
|
|
27
|
+
# datafc v2.3.0
|
|
28
28
|
|
|
29
29
|
## Overview
|
|
30
30
|
|
|
@@ -530,15 +530,34 @@ ucl_df = match_data(
|
|
|
530
530
|
tournament_type="uefa",
|
|
531
531
|
tournament_stage="round_of_16",
|
|
532
532
|
)
|
|
533
|
+
|
|
534
|
+
# World Cup knockout stages — week_number not needed:
|
|
535
|
+
wc_df = match_data(
|
|
536
|
+
tournament_id=16,
|
|
537
|
+
season_id=58210,
|
|
538
|
+
tournament_type="world_cup",
|
|
539
|
+
tournament_stage="round_of_16",
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# World Cup group stage — week_number required:
|
|
543
|
+
wc_group_df = match_data(
|
|
544
|
+
tournament_id=16,
|
|
545
|
+
season_id=58210,
|
|
546
|
+
week_number=1,
|
|
547
|
+
tournament_type="world_cup",
|
|
548
|
+
tournament_stage="group_stage_week",
|
|
549
|
+
)
|
|
533
550
|
```
|
|
534
551
|
|
|
535
552
|
Parameters:
|
|
536
553
|
|
|
537
554
|
- `tournament_id` (int)
|
|
538
555
|
- `season_id` (int)
|
|
539
|
-
- `week_number` (int)
|
|
540
|
-
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions. `None` assumes a domestic league.
|
|
541
|
-
- `tournament_stage` (str, optional): Required when `tournament_type
|
|
556
|
+
- `week_number` (int, optional): Required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Not needed for other `world_cup` stages.
|
|
557
|
+
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions, `"world_cup"` for FIFA World Cup. `None` assumes a domestic league.
|
|
558
|
+
- `tournament_stage` (str, optional): Required when `tournament_type` is set.
|
|
559
|
+
- `"uefa"` options: `preliminary_semifinals`, `preliminary_final`, `qualification_round`, `qualification_playoff`, `group_stage_week`, `playoff_round`, `round_of_16`, `quarterfinals`, `semifinals`, `match_for_3rd_place`, `final`.
|
|
560
|
+
- `"world_cup"` options: `group_stage_week`, `round_of_32`, `round_of_16`, `quarterfinals`, `semifinals`, `match_for_3rd_place`, `final`.
|
|
542
561
|
|
|
543
562
|
Columns: `country`, `tournament`, `season`, `week`, `game_id`, `home_team`, `home_team_id`, `away_team`, `away_team_id`, `injury_time_1`, `injury_time_2`, `start_timestamp`, `status`, `home_score_current`, `home_score_display`, `home_score_period1`, `home_score_period2`, `home_score_normaltime`, `away_score_current`, `away_score_display`, `away_score_period1`, `away_score_period2`, `away_score_normaltime`.
|
|
544
563
|
|
|
@@ -775,9 +794,9 @@ Parameters:
|
|
|
775
794
|
|
|
776
795
|
- `tournament_id` (int)
|
|
777
796
|
- `season_id` (int)
|
|
778
|
-
- `week_number` (int)
|
|
779
|
-
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions.
|
|
780
|
-
- `tournament_stage` (str, optional): Required when `tournament_type
|
|
797
|
+
- `week_number` (int, optional): Required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Not needed for other `world_cup` stages.
|
|
798
|
+
- `tournament_type` (str, optional): `"uefa"` for UEFA competitions, `"world_cup"` for FIFA World Cup.
|
|
799
|
+
- `tournament_stage` (str, optional): Required when `tournament_type` is set. Same options as `match_data`.
|
|
781
800
|
|
|
782
801
|
Same columns as `match_data`.
|
|
783
802
|
|
|
@@ -897,6 +916,20 @@ Columns: `referee_id`, `referee_name`, `tournament_id`, `tournament_name`, `stat
|
|
|
897
916
|
|
|
898
917
|
## Changelog
|
|
899
918
|
|
|
919
|
+
### v2.3.0
|
|
920
|
+
|
|
921
|
+
- **Fixed `match_data` for World Cup knockout stages across all seasons.** Round numbers are now resolved automatically from the API instead of being hardcoded, so older seasons work correctly.
|
|
922
|
+
- **Fixed `standings_data` for tournament-format competitions.** Calling this function for World Cup, Euro, or similar tournaments no longer raises an error. Only the available categories are returned.
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
### v2.2.0
|
|
927
|
+
|
|
928
|
+
- Added `tournament_type="world_cup"` support to `match_data` and `past_matches_data` for FIFA World Cup competitions. Knockout stage rounds are fixed internally; only `group_stage_week` requires `week_number`.
|
|
929
|
+
- `week_number` is now optional (`None` by default). It is required for league rounds, UEFA stages, and `world_cup` + `group_stage_week`. Omitting it when required raises `InvalidParameterError`.
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
900
933
|
### v2.1.0
|
|
901
934
|
|
|
902
935
|
- Added `team_match_history_data`: fetches the complete match history for a single team across all competitions using `team_id` directly (no standings dependency).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|