Skip to content

reprexlite.code

Attributes

NO_RETURN = object() module-attribute

Explicit placeholder object for statements, which have no return value (as opposed to expressions).

Classes

CodeBlock

Class that takes a block of Python code input and evaluates it. Call str(...) on an instance to get back a string containing the original source with evaluated outputs embedded as comments below each statement.

Attributes:

Name Type Description
input str

Block of Python code

style bool

Whether to use black to autoformat code in returned string representation.

comment str

Line prefix to use when rendering the evaluated results.

terminal bool

Whether to apply syntax highlighting to the string representation. Requires optional dependency Pygments.

tree Module

Parsed LibCST concrete syntax tree of input code.

statements List[Statement]

List of individual statements parsed from input code.

results List[Result]

List of evaluated results corresponding to each item of statements.

Source code in reprexlite/code.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
class CodeBlock:
    """Class that takes a block of Python code input and evaluates it. Call `str(...)` on an
    instance to get back a string containing the original source with evaluated outputs embedded
    as comments below each statement.

    Attributes:
        input (str): Block of Python code
        style (bool): Whether to use black to autoformat code in returned string representation.
        comment (str): Line prefix to use when rendering the evaluated results.
        terminal (bool): Whether to apply syntax highlighting to the string representation.
            Requires optional dependency Pygments.
        tree (libcst.Module): Parsed LibCST concrete syntax tree of input code.
        statements (List[Statement]): List of individual statements parsed from input code.
        results (List[Result]): List of evaluated results corresponding to each item of statements.
    """

    def __init__(
        self,
        input: str,
        style: bool = False,
        comment: str = "#>",
        terminal=False,
        old_results: bool = False,
    ):
        """Initializer method.

        Args:
            input (str): Block of Python code
            style (bool): Whether to use black to autoformat code in returned string
                representation. Defaults to False.
            comment (str): Line prefix to use when rendering the evaluated results. Defaults to
                "#>".
            terminal (bool): Whether to apply syntax highlighting to the string representation.
                Requires optional dependency Pygments. Defaults to False.
            old_results (bool): Whether to keep old results, i.e., comment lines in input that
                match the `comment` prefix. False means these lines are removed, in effect meaning
                an inputted regex will have its results regenerated. Defaults to False.
        """
        if any(line.startswith(">>>") for line in input.split("\n")):
            input = repl_to_reprex_code(input, comment=comment)
        if not old_results and comment in input:
            input = "\n".join(line for line in input.split("\n") if not line.startswith(comment))
        self.input: str = input
        self.terminal = terminal
        # Parse code
        self.tree: cst.Module = cst.parse_module(input)
        self.statements: List[Statement] = [
            Statement(stmt, style=style) for stmt in self.tree.body
        ]
        # Evaluate code
        self.namespace: dict = {}
        self.results: List[Result] = [stmt.evaluate(self.namespace) for stmt in self.statements]
        for res in self.results:
            res.comment = comment

    def __str__(self):
        header = cst.Module(body=[], header=self.tree.header).code.strip()
        code = "\n".join(
            str(line) for line in chain.from_iterable(zip(self.statements, self.results)) if line
        )
        footer = cst.Module(body=[], footer=self.tree.footer).code.strip()
        out = "\n".join([header, code, footer])
        if self.terminal:
            try:
                from pygments import highlight
                from pygments.formatters import Terminal256Formatter
                from pygments.lexers import PythonLexer

                out = highlight(out, PythonLexer(), Terminal256Formatter(style="friendly"))
            except ImportError:
                pass
        return out.strip()

    def _repr_html_(self):
        out = []
        try:
            from pygments import highlight
            from pygments.formatters import HtmlFormatter
            from pygments.lexers import PythonLexer

            formatter = HtmlFormatter(style="friendly", wrapcode=True)
            out.append(f"<style>{formatter.get_style_defs('.highlight')}</style>")
            out.append(highlight(str(self), PythonLexer(), formatter))
        except ImportError:
            out.append(f"<pre><code>{self}</code></pre>")
        return "\n".join(out)

Attributes

input = input instance-attribute
namespace = {} instance-attribute
results = [stmt.evaluate(self.namespace) for stmt in self.statements] instance-attribute
statements = [Statement(stmt, style=style) for stmt in self.tree.body] instance-attribute
terminal = terminal instance-attribute
tree = cst.parse_module(input) instance-attribute

Functions

__init__(input, style=False, comment='#>', terminal=False, old_results=False)

Initializer method.

Parameters:

Name Type Description Default
input str

Block of Python code

required
style bool

Whether to use black to autoformat code in returned string representation. Defaults to False.

False
comment str

Line prefix to use when rendering the evaluated results. Defaults to "#>".

'#>'
terminal bool

Whether to apply syntax highlighting to the string representation. Requires optional dependency Pygments. Defaults to False.

False
old_results bool

Whether to keep old results, i.e., comment lines in input that match the comment prefix. False means these lines are removed, in effect meaning an inputted regex will have its results regenerated. Defaults to False.

False
Source code in reprexlite/code.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def __init__(
    self,
    input: str,
    style: bool = False,
    comment: str = "#>",
    terminal=False,
    old_results: bool = False,
):
    """Initializer method.

    Args:
        input (str): Block of Python code
        style (bool): Whether to use black to autoformat code in returned string
            representation. Defaults to False.
        comment (str): Line prefix to use when rendering the evaluated results. Defaults to
            "#>".
        terminal (bool): Whether to apply syntax highlighting to the string representation.
            Requires optional dependency Pygments. Defaults to False.
        old_results (bool): Whether to keep old results, i.e., comment lines in input that
            match the `comment` prefix. False means these lines are removed, in effect meaning
            an inputted regex will have its results regenerated. Defaults to False.
    """
    if any(line.startswith(">>>") for line in input.split("\n")):
        input = repl_to_reprex_code(input, comment=comment)
    if not old_results and comment in input:
        input = "\n".join(line for line in input.split("\n") if not line.startswith(comment))
    self.input: str = input
    self.terminal = terminal
    # Parse code
    self.tree: cst.Module = cst.parse_module(input)
    self.statements: List[Statement] = [
        Statement(stmt, style=style) for stmt in self.tree.body
    ]
    # Evaluate code
    self.namespace: dict = {}
    self.results: List[Result] = [stmt.evaluate(self.namespace) for stmt in self.statements]
    for res in self.results:
        res.comment = comment

Result

Class that holds the result of evaluated code. Use str(...) on an instance to produce a pretty-formatted comment block representation of the result.

Attributes:

Name Type Description
result Any

Some Python object, intended to be the return value of evaluated Python code.

comment str

Line prefix to use when rendering the result for a reprex.

Source code in reprexlite/code.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Result:
    """Class that holds the result of evaluated code. Use `str(...)` on an instance to produce a
    pretty-formatted comment block representation of the result.

    Attributes:
        result (Any): Some Python object, intended to be the return value of evaluated Python code.
        comment (str): Line prefix to use when rendering the result for a reprex.
    """

    def __init__(self, result: Any, stdout: Optional[str] = None, comment: str = "#>"):
        self.result = result
        self.stdout = stdout
        self.comment = comment

    def __str__(self) -> str:
        lines = []
        if self.stdout:
            lines.extend(self.stdout.split("\n"))
        if self.result is not NO_RETURN and (self.result is not None or not self.stdout):
            # NO_RETURN -> don't print
            # None and stdout -> don't print
            # None and no stdout -> print
            # Anything else -> print
            lines.extend(pformat(self.result, indent=2, width=77).split("\n"))
        return "\n".join(f"{self.comment} " + line for line in lines)

    def __bool__(self) -> bool:
        # If result is NO_RETURN and blank stdout, nothing to print
        return self.result is not NO_RETURN or bool(self.stdout)

Attributes

comment = comment instance-attribute
result = result instance-attribute
stdout = stdout instance-attribute

Functions

__init__(result, stdout=None, comment='#>')
Source code in reprexlite/code.py
24
25
26
27
def __init__(self, result: Any, stdout: Optional[str] = None, comment: str = "#>"):
    self.result = result
    self.stdout = stdout
    self.comment = comment

Statement

Class that holds a LibCST parsed statement. The evaluate method will evaluate the statement and return a Result object. To recover the original source code for an instancement, call str(...) on it. You can optionally autoformat the returned source code, controlled by the style attribute.

Attributes:

Name Type Description
stmt Union[SimpleStatementLine, BaseCompoundStatement]

LibCST parsed statement.

style bool

Whether to autoformat the source code with black.

Source code in reprexlite/code.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
class Statement:
    """Class that holds a LibCST parsed statement. The evaluate method will evaluate the statement
    and return a [`Result`][reprexlite.code.Result] object. To recover the original source code
    for an instancement, call `str(...)` on it. You can optionally autoformat the returned source
    code, controlled by the `style` attribute.

    Attributes:
        stmt (Union[libcst.SimpleStatementLine, libcst.BaseCompoundStatement]): LibCST parsed
            statement.
        style (bool): Whether to autoformat the source code with black.
    """

    def __init__(
        self, stmt: Union[cst.SimpleStatementLine, cst.BaseCompoundStatement], style: bool = False
    ):
        self.stmt = stmt
        self.style = style

    def evaluate(self, scope: dict) -> Result:
        if "__name__" not in scope:
            scope["__name__"] = "__reprex__"
        stdout_io = StringIO()
        try:
            with redirect_stdout(stdout_io):
                try:
                    result = eval(str(self).strip(), scope)
                except SyntaxError:
                    exec(str(self).strip(), scope)
                    result = NO_RETURN
            stdout = stdout_io.getvalue().strip()
        except Exception as exc:
            result = NO_RETURN
            # Skip first step of traceback, since that is this evaluate method
            if exc.__traceback__ is not None:
                tb = exc.__traceback__.tb_next
                stdout = (
                    "Traceback (most recent call last):\n"
                    + "".join(line for line in traceback.format_tb(tb))
                    + f"{type(exc).__name__}: {exc}"
                )
        finally:
            stdout_io.close()
        return Result(result, stdout=stdout)

    def __str__(self) -> str:
        code = cst.Module(body=[self.stmt]).code
        if self.style:
            try:
                from black import Mode, format_str
            except ImportError:
                raise ImportError("Must install black to restyle code.")

            code = format_str(code, mode=Mode())
        if code.endswith("\n"):
            # Strip trailing newline without stripping deliberate ones.
            code = code[:-1]
        return code

Attributes

stmt = stmt instance-attribute
style = style instance-attribute

Functions

__init__(stmt, style=False)
Source code in reprexlite/code.py
58
59
60
61
62
def __init__(
    self, stmt: Union[cst.SimpleStatementLine, cst.BaseCompoundStatement], style: bool = False
):
    self.stmt = stmt
    self.style = style
evaluate(scope)
Source code in reprexlite/code.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def evaluate(self, scope: dict) -> Result:
    if "__name__" not in scope:
        scope["__name__"] = "__reprex__"
    stdout_io = StringIO()
    try:
        with redirect_stdout(stdout_io):
            try:
                result = eval(str(self).strip(), scope)
            except SyntaxError:
                exec(str(self).strip(), scope)
                result = NO_RETURN
        stdout = stdout_io.getvalue().strip()
    except Exception as exc:
        result = NO_RETURN
        # Skip first step of traceback, since that is this evaluate method
        if exc.__traceback__ is not None:
            tb = exc.__traceback__.tb_next
            stdout = (
                "Traceback (most recent call last):\n"
                + "".join(line for line in traceback.format_tb(tb))
                + f"{type(exc).__name__}: {exc}"
            )
    finally:
        stdout_io.close()
    return Result(result, stdout=stdout)

Functions

repl_to_reprex_code(input, comment='#>')

Reformat a code block copied from a Python REPL to a reprex-style code block.

Parameters:

Name Type Description Default
input str

code block

required
comment str

Line prefix to use when rendering the evaluated results. Defaults to "#>".

'#>'

Returns:

Type Description
str

Reformatted code block in reprex-style.

Source code in reprexlite/code.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def repl_to_reprex_code(input: str, comment: str = "#>") -> str:
    """Reformat a code block copied from a Python REPL to a reprex-style code block.

    Args:
        input (str): code block
        comment (str): Line prefix to use when rendering the evaluated results. Defaults to "#>".

    Returns:
        Reformatted code block in reprex-style.
    """
    out = []
    for line in input.split("\n"):
        if line.startswith(">>>") or line.startswith("..."):
            out.append(line[4:])
        elif line.strip() == "":
            out.append(line)
        else:
            out.append(comment + " " + line)
    return "\n".join(out)