import __future__ import importlib import inspect import textwrap import types from collections.abc import Callable from typing import Any, Optional import yaml from sglang.srt.debug_utils.source_patcher.source_editor import apply_edits from sglang.srt.debug_utils.source_patcher.types import ( EditSpec, PatchConfig, PatchSpec, PatchState, ) def apply_patches_from_config( yaml_content: str, *, extra_imports: Optional[list[str]] = None, ) -> list[PatchState]: """Parse a YAML config string and apply all patches. Args: yaml_content: YAML string with patch specifications. extra_imports: Import lines inserted once at the top of each patched function body (e.g. ["from pkg import foo"]). The caller (dumper) uses this so users don't have to write boilerplate in YAML. """ raw: dict[str, Any] = yaml.safe_load(yaml_content) config: PatchConfig = PatchConfig(**raw) if extra_imports: config = _inject_preamble(config=config, extra_imports=extra_imports) return _apply_specs(config.patches) class CodePatcher: """Context manager that patches functions on enter and restores on exit.""" def __init__(self, *, patches: list[PatchSpec]) -> None: self._patches = patches self._states: list[PatchState] = [] def __enter__(self) -> "CodePatcher": self._states = _apply_specs(self._patches) return self def __exit__( self, exc_type: Optional[type], exc_val: Optional[BaseException], exc_tb: Optional[Any], ) -> None: for state in reversed(self._states): state.restore() self._states.clear() def patch_function( *, target: Callable[..., Any], edits: list[EditSpec], preamble: str = "", ) -> PatchState: """Patch a function by modifying its source and replacing __code__. 1. inspect.getsource -> get original source 2. apply_edits -> modify source text 3. optionally prepend preamble (e.g. import lines) inside the function body 4. compile + exec -> get new code object 5. replace target.__code__ Returns PatchState that can restore the original code. """ original_code: types.CodeType = target.__code__ source: str = inspect.getsource(target) modified_source: str = apply_edits(source=source, edits=edits) modified_source = textwrap.dedent(modified_source) if preamble.strip(): modified_source = _insert_preamble(source=modified_source, preamble=preamble) code: types.CodeType = compile( modified_source, inspect.getfile(target), "exec", flags=__future__.annotations.compiler_flag, ) temp_namespace: dict[str, Any] = {} exec(code, target.__globals__, temp_namespace) new_fn: Any = temp_namespace[target.__name__] target.__code__ = new_fn.__code__ return PatchState(target_fn=target, original_code=original_code) # --------------------------------- private --------------------------------- def _apply_specs(specs: list[PatchSpec]) -> list[PatchState]: states: list[PatchState] = [] for spec in specs: target_fn: Callable[..., Any] = _resolve_target(spec.target) print(f"[source_patcher] patching {spec.target}") state: PatchState = patch_function( target=target_fn, edits=spec.edits, preamble=spec.preamble ) states.append(state) return states def _inject_preamble(*, config: PatchConfig, extra_imports: list[str]) -> PatchConfig: """Set preamble on every PatchSpec so imports are inserted once at function top.""" import_block: str = "\n".join(extra_imports) new_patches: list[PatchSpec] = [] for spec in config.patches: existing: str = spec.preamble combined: str = ( import_block + "\n" + existing if existing.strip() else import_block ) new_patches.append( PatchSpec(target=spec.target, edits=spec.edits, preamble=combined) ) return PatchConfig(patches=new_patches) def _insert_preamble(*, source: str, preamble: str) -> str: """Insert preamble lines right after the function signature (and optional docstring).""" lines: list[str] = source.splitlines() signature_end: int = _find_signature_end(lines) body_start: int = signature_end + 1 body_indent: str = "" for i in range(body_start, len(lines)): if lines[i].strip(): body_indent = " " * (len(lines[i]) - len(lines[i].lstrip())) body_start = i break preamble_lines: list[str] = [ body_indent + pl for pl in preamble.strip().splitlines() ] return "\n".join(lines[:body_start] + preamble_lines + lines[body_start:]) def _find_signature_end(lines: list[str]) -> int: """Find the line index where the function signature ends (the line with trailing colon).""" for i, line in enumerate(lines): if line.rstrip().endswith(":"): return i return 0 def _resolve_target(qualified_name: str) -> Callable[..., Any]: """Resolve 'pkg.mod.Class.method' to the actual function object. Tries progressively shorter module paths from right to left, then uses getattr for the remaining attribute chain. """ parts: list[str] = qualified_name.split(".") target: Any = None for split_idx in range(len(parts), 0, -1): module_path: str = ".".join(parts[:split_idx]) try: target = importlib.import_module(module_path) attr_parts: list[str] = parts[split_idx:] break except ImportError: continue else: raise ImportError(f"could not import any module prefix of '{qualified_name}'") for attr_name in attr_parts: target = getattr(target, attr_name) if isinstance(target, classmethod): target = target.__func__ if not callable(target): raise TypeError( f"resolved target '{qualified_name}' is not callable: {type(target)}" ) return target