Repository: ManimCommunity/manim
Pull request: Added sanity checks to :meth:`~.Mobject.add_to_back` for Mobjects
<!--
Thanks for your contribution to ManimCommunity!

Before filling in the details, ensure:
- Your local changes are up-to-date with ManimCommunity/manim
  
- The title of your PR gives a descriptive summary to end-users. Some examples:
  - Fixed last animations not running to completion
  - Added gradient support and documentation for SVG files
  Examples of what *NOT* to do:
  - "fixed that styling issue" - not descriptive enough
  - "fixed issue #XYZ" - end-user needs to do further research
-->
## Changelog / Overview
<!-- Optional (Recommended): a detailed overview of the PR for the upcoming
release's changelog entry. Useful for when the PR title isn't enough. 

DO NOT REMOVE THE FOLLOWING CHANGELOG LINES, EVEN IF YOU DON'T USE THEM.-->
<!--changelog-start-->
Add Mobject `add_to_back` sanity checks:
- Raises ValueError when Mobject tries to add itself
- Raises TypeError when a non-Mobject is added
- Filters out incoming duplicate submobjects if at least one instance of that submobject exists in the list
<!--changelog-end-->

Closes #1099 

## Motivation
<!-- In what way do your changes improve the library? -->
Additional checks were needed for `add_to_back` when dealing with `Mobject`, similar to `add`

## Explanation for Changes
<!-- How do your changes improve the library?

For PRs introducing new features, please provide code snippets using the
newly introduced functionality and ideally even the expected rendered output.
-->
See changelog / overview above

## Testing Status
<!-- Optional (Recommended): your computer specs and what tests you ran with
their results, if any. This section is also intended for other
testing-related comments. -->
Running `poetry` on **WSL: Ubuntu-20.04**
```py
def test_vmob_add_to_back():
    """Test the Mobject add_to_back method."""
    a = VMobject()
    b = Line()
    c = "text"
    with pytest.raises(ValueError):
        # Mobject cannot contain self
        a.add_to_back(a)
    with pytest.raises(TypeError):
        # All submobjects must be of type Mobject
        a.add_to_back(c)

    # No submobject gets added twice
    a.add_to_back(b)
    a.add_to_back(b, b)
    assert len(a.submobjects) == 1
```

## Further Comments
<!-- Optional: any further comments that might be useful for reviewers. -->
Duplication prevention may need additional testing for edge cases such as:
```py
a.add_to_back(b, b, b)
a.add_to_back(b, b)
```

## Checklist
- [x] I have read the [Contributing Guidelines](https://docs.manim.community/en/latest/contributing.html)
- [x] I have written a descriptive PR title (see top of PR template for examples)
- [x] I have written a changelog entry for the PR or deem it unnecessary
- [x] My new functions/classes either have a docstring or are private
- [x] My new functions/classes have [tests](https://github.com/ManimCommunity/manim/wiki/Testing) added and (optional) examples in the docs
- [x] My new documentation builds, looks correctly formatted, and adds no additional build warnings
<!-- Once again, thanks for contributing to ManimCommunity! -->


<!-- Do not modify the lines below. These are for the reviewers of your PR -->
## Reviewer Checklist
- [x] The PR title is descriptive enough
- [x] The PR is labeled correctly
- [x] The changelog entry is completed if necessary
- [x] Newly added functions/classes either have a docstring or are private
- [x] Newly added functions/classes have [tests](https://github.com/ManimCommunity/manim/wiki/Testing) added and (optional) examples in the docs
- [x] Newly added documentation builds, looks correctly formatted, and adds no additional build warnings


Problem statement:
Mobjects add_to_back is missing sanity checks
## Description of bug / unexpected behavior
the Mobject.add method contains sanity checks to make sure, no submobject gets added twice and self is not in self.submobjects.
Mobjext.add_to_back is missing this functionality.


## Expected behavior
Adding self should produce a ValueError: Mobject cannot contain self.
Addin a Mobject multiple times should result in it beeing just once in self.submobjects


## How to reproduce the issue
<details><summary>Code for reproducing the problem</summary>

```py
a = Mobject()
b = Line()
a.add_to_back(a)
a.add_to_back(b,b)
print(a.submobjects)
# results in [Mobject, Line, Line]
```

</details>

Hints from issue discussion:
Thanks for the tip! Sorry it took so long for one of us to respond.

If you'd like to tackle this problem, feel free to, and let me know by commenting. In the mean time I'll tag it as a good first issue.

Commit message: Merge branch 'master' into test/1099-mobject-add_to_back

Patch to review:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3c820ad6dc..c37b79399c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,4 +10,8 @@ repos:
   - repo: https://gitlab.com/PyCQA/flake8
     rev: 3.9.0
     hooks:
-    -   id: flake8
+      - id: flake8
+  - repo: https://github.com/PyCQA/isort
+    rev: 5.8.0
+    hooks:
+      - id: isort
\ No newline at end of file
diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py
index e93970311d..a551e5bd0e 100644
--- a/manim/mobject/mobject.py
+++ b/manim/mobject/mobject.py
@@ -347,9 +347,36 @@ def add_to_back(self, *mobjects: "Mobject") -> "Mobject":
             the head of :attr:`submobjects`. The head of this list is rendered
             first, which places the corresponding mobjects behind the
             subsequent list members.
+
+        Raises
+        ------
+        :class:`ValueError`
+            When a mobject tries to add itself.
+        :class:`TypeError`
+            When trying to add an object that is not an instance of :class:`Mobject`.
+
+        Notes
+        -----
+        A mobject cannot contain itself, and it cannot contain a submobject
+        more than once.  If the parent mobject is displayed, the newly-added
+        submobjects will also be displayed (i.e. they are automatically added
+        to the parent Scene).
+
+        See Also
+        --------
+        :meth:`remove`
+        :meth:`add`
+
         """
+        for mobject in mobjects:
+            if self in mobjects:
+                raise ValueError("Mobject cannot contain self")
+            if not isinstance(mobject, Mobject):
+                raise TypeError("All submobjects must be of type Mobject")
+
+        filtered = list_update(mobjects, self.submobjects)
         self.remove(*mobjects)
-        self.submobjects = list(mobjects) + self.submobjects
+        self.submobjects = list(filtered) + self.submobjects
         return self
 
     def remove(self, *mobjects: "Mobject") -> "Mobject":
diff --git a/tests/test_vectorized_mobject.py b/tests/test_vectorized_mobject.py
index f8f9e1e936..b9507682f3 100644
--- a/tests/test_vectorized_mobject.py
+++ b/tests/test_vectorized_mobject.py
@@ -1,6 +1,6 @@
 import pytest
 
-from manim import Mobject, VDict, VGroup, VMobject
+from manim import Line, Mobject, VDict, VGroup, VMobject
 
 
 def test_vgroup_init():
@@ -86,6 +86,24 @@ def test_vgroup_remove_dunder():
     assert len(obj.submobjects) == 0
 
 
+def test_vmob_add_to_back():
+    """Test the Mobject add_to_back method."""
+    a = VMobject()
+    b = Line()
+    c = "text"
+    with pytest.raises(ValueError):
+        # Mobject cannot contain self
+        a.add_to_back(a)
+    with pytest.raises(TypeError):
+        # All submobjects must be of type Mobject
+        a.add_to_back(c)
+
+    # No submobject gets added twice
+    a.add_to_back(b)
+    a.add_to_back(b, b)
+    assert len(a.submobjects) == 1
+
+
 def test_vdict_init():
     """Test the VDict instantiation."""
     # Test empty VDict


Code review comments:
diff_hunk: @@ -10,4 +10,8 @@ repos:
   - repo: https://gitlab.com/PyCQA/flake8
     rev: 3.9.0
     hooks:
-    -   id: flake8
+      - id: flake8
+  - repo: https://github.com/PyCQA/isort
+    rev: 5.8.0
+    hooks:
+      - id: isort
path: .pre-commit-config.yaml
text: @user1:
```suggestion
```