diff --git a/docs/source/installation/plugins.rst b/docs/source/installation/plugins.rst index 989084af33..f179777634 100644 --- a/docs/source/installation/plugins.rst +++ b/docs/source/installation/plugins.rst @@ -20,6 +20,10 @@ install, use, and create your own plugins. for the best practices on installing, using, and creating plugins; as well as new subcommands/flags for ``manim plugins`` +.. tip:: + + See https://plugins.manim.community/ for the list of plugins available. + Installing Plugins ****************** Plugins can be easily installed via the ``pip`` @@ -55,27 +59,37 @@ You can list plugins as such: Using Plugins in Projects ************************* -Plugins specified in ``plugins/__init__.py`` are imported automatically by -manim's ``__init__.py``. As such, writing: +For enabling a plugin ``manim.cfg`` or command line parameters should be used. + +.. important:: + + The plugins should be module name of the plugin and not PyPi name. -.. code-block:: python +Enabling plugins through ``manim.cfg`` - from manim import * +.. code-block:: ini -in your projects will import any of the plugins imported in -``plugins/__init__.py``. + [CLI] + plugins = manim_rubikscube -By default, ``plugins/__init__.py`` is not provided; although, there are -plans to support subcommands that would manage this file. It is especially -useful to create this file for projects that involve usage of the same -plugins. Alternatively, you may manually specify the plugins in your project -scripts. +For specifing multiple plugins, command separated values must be used. -.. code-block:: python +.. code-block:: ini - import manim_cool_plugin - # or - from manim_cool_plugin import feature_x, feature_y, ... + [CLI] + plugins = manim_rubikscube, manim_plugintemplate + +Enabling Plugins through CLI + +.. code-block:: bash + + manim basic.py --plugins=manim_plugintemplate + +For multiple plugins + +.. code-block:: bash + + manim basic.py --plugins=manim_rubikscube,manim_plugintemplate Creating Plugins **************** @@ -101,4 +115,33 @@ specified in poetry as: .. code-block:: toml [tool.poetry.plugins."manim.plugins"] - "name" = "object_reference" \ No newline at end of file + "name" = "object_reference" + +Here ``name`` is the name of the module of the plugin. + +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 returns a list of modules or functions that needs to defined globally and +it defined it. + +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. diff --git a/docs/source/tutorials/configuration.rst b/docs/source/tutorials/configuration.rst index 0b2b7b7eaa..8de132014e 100644 --- a/docs/source/tutorials/configuration.rst +++ b/docs/source/tutorials/configuration.rst @@ -353,12 +353,12 @@ A list of all config options 'frame_y_radius', 'from_animation_number', 'images_dir', 'input_file', 'js_renderer_path', 'leave_progress_bars', 'left_side', 'log_dir', 'log_to_file', 'max_files_cached', 'media_dir', 'movie_file_extension', 'output_file', - 'partial_movie_dir', 'pixel_height', 'pixel_width', 'png_mode', 'preview', - 'progress_bar', 'quality', 'right_side', 'save_as_gif', 'save_last_frame', + 'partial_movie_dir', 'pixel_height', 'pixel_width', 'plugins', 'png_mode', + 'preview', 'progress_bar', 'quality', 'right_side', 'save_as_gif', 'save_last_frame', 'save_pngs', 'scene_names', 'show_in_file_browser', 'sound', 'tex_dir', 'tex_template', 'tex_template_file', 'text_dir', 'top', 'transparent', - 'upto_animation_number', 'use_js_renderer', 'verbosity', 'video_dir', 'write_all', - 'write_to_movie'] + 'upto_animation_number', 'use_js_renderer', 'verbosity', 'video_dir', + 'write_all', 'write_to_movie'] A list of all CLI flags diff --git a/manim/_config/default.cfg b/manim/_config/default.cfg index 822bbea35f..da65017b14 100644 --- a/manim/_config/default.cfg +++ b/manim/_config/default.cfg @@ -112,6 +112,9 @@ disable_caching = False # --tex_template tex_template = +# specify the plugins as comma seperated values +# manim will load that plugin if it specified here. +plugins = # Overrides the default output folders, NOT the output file names. Note that # if the custom_folders flag is present, the Tex and text files will not be put diff --git a/manim/_config/utils.py b/manim/_config/utils.py index 292f821841..ed6b4daf76 100644 --- a/manim/_config/utils.py +++ b/manim/_config/utils.py @@ -272,6 +272,7 @@ class MyScene(Scene): "partial_movie_dir", "pixel_height", "pixel_width", + "plugins", "png_mode", "preview", "progress_bar", @@ -551,7 +552,8 @@ def digest_parser(self, parser: configparser.ConfigParser) -> "ManimConfig": # "frame_height", ]: setattr(self, key, parser["CLI"].getfloat(key)) - + # plugins + self.plugins = parser["CLI"].get("plugins", fallback="", raw=True).split(",") # the next two must be set AFTER digesting pixel_width and pixel_height self["frame_height"] = parser["CLI"].getfloat("frame_height", 8.0) width = parser["CLI"].getfloat("frame_width", None) @@ -1304,6 +1306,15 @@ def tex_template_file(self, val: str) -> None: self._d["tex_template_file"] = val # actually set the falsy value self._tex_template = TexTemplate() # but don't use it + @property + def plugins(self): + """List of plugins to enable.""" + return self._d["plugins"] + + @plugins.setter + def plugins(self, value): + self._d["plugins"] = value + class ManimFrame(Mapping): _OPTS: typing.Set[str] = { diff --git a/manim/plugins/.gitignore b/manim/plugins/.gitignore deleted file mode 100644 index 93f5256cda..0000000000 --- a/manim/plugins/.gitignore +++ /dev/null @@ -1 +0,0 @@ -__init__.py \ No newline at end of file diff --git a/manim/plugins/__init__.py b/manim/plugins/__init__.py new file mode 100644 index 0000000000..15c56a2b5e --- /dev/null +++ b/manim/plugins/__init__.py @@ -0,0 +1 @@ +from .import_plugins import * diff --git a/manim/plugins/import_plugins.py b/manim/plugins/import_plugins.py new file mode 100644 index 0000000000..2afdfee7bc --- /dev/null +++ b/manim/plugins/import_plugins.py @@ -0,0 +1,42 @@ +import types +from importlib import import_module + +import pkg_resources + +from .. import config, logger + +__all__ = [] + + +plugins_requested: list = 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__: + 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 l in lists: + exec(f"{l.__name__}=l") + __all__.append(l.__name__) + plugins_requested.remove(plugin.name) +else: + if plugins_requested != []: + logger.warning("Missing Plugins: %s", plugins_requested) diff --git a/poetry.lock b/poetry.lock index 737f7f74ae..b06da76687 100644 --- a/poetry.lock +++ b/poetry.lock @@ -206,7 +206,7 @@ optional = false python-versions = "*" [package.extras] -test = ["flake8 (3.7.8)", "hypothesis (3.55.3)"] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "contextvars" @@ -436,7 +436,7 @@ python-versions = ">=3.6" parso = ">=0.8.0,<0.9.0" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] @@ -627,7 +627,7 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] name = "matplotlib" -version = "3.3.3" +version = "3.3.4" description = "Python plotting package" category = "dev" optional = false @@ -724,11 +724,11 @@ testpath = "*" traitlets = ">=4.2" [package.extras] -all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] +all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] serve = ["tornado (>=4.0)"] -test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)"] -webpdf = ["pyppeteer (0.2.2)"] +test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)"] +webpdf = ["pyppeteer (==0.2.2)"] [[package]] name = "nbformat" @@ -845,7 +845,7 @@ optional = true python-versions = ">=3.6" [package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] [[package]] @@ -1068,7 +1068,7 @@ python-versions = "*" [[package]] name = "pyzmq" -version = "21.0.2" +version = "22.0.0" description = "Python bindings for 0MQ" category = "main" optional = true @@ -1109,7 +1109,7 @@ python-versions = "*" [package.extras] security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "rich" @@ -1406,7 +1406,7 @@ python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] js_renderer = ["grpcio", "grpcio-tools", "watchdog"] @@ -1415,7 +1415,7 @@ jupyterlab = ["jupyterlab"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "4c68a31d9acaac6f59bee68ecf249398412211dca0180bb60d30ef527a813205" +content-hash = "78c1de7eb393f81861360740f69ee0edca81813e399ab1b41d39f6545a295d63" [metadata.files] alabaster = [ @@ -1871,31 +1871,31 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] matplotlib = [ - {file = "matplotlib-3.3.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b2a5e1f637a92bb6f3526cc54cc8af0401112e81ce5cba6368a1b7908f9e18bc"}, - {file = "matplotlib-3.3.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c586ac1d64432f92857c3cf4478cfb0ece1ae18b740593f8a39f2f0b27c7fda5"}, - {file = "matplotlib-3.3.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b03722c89a43a61d4d148acfc89ec5bb54cd0fd1539df25b10eb9c5fa6c393a"}, - {file = "matplotlib-3.3.3-cp36-cp36m-win32.whl", hash = "sha256:2c2c5041608cb75c39cbd0ed05256f8a563e144234a524c59d091abbfa7a868f"}, - {file = "matplotlib-3.3.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c092fc4673260b1446b8578015321081d5db73b94533fe4bf9b69f44e948d174"}, - {file = "matplotlib-3.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27c9393fada62bd0ad7c730562a0fecbd3d5aaa8d9ed80ba7d3ebb8abc4f0453"}, - {file = "matplotlib-3.3.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b8ba2a1dbb4660cb469fe8e1febb5119506059e675180c51396e1723ff9b79d9"}, - {file = "matplotlib-3.3.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0caa687fce6174fef9b27d45f8cc57cbc572e04e98c81db8e628b12b563d59a2"}, - {file = "matplotlib-3.3.3-cp37-cp37m-win32.whl", hash = "sha256:b7b09c61a91b742cb5460b72efd1fe26ef83c1c704f666e0af0df156b046aada"}, - {file = "matplotlib-3.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6ffd2d80d76df2e5f9f0c0140b5af97e3b87dd29852dcdb103ec177d853ec06b"}, - {file = "matplotlib-3.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5111d6d47a0f5b8f3e10af7a79d5e7eb7e73a22825391834734274c4f312a8a0"}, - {file = "matplotlib-3.3.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a4fe54eab2c7129add75154823e6543b10261f9b65b2abe692d68743a4999f8c"}, - {file = "matplotlib-3.3.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:83e6c895d93fdf93eeff1a21ee96778ba65ef258e5d284160f7c628fee40c38f"}, - {file = "matplotlib-3.3.3-cp38-cp38-win32.whl", hash = "sha256:b26c472847911f5a7eb49e1c888c31c77c4ddf8023c1545e0e8e0367ba74fb15"}, - {file = "matplotlib-3.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:09225edca87a79815822eb7d3be63a83ebd4d9d98d5aa3a15a94f4eee2435954"}, - {file = "matplotlib-3.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eb6b6700ea454bb88333d98601e74928e06f9669c1ea231b4c4c666c1d7701b4"}, - {file = "matplotlib-3.3.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2d31aff0c8184b05006ad756b9a4dc2a0805e94d28f3abc3187e881b6673b302"}, - {file = "matplotlib-3.3.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d082f77b4ed876ae94a9373f0db96bf8768a7cca6c58fc3038f94e30ffde1880"}, - {file = "matplotlib-3.3.3-cp39-cp39-win32.whl", hash = "sha256:e71cdd402047e657c1662073e9361106c6981e9621ab8c249388dfc3ec1de07b"}, - {file = "matplotlib-3.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:756ee498b9ba35460e4cbbd73f09018e906daa8537fff61da5b5bf8d5e9de5c7"}, - {file = "matplotlib-3.3.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ad44f2c74c50567c694ee91c6fa16d67e7c8af6f22c656b80469ad927688457"}, - {file = "matplotlib-3.3.3-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:3a4c3e9be63adf8e9b305aa58fb3ec40ecc61fd0f8fd3328ce55bc30e7a2aeb0"}, - {file = "matplotlib-3.3.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:746897fbd72bd462b888c74ed35d812ca76006b04f717cd44698cdfc99aca70d"}, - {file = "matplotlib-3.3.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:5ed3d3342698c2b1f3651f8ea6c099b0f196d16ee00e33dc3a6fee8cb01d530a"}, - {file = "matplotlib-3.3.3.tar.gz", hash = "sha256:b1b60c6476c4cfe9e5cf8ab0d3127476fd3d5f05de0f343a452badaad0e4bdec"}, + {file = "matplotlib-3.3.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:672960dd114e342b7c610bf32fb99d14227f29919894388b41553217457ba7ef"}, + {file = "matplotlib-3.3.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7c155437ae4fd366e2700e2716564d1787700687443de46bcb895fe0f84b761d"}, + {file = "matplotlib-3.3.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a17f0a10604fac7627ec82820439e7db611722e80c408a726cd00d8c974c2fb3"}, + {file = "matplotlib-3.3.4-cp36-cp36m-win32.whl", hash = "sha256:215e2a30a2090221a9481db58b770ce56b8ef46f13224ae33afe221b14b24dc1"}, + {file = "matplotlib-3.3.4-cp36-cp36m-win_amd64.whl", hash = "sha256:348e6032f666ffd151b323342f9278b16b95d4a75dfacae84a11d2829a7816ae"}, + {file = "matplotlib-3.3.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:94bdd1d55c20e764d8aea9d471d2ae7a7b2c84445e0fa463f02e20f9730783e1"}, + {file = "matplotlib-3.3.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a1acb72f095f1d58ecc2538ed1b8bca0b57df313b13db36ed34b8cdf1868e674"}, + {file = "matplotlib-3.3.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:46b1a60a04e6d884f0250d5cc8dc7bd21a9a96c584a7acdaab44698a44710bab"}, + {file = "matplotlib-3.3.4-cp37-cp37m-win32.whl", hash = "sha256:ed4a9e6dcacba56b17a0a9ac22ae2c72a35b7f0ef0693aa68574f0b2df607a89"}, + {file = "matplotlib-3.3.4-cp37-cp37m-win_amd64.whl", hash = "sha256:c24c05f645aef776e8b8931cb81e0f1632d229b42b6d216e30836e2e145a2b40"}, + {file = "matplotlib-3.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7310e353a4a35477c7f032409966920197d7df3e757c7624fd842f3eeb307d3d"}, + {file = "matplotlib-3.3.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:451cc89cb33d6652c509fc6b588dc51c41d7246afdcc29b8624e256b7663ed1f"}, + {file = "matplotlib-3.3.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d2eb9c1cc254d0ffa90bc96fde4b6005d09c2228f99dfd493a4219c1af99644"}, + {file = "matplotlib-3.3.4-cp38-cp38-win32.whl", hash = "sha256:e15fa23d844d54e7b3b7243afd53b7567ee71c721f592deb0727ee85e668f96a"}, + {file = "matplotlib-3.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:1de0bb6cbfe460725f0e97b88daa8643bcf9571c18ba90bb8e41432aaeca91d6"}, + {file = "matplotlib-3.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f44149a0ef5b4991aaef12a93b8e8d66d6412e762745fea1faa61d98524e0ba9"}, + {file = "matplotlib-3.3.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:746a1df55749629e26af7f977ea426817ca9370ad1569436608dc48d1069b87c"}, + {file = "matplotlib-3.3.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:5f571b92a536206f7958f7cb2d367ff6c9a1fa8229dc35020006e4cdd1ca0acd"}, + {file = "matplotlib-3.3.4-cp39-cp39-win32.whl", hash = "sha256:9265ae0fb35e29f9b8cc86c2ab0a2e3dcddc4dd9de4b85bf26c0f63fe5c1c2ca"}, + {file = "matplotlib-3.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:9a79e5dd7bb797aa611048f5b70588b23c5be05b63eefd8a0d152ac77c4243db"}, + {file = "matplotlib-3.3.4-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1e850163579a8936eede29fad41e202b25923a0a8d5ffd08ce50fc0a97dcdc93"}, + {file = "matplotlib-3.3.4-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:d738acfdfb65da34c91acbdb56abed46803db39af259b7f194dc96920360dbe4"}, + {file = "matplotlib-3.3.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aa49571d8030ad0b9ac39708ee77bd2a22f87815e12bdee52ecaffece9313ed8"}, + {file = "matplotlib-3.3.4-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:cf3a7e54eff792f0815dbbe9b85df2f13d739289c93d346925554f71d484be78"}, + {file = "matplotlib-3.3.4.tar.gz", hash = "sha256:3e477db76c22929e4c6876c44f88d790aacdf3c3f8f3a90cb1975c0bf37825b0"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2141,34 +2141,35 @@ pywinpty = [ {file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"}, ] pyzmq = [ - {file = "pyzmq-21.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fcb790ff9df5d85d059069a7847f5696ec9296b719ed3e7e675a61a7af390e2f"}, - {file = "pyzmq-21.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d91cbc637a34e1a72ebc47da8bf21a2e6c5e386d1b04143c07c8082258e9b430"}, - {file = "pyzmq-21.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:082abbb95936f7475cee098153191058350878e33b8fb1dbefc82264978297e4"}, - {file = "pyzmq-21.0.2-cp36-cp36m-win32.whl", hash = "sha256:a3da3d5a66545fa127ad12784babd78859656e0c9614324d40c72d4210aa5bbe"}, - {file = "pyzmq-21.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:dbccca5b77162f610727b664804216674b1974a7a65e03a6ed638a9434cdf2b2"}, - {file = "pyzmq-21.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62b3c8196b2fa106552b03ed8ea7b91e1047e9a614849c87aea468f0caac4076"}, - {file = "pyzmq-21.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:664f075d38869c6117507193ae3f3d5319491900f11b344030345c11d74863f2"}, - {file = "pyzmq-21.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:530ee5571bea541ff68c6e92819a0da0bdab9457c9b637b6c142c267c02a799e"}, - {file = "pyzmq-21.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8b984feb536152009e2dc306140ec47f88dd85922063d9e9e8b07f4ff5a0832a"}, - {file = "pyzmq-21.0.2-cp37-cp37m-win32.whl", hash = "sha256:a0d3aaff782ee1d423e90604c2abe4e573062e9a2008b27c01c86d94f94dbfa7"}, - {file = "pyzmq-21.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0a6890d626b4f95f276a2381aea8d3435bb25ef7a2735bbc74966b105b09e758"}, - {file = "pyzmq-21.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82f59dbbdc47987f7ce0daea4d6ee21059ab9d5896bd8110215736c62762cc7f"}, - {file = "pyzmq-21.0.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:43df5e2fe06e03f41649a48e6339045fe8c68feaedef700a54440551f0ba94a3"}, - {file = "pyzmq-21.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b4b7e6edea41257562e9d4b28e717ee04ef078720d46ddb4c2241b9b60dbecc2"}, - {file = "pyzmq-21.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71ff9975f23a78c14a303bf4efd8b8924830a170a8eabcffff7f5e5a5b583b9e"}, - {file = "pyzmq-21.0.2-cp38-cp38-win32.whl", hash = "sha256:c34ec0218319f7a78b15315038125d08ab0b37ff1fe2ce002e70b7aafe1423cf"}, - {file = "pyzmq-21.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:544963322b1cb650de3d2f45d81bc644e5d9ada6f8f1f5718d9837cda78ee948"}, - {file = "pyzmq-21.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:efd3685579d93f01a742827d4d364df6a3c08df25e14ea091828e3f77d054f19"}, - {file = "pyzmq-21.0.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7307f6efb568a20bb56662041555d08aa2cbc71df91638344b6a088c10b44da7"}, - {file = "pyzmq-21.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:42ddd761ac71dd7a386849bceffdcf4f35798caf844b762693456fc55c19c721"}, - {file = "pyzmq-21.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:46ff042f883bb22242ba5a3817fbcb2ff0cc0990827b8f925d49c176b1cb7394"}, - {file = "pyzmq-21.0.2-cp39-cp39-win32.whl", hash = "sha256:fe714a0aeee5d5f230cb67af8e584f243adce63f32e81519dd80f605d036feea"}, - {file = "pyzmq-21.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:84ccd4d9f8839353278480d1f06372f5fd149abcb7621f85c4ebe0924acbd110"}, - {file = "pyzmq-21.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a70ef4e3835333e020c697ebfe3e6be172dd4ef8fe19ad047cd88678c1259c5"}, - {file = "pyzmq-21.0.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:f91a6dd45678fa6bac889267328ed9cfec56e2adeab7af2dddfa8c7e9dab24de"}, - {file = "pyzmq-21.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:68f8120ba7ec704d5acfabdcd1328c37806d8a23e1688a7ae3f59193c3cd46e3"}, - {file = "pyzmq-21.0.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:b7f471ecead3c4b3c88d00eeff5d78f2b2a6a9f56dd33aa96620019d83fcc3dd"}, - {file = "pyzmq-21.0.2.tar.gz", hash = "sha256:098c13c6198913c2a0690235fa74d2e49161755f66b663beaec89651554cc79c"}, + {file = "pyzmq-22.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a55335ecabc0c17ce6bd51bd96a8c5d48289ff715fcc292f0bc785b21c6abb75"}, + {file = "pyzmq-22.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e49ceb319240eaa38ae402939c6a0779205f6d3c9b9e860b37513cd3af5d39b0"}, + {file = "pyzmq-22.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b412fb8fb0f5f85e0e63587fe5f16018688c0dc1db25e28691ee23e193ebb2c"}, + {file = "pyzmq-22.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:09741d6c934e4a20c3b5019de23981298547695326fa01b4872d73d34e593f97"}, + {file = "pyzmq-22.0.0-cp36-cp36m-win32.whl", hash = "sha256:f588bee64a592cf949d53f5fc26802d8832c5ef419a4ec08cb9302a35918c46a"}, + {file = "pyzmq-22.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f5642b639d14351b1ae8480eb75a80f5933947864391ffd1a93280c620f7447c"}, + {file = "pyzmq-22.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e823078f28f1c11e32f513a4a638559036cd8cbddf7360e5fe72074e6050b5b4"}, + {file = "pyzmq-22.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9e89b982b041b4b3727eb5818029c9cb9050d490a4d175ea0bba876965a0dadc"}, + {file = "pyzmq-22.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eb855cc6d4f61d27171f05950d76338d606d0f118dbc4ac9156d6b47db322c92"}, + {file = "pyzmq-22.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c59bfdb14f1c51eb999624cbd5346dcaf9d888a0936d7aa0a4ea37f6cad2d2ca"}, + {file = "pyzmq-22.0.0-cp37-cp37m-win32.whl", hash = "sha256:296bf85bde1405d4b01019a6afc3ad1e3cb51510419424e306b4497b809a461d"}, + {file = "pyzmq-22.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:91190910c62b9609f25ac6f3665b232010631f53e8021f2a11aa8bb01c4c98ab"}, + {file = "pyzmq-22.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:471d46de1440645e58fd541490223c84b2583a909d5f16f6cab5e6584c4ba049"}, + {file = "pyzmq-22.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:4b1bdc20771203048eabd2385a67c5ebf5503dd86f3a09e44e34cfaf744decd7"}, + {file = "pyzmq-22.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5bbbf21998a97f6f3864a628890f1bef44308774be094c95933783e39b5c083e"}, + {file = "pyzmq-22.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ed9f3146edff26677aa95922c1ce9f2b17c04be23c5d247910d4907606ee9188"}, + {file = "pyzmq-22.0.0-cp38-cp38-win32.whl", hash = "sha256:5e742a8f24154285ba806b331ed130e036bc8fa5652a5931709a33d776f9555e"}, + {file = "pyzmq-22.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:99132b52be295879ff5a05df8b69be88face3c103550ea32debc22380add4042"}, + {file = "pyzmq-22.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00049ade45a08deee510ee5eefa5800d02163608e5efbf9d7a649ac9188791a5"}, + {file = "pyzmq-22.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bdd35a506d184cab584df7ba826a0f1e8524d8d22e0e97777a100800ab9fbc8f"}, + {file = "pyzmq-22.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:3fd5c49cdc31b13685fe1253700c31d1b073460c08508b4aac885decc4f24f0b"}, + {file = "pyzmq-22.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ee0366ab14db240b8ac578e1a6eac866d592dc79979efdfc51e85e7086cd7f1"}, + {file = "pyzmq-22.0.0-cp39-cp39-win32.whl", hash = "sha256:e001f00d45f39b234b66d8e37fb7e71c8d47557f3a9695501379984a5aae9729"}, + {file = "pyzmq-22.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:33ecd0f77136c6d56ac3b3af66d673d27c41bc976ffa8f6b77cf1e6e2fa529ee"}, + {file = "pyzmq-22.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e0a9955cd4ddd5da498757642184733b703ed1160bc47af7e6f1c500edb64915"}, + {file = "pyzmq-22.0.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:e66d04813dbf7e3343e0e23bcf935115f60765f33f9919e27690f115c95a8d2c"}, + {file = "pyzmq-22.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c19894d744c92d1f2c2e232278e8cffe7a463ee3e6a0b60421ddf479934a6d6"}, + {file = "pyzmq-22.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:afae6fd49a3ba3ef57bb2b263df0474218d0da72a9597247b4b3c376de51fe0f"}, + {file = "pyzmq-22.0.0.tar.gz", hash = "sha256:10b86bd04343b1de89ee03ec0bbaac646291de1a6c873228bb9ed22b4d8b32a2"}, ] recommonmark = [ {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, diff --git a/pyproject.toml b/pyproject.toml index 22554e2602..b51da3d90b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,12 +35,13 @@ pygments = "*" rich = "^6.0" pycairo = "^1.19" manimpango = "^0.1.7" +networkx = "^2.5" +setuptools = "*" +importlib-metadata = {version = "^1.0", python = "<3.8"} grpcio = { version = "1.33.*", optional = true } grpcio-tools = { version = "1.33.*", optional = true } watchdog = { version = "*", optional = true } -networkx = "^2.5" jupyterlab = { version = "^3.0", optional = true } -importlib-metadata = {version = "^1.0", python = "<3.8"} [tool.poetry.extras] js_renderer = ["grpcio","grpcio-tools","watchdog"] diff --git a/tests/conftest.py b/tests/conftest.py index abef7f47d2..fc68265763 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,7 +39,10 @@ def pytest_collection_modifyitems(config, items): @pytest.fixture(scope="session") def python_version(): - return "python3" if sys.platform == "darwin" else "python" + # use the same python executable as it is running currently + # rather than randomly calling using python or python3, which + # may create problems. + return sys.executable @pytest.fixture diff --git a/tests/test_plugins/__init__.py b/tests/test_plugins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_plugins/simple_scenes.py b/tests/test_plugins/simple_scenes.py new file mode 100644 index 0000000000..41edca5f1b --- /dev/null +++ b/tests/test_plugins/simple_scenes.py @@ -0,0 +1,29 @@ +from manim import * + + +class SquareToCircle(Scene): + def construct(self): + square = Square() + circle = Circle() + self.play(Transform(square, circle)) + + +class FunctionLikeTest(Scene): + def contruct(self): + assert "FunctionLike" in globals() + a = FunctionLike() + self.play(FadeIn(a)) + + +class NoAllTest(Scene): + def construct(self): + assert "test_plugin" in globals() + a = test_plugin.NoAll() + 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 new file mode 100644 index 0000000000..b1ac16d64b --- /dev/null +++ b/tests/test_plugins/test_plugins.py @@ -0,0 +1,273 @@ +import shutil +import textwrap +from pathlib import Path + +import pytest + +from ..utils.commands import capture + +plugin_pyproject_template = textwrap.dedent( + """ + [tool.poetry] + name = "{plugin_name}" + authors = ["ManimCE"] + version = "0.1.0" + description = "" + + [tool.poetry.dependencies] + python = "^3.6" + + [tool.poetry.plugins."manim.plugins"] + "{plugin_name}" = "{plugin_entrypoint}" + + [build-system] + requires = ["poetry-core>=1.0.0"] + build-backend = "poetry.core.masonry.api" + """ +) + +plugin_init_template = textwrap.dedent( + """ + from manim import * + class {class_name}(VMobject): + def __init__(self): + super().__init__() + dot1 = Dot(fill_color=GREEN).shift(LEFT) + dot2 = Dot(fill_color=BLUE) + dot3 = Dot(fill_color=RED).shift(RIGHT) + self.dotgrid = VGroup(dot1, dot2, dot3) + self.add(self.dotgrid) + + def update_dot(self): + self.dotgrid.become(self.dotgrid.shift(UP)) + def {function_name}(): + return [{class_name}] + """ +) + +cfg_file_contents = textwrap.dedent( + """ + [CLI] + plugins = test_plugin + """ +) + + +def simple_scenes_path(): + return str(Path(__file__).parent / "simple_scenes.py") + + +def cfg_file_create(cfg_file_contents, path): + file_loc = (path / "manim.cfg").absolute() + with open(file_loc, "w") as f: + f.write(cfg_file_contents) + return file_loc + + +def test_plugin_warning(tmp_path, python_version): + cfg_file_contents = textwrap.dedent( + """ + [CLI] + plugins = DNEPlugin + """ + ) + cfg_file = cfg_file_create(cfg_file_contents, tmp_path) + scene_name = "SquareToCircle" + command = [ + python_version, + "-m", + "manim", + simple_scenes_path(), + scene_name, + "-ql", + "--media_dir", + str(cfg_file.parent), + "--config_file", + str(cfg_file), + ] + out, err, exit_code = capture(command, cwd=str(cfg_file.parent)) + assert exit_code == 0, err + assert "Missing Plugins" in out, "Missing Plugins isn't in Output." + + +@pytest.fixture +def function_like_plugin(tmp_path, python_version): + plugin_name = "test_plugin" + entry_point = f"{plugin_name}.__init__:import_all" + plugin_dir = tmp_path / "function_entrypoint" + module_dir = plugin_dir / plugin_name + module_dir.mkdir(parents=True) + with open(module_dir / "__init__.py", "w") as f: + f.write( + plugin_init_template.format( + class_name="FunctionLike", + function_name="import_all", + ) + ) + with open(plugin_dir / "pyproject.toml", "w") as f: + f.write( + plugin_pyproject_template.format( + plugin_name=plugin_name, + plugin_entrypoint=entry_point, + ) + ) + command = [ + python_version, + "-m", + "pip", + "install", + str(plugin_dir.absolute()), + ] + out, err, exit_code = capture(command, cwd=str(plugin_dir)) + print(out) + assert exit_code == 0, err + yield module_dir + shutil.rmtree(plugin_dir) + command = [python_version, "-m", "pip", "uninstall", plugin_name, "-y"] + out, err, exit_code = capture(command) + print(out) + assert exit_code == 0, err + + +@pytest.mark.slow +def test_plugin_function_like(tmp_path, function_like_plugin, python_version): + cfg_file = cfg_file_create(cfg_file_contents, tmp_path) + scene_name = "FunctionLikeTest" + command = [ + python_version, + "-m", + "manim", + simple_scenes_path(), + scene_name, + "-ql", + "--media_dir", + str(cfg_file.parent), + "--config_file", + str(cfg_file), + ] + out, err, exit_code = capture(command, cwd=str(cfg_file.parent)) + print(out) + assert exit_code == 0, err + + +@pytest.fixture +def module_no_all_plugin(tmp_path, python_version): + plugin_name = "test_plugin" + entry_point = f"{plugin_name}" + plugin_dir = tmp_path / "module_entrypoint_no_all" + module_dir = plugin_dir / plugin_name + module_dir.mkdir(parents=True) + with open(module_dir / "__init__.py", "w") as f: + f.write( + plugin_init_template.format( + class_name="NoAll", + function_name="import_all", + ) + ) + with open(plugin_dir / "pyproject.toml", "w") as f: + f.write( + plugin_pyproject_template.format( + plugin_name=plugin_name, + plugin_entrypoint=entry_point, + ) + ) + command = [ + python_version, + "-m", + "pip", + "install", + str(plugin_dir.absolute()), + ] + out, err, exit_code = capture(command, cwd=str(plugin_dir)) + print(out) + assert exit_code == 0, err + yield module_dir + shutil.rmtree(plugin_dir) + command = [python_version, "-m", "pip", "uninstall", plugin_name, "-y"] + out, err, exit_code = capture(command) + print(out) + assert exit_code == 0, err + + +@pytest.mark.slow +def test_plugin_no_all(tmp_path, module_no_all_plugin, python_version): + cfg_file = cfg_file_create(cfg_file_contents, tmp_path) + scene_name = "NoAllTest" + command = [ + python_version, + "-m", + "manim", + simple_scenes_path(), + scene_name, + "-ql", + "--media_dir", + str(cfg_file.parent), + "--config_file", + str(cfg_file), + ] + out, err, exit_code = capture(command, cwd=str(cfg_file.parent)) + print(out) + print(err) + assert exit_code == 0, err + + +@pytest.fixture +def module_with_all_plugin(tmp_path, python_version): + plugin_name = "test_plugin" + entry_point = f"{plugin_name}" + plugin_dir = tmp_path / "module_entrypoint_with_all" + module_dir = plugin_dir / plugin_name + module_dir.mkdir(parents=True) + with open(module_dir / "__init__.py", "w") as f: + f.write("__all__=['WithAll']\n") + f.write( + plugin_init_template.format( + class_name="WithAll", + function_name="import_all", + ) + ) + with open(plugin_dir / "pyproject.toml", "w") as f: + f.write( + plugin_pyproject_template.format( + plugin_name=plugin_name, + plugin_entrypoint=entry_point, + ) + ) + command = [ + python_version, + "-m", + "pip", + "install", + str(plugin_dir.absolute()), + ] + out, err, exit_code = capture(command, cwd=str(plugin_dir)) + print(out) + assert exit_code == 0, err + yield module_dir + shutil.rmtree(plugin_dir) + command = [python_version, "-m", "pip", "uninstall", plugin_name, "-y"] + out, err, exit_code = capture(command) + print(out) + assert exit_code == 0, err + + +@pytest.mark.slow +def test_plugin_with_all(tmp_path, module_with_all_plugin, python_version): + cfg_file = cfg_file_create(cfg_file_contents, tmp_path) + scene_name = "WithAllTest" + command = [ + python_version, + "-m", + "manim", + simple_scenes_path(), + scene_name, + "-ql", + "--media_dir", + str(cfg_file.parent), + "--config_file", + str(cfg_file), + ] + out, err, exit_code = capture(command, cwd=str(cfg_file.parent)) + print(out) + print(err) + assert exit_code == 0, err diff --git a/tests/utils/commands.py b/tests/utils/commands.py index 90c5d3caf1..11316d6844 100644 --- a/tests/utils/commands.py +++ b/tests/utils/commands.py @@ -1,12 +1,13 @@ import subprocess -def capture(command): +def capture(command, cwd=None): proc = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf8", + cwd=cwd, ) out, err = proc.communicate() return out, err, proc.returncode