Skip to content

Add type annotations to mobject_update_utils.py #4382

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions manim/animation/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def __init__(
) -> None:
self._typecheck_input(mobject)
self.run_time: float = run_time
self.total_time: float
self.rate_func: Callable[[float], float] = rate_func
self.reverse_rate_function: bool = reverse_rate_function
self.name: str | None = name
Expand Down
37 changes: 23 additions & 14 deletions manim/animation/updaters/mobject_update_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


import inspect
from typing import TYPE_CHECKING, Callable
from typing import TYPE_CHECKING, Any, Callable

import numpy as np

Expand All @@ -26,6 +26,7 @@

if TYPE_CHECKING:
from manim.animation.animation import Animation
from manim.typing import Vector3DLike


def assert_is_mobject_method(method: Callable) -> None:
Copy link
Contributor

@chopan050 chopan050 Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, all the errors you describe below (method not having attributes __self__ and __func__) could be fixed by typing the method parameter as a MethodType instead, which you have to import from the types standard library.

However, if you try to pass a method directly to these functions after typing method as MethodType, MyPy raises an error. There's an issue about this on the MyPy GitHub: python/mypy#14235

In the meantime, I would say it's still fine to type them as MethodType to prevent these errors you're having. We already have some parameters and returned values typed as MethodType anyways.

EDIT: also see my implementation of MobjectMethod in PR #3976 if you're interested. The point of it is getting better autocompletion for Mobjects, but it still suffers from the issue described above (and I forgot to include the __self__ attribute).

Suggested change
def assert_is_mobject_method(method: Callable) -> None:
def assert_is_mobject_method(method: MethodType) -> None:

Expand All @@ -34,33 +35,39 @@ def assert_is_mobject_method(method: Callable) -> None:
assert isinstance(mobject, (Mobject, OpenGLMobject))


def always(method: Callable, *args, **kwargs) -> Mobject:
def always(method: Callable, *args: Any, **kwargs: Any) -> Mobject | OpenGLMobject:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def always(method: Callable, *args: Any, **kwargs: Any) -> Mobject | OpenGLMobject:
def always(method: MethodType, *args: Any, **kwargs: Any) -> Mobject | OpenGLMobject:

assert_is_mobject_method(method)
mobject = method.__self__
# error: "Callable[..., Any]" has no attribute "__self__" [attr-defined]
mobject: Mobject | OpenGLMobject = method.__self__
# error: "Callable[..., Any]" has no attribute "__func__" [attr-defined]
func = method.__func__
mobject.add_updater(lambda m: func(m, *args, **kwargs))
return mobject


def f_always(method: Callable[[Mobject], None], *arg_generators, **kwargs) -> Mobject:
def f_always(
method: Callable[[Mobject], None], *arg_generators: Any, **kwargs: Any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
method: Callable[[Mobject], None], *arg_generators: Any, **kwargs: Any
method: MethodType, *arg_generators: Any, **kwargs: Any

) -> Mobject | OpenGLMobject:
"""
More functional version of always, where instead
of taking in args, it takes in functions which output
the relevant arguments.
"""
assert_is_mobject_method(method)
mobject = method.__self__
# error: "Callable[[Mobject], None]" has no attribute "__self__" [attr-defined]
mobject: Mobject | OpenGLMobject = method.__self__
# error: "Callable[[Mobject], None]" has no attribute "__func__" [attr-defined]
func = method.__func__

def updater(mob):
def updater(mob: Mobject | OpenGLMobject) -> None:
args = [arg_generator() for arg_generator in arg_generators]
func(mob, *args, **kwargs)

mobject.add_updater(updater)
return mobject


def always_redraw(func: Callable[[], Mobject]) -> Mobject:
def always_redraw(func: Callable[[], Mobject]) -> Mobject | OpenGLMobject:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the return value is any of these two classes, the func should be able to return them:

Suggested change
def always_redraw(func: Callable[[], Mobject]) -> Mobject | OpenGLMobject:
def always_redraw(
func: Callable[[], Mobject | OpenGLMobject]
) -> Mobject | OpenGLMobject:

"""Redraw the mobject constructed by a function every frame.

This function returns a mobject with an attached updater that
Expand Down Expand Up @@ -106,8 +113,8 @@ def construct(self):


def always_shift(
mobject: Mobject, direction: np.ndarray[np.float64] = RIGHT, rate: float = 0.1
) -> Mobject:
mobject: Mobject | OpenGLMobject, direction: Vector3DLike = RIGHT, rate: float = 0.1
) -> Mobject | OpenGLMobject:
"""A mobject which is continuously shifted along some direction
at a certain rate.

Expand Down Expand Up @@ -144,7 +151,9 @@ def construct(self):
return mobject


def always_rotate(mobject: Mobject, rate: float = 20 * DEGREES, **kwargs) -> Mobject:
def always_rotate(
mobject: Mobject | OpenGLMobject, rate: float = 20 * DEGREES, **kwargs: Any
) -> Mobject | OpenGLMobject:
"""A mobject which is continuously rotated at a certain rate.

Parameters
Expand Down Expand Up @@ -178,8 +187,8 @@ def construct(self):


def turn_animation_into_updater(
animation: Animation, cycle: bool = False, delay: float = 0, **kwargs
) -> Mobject:
animation: Animation, cycle: bool = False, delay: float = 0, **kwargs: Any
) -> Mobject | OpenGLMobject:
"""
Add an updater to the animation's mobject which applies
the interpolation and update functions of the animation
Expand Down Expand Up @@ -210,7 +219,7 @@ def construct(self):
animation.begin()
animation.total_time = -delay

def update(m: Mobject, dt: float):
def update(m: Mobject, dt: float) -> None:
if animation.total_time >= 0:
run_time = animation.get_run_time()
time_ratio = animation.total_time / run_time
Expand All @@ -230,5 +239,5 @@ def update(m: Mobject, dt: float):
return mobject


def cycle_animation(animation: Animation, **kwargs) -> Mobject:
def cycle_animation(animation: Animation, **kwargs: Any) -> Mobject | OpenGLMobject:
return turn_animation_into_updater(animation, cycle=True, **kwargs)
3 changes: 0 additions & 3 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ ignore_errors = True
[mypy-manim.animation.transform]
ignore_errors = True

[mypy-manim.animation.updaters.mobject_update_utils]
ignore_errors = True

[mypy-manim.camera.mapping_camera]
ignore_errors = True

Expand Down
Loading