Skip to content

Typing logger utils - continuation of PR #4134 #4230

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
12 changes: 7 additions & 5 deletions manim/_config/cli_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


def parse_cli_ctx(parser: configparser.SectionProxy) -> dict[str, Any]:
formatter_settings: dict[str, str | int] = {
formatter_settings: dict[str, str | int | None] = {
"indent_increment": int(parser["indent_increment"]),
"width": int(parser["width"]),
"col1_max_width": int(parser["col1_max_width"]),
Expand Down Expand Up @@ -37,22 +37,24 @@ def parse_cli_ctx(parser: configparser.SectionProxy) -> dict[str, Any]:
if theme is None:
formatter = HelpFormatter.settings(
theme=HelpTheme(**theme_settings),
**formatter_settings, # type: ignore[arg-type]
**formatter_settings,
)
elif theme.lower() == "dark":
formatter = HelpFormatter.settings(
theme=HelpTheme.dark().with_(**theme_settings),
**formatter_settings, # type: ignore[arg-type]
**formatter_settings,
)
elif theme.lower() == "light":
formatter = HelpFormatter.settings(
theme=HelpTheme.light().with_(**theme_settings),
**formatter_settings, # type: ignore[arg-type]
**formatter_settings,
)

return Context.settings(
return_val: dict[str, Any] = Context.settings(
align_option_groups=parser["align_option_groups"].lower() == "true",
align_sections=parser["align_sections"].lower() == "true",
show_constraints=True,
formatter_settings=formatter,
)

return return_val
15 changes: 9 additions & 6 deletions manim/_config/logger_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import copy
import json
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from rich import color, errors
from rich import print as printf
Expand Down Expand Up @@ -91,7 +91,7 @@ def make_logger(
# set the rich handler
rich_handler = RichHandler(
console=console,
show_time=parser.getboolean("log_timestamps"),
show_time=parser.getboolean("log_timestamps", fallback=False),
keywords=HIGHLIGHTED_KEYWORDS,
)

Expand All @@ -108,7 +108,7 @@ def make_logger(
return logger, console, error_console


def parse_theme(parser: configparser.SectionProxy) -> Theme:
def parse_theme(parser: configparser.SectionProxy) -> Theme | None:
"""Configure the rich style of logger and console output.

Parameters
Expand All @@ -126,7 +126,7 @@ def parse_theme(parser: configparser.SectionProxy) -> Theme:
:func:`make_logger`.

"""
theme = {key.replace("_", "."): parser[key] for key in parser}
theme: dict[str, Any] = {key.replace("_", "."): parser[key] for key in parser}

theme["log.width"] = None if theme["log.width"] == "-1" else int(theme["log.width"])
theme["log.height"] = (
Expand Down Expand Up @@ -188,8 +188,11 @@ def format(self, record: logging.LogRecord) -> str:
"""Format the record in a custom JSON format."""
record_c = copy.deepcopy(record)
if record_c.args:
for arg in record_c.args:
record_c.args[arg] = "<>"
if isinstance(record_c.args, dict):
for arg in record_c.args:
record_c.args[arg] = "<>"
else:
record_c.args = ("<>",) * len(record_c.args)
return json.dumps(
{
"levelname": record_c.levelname,
Expand Down
93 changes: 50 additions & 43 deletions manim/_config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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"]

Expand Down Expand Up @@ -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"]

Expand Down Expand Up @@ -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
Expand All @@ -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
)

Expand Down Expand Up @@ -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]
)

Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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, ...]:
"""Enable GUI interaction."""
return self._d["gui_location"]

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"]

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -1842,15 +1849,15 @@ 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:
return self._CONSTANTS[key]
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:
Expand All @@ -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]
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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.animation.*]
Expand Down