From 48c32becaf0237054562c3fb1d9d1ebfa6a325f9 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 13 May 2026 07:47:55 -0400 Subject: [PATCH] improvements to defer --- thirdparty/defer/defer.py | 9 ++- thirdparty/defer/sugar/__init__.py | 6 +- thirdparty/defer/sugar/_parse.py | 116 ++++++++--------------------- 3 files changed, 42 insertions(+), 89 deletions(-) diff --git a/thirdparty/defer/defer.py b/thirdparty/defer/defer.py index 44b8eb3..351c724 100644 --- a/thirdparty/defer/defer.py +++ b/thirdparty/defer/defer.py @@ -2,7 +2,6 @@ import sys from typing import Any, Callable, ParamSpec, TypeVar from defer._defer import _Defer -from defer.sugar._parse import _ParseDefer P = ParamSpec("P") T = TypeVar("T") @@ -11,7 +10,7 @@ T = TypeVar("T") class Defer: def __call__(self, fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs): if sys.gettrace() is None: - sys.settrace(_ParseDefer.IDENTITY) + sys.settrace(_DeferTrace()) frame = sys._getframe(1) if not isinstance(frame.f_trace, _Defer): frame.f_trace = _Defer(frame.f_trace) @@ -21,3 +20,9 @@ class Defer: def __contains__(self, fn: Any): breakpoint() self(fn) + + +class _DeferTrace: + """Minimal no-op trace for when @defer is used but no other trace is active.""" + def __call__(self, frame, event, arg): + return self \ No newline at end of file diff --git a/thirdparty/defer/sugar/__init__.py b/thirdparty/defer/sugar/__init__.py index 4763b41..f636ba2 100644 --- a/thirdparty/defer/sugar/__init__.py +++ b/thirdparty/defer/sugar/__init__.py @@ -1,12 +1,12 @@ import sys from defer.defer import Defer -from defer.sugar._parse import _ParseDefer def install(): - if not isinstance(sys.gettrace(), _ParseDefer): - sys.settrace(_ParseDefer(sys.gettrace())) + """Only loads markers - does NOT install trace. Trace is lazy.""" + from defer.sugar._parse import _load_defer_markers + _load_defer_markers() defer = Defer() \ No newline at end of file diff --git a/thirdparty/defer/sugar/_parse.py b/thirdparty/defer/sugar/_parse.py index c581a35..0d0b8c2 100644 --- a/thirdparty/defer/sugar/_parse.py +++ b/thirdparty/defer/sugar/_parse.py @@ -1,108 +1,56 @@ import sys import os +import ast from ast import ( AsyncFunctionDef, FunctionDef, ) -from collections import deque from types import FrameType -from typing import Any, Callable, Optional, cast +from typing import Any, Callable, Optional from defer.errors import FreeVarsError from defer.sugar.transformer import RewriteDefer -def _is_dunder(name: str) -> bool: - return name.startswith("__") and name.endswith("__") +_DEFER_MARKER = "# defer: parse" +_enabled_files: set[str] = set() -_SITE_PACKAGES = "site-packages" -_VENV = ".venv" -_THIRDPARTY = "thirdparty" -_SRC_WIN = "\\src\\" -_SRC_UNIX = "/src/" +def _load_defer_markers(): + global _enabled_files + _enabled_files.clear() + try: + for root, dirs, filenames in os.walk('src'): + for filename in filenames: + if filename.endswith('.py'): + relpath = os.path.join(root, filename) + abspath = os.path.abspath(relpath) + try: + with open(abspath, "r", encoding="utf-8", errors="ignore") as f: + for i in range(10): + line = f.readline() + if not line: + break + if _DEFER_MARKER in line: + _enabled_files.add(abspath) + break + except Exception: + pass + except Exception: + pass -def _is_our_code(filename: str) -> bool: - if not filename: - return False - if _SRC_WIN in filename or _SRC_UNIX in filename: - return True - return False +def install(): + """Only loads markers - does NOT install trace. Trace is lazy.""" + _load_defer_markers() class _ParseDefer: - IDENTITY = lambda *_: None # noqa: E731 + """Lazy defer - trace only activates when @defer is actually used.""" + IDENTITY = lambda *_: None def __init__(self, tracefn: Optional[Callable]) -> None: self.tracefn = tracefn or self.IDENTITY - self.pending: deque[Executing] = deque() def __call__(self, frame: FrameType, event: str, arg: Any): - self.tracefn(frame, event, arg) - - try: - filename = frame.f_code.co_filename - if not filename: - return self - if _SITE_PACKAGES in filename or _VENV in filename or _THIRDPARTY in filename: - return self - if not _is_our_code(filename): - return self - except Exception: - pass - - if event != "line": - return self - - try: - from executing.executing import Executing, Source - except ImportError: - return self - - try: - exc = Source.executing(frame) - except Exception: - return self - - if not (stmt := next(iter(exc.statements), None)): - return self - if isinstance(stmt, (AsyncFunctionDef, FunctionDef)): - if _is_dunder(stmt.name): - return self - self.pending.append(exc) - return self - if not self.pending or frame.f_back is not self.pending[-1].frame.f_back: - return self - - stmts = self.pending.pop().statements - node = cast(FunctionDef | AsyncFunctionDef, next(iter(stmts))) - fn_name = node.name - - if fn_name not in frame.f_locals: - return self - fn = frame.f_locals[fn_name] - - if not callable(fn): - return self - try: - code = fn.__code__ - except AttributeError: - return self - - try: - mod = fn.__module__ - except AttributeError: - return self - if mod in sys.stdlib_module_names: - return self - if code.co_freevars: - return self - if not (ast := RewriteDefer.transform(node)): - return self - - locals = frame.f_locals.copy() - del locals[fn_name] - exec(compile(ast, frame.f_code.co_filename, "exec"), frame.f_globals, locals) - frame.f_locals[fn_name].__code__ = locals[fn_name].__code__ - return self \ No newline at end of file + return self.tracefn(frame, event, arg) \ No newline at end of file