From 2401d8b1b06d593468e6bfbf8b77d0c30341967b Mon Sep 17 00:00:00 2001 From: Pieter Robberechts Date: Mon, 22 Dec 2025 23:28:33 +0100 Subject: [PATCH 1/5] ci(docs): Use strict mode to build docs --- docs/build.sh | 2 +- pyproject.toml | 2 +- requirements-docs.txt | 14 ++++++++++++-- uv.lock | 34 ++++++++++++++++++++++++++++++---- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/docs/build.sh b/docs/build.sh index 2b0f978fe..a30283cb7 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -10,4 +10,4 @@ pip install . pip install -r requirements-docs.txt # Build MkDocs site -mkdocs build -d site +mkdocs build --strict -d site diff --git a/pyproject.toml b/pyproject.toml index d36993410..6a4ee028a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ docs = [ "mkdocs-autorefs==1.2.0", # Automatic cross-referencing for MkDocs "mkdocs-exclude==1.0.2", # Exclude files/folders from MkDocs builds "mkdocs-jupyter==0.24.7", # Embed Jupyter notebooks in MkDocs - "mkdocs-macros-plugin==1.0.5", # Template macros for MkDocs + "mkdocs-macros-plugin>=1.0.7", # Template macros for MkDocs "mplsoccer==1.3.0", # Soccer plotting package (used in docs) "pandas[output-formatting]>=2.2.3", # Output formatting for dataframes ] diff --git a/requirements-docs.txt b/requirements-docs.txt index 3bb8a0908..454ae456a 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -76,6 +76,10 @@ griffe==1.15.0 ; python_full_version >= '3.10' # griffe-generics # mkdocstrings-python griffe-generics==1.0.13 +hjson==3.1.0 + # via + # mkdocs-macros-plugin + # super-collections idna==3.11 # via requests importlib-metadata==8.7.0 ; python_full_version < '3.10' @@ -213,7 +217,7 @@ mkdocs-exclude==1.0.2 mkdocs-get-deps==0.2.0 # via mkdocs mkdocs-jupyter==0.24.7 -mkdocs-macros-plugin==1.0.5 +mkdocs-macros-plugin==1.5.0 mkdocs-material==9.7.0 # via mkdocs-jupyter mkdocs-material-extensions==1.3.1 @@ -263,6 +267,7 @@ packaging==25.0 # jupytext # matplotlib # mkdocs + # mkdocs-macros-plugin # nbconvert paginate==0.5.7 # via mkdocs-material @@ -275,7 +280,9 @@ pandocfilters==1.5.1 parso==0.8.5 # via jedi pathspec==0.12.1 - # via mkdocs + # via + # mkdocs + # mkdocs-macros-plugin pexpect==4.9.0 ; (python_full_version < '3.10' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32') # via ipython pillow==11.3.0 ; python_full_version < '3.10' @@ -354,6 +361,7 @@ referencing==0.36.2 # jsonschema-specifications requests==2.32.5 # via + # mkdocs-macros-plugin # mkdocs-material # mplsoccer rpds-py==0.27.1 ; python_full_version < '3.10' @@ -378,6 +386,8 @@ soupsieve==2.8 # via beautifulsoup4 stack-data==0.6.3 # via ipython +super-collections==0.6.2 + # via mkdocs-macros-plugin tabulate==0.9.0 # via pandas termcolor==3.1.0 ; python_full_version < '3.10' diff --git a/uv.lock b/uv.lock index d6a932f15..25586d297 100644 --- a/uv.lock +++ b/uv.lock @@ -1547,6 +1547,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/59/96c5bfdc24f5942690ac6161d425d4cc181d4c4624eb3f54b5d244672908/griffe_generics-1.0.13-py3-none-any.whl", hash = "sha256:e8139e485d256d0eba97ab310368c8800048918f0d5c7257817d769bba76ac94", size = 10557, upload-time = "2025-01-18T07:44:03.507Z" }, ] +[[package]] +name = "hjson" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/e5/0b56d723a76ca67abadbf7fb71609fb0ea7e6926e94fcca6c65a85b36a0e/hjson-3.1.0.tar.gz", hash = "sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75", size = 40541, upload-time = "2022-08-13T02:53:01.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl", hash = "sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89", size = 54018, upload-time = "2022-08-13T02:52:59.899Z" }, +] + [[package]] name = "identify" version = "2.6.15" @@ -2275,7 +2284,7 @@ docs = [ { name = "mkdocs-autorefs", specifier = "==1.2.0" }, { name = "mkdocs-exclude", specifier = "==1.0.2" }, { name = "mkdocs-jupyter", specifier = "==0.24.7" }, - { name = "mkdocs-macros-plugin", specifier = "==1.0.5" }, + { name = "mkdocs-macros-plugin", specifier = ">=1.0.7" }, { name = "mkdocs-material", specifier = ">=9.5.44" }, { name = "mkdocs-material-extensions", specifier = "==1.3.1" }, { name = "mkdocstrings-python", specifier = "==1.12.2" }, @@ -2976,19 +2985,24 @@ wheels = [ [[package]] name = "mkdocs-macros-plugin" -version = "1.0.5" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "hjson" }, { name = "jinja2" }, { name = "mkdocs" }, + { name = "packaging" }, + { name = "pathspec" }, { name = "python-dateutil" }, { name = "pyyaml" }, + { name = "requests" }, + { name = "super-collections" }, { name = "termcolor", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "termcolor", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/92/06948e4d60aac760c227f8fcc86d20dfc0cf9b1735b087302ac6c16a9479/mkdocs-macros-plugin-1.0.5.tar.gz", hash = "sha256:fe348d75f01c911f362b6d998c57b3d85b505876dde69db924f2c512c395c328", size = 566013, upload-time = "2023-10-31T15:18:44.214Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/15/e6a44839841ebc9c5872fa0e6fad1c3757424e4fe026093b68e9f386d136/mkdocs_macros_plugin-1.5.0.tar.gz", hash = "sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f", size = 37730, upload-time = "2025-11-13T08:08:55.545Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/60/81e3b580bafe20a3103163b8fd89f62911f0df2e50f331d316313f8c2dc8/mkdocs_macros_plugin-1.0.5-py3-none-any.whl", hash = "sha256:f60e26f711f5a830ddf1e7980865bf5c0f1180db56109803cdd280073c1a050a", size = 21401, upload-time = "2023-10-31T15:18:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl", hash = "sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f", size = 44626, upload-time = "2025-11-13T08:08:53.878Z" }, ] [[package]] @@ -5640,6 +5654,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] +[[package]] +name = "super-collections" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/de/a0c3d1244912c260638f0f925e190e493ccea37ecaea9bbad7c14413b803/super_collections-0.6.2.tar.gz", hash = "sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a", size = 31315, upload-time = "2025-09-30T00:37:08.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" }, +] + [[package]] name = "sympy" version = "1.14.0" From bf846b09c797448139a2eb1b0b1fb9162a87cff5 Mon Sep 17 00:00:00 2001 From: Pieter Robberechts Date: Mon, 22 Dec 2025 23:30:07 +0100 Subject: [PATCH 2/5] fix(docs): fix build Griffe does not support writing union types as X | Y --- .../domain/models/event/event/index.md | 2 + .../domain/models/time/timecontainer/index.md | 1 + docs/reference/event-data/qualifiers/card.md | 2 + kloppy/domain/models/common.py | 155 +++++++++--------- kloppy/domain/models/event.py | 64 ++++---- 5 files changed, 117 insertions(+), 107 deletions(-) create mode 100644 docs/reference/domain/models/time/timecontainer/index.md diff --git a/docs/reference/domain/models/event/event/index.md b/docs/reference/domain/models/event/event/index.md index f959bf3fb..955413567 100644 --- a/docs/reference/domain/models/event/event/index.md +++ b/docs/reference/domain/models/event/event/index.md @@ -1 +1,3 @@ ::: kloppy.domain.Event + +::: kloppy.domain.EventFactory diff --git a/docs/reference/domain/models/time/timecontainer/index.md b/docs/reference/domain/models/time/timecontainer/index.md new file mode 100644 index 000000000..f00b554c5 --- /dev/null +++ b/docs/reference/domain/models/time/timecontainer/index.md @@ -0,0 +1 @@ +::: kloppy.domain.TimeContainer diff --git a/docs/reference/event-data/qualifiers/card.md b/docs/reference/event-data/qualifiers/card.md index 00301acd6..f81afab2f 100644 --- a/docs/reference/event-data/qualifiers/card.md +++ b/docs/reference/event-data/qualifiers/card.md @@ -1,2 +1,4 @@ [](){#kloppy.domain.CardQualifier} {{ render_qualifier("kloppy.domain.CardQualifier", "kloppy.domain.CardType") }} + +[](){#kloppy.domain.CardType} diff --git a/kloppy/domain/models/common.py b/kloppy/domain/models/common.py index 78ebef84c..bd6f4503d 100644 --- a/kloppy/domain/models/common.py +++ b/kloppy/domain/models/common.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from abc import ABC, abstractmethod from collections import defaultdict from collections.abc import Iterable @@ -13,7 +11,9 @@ Callable, Generic, Literal, + Optional, TypeVar, + Union, overload, ) @@ -157,10 +157,10 @@ class Official: """ official_id: str - name: str | None = None - first_name: str | None = None - last_name: str | None = None - role: OfficialType | None = None + name: Optional[str] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + role: Optional[OfficialType] = None @property def full_name(self): @@ -201,15 +201,15 @@ class Player: """ player_id: str - team: Team + team: "Team" jersey_no: int - first_name: str | None = None - last_name: str | None = None - name: str | None = None + first_name: Optional[str] = None + last_name: Optional[str] = None + name: Optional[str] = None # match specific starting: bool = False - starting_position: PositionType | None = None + starting_position: Optional[PositionType] = None positions: TimeContainer[PositionType] = field( default_factory=TimeContainer, compare=False ) @@ -226,7 +226,7 @@ def full_name(self): @property @deprecated("starting_position or positions should be used") - def position(self) -> PositionType | None: + def position(self) -> Optional[PositionType]: try: return self.positions.last() except KeyError: @@ -246,7 +246,7 @@ def __eq__(self, other): return False return self.player_id == other.player_id - def set_position(self, time: Time, position: PositionType | None): + def set_position(self, time: Time, position: Optional[PositionType]): self.positions.set(time, position) @@ -266,7 +266,7 @@ class Team: team_id: str name: str ground: Ground - starting_formation: FormationType | None = None + starting_formation: Optional[FormationType] = None formations: TimeContainer[FormationType] = field( default_factory=TimeContainer, compare=False ) @@ -283,7 +283,7 @@ def __eq__(self, other): return False return self.team_id == other.team_id - def get_player_by_jersey_number(self, jersey_no: int) -> Player | None: + def get_player_by_jersey_number(self, jersey_no: int) -> Optional[Player]: """Get a player by their jersey number. Args: @@ -302,7 +302,7 @@ def get_player_by_jersey_number(self, jersey_no: int) -> Player | None: def get_player_by_position( self, position: PositionType, time: Time - ) -> Player | None: + ) -> Optional[Player]: """Get a player by their position at a given time. Args: @@ -324,7 +324,7 @@ def get_player_by_position( return None - def get_player_by_id(self, player_id: int | str) -> Player | None: + def get_player_by_id(self, player_id: int | str) -> Optional[Player]: """Get a player by their identifier. Args: @@ -342,7 +342,7 @@ def get_player_by_id(self, player_id: int | str) -> Player | None: return None - def set_formation(self, time: Time, formation: FormationType | None): + def set_formation(self, time: Time, formation: Optional[FormationType]): self.formations.set(time, formation) @@ -426,10 +426,10 @@ class AttackingDirection(Enum): @staticmethod def from_orientation( orientation: Orientation, - period: Period | None = None, - ball_owning_team: Team | None = None, - action_executing_team: Team | None = None, - ) -> AttackingDirection: + period: Optional[Period] = None, + ball_owning_team: Optional[Team] = None, + action_executing_team: Optional[Team] = None, + ) -> "AttackingDirection": """Determines the attacking direction for a specific data record. Args: @@ -584,11 +584,11 @@ def normalized(self) -> bool: return isinstance(self.pitch_dimensions, NormalizedPitchDimensions) @property - def pitch_length(self) -> float | None: + def pitch_length(self) -> Optional[float]: return self.pitch_dimensions.pitch_length @property - def pitch_width(self) -> float | None: + def pitch_width(self) -> Optional[float]: return self.pitch_dimensions.pitch_width def __eq__(self, other): @@ -748,8 +748,8 @@ def to_mplsoccer(self): class ProviderCoordinateSystem(CoordinateSystem): def __init__( self, - pitch_length: float | None = None, - pitch_width: float | None = None, + pitch_length: Optional[float] = None, + pitch_width: Optional[float] = None, ): self._pitch_length = pitch_length self._pitch_width = pitch_width @@ -1381,8 +1381,8 @@ def __repr__(self): def build_coordinate_system( provider: Provider, dataset_type: DatasetType = DatasetType.EVENT, - pitch_length: float | None = None, - pitch_width: float | None = None, + pitch_length: Optional[float] = None, + pitch_width: Optional[float] = None, ) -> CoordinateSystem: """Build a coordinate system for a given provider and dataset type. @@ -1466,13 +1466,13 @@ class ActionValue(Statistic): """Action value""" name: str - action_value_scoring_before: float | None = field(default=None) - action_value_scoring_after: float | None = field(default=None) - action_value_conceding_before: float | None = field(default=None) - action_value_conceding_after: float | None = field(default=None) + action_value_scoring_before: Optional[float] = field(default=None) + action_value_scoring_after: Optional[float] = field(default=None) + action_value_conceding_before: Optional[float] = field(default=None) + action_value_conceding_after: Optional[float] = field(default=None) @property - def offensive_value(self) -> float | None: + def offensive_value(self) -> Optional[float]: if ( self.action_value_scoring_before is None or self.action_value_scoring_after is None @@ -1483,7 +1483,7 @@ def offensive_value(self) -> float | None: ) @property - def defensive_value(self) -> float | None: + def defensive_value(self) -> Optional[float]: if ( self.action_value_conceding_before is None or self.action_value_conceding_after is None @@ -1495,7 +1495,7 @@ def defensive_value(self) -> float | None: ) @property - def value(self) -> float | None: + def value(self) -> Optional[float]: if self.offensive_value is None or self.defensive_value is None: return None return self.offensive_value - self.defensive_value @@ -1519,14 +1519,14 @@ class DataRecord(ABC): ball_state: The state of the ball at the time of the observation. """ - dataset: Dataset = field(init=False) - prev_record: Self | None = field(init=False) - next_record: Self | None = field(init=False) + dataset: "Dataset" = field(init=False) + prev_record: Optional[Self] = field(init=False) + next_record: Optional[Self] = field(init=False) period: Period timestamp: timedelta statistics: list[Statistic] - ball_owning_team: Team | None - ball_state: BallState | None + ball_owning_team: Optional[Team] + ball_state: Optional[BallState] @property @abstractmethod @@ -1539,9 +1539,9 @@ def time(self) -> Time: def set_refs( self, - dataset: Dataset, - prev: Self | None, - next_: Self | None, + dataset: "Dataset", + prev: Optional[Self], + next_: Optional[Self], ): if hasattr(self, "dataset"): # TODO: determine if next/prev record should be affected @@ -1569,7 +1569,9 @@ def attacking_direction(self): return AttackingDirection.NOT_SET return AttackingDirection.NOT_SET - def matches(self, filter_: str | Callable[[Self], bool] | None) -> bool: + def matches( + self, filter_: Optional[Union[str, Callable[[Self], bool]]] + ) -> bool: if filter_ is None: return True elif callable(filter_): @@ -1578,8 +1580,8 @@ def matches(self, filter_: str | Callable[[Self], bool] | None) -> bool: raise InvalidFilterError() def prev( - self, filter_: str | Callable[[Self], bool] | None = None - ) -> Self | None: + self, filter_: Optional[Union[str, Callable[[Self], bool]]] = None + ) -> Optional[Self]: if self.prev_record: prev_record = self.prev_record while prev_record: @@ -1588,8 +1590,8 @@ def prev( prev_record = prev_record.prev_record def next( - self, filter_: str | Callable[[Self], bool] | None = None - ) -> Self | None: + self, filter_: Optional[Union[str, Callable[[Self], bool]]] = None + ) -> Optional[Self]: if self.next_record: next_record = self.next_record while next_record: @@ -1640,15 +1642,15 @@ class Metadata: orientation: Orientation flags: DatasetFlag provider: Provider - score: Score | None = None - frame_rate: float | None = None - date: datetime | None = None - game_week: str | None = None - game_id: str | None = None - home_coach: str | None = None - away_coach: str | None = None - officials: list | None = field(default_factory=list) - attributes: dict | None = field(default_factory=dict, compare=False) + score: Optional[Score] = None + frame_rate: Optional[float] = None + date: Optional[datetime] = None + game_week: Optional[str] = None + game_id: Optional[str] = None + home_coach: Optional[str] = None + away_coach: Optional[str] = None + officials: Optional[list] = field(default_factory=list) + attributes: Optional[dict] = field(default_factory=dict, compare=False) def __post_init__(self): if self.coordinate_system is not None: @@ -1729,9 +1731,9 @@ def dataset_type(self) -> DatasetType: @abstractmethod def to_pandas( self, - record_converter: Callable[[T], dict] | None = None, - additional_columns: NamedColumns | None = None, - ) -> DataFrame: # noqa: F821 + record_converter: Optional[Callable[[T], dict]] = None, + additional_columns: Optional["NamedColumns"] = None, + ) -> "DataFrame": # noqa: F821 pass def transform(self, *args, **kwargs): @@ -1770,13 +1772,15 @@ def map(self, mapper): def find_all(self, filter_) -> list[T]: return [record for record in self.records if record.matches(filter_)] - def find(self, filter_) -> T | None: + def find(self, filter_) -> Optional[T]: for record in self.records: if record.matches(filter_): return record @classmethod - def from_dataset(cls, dataset: Dataset, mapper_fn: Callable[[Self], Self]): + def from_dataset( + cls, dataset: "Dataset", mapper_fn: Callable[[Self], Self] + ): """ Create a new Dataset from other dataset @@ -1809,7 +1813,7 @@ def from_dataset(cls, dataset: Dataset, mapper_fn: Callable[[Self], Self]): records=[mapper_fn(record) for record in dataset.records], ) - def get_record_by_id(self, record_id: int | str) -> T | None: + def get_record_by_id(self, record_id: int | str) -> Optional[T]: for record in self.records: if record.record_id == record_id: return record @@ -1817,24 +1821,24 @@ def get_record_by_id(self, record_id: int | str) -> T | None: @overload def to_records( self, - *columns: Unpack[tuple[Column]], + *columns: Unpack[tuple["Column"]], as_list: Literal[True] = True, - **named_columns: NamedColumns, + **named_columns: "NamedColumns", ) -> list[dict[str, Any]]: ... @overload def to_records( self, - *columns: Unpack[tuple[Column]], + *columns: Unpack[tuple["Column"]], as_list: Literal[False] = False, - **named_columns: NamedColumns, + **named_columns: "NamedColumns", ) -> Iterable[dict[str, Any]]: ... def to_records( self, - *columns: Unpack[tuple[Column]], + *columns: Unpack[tuple["Column"]], as_list: bool = True, - **named_columns: NamedColumns, + **named_columns: "NamedColumns", ) -> list[dict[str, Any]] | Iterable[dict[str, Any]]: from ..services.transformers.data_record import get_transformer_cls @@ -1849,9 +1853,9 @@ def to_records( def to_dict( self, - *columns: Unpack[tuple[Column]], + *columns: Unpack[tuple["Column"]], orient: Literal["list"] = "list", - **named_columns: NamedColumns, + **named_columns: "NamedColumns", ) -> dict[str, list[Any]]: if orient == "list": from ..services.transformers.data_record import get_transformer_cls @@ -1875,12 +1879,9 @@ def to_dict( def to_df( self, - *columns: Unpack[tuple[Column]], - engine: Literal["polars"] - | Literal["pandas"] - | Literal["pandas[pyarrow]"] - | None = None, - **named_columns: NamedColumns, + *columns: Unpack[tuple["Column"]], + engine: Optional[Literal["polars", "pandas", "pandas[pyarrow]"]] = None, + **named_columns: "NamedColumns", ): from kloppy.config import get_config diff --git a/kloppy/domain/models/event.py b/kloppy/domain/models/event.py index 6fb898492..4e2d8ed93 100644 --- a/kloppy/domain/models/event.py +++ b/kloppy/domain/models/event.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import timedelta @@ -205,7 +203,9 @@ class NoResultMixin: result: None - def matches(self, filter_: str | Callable[[Event], bool] | None) -> bool: + def matches( + self, filter_: Optional[Union[str, Callable[["Event"], bool]]] + ) -> bool: return super().matches(filter_) # type: ignore @@ -217,7 +217,9 @@ class ResultMixin(Generic[ResultT]): result: ResultT - def matches(self, filter_: str | Callable[[Event], bool] | None) -> bool: + def matches( + self, filter_: Optional[Union[str, Callable[["Event"], bool]]] + ) -> bool: if filter_ is None: return True elif callable(filter_): @@ -718,7 +720,7 @@ class Event(DataRecord, ABC): state: dict[str, Any] related_event_ids: list[str] - freeze_frame: Frame | None + freeze_frame: Optional["Frame"] @property def record_id(self) -> str: @@ -752,7 +754,7 @@ def attacking_direction(self) -> AttackingDirection: return AttackingDirection.NOT_SET return AttackingDirection.NOT_SET - def get_related_events(self) -> list[Event]: + def get_related_events(self) -> list["Event"]: if not self.dataset: raise OrphanedRecordError() @@ -762,7 +764,7 @@ def get_related_events(self) -> list[Event]: if (event := self.dataset.get_record_by_id(event_id)) is not None ] - def get_related_event(self, type_: str | EventType) -> Event | None: + def get_related_event(self, type_: str | EventType) -> Optional["Event"]: event_type = ( EventType[type_.upper()] if isinstance(type_, str) else type_ ) @@ -773,66 +775,68 @@ def get_related_event(self, type_: str | EventType) -> Event | None: """Define all related events for easy access""" - def related_pass(self) -> PassEvent | None: + def related_pass(self) -> Optional["PassEvent"]: return cast(Optional[PassEvent], self.get_related_event(EventType.PASS)) - def related_shot(self) -> ShotEvent | None: + def related_shot(self) -> Optional["ShotEvent"]: return cast(Optional[ShotEvent], self.get_related_event(EventType.SHOT)) - def related_take_on(self) -> TakeOnEvent | None: + def related_take_on(self) -> Optional["TakeOnEvent"]: return cast( Optional[TakeOnEvent], self.get_related_event(EventType.TAKE_ON) ) - def related_carry(self) -> CarryEvent | None: + def related_carry(self) -> Optional["CarryEvent"]: return cast( Optional[CarryEvent], self.get_related_event(EventType.CARRY) ) - def related_substitution(self) -> SubstitutionEvent | None: + def related_substitution(self) -> Optional["SubstitutionEvent"]: return cast( Optional[SubstitutionEvent], self.get_related_event(EventType.SUBSTITUTION), ) - def related_card(self) -> CardEvent | None: + def related_card(self) -> Optional["CardEvent"]: return cast(Optional[CardEvent], self.get_related_event(EventType.CARD)) - def related_player_on(self) -> PlayerOnEvent | None: + def related_player_on(self) -> Optional["PlayerOnEvent"]: return cast( Optional[PlayerOnEvent], self.get_related_event(EventType.PLAYER_ON), ) - def related_player_off(self) -> PlayerOffEvent | None: + def related_player_off(self) -> Optional["PlayerOffEvent"]: return cast( Optional[PlayerOffEvent], self.get_related_event(EventType.PLAYER_OFF), ) - def related_recovery(self) -> RecoveryEvent | None: + def related_recovery(self) -> Optional["RecoveryEvent"]: return cast( Optional[RecoveryEvent], self.get_related_event(EventType.RECOVERY) ) - def related_ball_out(self) -> BallOutEvent | None: + def related_ball_out(self) -> Optional["BallOutEvent"]: return cast( Optional[BallOutEvent], self.get_related_event(EventType.BALL_OUT) ) - def related_foul_committed(self) -> FoulCommittedEvent | None: + def related_foul_committed(self) -> Optional["FoulCommittedEvent"]: return cast( Optional[FoulCommittedEvent], self.get_related_event(EventType.FOUL_COMMITTED), ) - def related_formation_change(self) -> FormationChangeEvent | None: + def related_formation_change(self) -> Optional["FormationChangeEvent"]: return cast( Optional[FormationChangeEvent], self.get_related_event(EventType.FORMATION_CHANGE), ) - def matches(self, filter_: str | Callable[[Event], bool] | None) -> bool: + def matches( + self, filter_: Optional[Union[str, Callable[["Event"], bool]]] + ) -> bool: if filter_ is None: return True elif callable(filter_): @@ -932,7 +936,7 @@ class ShotEvent( qualifiers: A list of qualifiers providing additional information about the shot. """ - result_coordinates: Point | None = None + result_coordinates: Optional[Point] = None @property def event_type(self) -> EventType: @@ -970,9 +974,9 @@ class PassEvent( qualifiers: A list of qualifiers providing additional information about the pass. """ - receive_timestamp: Time | None = None - receiver_player: Player | None = None - receiver_coordinates: Point | None = None + receive_timestamp: Optional[Time] = None + receiver_player: Optional[Player] = None + receiver_coordinates: Optional[Point] = None @property def event_type(self) -> EventType: @@ -1135,7 +1139,7 @@ class SubstitutionEvent(NoQualifierMixin, NoResultMixin, Event): """ replacement_player: Player - position: PositionType | None = None + position: Optional[PositionType] = None @property def event_type(self) -> EventType: @@ -1223,7 +1227,7 @@ class FormationChangeEvent(NoQualifierMixin, NoResultMixin, Event): """ formation_type: FormationType - player_positions: dict[Player, PositionType] | None = None + player_positions: Optional[dict[Player, PositionType]] = None @property def event_type(self) -> EventType: @@ -1471,12 +1475,12 @@ def _update_formations_and_positions(self): def events(self): return self.records - def get_event_by_id(self, event_id: str) -> Event | None: + def get_event_by_id(self, event_id: str) -> Optional[Event]: return self.get_record_by_id(event_id) def add_state(self, *builder_keys): """ - See [`add_state`][kloppy.domain.services.state_builder.add_state] + See kloppy.domain.services.state_builder.add_state """ from kloppy.domain.services.state_builder import add_state @@ -1487,8 +1491,8 @@ def add_state(self, *builder_keys): ) def to_pandas( self, - record_converter: Callable[[Event], dict] | None = None, - additional_columns: NamedColumns | None = None, + record_converter: Optional[Callable[[Event], dict]] = None, + additional_columns: Optional["NamedColumns"] = None, ) -> "DataFrame": # noqa F821 try: import pandas as pd From 0f41f6224f8a9d5325361c4ac60170d7dbde0c3b Mon Sep 17 00:00:00 2001 From: Pieter Robberechts Date: Mon, 22 Dec 2025 23:52:34 +0100 Subject: [PATCH 3/5] fix: Forgot one pipe --- docs/build.sh | 2 +- kloppy/domain/models/common.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/build.sh b/docs/build.sh index a30283cb7..e6baf38fe 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -6,7 +6,7 @@ curl -fsSL https://d2lang.com/install.sh | sh -s -- --prefix $PWD/deps/d2 export PATH="$PWD/deps/d2/bin:$PATH" # Install kloppy and docs requirements -pip install . +pip install .[query,pandas] pip install -r requirements-docs.txt # Build MkDocs site diff --git a/kloppy/domain/models/common.py b/kloppy/domain/models/common.py index bd6f4503d..cb1f8a9fe 100644 --- a/kloppy/domain/models/common.py +++ b/kloppy/domain/models/common.py @@ -324,7 +324,7 @@ def get_player_by_position( return None - def get_player_by_id(self, player_id: int | str) -> Optional[Player]: + def get_player_by_id(self, player_id: Union[int, str]) -> Optional[Player]: """Get a player by their identifier. Args: From 5b983fab8c42e7530b3fe23c3d04bdfe0a2fc098 Mon Sep 17 00:00:00 2001 From: Pieter Robberechts Date: Mon, 22 Dec 2025 23:58:04 +0100 Subject: [PATCH 4/5] fix: another one --- kloppy/domain/models/common.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kloppy/domain/models/common.py b/kloppy/domain/models/common.py index cb1f8a9fe..64470d0f8 100644 --- a/kloppy/domain/models/common.py +++ b/kloppy/domain/models/common.py @@ -1530,7 +1530,7 @@ class DataRecord(ABC): @property @abstractmethod - def record_id(self) -> int | str: + def record_id(self) -> Union[int, str]: pass @property @@ -1744,7 +1744,7 @@ def transform(self, *args, **kwargs): return transform(self, *args, **kwargs) - def filter(self, filter_: str | Callable[[T], bool]): + def filter(self, filter_: Union[str, Callable[[T], bool]]): """ Filter all records used `filter_` @@ -1813,7 +1813,7 @@ def from_dataset( records=[mapper_fn(record) for record in dataset.records], ) - def get_record_by_id(self, record_id: int | str) -> Optional[T]: + def get_record_by_id(self, record_id: Union[int, str]) -> Optional[T]: for record in self.records: if record.record_id == record_id: return record @@ -1839,7 +1839,7 @@ def to_records( *columns: Unpack[tuple["Column"]], as_list: bool = True, **named_columns: "NamedColumns", - ) -> list[dict[str, Any]] | Iterable[dict[str, Any]]: + ) -> Union[list[dict[str, Any]], Iterable[dict[str, Any]]]: from ..services.transformers.data_record import get_transformer_cls transformer = get_transformer_cls(self.dataset_type)( From 27e0db830588fd0e316eff8c08c8abbb5d329cab Mon Sep 17 00:00:00 2001 From: Pieter Robberechts Date: Tue, 23 Dec 2025 00:00:56 +0100 Subject: [PATCH 5/5] fix: another one --- kloppy/domain/models/event.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kloppy/domain/models/event.py b/kloppy/domain/models/event.py index 4e2d8ed93..2079e0e3c 100644 --- a/kloppy/domain/models/event.py +++ b/kloppy/domain/models/event.py @@ -764,7 +764,9 @@ def get_related_events(self) -> list["Event"]: if (event := self.dataset.get_record_by_id(event_id)) is not None ] - def get_related_event(self, type_: str | EventType) -> Optional["Event"]: + def get_related_event( + self, type_: Union[str, EventType] + ) -> Optional["Event"]: event_type = ( EventType[type_.upper()] if isinstance(type_, str) else type_ )