From f6921e6729ac019c2832dc6503c42997b1a47abc Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sat, 30 Mar 2024 10:14:37 -0400 Subject: [PATCH 1/2] allow for csp nodes and graphs to be defined in repl Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/impl/wiring/base_parser.py | 59 +++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/csp/impl/wiring/base_parser.py b/csp/impl/wiring/base_parser.py index 5b90fc6d6..a0d25af8f 100644 --- a/csp/impl/wiring/base_parser.py +++ b/csp/impl/wiring/base_parser.py @@ -1,6 +1,7 @@ import ast import copy import inspect +import re import sys import textwrap import typing @@ -87,6 +88,54 @@ def wrapper(*args, **kwargs): return wrapper +def _get_source_from_interpreter_function(raw_func): + try: + import readline + except ImportError as exc: + raise OSError( + "Could not get source code for interpreter-defined function without `pyreadline` installed." + ) from exc + + current_interpreter_history = readline.get_current_history_length() + + try: + search_pattern = re.compile(r"^(\s*def\s)") + decorator_pattern = re.compile(r"^(\s*@)") + func_name = raw_func.__name__ + code_object = raw_func.__func__ if inspect.ismethod(raw_func) else raw_func.__code__ + except Exception: + raise OSError("Could not get source code for interpreter-defined function.") + + if not hasattr(code_object, "co_firstlineno"): + raise OSError("Could not find function definition for interpreter-defined function.") + + reassembled_function = "" + starting_index_of_function = current_interpreter_history + + # walk back through history to find the function definition + while starting_index_of_function > 0: + line = readline.get_history_item(starting_index_of_function) + + # if its a def name_of_function(... + if search_pattern.match(line): + # go through to get decorators + if func_name in line: + # reassemble function + for i in range(starting_index_of_function, current_interpreter_history + 1): + reassembled_function += f"{readline.get_history_item(i)}\n" + + for line_number_with_decorator in range(starting_index_of_function - 1, -1, -1): + if decorator_pattern.match(readline.get_history_item(line_number_with_decorator)): + reassembled_function = ( + f"{readline.get_history_item(line_number_with_decorator)}\n" + reassembled_function + ) + else: + break + break + starting_index_of_function -= 1 + return reassembled_function + + class BaseParser(ast.NodeTransformer, metaclass=ABCMeta): _DEBUG_PARSE = False @@ -109,7 +158,15 @@ def __init__(self, name, raw_func, func_frame, debug_print=False): self._func_globals_modified["csp"] = csp self._func_globals_modified.update(self._func_frame.f_globals) - source = textwrap.dedent(inspect.getsource(raw_func)) + if raw_func.__code__.co_filename == "": + raw_source = _get_source_from_interpreter_function(raw_func) + elif raw_func.__code__.co_filename == "": + raise OSError("Could not find function definition for exec'd function.") + else: + raw_source = inspect.getsource(raw_func) + + source = textwrap.dedent(raw_source) + body = ast.parse(source) self._funcdef = body.body[0] self._type_annotation_normalizer.normalize_type_annotations(self._funcdef) From fc7a39657038c74aaf734c9222e8cc91604a763b Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sat, 30 Mar 2024 11:01:52 -0400 Subject: [PATCH 2/2] Tweak regex to avoid second lookup and be more durable to colliding names Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- csp/impl/wiring/base_parser.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/csp/impl/wiring/base_parser.py b/csp/impl/wiring/base_parser.py index a0d25af8f..b154be55f 100644 --- a/csp/impl/wiring/base_parser.py +++ b/csp/impl/wiring/base_parser.py @@ -99,9 +99,8 @@ def _get_source_from_interpreter_function(raw_func): current_interpreter_history = readline.get_current_history_length() try: - search_pattern = re.compile(r"^(\s*def\s)") + search_pattern = re.compile(r"^(\s*def\s*" + raw_func.__name__ + r"\s*\()") decorator_pattern = re.compile(r"^(\s*@)") - func_name = raw_func.__name__ code_object = raw_func.__func__ if inspect.ismethod(raw_func) else raw_func.__code__ except Exception: raise OSError("Could not get source code for interpreter-defined function.") @@ -118,21 +117,23 @@ def _get_source_from_interpreter_function(raw_func): # if its a def name_of_function(... if search_pattern.match(line): - # go through to get decorators - if func_name in line: - # reassemble function - for i in range(starting_index_of_function, current_interpreter_history + 1): - reassembled_function += f"{readline.get_history_item(i)}\n" - - for line_number_with_decorator in range(starting_index_of_function - 1, -1, -1): - if decorator_pattern.match(readline.get_history_item(line_number_with_decorator)): - reassembled_function = ( - f"{readline.get_history_item(line_number_with_decorator)}\n" + reassembled_function - ) - else: - break - break + # reassemble function + for i in range(starting_index_of_function, current_interpreter_history + 1): + reassembled_function += f"{readline.get_history_item(i)}\n" + + for line_number_with_decorator in range(starting_index_of_function - 1, -1, -1): + if decorator_pattern.match(readline.get_history_item(line_number_with_decorator)): + reassembled_function = ( + f"{readline.get_history_item(line_number_with_decorator)}\n" + reassembled_function + ) + else: + break + break starting_index_of_function -= 1 + + if reassembled_function == "": + raise OSError("Could not find function definition for interpreter-defined function.") + return reassembled_function