diff --git a/manim/_config/utils.py b/manim/_config/utils.py index f109af6939..a10fde0031 100644 --- a/manim/_config/utils.py +++ b/manim/_config/utils.py @@ -20,7 +20,7 @@ import os import re import sys -from collections.abc import Iterable, Iterator, Mapping, MutableMapping +from collections.abc import Iterator, Mapping, MutableMapping from pathlib import Path from typing import TYPE_CHECKING, Any, ClassVar, NoReturn @@ -131,7 +131,7 @@ def make_config_parser( return parser -def _determine_quality(qual: str) -> str: +def _determine_quality(qual: str | None) -> str: for quality, values in constants.QUALITIES.items(): if values["flag"] is not None and values["flag"] == qual: return quality @@ -334,6 +334,7 @@ def __len__(self) -> int: def __contains__(self, key: object) -> bool: try: + assert isinstance(key, str) self.__getitem__(key) return True except AttributeError: @@ -424,7 +425,7 @@ def __deepcopy__(self, memo: dict[str, Any]) -> Self: # Deepcopying the underlying dict is enough because all properties # either read directly from it or compute their value on the fly from # values read directly from it. - c._d = copy.deepcopy(self._d, memo) + c._d = copy.deepcopy(self._d, memo) # type: ignore[arg-type] return c # helper type-checking methods @@ -648,13 +649,15 @@ def digest_parser(self, parser: configparser.ConfigParser) -> Self: "window_size" ] # if not "default", get a tuple of the position if window_size != "default": - window_size = tuple(map(int, re.split(r"[;,\-]", window_size))) - self.window_size = window_size + window_size_numbers = tuple(map(int, re.split(r"[;,\-]", window_size))) + self.window_size = window_size_numbers + else: + self.window_size = window_size # plugins plugins = parser["CLI"].get("plugins", fallback="", raw=True) - plugins = [] if plugins == "" else plugins.split(",") - self.plugins = plugins + plugin_list = [] if plugins is None or plugins == "" else plugins.split(",") + self.plugins = plugin_list # 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) @@ -664,31 +667,31 @@ def digest_parser(self, parser: configparser.ConfigParser) -> Self: self["frame_width"] = width # other logic - val = parser["CLI"].get("tex_template_file") - if val: - self.tex_template_file = val + tex_template_file = parser["CLI"].get("tex_template_file") + if tex_template_file: + self.tex_template_file = Path(tex_template_file) - val = parser["CLI"].get("progress_bar") - if val: - self.progress_bar = val + progress_bar = parser["CLI"].get("progress_bar") + if progress_bar: + self.progress_bar = progress_bar - val = parser["ffmpeg"].get("loglevel") - if val: - self.ffmpeg_loglevel = val + ffmpeg_loglevel = parser["ffmpeg"].get("loglevel") + if ffmpeg_loglevel: + self.ffmpeg_loglevel = ffmpeg_loglevel try: - val = parser["jupyter"].getboolean("media_embed") + media_embed = parser["jupyter"].getboolean("media_embed") except ValueError: - val = None - self.media_embed = val + media_embed = None + self.media_embed = media_embed - val = parser["jupyter"].get("media_width") - if val: - self.media_width = val + media_width = parser["jupyter"].get("media_width") + if media_width: + self.media_width = media_width - val = parser["CLI"].get("quality", fallback="", raw=True) - if val: - self.quality = _determine_quality(val) + quality = parser["CLI"].get("quality", fallback="", raw=True) + if quality: + self.quality = _determine_quality(quality) return self @@ -1036,7 +1039,7 @@ def verbosity(self, val: str) -> None: logger.setLevel(val) @property - def format(self) -> str: + def format(self) -> str | None: """File format; "png", "gif", "mp4", "webm" or "mov".""" return self._d["format"] @@ -1068,7 +1071,7 @@ def ffmpeg_loglevel(self, val: str) -> None: logging.getLogger("libav").setLevel(self.ffmpeg_loglevel) @property - def media_embed(self) -> bool: + def media_embed(self) -> bool | None: """Whether to embed videos in Jupyter notebook.""" return self._d["media_embed"] @@ -1104,8 +1107,10 @@ def pixel_height(self, value: int) -> None: self._set_pos_number("pixel_height", value, False) @property - def aspect_ratio(self) -> int: + def aspect_ratio(self) -> float: """Aspect ratio (width / height) in pixels (--resolution, -r).""" + assert isinstance(self._d["pixel_width"], int) + assert isinstance(self._d["pixel_height"], int) return self._d["pixel_width"] / self._d["pixel_height"] @property @@ -1129,22 +1134,22 @@ def frame_width(self, value: float) -> None: @property def frame_y_radius(self) -> float: """Half the frame height (no flag).""" - return self._d["frame_height"] / 2 + return self._d["frame_height"] / 2 # type: ignore[operator] @frame_y_radius.setter def frame_y_radius(self, value: float) -> None: - self._d.__setitem__("frame_y_radius", value) or self._d.__setitem__( + self._d.__setitem__("frame_y_radius", value) or self._d.__setitem__( # type: ignore[func-returns-value] "frame_height", 2 * value ) @property def frame_x_radius(self) -> float: """Half the frame width (no flag).""" - return self._d["frame_width"] / 2 + return self._d["frame_width"] / 2 # type: ignore[operator] @frame_x_radius.setter def frame_x_radius(self, value: float) -> None: - self._d.__setitem__("frame_x_radius", value) or self._d.__setitem__( + self._d.__setitem__("frame_x_radius", value) or self._d.__setitem__( # type: ignore[func-returns-value] "frame_width", 2 * value ) @@ -1277,7 +1282,7 @@ def frame_size(self) -> tuple[int, int]: @frame_size.setter def frame_size(self, value: tuple[int, int]) -> None: - self._d.__setitem__("pixel_width", value[0]) or self._d.__setitem__( + self._d.__setitem__("pixel_width", value[0]) or self._d.__setitem__( # type: ignore[func-returns-value] "pixel_height", value[1] ) @@ -1287,7 +1292,7 @@ def quality(self) -> str | None: keys = ["pixel_width", "pixel_height", "frame_rate"] q = {k: self[k] for k in keys} for qual in constants.QUALITIES: - if all(q[k] == constants.QUALITIES[qual][k] for k in keys): + if all(q[k] == constants.QUALITIES[qual][k] for k in keys): # type: ignore[literal-required] return qual return None @@ -1304,6 +1309,7 @@ def quality(self, value: str | None) -> None: @property def transparent(self) -> bool: """Whether the background opacity is less than 1.0 (-t).""" + assert isinstance(self._d["background_opacity"], float) return self._d["background_opacity"] < 1.0 @transparent.setter @@ -1413,12 +1419,12 @@ def window_position(self, value: str) -> None: self._d.__setitem__("window_position", value) @property - def window_size(self) -> str: + def window_size(self) -> str | tuple[int, ...]: """The size of the opengl window. 'default' to automatically scale the window based on the display monitor.""" return self._d["window_size"] @window_size.setter - def window_size(self, value: str) -> None: + def window_size(self, value: str | tuple[int, ...]) -> None: self._d.__setitem__("window_size", value) def resolve_movie_file_extension(self, is_transparent: bool) -> None: @@ -1447,7 +1453,7 @@ def enable_gui(self, value: bool) -> None: self._set_boolean("enable_gui", value) @property - def gui_location(self) -> tuple[Any]: + def gui_location(self) -> tuple[int, ...]: """Location parameters for the GUI window (e.g., screen coordinates or layout settings).""" return self._d["gui_location"] @@ -1631,6 +1637,7 @@ def get_dir(self, key: str, **kwargs: Any) -> Path: all_args["quality"] = f"{self.pixel_height}p{self.frame_rate:g}" path = self._d[key] + assert isinstance(path, str) while "{" in path: try: path = path.format(**all_args) @@ -1730,7 +1737,7 @@ def custom_folders(self, value: str | Path) -> None: self._set_dir("custom_folders", value) @property - def input_file(self) -> str: + def input_file(self) -> str | Path: """Input file name.""" return self._d["input_file"] @@ -1759,7 +1766,7 @@ def scene_names(self, value: list[str]) -> None: @property def tex_template(self) -> TexTemplate: """Template used when rendering Tex. See :class:`.TexTemplate`.""" - if not hasattr(self, "_tex_template") or not self._tex_template: + if not hasattr(self, "_tex_template") or not self._tex_template: # type: ignore[has-type] fn = self._d["tex_template_file"] if fn: self._tex_template = TexTemplate.from_file(fn) @@ -1795,7 +1802,7 @@ def plugins(self) -> list[str]: return self._d["plugins"] @plugins.setter - def plugins(self, value: list[str]): + def plugins(self, value: list[str]) -> None: self._d["plugins"] = value @@ -1842,7 +1849,7 @@ def __init__(self, c: ManimConfig) -> None: self.__dict__["_c"] = c # there are required by parent class Mapping to behave like a dict - def __getitem__(self, key: str | int) -> Any: + def __getitem__(self, key: str) -> Any: if key in self._OPTS: return self._c[key] elif key in self._CONSTANTS: @@ -1850,7 +1857,7 @@ def __getitem__(self, key: str | int) -> Any: else: raise KeyError(key) - def __iter__(self) -> Iterable[str]: + def __iter__(self) -> Iterator[Any]: return iter(list(self._OPTS) + list(self._CONSTANTS)) def __len__(self) -> int: @@ -1868,4 +1875,4 @@ def __delitem__(self, key: Any) -> NoReturn: for opt in list(ManimFrame._OPTS) + list(ManimFrame._CONSTANTS): - setattr(ManimFrame, opt, property(lambda self, o=opt: self[o])) + setattr(ManimFrame, opt, property(lambda self, o=opt: self[o])) # type: ignore[misc] diff --git a/mypy.ini b/mypy.ini index 1d2340ca3c..699bbe840e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -49,7 +49,7 @@ warn_return_any = True # disable_recursive_aliases = True [mypy-manim._config.*] -ignore_errors = True +ignore_errors = False disable_error_code = return-value [mypy-manim._config.cli_colors]