Skip to content

Add type annotations to opengl_mobject.py #4398

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

Merged
merged 46 commits into from
Aug 21, 2025

Conversation

RBerga06
Copy link
Contributor

@RBerga06 RBerga06 commented Aug 14, 2025

Overview: What does this pull request change?

Part of #3375.

Motivation and Explanation: Why and how do your changes improve the library?

This PR adds type annotations to manim/mobject/opengl/opengl_mobject.py, to improve type coverage for manim's public interface.

Edit: By thoroughly annotating a number of methods, I also discovered and fixed what appeared to be typos or mistakes. For reference, I'm listing them here, because these changes can in principle affect runtime behaviour.

  • Fixed a typo in OpenGLMobject.restore (commit 40dc43f)
  • Fixed extra **kwargs being passed to OpenGLMobject.apply_points_function that could lead to runtime TypeErrors (see my comment below)
  • Implemented a missing OpenGLMobject.get_array_attrs() method (commit 2909380)
  • Fixed a missing return self in OpenGLMobject.pointwise_become_partial (commit 5d5de9a)

Reviewer Checklist

  • The PR title is descriptive enough for the changelog, and the PR is labeled correctly
  • If applicable: newly added non-private functions and classes have a docstring including a short summary and a PARAMETERS section
  • If applicable: newly added functions and classes are tested

@RBerga06
Copy link
Contributor Author

I also made the manim.utils.config_ops._Data descriptor generic, to allow annotating the corresponding attributes in OpenGLMobject with the relevant array types. I also added a _HasData protocol to avoid using _Data in objects that are not guaranteed to have the data attribute.

@RBerga06
Copy link
Contributor Author

I have also discovered and fixed a typo in OpenGLMobject.restore (commit 40dc43f).

@RBerga06
Copy link
Contributor Author

It is not completely clear where extra **kwargs should be allowed (sometimes they are, sometimes they're not).

For example, I noticed that the apply_points_function method only accepts a fixed set of kwargs and that the scale method explicitely sets all of them. However, scale also forwards to apply_points_function additional **kwargs! This means that passing any extra keyword arguments to scale WILL introduce a runtime error.

That's why, at first, I annotated **kwargs in scale as **kwargs: Never: they must not exist at runtime. However, as soon as I did this mypy rejected my code, because it had introduced an error in manim/animation/growing.py, at line 208, where the scale method is invoked on a Mobject | OpenGLMobject with additional parameters (in particular, scale_tips=True), thus triggering a runtime error every time the object was an OpenGLObject!

I suspect the intention was to override the scale method in Mobject/OpenGLMobject subclasses and simply ignore all unrecognized keyword argument in the base class. Therefore, I kept **kwargs: object in scale and removed them from the apply_points_function call.

I think it would help to define a MobjectLike protocol with the interface that should be common to both Mobject and OpenGLMobject, but I think I should introduce it in a separate PR. This way, at least, the common object interface is clear and type checked.

@henrikmidtiby whay do you think about this proposal? I'm asking you because I saw you're working on typing Mobject in #4388 and I guess you might have noticed similar problems to the ones I'm describing here.

@RBerga06
Copy link
Contributor Author

I also think clarifying the current state and intended Mobject/OpenGLObject interface/behaviour might also help the work in #3112.

@RBerga06 RBerga06 changed the title Add type annotations to manim/mobject/opengl/*.py Add type annotations to manim/mobject/opengl/opengl_mobject.py Aug 16, 2025
…ginal__init__` at class scope).

I followed the advice of an existing `# TODO` comment and the implementation in `Mobject`. This also resolves a mypy error in this class (missing attribute).
It's interesting because the `get_array_attrs` method is (was) only defined in three classes:
- `Mobject`
- `PMobject`
- `OpenGLPMobject`
…`_AnimationBuilder` and some methods in `OpenGLPoint`.


class _OverrideAnimateDecorator(Protocol):
def __call__(self, decorated: _Decorated, /) -> _Decorated: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a callback protocol, I've used it to annotate the override_animate function return value. I've used a callback protocol beacuse the _Decorated TypeVar should be defined in the scope of that returned callable, not the override_animate function itself.

@henrikmidtiby
Copy link
Contributor

@RBerga06 I am happy to provide feedback.

I really like the use use of Unpack[TypedDict] for adding type annotations to **kwargs. This approach is much better than the **kwargs: Any I have used until now.

I'll try and explain the rest of them; although, if I recall correctly, they're
all about incompatible assignment between Mobject and OpenGLMobject.
Yes, that can be quite difficult to get right.

Regarding the issue Name ... already defined on line ... [no-redef] with the method arrange_in_grid of 270 lines in opengl_mobject.py. To me the main issue is that the method needs to be refactored, right now way to much is happening inside the method.

I get your point that the type checker should be able to deal with the situation, this is however not the case yet ... For me the cost of polluting the namespace with one additional variable, is worth it, if we can get rid of any type: ignore statements.

Regarding to locate a common subset of **kwargs between Mobject and OpenGLMobject, I would like to raise the question on the discord server.

Finally the size of this PR is getting quite large with multiple changes at once (e.g. add type annotations and update dependency on typing-extensions). It is much easier for the reviewers to handle several small PR than one large PR.

@chopan050 Would you have time to look at this PR?

@RBerga06
Copy link
Contributor Author

RBerga06 commented Aug 18, 2025

@henrikmidtiby

Finally the size of this PR is getting quite large with multiple changes at once (e.g. add type annotations and update dependency on typing-extensions).

Yes, I agree this is a pretty large PR. Given that **kwargs: Unpack[...] annotations require both adding several TypedDicts and bumping typing-extensions and given that we might even be moving those TypedDicts around anyway (to also type OpenGLMobject), I think the right thing to do is probably remove those changes from this PR (this ways it only adds type annotations) and reintroduce those changes in a dedicated PR, once we've discussed the design on Discord.

I'll update this PR accordingly ASAP (thus annotating **kwargs: Any for now). Edit: done.

@henrikmidtiby henrikmidtiby added the typehints For adding/discussing typehints label Aug 21, 2025
@@ -73,7 +82,7 @@ def copy(self):
result.texture_paths = dict(self.texture_paths)
return result

def is_valid(self):
def is_valid(self, /) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

I can see that the slash separator '/' have been added in multiple locations in the code and I am a bit puzzled over what the benefit is of doing that. Could you explain your reasoning for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, that was a mistake! Thanks for fixing it...

if TYPE_CHECKING:

@overload
def reverse(maybe_list: None) -> None: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.
@overload
def reverse(maybe_list: None) -> None: ...
@overload
def reverse(maybe_list: Sequence[_T]) -> list[_T]: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.
@overload
def reverse(maybe_list: Sequence[_T]) -> list[_T]: ...
@overload
def reverse(maybe_list: Sequence[_T] | None) -> list[_T] | None: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.
Copy link
Contributor

@henrikmidtiby henrikmidtiby left a comment

Choose a reason for hiding this comment

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

Looks good to me!

Thanks for the effort put into this.

@github-project-automation github-project-automation bot moved this from 🆕 New to 👍 To be merged in Dev Board Aug 21, 2025
@henrikmidtiby henrikmidtiby enabled auto-merge (squash) August 21, 2025 20:48
@henrikmidtiby henrikmidtiby merged commit 855ea86 into ManimCommunity:main Aug 21, 2025
21 checks passed
@github-project-automation github-project-automation bot moved this from 👍 To be merged to ✅ Done in Dev Board Aug 21, 2025
@RBerga06
Copy link
Contributor Author

@henrikmidtiby Happy to help this awesome project!

By the way, those /s in function signatures just mean that the preceeding (i.e. to the left) function arguments are positional-only. If I'm not mistaken, callback protocols should always define self in __call__ as positional-only.

@henrikmidtiby
Copy link
Contributor

henrikmidtiby commented Aug 25, 2025

@RBerga06

By the way, those /s in function signatures just mean that the preceeding (i.e. to the left)
function arguments are positional-only.

Well that makes sense.
Currently this type of parameter syntax is not used in other locations of the codebase of manim.
If there is a significant benefit of using this we could consider to add that; but right now I don't see a benefit of adding this to the manim codebase. In any case such a change should be suggested in a separate PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typehints For adding/discussing typehints
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants