refactor pydoc: inline into tools.py with @in_separate_process decorator for subprocess isolation

This commit is contained in:
Andrew Hamilton 2026-07-30 09:33:05 +10:00
parent 7e758b4da5
commit 3925ded957
6 changed files with 59 additions and 32 deletions

View file

@ -1,24 +0,0 @@
import pydoc
import sys
import termstr
class TermDoc(pydoc.TextDoc):
def bold(self, text):
return str(termstr.TermStr(text, is_bold=True))
def main():
path = sys.argv[1]
try:
print(pydoc.render_doc(pydoc.importfile(path), renderer=TermDoc()))
except pydoc.ErrorDuringImport as e:
print(e)
return 1
if __name__ == "__main__":
main()

View file

@ -1,6 +1,7 @@
import ast
import concurrent.futures
import contextlib
import enum
import functools
@ -103,6 +104,30 @@ def deps(**kwargs):
return decorating_func
def _call_in_process(module_name, func_name, *args, **kwargs):
import importlib
fn = getattr(importlib.import_module(module_name), func_name)
return fn(*args, **kwargs)
def in_separate_process(func):
mangled = f"_original_{func.__name__}"
sys.modules[func.__module__].__dict__[mangled] = func
@functools.wraps(func)
def wrapper(*args, **kwargs):
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
future = executor.submit(
_call_in_process, func.__module__, mangled, *args, **kwargs)
return future.result()
for attr in ('deps', 'url', 'executables'):
if hasattr(func, attr):
setattr(wrapper, attr, getattr(func, attr))
return wrapper
def _syntax_highlight(text, lexer, style):
@functools.lru_cache(maxsize=500)
def _parse_rgb(hex_rgb):
@ -199,6 +224,25 @@ def metadata(path):
return Status.ok, termstr.join("", text)
@deps(url="https://docs.python.org/3/library/pydoc.html")
@in_separate_process
def pydoc(path):
import os
import pydoc
class TermDoc(pydoc.TextDoc):
def bold(self, text):
import termstr
return str(termstr.TermStr(text, is_bold=True))
try:
doc = pydoc.render_doc(pydoc.importfile(path), renderer=TermDoc())
except pydoc.ErrorDuringImport as e:
return Status.not_applicable, str(e)
doc = doc.replace(os.path.abspath(path), path)
return Status.ok, termstr.TermStr.from_term(doc)
@deps(deps={"python3-pygments"}, url="http://pygments.org/")
def contents(path):
with open(path) as file_:

View file

@ -61,13 +61,6 @@ tools_for_extensions = [
url = "https://en.wikipedia.org/wiki/Python_syntax_and_semantics"
command = "python3 -m py_compile"
[pydoc]
dependencies = []
url = "https://docs.python.org/3/library/pydoc.html"
command = "pydoc_color"
error_status = "not_applicable"
has_color = true
timeout = 60
[pycodestyle]
dependencies = ["python3-pycodestyle"]

View file

@ -27,7 +27,6 @@ dependencies = [
eris = "eris.__main__:entry_point"
eris-worker = "eris.worker:main"
eris-webserver = "eris.webserver:main"
pydoc_color = "eris.pydoc_color:main"
[project.urls]
homepage = "https://github.com/ahamilton/eris"

View file

@ -0,0 +1,12 @@
Python Library Documentation: module hi3
NAME
hi3
FUNCTIONS
hi()
FILE
./input/hi3.py


View file

@ -134,6 +134,9 @@ class ToolsTestCase(unittest.TestCase):
for extension in ["png", "jpg", "gif", "bmp", "ppm", "tiff", "tga"]:
self._test_tool(tools.pil, [("circle." + extension, tools.Status.ok)])
def test_pydoc(self):
self._test_tool(tools.pydoc, [("hi3.py", tools.Status.ok)])
class LruCacheWithEvictionTestCase(unittest.TestCase):