fix defer

This commit is contained in:
2026-05-13 05:55:46 -04:00
parent 9266add6a1
commit 1a529ed750
6 changed files with 66 additions and 106 deletions
View File
+1 -2
View File
@@ -9,5 +9,4 @@ def install():
sys.settrace(_ParseDefer(sys.gettrace()))
defer = Defer()
install()
defer = Defer()
+65 -27
View File
@@ -4,15 +4,35 @@ from ast import (
FunctionDef,
)
from collections import deque
from os.path import isabs
from types import FrameType
from typing import Any, Callable, Optional, cast
from executing.executing import Executing, Source
from defer.errors import FreeVarsError
from defer.sugar.transformer import RewriteDefer
def _is_dunder(name: str) -> bool:
return name.startswith("__") and name.endswith("__")
def _is_excluded_path(filename: str) -> bool:
if not filename:
return True
return (
filename.startswith(sys.base_exec_prefix)
or filename.startswith(sys.base_prefix)
or filename.startswith(sys.exec_prefix)
or filename.startswith(sys.prefix)
or ".venv" in filename
or filename.startswith("<frozen ")
or filename.startswith("<string>")
or filename.startswith("<pytest ")
or "thirdparty" in filename
or "__future__" in filename
)
class _ParseDefer:
IDENTITY = lambda *_: None # noqa: E731
@@ -21,47 +41,65 @@ class _ParseDefer:
self.pending: deque[Executing] = deque()
def __call__(self, frame: FrameType, event: str, arg: Any):
try:
self._do_call(frame, event, arg)
except Exception:
pass
return self
def _do_call(self, frame: FrameType, event: str, arg: Any):
self.tracefn(frame, event, arg)
if any(
frame.f_code.co_filename.startswith(path)
for path in {
sys.base_exec_prefix,
sys.base_prefix,
sys.exec_prefix,
sys.prefix,
"<frozen ",
"<string>",
"<pytest ",
}
):
return self
try:
filename = frame.f_code.co_filename
except Exception:
return
if _is_excluded_path(filename):
return
if not isabs(filename):
return
if event != "line":
return self
return
try:
from executing.executing import Executing, Source
except ImportError:
return
try:
exc = Source.executing(frame)
except Exception:
return
if not (stmt := next(iter(exc.statements), None)):
return self
return
if isinstance(stmt, (AsyncFunctionDef, FunctionDef)):
if stmt.name.startswith("__") and stmt.name.endswith("__"):
return self
if _is_dunder(stmt.name):
return
self.pending.append(exc)
return self
return self
return
if not self.pending or frame.f_back is not self.pending[-1].frame.f_back:
return
stmts = self.pending.pop().statements
node = cast(FunctionDef | AsyncFunctionDef, next(iter(stmts)))
fn = frame.f_locals[node.name]
fn_name = node.name
if fn_name not in frame.f_locals:
return
fn = frame.f_locals[fn_name]
if fn.__module__ in sys.stdlib_module_names:
return self
return
if fn.__code__.co_freevars:
raise FreeVarsError(fn)
if not (ast := RewriteDefer.transform(node)):
return self
return
locals = frame.f_locals.copy()
del locals[node.name]
del locals[fn_name]
exec(compile(ast, frame.f_code.co_filename, "exec"), frame.f_globals, locals)
frame.f_locals[node.name].__code__ = locals[node.name].__code__
return self
frame.f_locals[fn_name].__code__ = locals[fn_name].__code__