Skip to content

Remove support for dynamic plugin imports #3524

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 10 commits into from
Feb 15, 2024
Merged
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
32 changes: 5 additions & 27 deletions docs/source/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,40 +99,18 @@ directory structure, build system, and naming are completely up to your
discretion as an author. The aforementioned template plugin is only a model
using Poetry since this is the build system Manim uses. The plugin's `entry
point <https://packaging.python.org/specifications/entry-points/>`_ can be
specified in poetry as:
specified in Poetry as:
Copy link
Member Author

Choose a reason for hiding this comment

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

Self-reminder: a generic example should be given, not necessarily Poetry


.. code-block:: toml

[tool.poetry.plugins."manim.plugins"]
"name" = "object_reference"

Here ``name`` is the name of the module of the plugin.
.. versionremoved:: 0.19.0

Here ``object_reference`` can point to either a function in a module or a module
itself. For example,

.. code-block:: toml

[tool.poetry.plugins."manim.plugins"]
"manim_plugintemplate" = "manim_plugintemplate"

Here a module is used as ``object_reference``, and when this plugin is enabled,
Manim will look for ``__all__`` keyword defined in ``manim_plugintemplate`` and
everything as a global variable one by one.

If ``object_reference`` is a function, Manim calls the function and expects the
function to return a list of modules or functions that need to be defined globally.

For example,

.. code-block:: toml

[tool.poetry.plugins."manim.plugins"]
"manim_plugintemplate" = "manim_awesomeplugin.imports:setup_things"

Here, Manim will call the function ``setup_things`` defined in
``manim_awesomeplugin.imports`` and calls that. It returns a list of function or
modules which will be imported globally.
Plugins should be imported explicitly to be usable in user code. The plugin
system will probably be refactored in the future to provide a more structured
interface.

A note on Renderer Compatibility
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
16 changes: 15 additions & 1 deletion manim/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
from __future__ import annotations

from .import_plugins import *
from manim import config, logger

from .plugins_flags import get_plugins, list_plugins
Copy link
Member

Choose a reason for hiding this comment

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

Same as above

Copy link
Member Author

Choose a reason for hiding this comment

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

As said in my previous comment, relative imports are beneficial to some extent (i.e. if not going up too much). It is easier to refactor (if you rename a parent module, you'd have to rename your imports as well if you had used absolute imports).


__all__ = [
"get_plugins",
"list_plugins",
]

requested_plugins: set[str] = set(config["plugins"])
missing_plugins = requested_plugins - set(get_plugins().keys())


if missing_plugins:
logger.warning("Missing Plugins: %s", missing_plugins)
43 changes: 0 additions & 43 deletions manim/plugins/import_plugins.py

This file was deleted.

16 changes: 11 additions & 5 deletions manim/plugins/plugins_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@

from __future__ import annotations

import pkg_resources
import sys
from typing import Any

if sys.version_info < (3, 10):
from importlib_metadata import entry_points
else:
from importlib.metadata import entry_points

from manim import console

__all__ = ["list_plugins"]


def get_plugins():
plugins = {
def get_plugins() -> dict[str, Any]:
plugins: dict[str, Any] = {
entry_point.name: entry_point.load()
for entry_point in pkg_resources.iter_entry_points("manim.plugins")
for entry_point in entry_points(group="manim.plugins")
}
return plugins


def list_plugins():
def list_plugins() -> None:
console.print("[green bold]Plugins:[/green bold]", justify="left")

plugins = get_plugins()
Expand Down
8 changes: 0 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ click = ">=8.0"
cloup = ">=2.0.0"
dearpygui = { version = ">=1.0.0", optional = true }
decorator = ">=4.3.2"
importlib-metadata = {version = ">=3.6", python = "<=3.9"} # Required to discover plugins
isosurfaces = ">=0.1.0"
jupyterlab = { version = ">=3.0.0", optional = true }
manimpango = ">=0.5.0,<1.0.0" # Complete API change in 1.0.0
Expand Down
14 changes: 0 additions & 14 deletions tests/test_plugins/simple_scenes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,3 @@ def construct(self):
square = Square()
circle = Circle()
self.play(Transform(square, circle))


class FunctionLikeTest(Scene):
def construct(self):
assert "FunctionLike" in globals()
a = FunctionLike()
self.play(FadeIn(a))


class WithAllTest(Scene):
def construct(self):
assert "WithAll" in globals()
a = WithAll()
self.play(FadeIn(a))
114 changes: 0 additions & 114 deletions tests/test_plugins/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import random
import string
import tempfile
import textwrap
from pathlib import Path

Expand Down Expand Up @@ -142,116 +141,3 @@ def _create_plugin(entry_point, class_name, function_name, all_dec=""):
out, err, exit_code = capture(command)
print(out)
assert exit_code == 0, err


@pytest.mark.slow
def test_plugin_function_like(
tmp_path,
create_plugin,
python_version,
simple_scenes_path,
):
function_like_plugin = create_plugin(
"{plugin_name}.__init__:import_all",
"FunctionLike",
"import_all",
)
cfg_file = cfg_file_create(
cfg_file_contents.format(plugin_name=function_like_plugin["plugin_name"]),
tmp_path,
)
scene_name = "FunctionLikeTest"
command = [
python_version,
"-m",
"manim",
"-ql",
"--media_dir",
str(cfg_file.parent),
"--config_file",
str(cfg_file),
str(simple_scenes_path),
scene_name,
]
out, err, exit_code = capture(command, cwd=str(cfg_file.parent))
print(out)
print(err)
assert exit_code == 0, err


@pytest.mark.slow
def test_plugin_no_all(tmp_path, create_plugin, python_version):
create_plugin = create_plugin("{plugin_name}", "NoAll", "import_all")
plugin_name = create_plugin["plugin_name"]
cfg_file = cfg_file_create(
cfg_file_contents.format(plugin_name=plugin_name),
tmp_path,
)
test_class = textwrap.dedent(
f"""\
from manim import *
class NoAllTest(Scene):
def construct(self):
assert "{plugin_name}" in globals()
a = {plugin_name}.NoAll()
self.play(FadeIn(a))
""",
)

with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
suffix=".py",
delete=False,
) as tmpfile:
tmpfile.write(test_class)
scene_name = "NoAllTest"
command = [
python_version,
"-m",
"manim",
"-ql",
"--media_dir",
str(cfg_file.parent),
"--config_file",
str(cfg_file),
tmpfile.name,
scene_name,
]
out, err, exit_code = capture(command, cwd=str(cfg_file.parent))
print(out)
print(err)
assert exit_code == 0, err
Path(tmpfile.name).unlink()


@pytest.mark.slow
def test_plugin_with_all(tmp_path, create_plugin, python_version, simple_scenes_path):
create_plugin = create_plugin(
"{plugin_name}",
"WithAll",
"import_all",
all_dec="__all__=['WithAll']",
)
plugin_name = create_plugin["plugin_name"]
cfg_file = cfg_file_create(
cfg_file_contents.format(plugin_name=plugin_name),
tmp_path,
)
scene_name = "WithAllTest"
command = [
python_version,
"-m",
"manim",
"-ql",
"--media_dir",
str(cfg_file.parent),
"--config_file",
str(cfg_file),
str(simple_scenes_path),
scene_name,
]
out, err, exit_code = capture(command, cwd=str(cfg_file.parent))
print(out)
print(err)
assert exit_code == 0, err