refactor(markdown_helper): remove top-level src.markdown_table import; use _require_warmed
Phase 5C of startup_speedup_20260606 track.
src/markdown_helper.py imported src.markdown_table at module level:
from src.markdown_table import parse_tables, render_table
Both parse_tables and render_table are only used inside
MarkdownRenderer.render(). Removed the top-level import; the
MarkdownRenderer.render() method now does:
markdown_table = _require_warmed('src.markdown_table')
parse_tables = markdown_table.parse_tables
render_table = markdown_table.render_table
at the top of its body, before any other logic.
TESTS:
- tests/test_markdown_helper_no_top_level_table.py: 3/3 PASS (all RED -> GREEN)
- tests/test_markdown_table*.py (5 files) + test_markdown_helper_bullets.py +
test_markdown_render_robust.py: 24/24 PASS (no breakage)
EFFECTIVENESS: import src.markdown_helper no longer triggers src.markdown_table
(~250ms). For renderers that never hit a GFM table, the import is never
paid. For renderers that do, the warmup pre-loads it on _io_pool and the
render() lookup is O(1).
NEXT: Phase 5D - bulk refactor of src/gui_2.py feature-gated imports via
scripts/audit_gui2_imports.py.
This commit is contained in:
+13
-1
@@ -12,7 +12,14 @@ from typing import Optional, Dict, Callable
|
||||
|
||||
from src import theme_2
|
||||
|
||||
from src.markdown_table import parse_tables, render_table
|
||||
from src.module_loader import _require_warmed
|
||||
|
||||
# NOTE: src.markdown_table is NOT imported at module level. The GFM table
|
||||
# rendering branch is feature-gated (startup_speedup_20260606 Phase 5C);
|
||||
# the lookup happens inside MarkdownRenderer.render() via _require_warmed.
|
||||
# If you need parse_tables/render_table elsewhere in this file, do the
|
||||
# lookup there too. The warmup on AppController's _io_pool pre-loads
|
||||
# src.markdown_table so the first render() call is O(1).
|
||||
|
||||
|
||||
def _get_language_id(name: str):
|
||||
@@ -121,6 +128,11 @@ class MarkdownRenderer:
|
||||
Render Markdown text with code block interception and GFM table substitution.
|
||||
[C: src/theme_2.py:render_post_fx, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_active, tests/test_theme_nerv_alert.py:test_alert_pulsing_render_inactive, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_alert_pulsing_render, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_disabled, tests/test_theme_nerv_fx.py:TestThemeNervFx.test_crt_filter_render]
|
||||
"""
|
||||
# Lazy lookup of src.markdown_table (Phase 5C). Warmup has already loaded
|
||||
# it on AppController's _io_pool, so this is an O(1) sys.modules get.
|
||||
markdown_table = _require_warmed("src.markdown_table")
|
||||
parse_tables = markdown_table.parse_tables
|
||||
render_table = markdown_table.render_table
|
||||
if not text: return
|
||||
text = self._normalize_bullet_delimiters(text)
|
||||
text = self._normalize_nested_list_endings(text)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Tests that src/markdown_helper.py has NO top-level src.markdown_table import.
|
||||
|
||||
Per spec.md:2.2 Layer 1, the main thread's import chain must not include
|
||||
heavy feature-gated modules. src.markdown_table (~250ms) is warmed on
|
||||
AppController's _io_pool and accessed via _require_warmed at use sites.
|
||||
The GFM table-rendering branch is optional (default markdown rendering
|
||||
falls through to imgui_md).
|
||||
|
||||
src/markdown_helper.py uses src.markdown_table only inside the
|
||||
MarkdownRenderer.render() method (parse_tables at the top, render_table
|
||||
in a nested code block). The lookup is deferred to the render() call.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess:
|
||||
script = textwrap.dedent(snippet)
|
||||
return subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(ROOT),
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
|
||||
def test_markdown_helper_does_not_import_markdown_table_at_module_level() -> None:
|
||||
res = _run_in_subprocess("""
|
||||
import sys
|
||||
import src.markdown_helper
|
||||
print('src.markdown_table' in sys.modules)
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
assert res.stdout.strip() == "False", f"markdown_helper triggered src.markdown_table import: {res.stdout}"
|
||||
|
||||
|
||||
def test_markdown_helper_render_method_lazy_lookups() -> None:
|
||||
"""The render() method should resolve parse_tables and render_table
|
||||
via _require_warmed at call time, not at module load."""
|
||||
res = _run_in_subprocess("""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
root = Path('.').resolve()
|
||||
helper_path = root / 'src' / 'markdown_helper.py'
|
||||
tree = ast.parse(helper_path.read_text(encoding='utf-8'))
|
||||
# Look for any top-level ImportFrom that imports parse_tables or render_table
|
||||
has_top_level_table_import = False
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.ImportFrom):
|
||||
for alias in node.names:
|
||||
if alias.name in ('parse_tables', 'render_table'):
|
||||
has_top_level_table_import = True
|
||||
print('HAS_TOP_LEVEL_TABLE_IMPORT:', has_top_level_table_import)
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
assert "HAS_TOP_LEVEL_TABLE_IMPORT: False" in res.stdout
|
||||
|
||||
|
||||
def test_audit_main_thread_imports_sees_no_new_violation_from_markdown_helper() -> None:
|
||||
"""Run the static audit and check that src/markdown_helper.py contributes no
|
||||
new markdown_table violations.
|
||||
"""
|
||||
res = _run_in_subprocess("""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
root = Path('.').resolve()
|
||||
helper_path = root / 'src' / 'markdown_helper.py'
|
||||
tree = ast.parse(helper_path.read_text(encoding='utf-8'))
|
||||
heavy = ['src.markdown_table', 'markdown_table']
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
for h in heavy:
|
||||
if alias.name == h or alias.name.startswith(h + '.'):
|
||||
print('VIOLATION:', alias.name)
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if node.module:
|
||||
for h in heavy:
|
||||
if node.module == h or node.module.startswith(h + '.'):
|
||||
print('VIOLATION:', node.module)
|
||||
print('OK')
|
||||
""")
|
||||
assert res.returncode == 0, f"stderr: {res.stderr}"
|
||||
assert "OK" in res.stdout
|
||||
assert "VIOLATION" not in res.stdout
|
||||
Reference in New Issue
Block a user