Skip to content

depr: deprecate GazeDataFrame in favor of Gaze #1119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c465732
rename gaze_dataframe.py to gaze.py
dkrako Apr 24, 2025
d58f7ad
rename gaze test files
dkrako Apr 24, 2025
999492a
rename GazeDataFrame to Gaze
dkrako Apr 24, 2025
f8442d4
create deprecated GazeDataFrame alias
dkrako Apr 24, 2025
022d927
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2025
8144cf2
Update __init__.py
dkrako Apr 24, 2025
d69a53f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2025
db25929
rename gaze_df variables to gaze
dkrako Apr 24, 2025
6e112d7
raise coverage
dkrako Apr 24, 2025
c712c6e
improve documentation and linting
dkrako Apr 24, 2025
36daf79
improve documentation and linting
dkrako Apr 24, 2025
f8fe79d
improve documentation and linting
dkrako Apr 24, 2025
30b734d
rename to
dkrako Apr 27, 2025
0729009
Merge branch 'main' into feat/rename-gazedf-to-gaze
dkrako Apr 27, 2025
02b97a1
cleanup
dkrako Apr 27, 2025
3b3aaf2
make pylint happy
dkrako Apr 27, 2025
857e575
ignore pylint error
dkrako Apr 27, 2025
fb246a0
avoid warnings in GazeDataFrame docstring
dkrako Apr 27, 2025
b4d699a
fix doctest
dkrako Apr 27, 2025
0d3a7df
raise coverage
dkrako Apr 27, 2025
1d6a649
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 27, 2025
12bf84f
ignore pylint in test
dkrako Apr 27, 2025
2a56d43
raise coverage
dkrako Apr 28, 2025
f015fbb
Merge branch 'main' into feat/rename-gazedf-to-gaze
dkrako Apr 28, 2025
67b06bb
more cleanup
dkrako Apr 28, 2025
c57dbe2
Update imports in heatmap_test.py
dkrako Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/pymovements/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from pymovements.events import EventProcessor
from pymovements.gaze import Experiment
from pymovements.gaze import EyeTracker
from pymovements.gaze import Gaze
from pymovements.gaze import GazeDataFrame
from pymovements.gaze import Screen
from pymovements.measure import register_sample_measure
Expand All @@ -63,6 +64,7 @@
'Experiment',
'EyeTracker',
'Screen',
'Gaze',
'GazeDataFrame',

'exceptions',
Expand Down
127 changes: 127 additions & 0 deletions src/pymovements/_utils/_deprecated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Copyright (c) 2025 The pymovements Project Authors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Helpers for deprecations."""
from __future__ import annotations

from typing import Any
from warnings import warn


class DeprecatedMetaClass(type):
"""MetaClass for deprecated class aliases.

The class serves as an equivalent alias for the `isinstance()` and `issubclass()` methods.
It supports subclassing of the deprecated class.

Examples
--------
This is how a deprecated alias is defined:
>>> class NewClass:
... variable = 42
>>>
>>> class OldClass(metaclass=DeprecatedMetaClass):
... _DeprecatedMetaClass__alias = NewClass
... _DeprecatedMetaClass__version_deprecated = 'v1.23.4'
... _DeprecatedMetaClass__version_removed = 'v2.0.0'

Instantiating `OldClass` gives a warning:
>>> old_class = OldClass() # doctest: +SKIP
DeprecationWarning('OldClass has been renamed to NewClass in v1.23.4 and will be removed in
v2.0.0.')

As you see, an `OldClass` object is an instance of both OldClass and NewClass:
>>> isinstance(old_class, NewClass) # doctest: +SKIP
True

As well as vice versa:
>>> isinstance(NewClass(), OldClass)
True
"""

def __new__(
mcs: type[DeprecatedMetaClass],
name: str,
bases: tuple,
classdict: dict,
*args: Any,
**kwargs: Any,
) -> DeprecatedMetaClass:
"""Create new deprecated class."""
alias = classdict.get('_DeprecatedMetaClass__alias')
version_deprecated = classdict.get('_DeprecatedMetaClass__version_deprecated')
version_removed = classdict.get('_DeprecatedMetaClass__version_removed')

if alias is not None:
def new(cls: type, *args: Any, **kwargs: Any) -> type:
alias = getattr(cls, '_DeprecatedMetaClass__alias')
version_deprecated = getattr(cls, '_DeprecatedMetaClass__version_deprecated')
version_removed = getattr(cls, '_DeprecatedMetaClass__version_removed')

warn(
f"{cls.__name__} has been renamed to {alias.__name__} "
f"in {version_deprecated} "
f"and will be removed in {version_removed}.",
DeprecationWarning, stacklevel=2,
)

return alias(*args, **kwargs)

classdict['__new__'] = new
classdict['_DeprecatedMetaClass__alias'] = alias
classdict['_DeprecatedMetaClass__version_deprecated'] = version_deprecated
classdict['_DeprecatedMetaClass__version_removed'] = version_removed

fixed_bases = set()

for b in bases:
alias = getattr(b, '_DeprecatedMetaClass__alias', None)
version_deprecated = classdict.get('_DeprecatedMetaClass__version_deprecated')
version_removed = classdict.get('_DeprecatedMetaClass__version_removed')

if alias is not None:
warn(
f"{mcs.__name__} has been renamed to {alias.__name__} "
f"in {version_deprecated} "
f"and will be removed in {version_removed}.",
DeprecationWarning, stacklevel=2,
)

fixed_bases.add(alias or b)

return super().__new__(mcs, name, tuple(fixed_bases), classdict, *args, **kwargs)

def __subclasscheck__(cls, subclass: Any) -> bool:
"""Check if is subclass of deprecated class.

Provides implementation for issubclass().
"""
if subclass is cls:
return True
return issubclass(subclass, getattr(cls, '_DeprecatedMetaClass__alias'))

def __instancecheck__(cls, instance: Any) -> bool:
"""Check if is instance of deprecated class.

Provides implementation for isinstance().
"""
# pylint: disable=no-value-for-parameter
# pylint doesn't get that this is a metaclass method:
# see: https://github.com/pylint-dev/pylint/issues/3268
return any(cls.__subclasscheck__(c) for c in (type(instance), instance.__class__))
22 changes: 11 additions & 11 deletions src/pymovements/dataset/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from pymovements.dataset.dataset_paths import DatasetPaths
from pymovements.events import EventDataFrame
from pymovements.events.precomputed import PrecomputedEventDataFrame
from pymovements.gaze import GazeDataFrame
from pymovements.gaze import Gaze
from pymovements.reading_measures import ReadingMeasures


Expand Down Expand Up @@ -65,7 +65,7 @@ def __init__(
path: str | Path | DatasetPaths,
):
self.fileinfo: pl.DataFrame = pl.DataFrame()
self.gaze: list[GazeDataFrame] = []
self.gaze: list[Gaze] = []
self.events: list[EventDataFrame] = []
self.precomputed_events: list[PrecomputedEventDataFrame] = []
self.precomputed_reading_measures: list[ReadingMeasures] = []
Expand Down Expand Up @@ -163,8 +163,8 @@ def load(
events_dirname=events_dirname,
extension=extension,
)
for loaded_gaze_df, loaded_events_df in zip(self.gaze, self.events):
loaded_gaze_df.events = loaded_events_df
for loaded_gaze, loaded_events_df in zip(self.gaze, self.events):
loaded_gaze.events = loaded_events_df

return self

Expand Down Expand Up @@ -255,7 +255,7 @@ def split_gaze_data(
self,
by: Sequence[str],
) -> None:
"""Split gaze data into separated GazeDataFrame's.
"""Split gaze data into separated Gaze's.

Parameters
----------
Expand Down Expand Up @@ -340,7 +340,7 @@ def apply(
verbose: bool = True,
**kwargs: Any,
) -> Dataset:
"""Apply preprocessing method to all GazeDataFrames in Dataset.
"""Apply preprocessing method to all Gazes in Dataset.

Parameters
----------
Expand Down Expand Up @@ -389,7 +389,7 @@ def apply(
>>> dataset.apply('resample', resampling_rate=2000)# doctest:+ELLIPSIS
<pymovements.dataset.dataset.Dataset object at ...>
"""
self._check_gaze_dataframe()
self._check_gaze()

disable_progressbar = not verbose
for gaze in tqdm(self.gaze, disable=disable_progressbar):
Expand Down Expand Up @@ -721,7 +721,7 @@ def detect(
AttributeError
If gaze files have not been loaded yet or gaze files do not contain the right columns.
"""
self._check_gaze_dataframe()
self._check_gaze()

if not self.events:
self.events = [gaze.events for gaze in self.gaze]
Expand All @@ -732,7 +732,7 @@ def detect(
disable=disable_progressbar,
):
gaze.detect(method, eye=eye, clear=clear, **kwargs)
# workaround until events are fully part of the GazeDataFrame
# workaround until events are fully part of the Gaze
gaze.events.frame = dataset_files.add_fileinfo(
definition=self.definition,
df=gaze.events.frame,
Expand Down Expand Up @@ -947,7 +947,7 @@ def save_preprocessed(
If extension is not in list of valid extensions.
"""
dataset_files.save_preprocessed(
gaze=self.gaze,
gazes=self.gaze,
fileinfo=self.fileinfo['gaze'],
paths=self.paths,
preprocessed_dirname=preprocessed_dirname,
Expand Down Expand Up @@ -1101,7 +1101,7 @@ def _check_fileinfo(self) -> None:
if len(self.fileinfo) == 0:
raise AttributeError('no files present in fileinfo attribute')

def _check_gaze_dataframe(self) -> None:
def _check_gaze(self) -> None:
"""Check if gaze attribute is set and there is at least one gaze dataframe available."""
if self.gaze is None:
raise AttributeError('gaze files were not loaded yet. please run load() beforehand')
Expand Down
Loading
Loading