Source code for penaltyblog.matchflow.contrib.opta

# penaltyblog/matchflow/contrib/opta.py
import os
from datetime import date, datetime
from typing import TYPE_CHECKING, List, Optional, Union

from typing_extensions import Literal

if TYPE_CHECKING:
    from ..flow import Flow


# --- Helper for formatting dates ---
def _format_opta_datetime(dt: Union[str, datetime, date]) -> str:
    """
    Format a datetime/date object or string into Opta's required Z-format.

    If a date or date string is provided, it is assumed to be the start of the day (00:00:00).

    Parameters
    ----------
    dt : str, datetime, or date
        The datetime/date object or string to format.

    Returns
    -------
    str
        The formatted datetime string in ISO 8601 format with 'Z' suffix.
    """
    if isinstance(dt, datetime):
        return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
    if isinstance(dt, date):  # Must be before str check
        return dt.strftime("%Y-%m-%dT00:00:00Z")
    if isinstance(dt, str):
        if "T" not in dt:
            try:
                datetime.strptime(dt, "%Y-%m-%d")
                return f"{dt}T00:00:00Z"
            except ValueError:
                pass  # Not a date string, fall through and return as is
    return dt


def _format_opta_date(dt: Union[str, datetime, date]) -> str:
    """
    Format a datetime/date object or string into Opta's required YYYY-MM-DD format.
    """
    if isinstance(dt, (datetime, date)):
        return dt.strftime("%Y-%m-%d")
    return dt


[docs] class Opta: """ A lazy Flow builder for the Stats Perform (Opta) Soccer API. This class provides methods that correspond to specific Opta feeds. Calling a method returns a Flow object that, when executed, will make the necessary API requests to fetch the data. Supported Feeds (in order): - OT2 (Tournament Calendars): .tournament_calendars() - OT3 (Venues): .venues() - OT4 (Areas): .areas() - MA0 (Tournament Schedule): .tournament_schedule() - MA1 (Match - Basic): .matches() / .match() - MA2 (Match Stats - Basic): .match_player_stats() / .match_team_stats() - MA3 (Match Events): .events() - MA4 (Pass Matrix): .pass_matrix() - MA5 (Possession): .possession() - PE2 (Player Career): .player_career() - PE3 (Referees): .referees() - PE4 (Rankings): .rankings() - PE7 (Injuries): .injuries() - TM1 (Teams): .teams() - TM2 (Team Standings): .team_standings() - TM3 (Squads): .squads() - TM4 (Season Stats): .player_season_stats() / .team_season_stats() - TM7 (Transfers): .transfers() - TM16 (Contestant Part.): .contestant_participation() """ @property def DEFAULT_CREDS(self) -> dict: """Get default credentials from environment variables.""" return { "auth_key": os.environ.get("OPTA_AUTH_KEY"), "rt_mode": os.environ.get("OPTA_RT_MODE", "b"), } @property def BASE_URL(self) -> str: """Get base URL from environment variables.""" return os.environ.get("OPTA_BASE_URL", "http://api.performfeeds.com") @property def ASSET_TYPE(self) -> str: """Get asset type (constant for now).""" return "soccerdata" def _step(self, source: str, optimize: bool = False, **args) -> "Flow": """ Internal helper to build a lazy Flow plan for Opta API requests. Parameters ---------- source : str The name of the Opta data source/feed to request. optimize : bool, optional Whether to optimize the execution plan (default: False). **args : dict Additional arguments to pass to the Opta API request. Returns ------- Flow A Flow object representing the lazy execution plan for the Opta request. """ from ..flow import Flow args = args.copy() return Flow( plan=[ { "op": "from_opta", "source": source, "base_url": self.BASE_URL, "asset_type": self.ASSET_TYPE, "args": args, } ], optimize=optimize, ) # --- IN-SCOPE METHODS (Ordered by Feed ID) ---
[docs] def tournament_calendars( self, status: Literal["all", "active", "authorized", "active_authorized"] = "all", competition_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, include_stages: bool = False, include_coverage: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw tournament calendar data (Feed OT2). Parameters ---------- status : Literal["all", "active", "authorized", "active_authorized"], optional Filter tournaments by status (default: "all"). competition_uuid : str, optional Filter by a specific competition UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. include_stages : bool, optional Include tournament stage information (default: False). include_coverage : bool, optional Include coverage information (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw tournament calendar data. """ return self._step( "tournament_calendars", status=status, competition_uuid=competition_uuid, contestant_uuid=contestant_uuid, include_stages="yes" if include_stages else None, include_coverage="yes" if include_coverage else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def venues( self, tournament_calendar_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, venue_uuid: Optional[str] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw venue data (Feed OT3). This feed is paginated. Note: You must specify at least one of 'tournament_calendar_uuid', 'contestant_uuid', or 'venue_uuid'. Parameters ---------- tournament_calendar_uuid : str, optional Filter by a specific tournament calendar UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. venue_uuid : str, optional Filter by a specific venue UUID. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw venue data. Raises ------ ValueError If no filter (tmcl, ctst, or venue) is provided. """ if not tournament_calendar_uuid and not contestant_uuid and not venue_uuid: raise ValueError( "At least one of 'tournament_calendar_uuid', 'contestant_uuid', or 'venue_uuid' must be provided." ) return self._step( "venues", tournament_calendar_uuid=tournament_calendar_uuid, contestant_uuid=contestant_uuid, venue_uuid=venue_uuid, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def areas( self, area_uuid: Optional[str] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw area data (Feed OT4). If 'area_uuid' is provided, fetches data for a specific area. If 'area_uuid' is None, fetches a paginated list of all areas. Parameters ---------- area_uuid : str, optional The UUID for a specific area. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw area data. """ if area_uuid: # Fetch a specific area (non-paginated) source = "area_specific" else: # Fetch all areas (paginated) source = "areas_all" return self._step( source, area_uuid=area_uuid, _lcl="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def tournament_schedule( self, tournament_calendar_uuid: str, coverage_level: Optional[Union[int, List[int]]] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw tournament schedule data (Feed MA0). Parameters ---------- tournament_calendar_uuid : str The UUID for the specific tournament calendar (season). coverage_level : int or List[int], optional Filter by coverage level(s). Can be a single integer or list of integers. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw tournament schedule data. """ cvlv_str = None if isinstance(coverage_level, list): cvlv_str = ",".join(map(str, coverage_level)) elif isinstance(coverage_level, int): cvlv_str = str(coverage_level) return self._step( "tournament_schedule", tournament_calendar_uuid=tournament_calendar_uuid, coverage_level=cvlv_str, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def matches( self, fixture_uuids: Optional[List[str]] = None, tournament_calendar_uuid: Optional[str] = None, competition_uuids: Optional[List[str]] = None, contestant_uuid: Optional[str] = None, opponent_uuid: Optional[str] = None, contestant_position: Optional[Literal["home", "away"]] = None, date_from: Optional[Union[str, datetime, date]] = None, date_to: Optional[Union[str, datetime, date]] = None, delta_timestamp: Optional[Union[str, datetime, date]] = None, live: bool = True, lineups: bool = False, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw match data (Feed MA1 - Basic). This feed is paginated. Yields one raw JSON object per page. Parameters ---------- fixture_uuids : List[str], optional Get specific matches by UUID (comma-separated). tournament_calendar_uuid : str, optional Filter by tournament calendar. competition_uuids : List[str], optional Filter by competition(s) (comma-separated). contestant_uuid : str, optional Filter by a specific contestant (team). opponent_uuid : str, optional Filter for matches where contestant_uuid played opponent_uuid (maps to ctst2). contestant_position : Literal["home", "away"], optional Filter for matches where contestant_uuid played home or away. date_from : str, datetime, or date, optional Start of date range. date_to : str, datetime, or date, optional End of date range. delta_timestamp : str, datetime, or date, optional Get updates since this time. live : bool, optional Request live data (default: False). lineups : bool, optional Request lineup data (requires live=True) (default: False). use_opta_names : bool, optional Request 'en-op' locale (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}) optimize : bool, optional Whether to optimize the plan. Returns ------- Flow A Flow yielding raw JSON data per page. """ date_range_str = None if date_from and date_to: def _to_datetime(d): if isinstance(d, datetime): return d if isinstance(d, date): return datetime.combine(d, datetime.min.time()) if isinstance(d, str): if "T" in d: return datetime.fromisoformat(d.replace("Z", "")) else: return datetime.strptime(d, "%Y-%m-%d") raise TypeError(f"Unsupported date type: {type(d)}") if _to_datetime(date_from) > _to_datetime(date_to): raise ValueError("'date_from' cannot be after 'date_to'") date_range_str = f"[{_format_opta_datetime(date_from)} TO {_format_opta_datetime(date_to)}]" elif date_from or date_to: raise ValueError("Both 'date_from' and 'date_to' must be provided") return self._step( "matches_basic", fixture_uuids=fixture_uuids, tournament_calendar_uuid=tournament_calendar_uuid, competition_uuids=competition_uuids, contestant_uuid=contestant_uuid, opponent_uuid=opponent_uuid, contestant_position=contestant_position, date_range=date_range_str, delta_timestamp=delta_timestamp, live="yes" if live or lineups else "no", lineups="yes" if lineups else "no", use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def match( self, fixture_uuid: str, live: bool = False, lineups: bool = False, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw data for a single match (Feed MA1 - Basic). Parameters ---------- fixture_uuid : str The UUID for the specific match/fixture. live : bool, optional Request live data (default: False). lineups : bool, optional Request lineup data (requires live=True) (default: False). use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw match data for the specified fixture. """ return self._step( "match_basic", fixture_uuid=fixture_uuid, live="yes" if live or lineups else "no", lineups="yes" if lineups else "no", use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def match_stats_player( self, fixture_uuids: Union[str, List[str]], use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw player match stats data (Feed MA2 - Basic). Parameters ---------- fixture_uuids : str or List[str] The UUID(s) for the specific match/fixture(es). Can be a single UUID or list of UUIDs. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw player match statistics data. """ if isinstance(fixture_uuids, str): fixture_uuids = [fixture_uuids] return self._step( "match_stats_player", fixture_uuids=fixture_uuids, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def match_stats_team( self, fixture_uuids: Union[str, List[str]], use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw team match stats data (Feed MA2 - Basic). Parameters ---------- fixture_uuids : str or List[str] The UUID(s) for the specific match/fixture(es). Can be a single UUID or list of UUIDs. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw team match statistics data. """ if isinstance(fixture_uuids, str): fixture_uuids = [fixture_uuids] return self._step( "match_stats_team", fixture_uuids=fixture_uuids, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def events( self, fixture_uuid: str, contestant_uuid: Optional[str] = None, person_uuid: Optional[str] = None, event_types: Optional[Union[int, List[int]]] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw match events data (Feed MA3). Parameters ---------- fixture_uuid : str The UUID for the specific match/fixture. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. person_uuid : str, optional Filter by a specific person (player) UUID. event_types : int or List[int], optional Filter by event type(s). Can be a single integer or list of integers. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw match events data. """ return self._step( "match_events", fixture_uuid=fixture_uuid, contestant_uuid=contestant_uuid, person_uuid=person_uuid, event_types=event_types, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def pass_matrix( self, fixture_uuid: str, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw pass matrix and average formation data (Feed MA4). Provides information on the number of completed passes between all player combinations and x/y coordinates of their average pitch positions during the match. Parameters ---------- fixture_uuid : str The UUID for the specific match/fixture. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw pass matrix and average formation data. Raises ------ ValueError If 'fixture_uuid' is not provided. """ if not fixture_uuid: raise ValueError( "'fixture_uuid' must be provided for the pass_matrix feed." ) return self._step( "pass_matrix", fixture_uuid=fixture_uuid, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def possession( self, fixture_uuid: str, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw possession and territorial advantage data (Feed MA5). Provides a breakdown of ball possession during a match, including overall % possession and territorial advantage, split by time period (last 5, 10, 15, 20, 25, 30 minutes). Parameters ---------- fixture_uuid : str The UUID for the specific match/fixture. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw possession and territorial advantage data. Raises ------ ValueError If 'fixture_uuid' is not provided. """ if not fixture_uuid: raise ValueError("'fixture_uuid' must be provided for the possession feed.") return self._step( "possession", fixture_uuid=fixture_uuid, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def player_career( self, person_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, active: bool = True, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw player career data (Feed PE2). Fetches by person (non-paginated) or contestant (paginated). You must specify exactly one of 'person_uuid' or 'contestant_uuid'. Parameters ---------- person_uuid : str, optional Filter by a specific person UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. active : bool, optional When using 'contestant_uuid', filter for active players (default: True). Ignored for 'person_uuid'. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw player career data. Raises ------ ValueError If neither or both 'person_uuid' and 'contestant_uuid' are provided. """ if not person_uuid and not contestant_uuid: raise ValueError( "Either 'person_uuid' or 'contestant_uuid' must be provided." ) if person_uuid and contestant_uuid: raise ValueError("Cannot provide both 'person_uuid' and 'contestant_uuid'.") if person_uuid: source = "player_career_person" active_param = None # 'active' only applies to ctst else: source = "player_career_contestant" active_param = active return self._step( source, person_uuid=person_uuid, contestant_uuid=contestant_uuid, active=active_param, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def referees( self, person_uuid: Optional[str] = None, tournament_calendar_uuid: Optional[str] = None, stage_uuid: Optional[str] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw referee data (Feed PE3). This feed is paginated. You must specify exactly one of 'person_uuid', 'tournament_calendar_uuid', or 'stage_uuid'. Parameters ---------- person_uuid : str, optional Filter by a specific person (referee) UUID. tournament_calendar_uuid : str, optional Filter by a specific tournament calendar UUID. stage_uuid : str, optional Filter by a specific stage UUID. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw referee data. Raises ------ ValueError If the parameter combination is invalid (e.g., none or multiple filter UUIDs are provided). """ # Check that exactly one filter is provided filters_provided = sum( 1 for f in [person_uuid, tournament_calendar_uuid, stage_uuid] if f is not None ) if filters_provided == 0: raise ValueError( "One of 'person_uuid', 'tournament_calendar_uuid', or 'stage_uuid' must be provided." ) if filters_provided > 1: raise ValueError( "Only one of 'person_uuid', 'tournament_calendar_uuid', or 'stage_uuid' can be provided at a time." ) if person_uuid: source = "referees_person" else: source = "referees" return self._step( source, person_uuid=person_uuid, tournament_calendar_uuid=tournament_calendar_uuid, stage_uuid=stage_uuid, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def rankings( self, tournament_calendar_uuid: str, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw rankings data (Feed PE4). Get rankings data for all players, teams and games in a range of statistical categories within a tournament calendar (season). Parameters ---------- tournament_calendar_uuid : str The UUID for the specific tournament calendar (season). use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw rankings data. Raises ------ ValueError If 'tournament_calendar_uuid' is not provided. """ if not tournament_calendar_uuid: raise ValueError("'tournament_calendar_uuid' must be provided.") return self._step( "rankings", tournament_calendar_uuid=tournament_calendar_uuid, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def injuries( self, person_uuid: Optional[str] = None, tournament_calendar_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw player injury data (Feed PE7). Fetches by person (non-paginated) or by tournament calendar (paginated). You must specify 'person_uuid' OR 'tournament_calendar_uuid'. 'contestant_uuid' can only be used in combination with 'tournament_calendar_uuid'. Parameters ---------- person_uuid : str, optional Filter by a specific person UUID. Can be used as the primary filter (path parameter) or with 'tournament_calendar_uuid' (query parameter). tournament_calendar_uuid : str, optional Filter by a specific tournament calendar UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. Must be used with 'tournament_calendar_uuid'. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw injury data. Raises ------ ValueError If parameter combinations are invalid (e.g., no args, 'ctst' without 'tmcl', or 'person_uuid' and 'contestant_uuid' together). """ if not person_uuid and not tournament_calendar_uuid: raise ValueError( "Either 'person_uuid' or 'tournament_calendar_uuid' must be provided." ) if contestant_uuid and not tournament_calendar_uuid: raise ValueError( "'contestant_uuid' can only be used in combination with 'tournament_calendar_uuid'." ) if contestant_uuid and person_uuid: raise ValueError( "Cannot use 'contestant_uuid' and 'person_uuid' in the same request." ) # Use specific path-based source if ONLY person_uuid is given if person_uuid and not tournament_calendar_uuid: source = "injuries_person_path" else: # All other combinations use the query endpoint source = "injuries_query" return self._step( source, person_uuid=person_uuid, tournament_calendar_uuid=tournament_calendar_uuid, contestant_uuid=contestant_uuid, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def transfers( self, person_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, competition_uuid: Optional[str] = None, tournament_calendar_uuid: Optional[str] = None, start_date: Optional[Union[str, datetime, date]] = None, end_date: Optional[Union[str, datetime, date]] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw player transfer data (Feed TM7). Fetches by person (non-paginated) or by other criteria (paginated). You must specify at least one of 'person_uuid', 'contestant_uuid', 'competition_uuid', or 'tournament_calendar_uuid'. Parameters ---------- person_uuid : str, optional Filter by a specific person UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. competition_uuid : str, optional Filter by a specific competition UUID. Required when using date parameters. tournament_calendar_uuid : str, optional Filter by a specific tournament calendar UUID. Cannot be used with date parameters. start_date : str, datetime, or date, optional The start date for filtering (YYYY-MM-DD). Requires 'end_date' and 'competition_uuid'. end_date : str, datetime, or date, optional The end date for filtering (YYYY-MM-DD). Requires 'start_date' and 'competition_uuid'. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw transfer data (grouped by person). Raises ------ ValueError If parameter combinations are invalid (e.g., no filters, partial date range, or invalid date parameter usage). """ if not any( [ person_uuid, contestant_uuid, competition_uuid, tournament_calendar_uuid, ] ): raise ValueError( "At least one of 'person_uuid', 'contestant_uuid', " "'competition_uuid', or 'tournament_calendar_uuid' must be provided." ) start_date_str = None end_date_str = None if start_date and end_date: start_date_str = _format_opta_date(start_date) end_date_str = _format_opta_date(end_date) elif start_date or end_date: raise ValueError( "Both 'start_date' and 'end_date' must be provided together." ) # Validate parameter combinations according to API documentation if start_date and end_date: # Date parameters can only be used with competition_uuid, not tournament_calendar_uuid if not competition_uuid: raise ValueError( "When using 'start_date' and 'end_date', 'competition_uuid' must be provided. " "Date parameters cannot be used with 'tournament_calendar_uuid'." ) if tournament_calendar_uuid: raise ValueError( "Date parameters ('start_date', 'end_date') cannot be used with " "'tournament_calendar_uuid'. Use 'competition_uuid' instead." ) return self._step( "transfers", person_uuid=person_uuid, contestant_uuid=contestant_uuid, competition_uuid=competition_uuid, tournament_calendar_uuid=tournament_calendar_uuid, start_date=start_date_str, end_date=end_date_str, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def teams( self, tournament_calendar_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, country_uuid: Optional[str] = None, stage_uuid: Optional[str] = None, series_uuid: Optional[str] = None, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw team data (Feed TM1). Parameters ---------- tournament_calendar_uuid : str, optional Filter by tournament calendar UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. country_uuid : str, optional Filter by country UUID. stage_uuid : str, optional Filter by stage UUID. series_uuid : str, optional Filter by series UUID. creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw team data. Raises ------ ValueError If neither 'tournament_calendar_uuid' nor 'contestant_uuid' is provided. """ if not tournament_calendar_uuid and not contestant_uuid: raise ValueError( "Either 'tournament_calendar_uuid' or 'contestant_uuid' must be provided for the teams feed." ) return self._step( "teams", tournament_calendar_uuid=tournament_calendar_uuid, contestant_uuid=contestant_uuid, country_uuid=country_uuid, stage_uuid=stage_uuid, series_uuid=series_uuid, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def team_standings( self, tournament_calendar_uuid: str, stage_uuid: Optional[str] = None, live: bool = False, type: Optional[ Literal[ "total", "home", "away", "form-total", "form-home", "form-away", "half-time-total", "half-time-home", "half-time-away", "attendance", "over-under", "relegation", "championship", ] ] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw team standings data (Feed TM2). Provides league table data including position, points, matches played/won/lost/drawn, goals scored/conceded, and goal difference. Supports different division types such as home/away form and half-time standings. Parameters ---------- tournament_calendar_uuid : str The UUID for the specific tournament calendar (season). stage_uuid : str, optional Filter by a specific stage UUID. live : bool, optional Request live standings data (default: False). type : str, optional Filter by division type. Available values: 'total' (default), 'home', 'away', 'form-total', 'form-home', 'form-away', 'half-time-total', 'half-time-home', 'half-time-away', 'attendance', 'over-under', 'relegation', 'championship'. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw team standings data. Raises ------ ValueError If 'tournament_calendar_uuid' is not provided. """ if not tournament_calendar_uuid: raise ValueError( "'tournament_calendar_uuid' must be provided for the team_standings feed." ) return self._step( "team_standings", tournament_calendar_uuid=tournament_calendar_uuid, stage_uuid=stage_uuid, live=live, type=type, use_opta_names="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def squads( self, tournament_calendar_uuid: Optional[str] = None, contestant_uuid: Optional[str] = None, use_opta_names: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of raw squad data (Feed TM3). Parameters ---------- tournament_calendar_uuid : str, optional Filter by tournament calendar UUID. contestant_uuid : str, optional Filter by a specific contestant (team) UUID. use_opta_names : bool, optional Request 'en-op' locale for Opta-specific names (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw squad data. Raises ------ ValueError If neither 'tournament_calendar_uuid' nor 'contestant_uuid' is provided. """ if not tournament_calendar_uuid and not contestant_uuid: raise ValueError( "Either 'tournament_calendar_uuid' or 'contestant_uuid' must be provided for the squads feed." ) return self._step( "squads", tournament_calendar_uuid=tournament_calendar_uuid, contestant_uuid=contestant_uuid, _lcl="en-op" if use_opta_names else None, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def player_season_stats( self, tournament_calendar_uuid: str, contestant_uuid: str, detailed: bool = True, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of cumulative player stats for a season (Feed TM4). This feed is paginated. Parameters ---------- tournament_calendar_uuid : str The UUID for the specific tournament calendar (season). contestant_uuid : str, optional Filter by a specific contestant (team). detailed : bool, optional Request detailed stats (default: True). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests. optimize : bool, optional Whether to optimize the plan. Returns ------- Flow A Flow yielding a stream of player stat records. """ return self._step( "player_season_stats", # New source name tournament_calendar_uuid=tournament_calendar_uuid, contestant_uuid=contestant_uuid, detailed="yes" if detailed else "no", creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def team_season_stats( self, tournament_calendar_uuid: str, contestant_uuid: Optional[str] = None, detailed: bool = True, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of cumulative team stats for a season (Feed TM4). This feed is paginated. Parameters ---------- tournament_calendar_uuid : str The UUID for the specific tournament calendar (season). contestant_uuid : str Filter by a specific contestant (team). detailed : bool, optional Request detailed stats (default: True). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests. optimize : bool, optional Whether to optimize the plan. Returns ------- Flow A Flow yielding a stream of team stat records. """ return self._step( "team_season_stats", # New source name tournament_calendar_uuid=tournament_calendar_uuid, contestant_uuid=contestant_uuid, detailed="yes" if detailed else "no", creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
[docs] def contestant_participation( self, contestant_uuid: Optional[str] = None, active: bool = False, creds: Optional[dict] = None, proxies: Optional[dict] = None, optimize: bool = False, ) -> "Flow": """ Return a Flow of contestant participation data (Feed TM16). Provides a list of competitions and tournament calendars for a team. This feed is paginated. Parameters ---------- contestant_uuid : str or List[str] The UUID(s) for the specific contestant(s) (team). Can be a single UUID, list of UUIDs, or an Opta ID. active : bool, optional Filter for active tournament calendars only (default: False). creds : dict, optional Credentials for Opta API. proxies : dict, optional Proxies dictionary for requests (e.g., {'http': 'socks5h://...'}). optimize : bool, optional Whether to optimize the plan (default: False). Returns ------- Flow A Flow yielding raw competition/tournament calendar data. Raises ------ ValueError If 'contestant_uuid' is not provided. """ if not contestant_uuid: raise ValueError( "'contestant_uuid' must be provided for the contestant_participation feed." ) return self._step( "contestant_participation", contestant_uuid=contestant_uuid, active=active, creds=creds or self.DEFAULT_CREDS, proxies=proxies, optimize=optimize, )
# Bind singleton instance opta = Opta()