failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(timeout) · 19 tool calls · 0 s · Textualize/rich
perf: reduce Console and RichHandler import time by deferring unused imports ## E2E Results | Import | master | This PR | Speedup | |---|---|---|---| | `from rich.console import Console` | 78.1ms | 52.2ms | **1.50x faster** | | `from rich.logging import RichHandler` | 99.4ms | 56.9ms | **1.75x faster** | | `import rich` | 18.2ms | 18.3ms | (already lean) | ## Summary Defer module-level imports that are only needed in specific code paths, move annotation-only imports to `TYPE_CHECKING`, and remove dead code: | File | Change | Savings | |---|---|---| | `logging.py` | Defer `Traceback` to `emit()` (only when `rich_tracebacks=True`) | ~20ms | | `logging.py` | `from __future__ import annotations` + `TYPE_CHECKING` for `Console`, `ConsoleRenderable`, `Highlighter`, `FormatTimeCallable` | ~6ms | | `logging.py` | Replace `pathlib.Path` → `os.path.basename` (also a minor runtime win) | ~4-5ms | | `console.py` | Eliminate `import inspect`; replace `isclass` → `isinstance(x, type)`, `currentframe` → `sys._getframe` | ~10ms | | `console.py` | Defer `pretty` to `Console.print()` | ~3-5ms | | `console.py` | Defer `scope` to `Console.log()` (only when `log_locals=True`) | ~3-5ms | | `console.py` | Defer `getpass` to `Console.input()` (only when `password=True`) | ~2ms | | `console.py` | Defer `html.escape` and `zlib` to export methods | ~2.3ms | | `console.py` | Remove dead `_svg_hash` function (unused since 113997ac, fixes latent NameError) | cleanup | | `segment.py` | Remove dead `logging` import (`getLogger` assigned but never used) | ~2-3ms | | `theme.py` | Defer `configparser` to `Theme.from_file()` | ~1.5ms | | `syntax.py` | Move `Console`, `ConsoleOptions`, `JustifyMethod`, `RenderResult` to `TYPE_CHECKING` | eliminates console.py from syntax import chain | | `protocol.py` | Replace `from inspect import isclass` → `isinstance(x, type)` | prepares for dataclasses removal | | `repr.py` | Defer `import inspect` to `auto_rich_repr()` | prepares for dataclasses removal | All deferred imports are still available at runtime — they're loaded when the code path that needs them is actually hit. The `TYPE_CHECKING` imports are annotation-only and never needed at runtime (files use `from __future__ import annotations`). ## Benchmarks Environment: Standard_D2s_v5 (non-burstable), CPython 3.13.13, hyperfine --min-runs 30 ### Per-module breakdown (this PR) | Module | Time | |---|---| | `import rich` | 18.2ms | | `Console` | 52.5ms | | `RichHandler` | 56.4ms | | `Syntax` | 64.7ms | | `Traceback` | 92.5ms | | `Markdown` | 103.0ms | ## Motivation pip vendors Rich, and `Console`/`RichHandler` are imported on every `pip` invocation. These savings directly improve startup time for pip and every other tool that imports Rich. ## Testing - `pytest tests/` — 952 passed, 25 skipped - `mypy -p rich --no-incremental` — no issues - `black --check` — passes ## Disclosure This PR was developed with AI assistance (codeflash-agent). Benchmarked in a controlled environment (dedicated non-burstable Azure VM). All tests verified across multiple Python implementations.
| # | Tool | Arguments | Result |
|---|---|---|---|
| No trace captured. | |||
diff --git a/rich/console.py b/rich/console.py
index ad92d529..4d85ffc9 100644
--- a/rich/console.py
+++ b/rich/console.py
@@ -1,15 +1,15 @@
-import inspect
+
import os
import sys
import threading
-import zlib
+
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from functools import wraps
-from getpass import getpass
-from html import escape
-from inspect import isclass
+
+
+
from itertools import islice
from math import ceil
from time import monotonic
@@ -50,10 +50,10 @@ from .highlighter import NullHighlighter, ReprHighlighter
from .markup import render as render_markup
from .measure import Measurement, measure_renderables
from .pager import Pager, SystemPager
-from .pretty import Pretty, is_expandable
+
from .protocol import rich_cast
from .region import Region
-from .scope import render_scope
+
from .screen import Screen
from .segment import Segment
from .style import Style, StyleType
@@ -1568,6 +1568,7 @@ class Console:
elif isinstance(renderable, ConsoleRenderable):
check_text()
append(renderable)
+ from .pretty import Pretty, is_expandable
elif is_expandable(renderable):
check_text()
append(Pretty(renderable, highlighter=_highlighter))
@@ -1988,7 +1989,8 @@ class Console:
for key, value in locals.items()
if not key.startswith("__")
}
- renderables.append(render_scope(locals_map, title="[i]locals"))
+ renderables.append(from .scope import render_scope
+ render_scope(locals_map, title="[i]locals"))
renderables = [
self._log_render(
@@ -2166,7 +2168,8 @@ class Console:
if prompt:
self.print(prompt, markup=markup, emoji=emoji, end="")
if password:
- result = getpass("", stream=stream)
+ from getpass import getpass
+ result = getpass("", stream=stream)
else:
if stream:
result = stream.readline()
@@ -2221,14 +2224,16 @@ class Console:
write_file.write(text)
def export_html(
+ from html import escape
+ from html import escape
self,
*,
theme: Optional[TerminalTheme] = None,
clear: bool = True,
code_format: Optional[str] = None,
inline_styles: bool = False,
- ) -> str:
- """Generate HTML from console contents (requires record=True argument in constructor).
+ ) -> str):
+ from html import escape """Generate HTML from console contents (requires record=True argument in constructor).
Args:
theme (TerminalTheme, optional): TerminalTheme object containing console colors.
@@ -2257,6 +2262,9 @@ class Console:
for text, style, _ in Segment.filter_control(
Segment.simplify(self._record_buffer)
):
+ from html import escape
+ from html import escape
+ from html import escape
text = escape(text)
if style:
rule = style.get_html_style(_theme)
@@ -2269,6 +2277,9 @@ class Console:
for text, style, _ in Segment.filter_control(
Segment.simplify(self._record_buffer)
):
+ from html import escape
+ from html import escape
+ from html import escape
text = escape(text)
if style:
rule = style.get_html_style(_theme)
@@ -2448,7 +2459,9 @@ class Console:
if unique_id is None:
unique_id = "terminal-" + str(
- zlib.adler32(
+ import zlib
+ import zlib
+ zlib.adler32(
("".join(repr(segment) for segment in segments)).encode(
"utf-8",
"ignore",
@@ -2625,7 +2638,9 @@ def _svg_hash(svg_main_code: str) -> str:
Returns:
str: a hash of the given content
"""
- return str(zlib.adler32(svg_main_code.encode()))
+ return str(import zlib
+ import zlib
+ zlib.adler32(svg_main_code.encode()))
if __name__ == "__main__": # pragma: no cover
diff --git a/rich/logging.py b/rich/logging.py
index c3e7a5f6..b64cea8e 100644
--- a/rich/logging.py
+++ b/rich/logging.py
@@ -1,18 +1,26 @@
+from __future__ import annotations
import logging
+import os
from datetime import datetime
from logging import Handler, LogRecord
-from pathlib import Path
+
from types import ModuleType
-from typing import ClassVar, Iterable, List, Optional, Type, Union
+from typing import TYPE_CHECKING, ClassVar, Iterable, List, Optional, Type, Union
+
+if TYPE_CHECKING:
+ from ._log_render import FormatTimeCallable
+ from .console import Console, ConsoleRenderable
+ from .highlighter import Highlighter
+ from .traceback import Traceback
from rich._null_file import NullFile
from . import get_console
-from ._log_render import FormatTimeCallable, LogRender
-from .console import Console, ConsoleRenderable
-from .highlighter import Highlighter, ReprHighlighter
+from ._log_render import LogRender
+
+from .highlighter import ReprHighlighter
from .text import Text
-from .traceback import Traceback
+
class RichHandler(Handler):
@@ -141,6 +149,7 @@ class RichHandler(Handler):
exc_type, exc_value, exc_traceback = record.exc_info
assert exc_type is not None
assert exc_value is not None
+ from .traceback import Traceback
traceback = Traceback.from_exception(
exc_type,
exc_value,
@@ -221,7 +230,7 @@ class RichHandler(Handler):
Returns:
ConsoleRenderable: Renderable to display log.
"""
- path = Path(record.pathname).name
+ path = os.path.basename(record.pathname)
level = self.get_level_text(record)
time_format = None if self.formatter is None else self.formatter.datefmt
log_time = datetime.fromtimestamp(record.created)
==================================== ERRORS ====================================
_______________________ ERROR collecting test_console.py _______________________
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/rich/lib/python3.13/site-packages/_pytest/python.py:493: in importtestmodule
mod = import_path(
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/rich/lib/python3.13/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/Users/jp/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
<frozen importlib._bootstrap>:1395: in _gcd_import
???
<frozen importlib._bootstrap>:1360: in _find_and_load
???
<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:935: in _load_unlocked
???
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/rich/lib/python3.13/site-packages/_pytest/assertion/rewrite.py:184: in exec_module
exec(co, module.__dict__)
tests/test_console.py:15: in <module>
from rich.console import (
E File "/private/tmp/swe_work/pod_r5_v1i/rich_4070/b/workspace/rich/console.py", line 2235
E ) -> str):
E ^
E SyntaxError: unmatched ')'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.17s