diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d88d1b3..5b55803 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -73,22 +73,22 @@ body: description: | Please copy and paste the output of `python --version`. Make sure to activate your virtual environment first (if any). - This will be automatically formatted into code, so no need for backticks. placeholder: Python 3.11.8 validations: - required: false + required: true - type: textarea id: venv attributes: label: Python environment description: | - Please copy and paste the output of `pip freeze`. + Please copy and paste the output of `manim-slides checkhealth`. Make sure to activate your virtual environment first (if any). This will be automatically formatted into code, so no need for backticks. + If Manim Slides installation failed, enter 'N/A' instead. render: shell validations: - required: false + required: true - type: dropdown id: platform diff --git a/CHANGELOG.md b/CHANGELOG.md index 56755ff..f1213c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (unreleased)= ## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.1.7...HEAD) +(unreleased-added)= +### Added + +- Added `manim-slides checkhealth` command to easily obtain important information + for debug purposes. + [#458](https://github.com/jeertmans/manim-slides/pull/458) + (unreleased-chore)= ### Chore diff --git a/manim_slides/__main__.py b/manim_slides/__main__.py index 9093237..f90cd4d 100644 --- a/manim_slides/__main__.py +++ b/manim_slides/__main__.py @@ -5,6 +5,7 @@ import requests from click_default_group import DefaultGroup from .__version__ import __version__ +from .checkhealth import checkhealth from .convert import convert from .logger import logger from .present import list_scenes, present @@ -63,6 +64,7 @@ def cli(notify_outdated_version: bool) -> None: cli.add_command(convert) +cli.add_command(checkhealth) cli.add_command(init) cli.add_command(list_scenes) cli.add_command(present) diff --git a/manim_slides/checkhealth.py b/manim_slides/checkhealth.py new file mode 100644 index 0000000..f79fa86 --- /dev/null +++ b/manim_slides/checkhealth.py @@ -0,0 +1,39 @@ +import sys + +import click + +from .__version__ import __version__ + + +@click.command() +def checkhealth() -> None: + """Check Manim Slides' installation.""" + click.echo(f"Manim Slides version: {__version__}") + click.echo(f"Python executable: {sys.executable}") + click.echo("Manim bindings:") + + click.echo(f"Modules: {sys.modules.keys()}") + + try: + from manim import __version__ as manimce_version + + click.echo(f"\tmanim (version: {manimce_version})") + except ImportError: + click.secho("\tmanim not found", bold=True) + + try: + from manimlib import __version__ as manimlib_version + + click.echo(f"\tmanimgl (version: {manimlib_version})") + except ImportError: + click.secho("\tmanimgl not found", bold=True) + + try: + from qtpy import API, QT_VERSION + + click.echo(f"Qt API: {API} (version: {QT_VERSION})") + except ImportError: + click.secho( + "No Qt API found, some Manim Slides commands will not be available", + bold=True, + ) diff --git a/pyproject.toml b/pyproject.toml index 9c9013c..49b2b84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dependencies = [ "rtoml==0.9.0;sys_platform=='win32' and python_version<'3.13'", "rtoml>=0.9.0;sys_platform!='win32' or python_version>='3.13'", "tqdm>=4.64.1", + "pytest-missing-modules>=0.1.0", ] description = "Tool for live presentations using manim" dynamic = ["readme", "version"] @@ -217,6 +218,7 @@ isort = {known-first-party = ["manim_slides", "tests"]} dev-dependencies = [ "bump-my-version>=0.20.3", "pre-commit>=3.5.0", + "setuptools>=73.0.1", ] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index ed8cb3a..51e3de5 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,7 +7,6 @@ # all-features: true # with-sources: false # generate-hashes: false -# universal: false -e file:. alabaster==1.0.0 @@ -49,12 +48,6 @@ click-default-group==1.2.4 # via manim-slides cloup==3.0.5 # via manim -colorama==0.4.6 - # via click - # via ipython - # via pytest - # via sphinx - # via tqdm colour==0.1.5 # via manimgl comm==0.2.2 @@ -198,6 +191,7 @@ nodeenv==1.9.1 numpy==1.24.0 # via --override (workspace) # via contourpy + # via ipython # via isosurfaces # via manim # via manim-slides @@ -205,6 +199,7 @@ numpy==1.24.0 # via mapbox-earcut # via matplotlib # via moderngl-window + # via networkx # via pyrr # via scipy packaging==24.1 @@ -220,6 +215,8 @@ pandocfilters==1.5.1 # via nbconvert parso==0.8.4 # via jedi +pexpect==4.9.0 + # via ipython pillow==10.4.0 # via manim # via manim-slides @@ -243,6 +240,8 @@ prompt-toolkit==3.0.47 # via questionary psutil==6.0.0 # via ipykernel +ptyprocess==0.7.0 + # via pexpect pure-eval==0.2.3 # via stack-data pycairo==1.26.1 @@ -294,11 +293,14 @@ pytest==8.3.2 # via manim-slides # via pytest-cov # via pytest-env + # via pytest-missing-modules # via pytest-qt pytest-cov==5.0.0 # via manim-slides pytest-env==1.1.3 # via manim-slides +pytest-missing-modules==0.1.0 + # via manim-slides pytest-qt==4.4.0 # via manim-slides python-dateutil==2.9.0.post0 @@ -308,9 +310,6 @@ python-dotenv==1.0.1 # via pydantic-settings python-pptx==1.0.2 # via manim-slides -pywin32==306 - # via jupyter-core - # via plumbum pyyaml==6.0.2 # via manimgl # via myst-parser @@ -347,6 +346,7 @@ scipy==1.14.1 screeninfo==0.8.1 # via manim # via manimgl +setuptools==73.0.1 shiboken6==6.7.2 # via pyside6 # via pyside6-addons diff --git a/requirements.lock b/requirements.lock index cef486b..324dcc7 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,7 +7,6 @@ # all-features: true # with-sources: false # generate-hashes: false -# universal: false -e file:. alabaster==1.0.0 @@ -42,12 +41,6 @@ click-default-group==1.2.4 # via manim-slides cloup==3.0.5 # via manim -colorama==0.4.6 - # via click - # via ipython - # via pytest - # via sphinx - # via tqdm colour==0.1.5 # via manimgl comm==0.2.2 @@ -183,6 +176,7 @@ networkx==3.3 numpy==1.24.0 # via --override (workspace) # via contourpy + # via ipython # via isosurfaces # via manim # via manim-slides @@ -190,6 +184,7 @@ numpy==1.24.0 # via mapbox-earcut # via matplotlib # via moderngl-window + # via networkx # via pyrr # via scipy packaging==24.1 @@ -205,6 +200,8 @@ pandocfilters==1.5.1 # via nbconvert parso==0.8.4 # via jedi +pexpect==4.9.0 + # via ipython pillow==10.4.0 # via manim # via manim-slides @@ -225,6 +222,8 @@ prompt-toolkit==3.0.47 # via ipython psutil==6.0.0 # via ipykernel +ptyprocess==0.7.0 + # via pexpect pure-eval==0.2.3 # via stack-data pycairo==1.26.1 @@ -272,11 +271,14 @@ pytest==8.3.2 # via manim-slides # via pytest-cov # via pytest-env + # via pytest-missing-modules # via pytest-qt pytest-cov==5.0.0 # via manim-slides pytest-env==1.1.3 # via manim-slides +pytest-missing-modules==0.1.0 + # via manim-slides pytest-qt==4.4.0 # via manim-slides python-dateutil==2.9.0.post0 @@ -284,9 +286,6 @@ python-dateutil==2.9.0.post0 # via matplotlib python-pptx==1.0.2 # via manim-slides -pywin32==306 - # via jupyter-core - # via plumbum pyyaml==6.0.2 # via manimgl # via myst-parser diff --git a/tests/test_checkhealth.py b/tests/test_checkhealth.py new file mode 100644 index 0000000..39c8f59 --- /dev/null +++ b/tests/test_checkhealth.py @@ -0,0 +1,66 @@ +import importlib.util +import sys +from itertools import chain, combinations + +import pytest +from click.testing import CliRunner +from pytest_missing_modules.plugin import MissingModulesContextGenerator + +from manim_slides.__version__ import __version__ +from manim_slides.checkhealth import checkhealth + +MANIM_NOT_INSTALLED = importlib.util.find_spec("manim") is None +MANIMGL_NOT_INSTALLED = importlib.util.find_spec("manimlib") is None +PYQT6_NOT_INSTALLED = importlib.util.find_spec("PyQt6") is None +PYSIDE6_NOT_INSTALLED = importlib.util.find_spec("PySide6") is None + + +@pytest.mark.filterwarnings("ignore:Selected binding 'pyqt6' could not be found") +@pytest.mark.parametrize( + "names", + list( + chain.from_iterable( + combinations(("manim", "manimlib", "PyQt6", "PySide6"), r=r) + for r in range(0, 5) + ) + ), +) +def test_checkhealth( + names: tuple[str, ...], missing_modules: MissingModulesContextGenerator +) -> None: + runner = CliRunner() + + manim_missing = "manim" in names or MANIM_NOT_INSTALLED + manimlib_missing = "manimlib" in names or MANIMGL_NOT_INSTALLED + pyqt6_missing = "PyQt6" in names or PYQT6_NOT_INSTALLED + pyside6_missing = "PySide6" in names or PYSIDE6_NOT_INSTALLED + + if "qtpy" in sys.modules: + del sys.modules["qtpy"] # Avoid using cached module + + with missing_modules(*names): + result = runner.invoke( + checkhealth, + env={"QT_API": "pyqt6", "FORCE_QT_API": "1"}, + ) + + assert result.exit_code == 0 + assert f"Manim Slides version: {__version__}" in result.output + assert sys.executable in result.output + + if manim_missing: + assert "manim not found" in result.output + else: + assert "manim (version:" in result.output + + if manimlib_missing: + assert "manimgl not found" in result.output + else: + assert "manimgl (version:" in result.output + + if pyqt6_missing and pyside6_missing: + assert "No Qt API found" in result.output + elif pyqt6_missing: + assert "Qt API: pyside6 (version:" in result.output + else: + assert "Qt API: pyqt6 (version:" in result.output