diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index 0359d29b5e..c4c631b655 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -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 `_ can be -specified in poetry as: +specified in Poetry as: .. 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/manim/plugins/__init__.py b/manim/plugins/__init__.py index a899f63bb8..06314895e1 100644 --- a/manim/plugins/__init__.py +++ b/manim/plugins/__init__.py @@ -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 + +__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) diff --git a/manim/plugins/import_plugins.py b/manim/plugins/import_plugins.py deleted file mode 100644 index 1f11accbf9..0000000000 --- a/manim/plugins/import_plugins.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import annotations - -import types - -import pkg_resources - -from .. import config, logger - -__all__ = [] - - -plugins_requested: list[str] = config["plugins"] -if "" in plugins_requested: - plugins_requested.remove("") -for plugin in pkg_resources.iter_entry_points("manim.plugins"): - if plugin.name not in plugins_requested: - continue - loaded_plugin = plugin.load() - if isinstance(loaded_plugin, types.ModuleType): - # it is a module so it can't be called - # see if __all__ is defined - # if it is defined use that to load all the modules necessary - # essentially this would be similar to `from plugin import *`` - # if not just import the module with the plugin name - if hasattr(loaded_plugin, "__all__"): - for thing in loaded_plugin.__all__: # type: ignore - exec(f"{thing}=loaded_plugin.{thing}") - __all__.append(thing) - else: - exec(f"{plugin.name}=loaded_plugin") - __all__.append(plugin.name) - elif isinstance(loaded_plugin, types.FunctionType): - # call the function first - # it will return a list of modules to add globally - # finally add it - lists = loaded_plugin() - for lst in lists: - exec(f"{lst.__name__}=lst") - __all__.append(lst.__name__) - plugins_requested.remove(plugin.name) - -if plugins_requested != []: - logger.warning("Missing Plugins: %s", plugins_requested) diff --git a/manim/plugins/plugins_flags.py b/manim/plugins/plugins_flags.py index a1487e5774..3733ac3f3f 100644 --- a/manim/plugins/plugins_flags.py +++ b/manim/plugins/plugins_flags.py @@ -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() diff --git a/poetry.lock b/poetry.lock index b5fd7d925a..4d97a42aa3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1981,14 +1981,6 @@ files = [ {file = "mapbox_earcut-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9af9369266bf0ca32f4d401152217c46c699392513f22639c6b1be32bde9c1cc"}, {file = "mapbox_earcut-1.0.1-cp311-cp311-win32.whl", hash = "sha256:ff9a13be4364625697b0e0e04ba6a0f77300148b871bba0a85bfa67e972e85c4"}, {file = "mapbox_earcut-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e736557539c74fa969e866889c2b0149fc12668f35e3ae33667d837ff2880d3"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4fe92174410e4120022393013705d77cb856ead5bdf6c81bec614a70df4feb5d"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:082f70a865c6164a60af039aa1c377073901cf1f94fd37b1c5610dfbae2a7369"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43d268ece49d0c9e22cb4f92cd54c2cc64f71bf1c5e10800c189880d923e1292"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7748f1730fd36dd1fcf0809d8f872d7e1ddaa945f66a6a466ad37ef3c552ae93"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a82d10c8dec2a0bd9a6a6c90aca7044017c8dad79f7e209fd0667826f842325"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:01b292588cd3f6bad7d76ee31c004ed1b557a92bbd9602a72d2be15513b755be"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fce236ddc3a56ea7260acc94601a832c260e6ac5619374bb2cec2e73e7414ff0"}, - {file = "mapbox_earcut-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:1ce86407353b4f09f5778c436518bbbc6f258f46c5736446f25074fe3d3a3bd8"}, {file = "mapbox_earcut-1.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:aa6111a18efacb79c081f3d3cdd7d25d0585bb0e9f28896b207ebe1d56efa40e"}, {file = "mapbox_earcut-1.0.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2911829d1e6e5e1282fbe2840fadf578f606580f02ed436346c2d51c92f810b"}, {file = "mapbox_earcut-1.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ff909a7b8405a923abedd701b53633c997cc2b5dc9d5b78462f51c25ec2c33"}, diff --git a/pyproject.toml b/pyproject.toml index 1ba36a47b1..fcb3f20e52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 diff --git a/tests/test_plugins/simple_scenes.py b/tests/test_plugins/simple_scenes.py index 1562196119..e22085123e 100644 --- a/tests/test_plugins/simple_scenes.py +++ b/tests/test_plugins/simple_scenes.py @@ -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)) diff --git a/tests/test_plugins/test_plugins.py b/tests/test_plugins/test_plugins.py index b73a5d6a1d..e81dc47085 100644 --- a/tests/test_plugins/test_plugins.py +++ b/tests/test_plugins/test_plugins.py @@ -2,7 +2,6 @@ import random import string -import tempfile import textwrap from pathlib import Path @@ -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