Skip to content

reprexlite.reprexes

Classes

ParsedResult dataclass

ParsedResult(config: ReprexConfig, lines: List[str])

Class that holds parsed result from reading a reprex.

Parameters:

Name Type Description Default
config ReprexConfig

Configuration for formatting and parsing

required
lines List[str]

String content of result parsed from a reprex

required

RawResult dataclass

RawResult(
    config: ReprexConfig, raw: Any, stdout: Optional[str]
)

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

Parameters:

Name Type Description Default
config ReprexConfig

Configuration for formatting and parsing

required
raw Any

Some Python object that is the raw return value of evaluated Python code.

required
stdout str

Standard output from evaluated Python code.

required

Reprex dataclass

Reprex(
    config: ReprexConfig,
    statements: List[Statement],
    results: List[RawResult],
    old_results: List[ParsedResult],
    scope: Dict[str, Any],
)

Dataclass for a reprex, which holds Python code and results from evaluation.

Parameters:

Name Type Description Default
config ReprexConfig

Configuration for formatting and parsing

required
statements List[Statement]

List of parsed Python code statements

required
results List[RawResult]

List of results evaluated from statements

required
old_results List[ParsedResult]

List of any old results parsed from input code

required
scope Dict[str, Any]

Dictionary holding the scope that the reprex was evaluated in

required

Attributes

results_match property
results_match: bool

Whether results of evaluating code match old results parsed from input.

Functions

from_input classmethod
from_input(
    input: str,
    config: Optional[ReprexConfig] = None,
    scope: Optional[Dict[str, Any]] = None,
) -> Self

Create a Reprex instance from parsing and evaluating code from a string.

Parameters:

Name Type Description Default
input str

Input code

required
config Optional[ReprexConfig]

Configuration. Defaults to None, which will use default settings.

None
scope Optional[Dict[str, Any]]

Dictionary holding scope that the parsed code will be evaluated with. Defaults to None, which will create an empty dictionary.

None

Returns:

Name Type Description
Reprex Self

New instance of Reprex.

Source code in reprexlite/reprexes.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
@classmethod
def from_input(
    cls,
    input: str,
    config: Optional[ReprexConfig] = None,
    scope: Optional[Dict[str, Any]] = None,
) -> Self:
    """Create a Reprex instance from parsing and evaluating code from a string.

    Args:
        input (str): Input code
        config (Optional[ReprexConfig], optional): Configuration. Defaults to None, which will
            use default settings.
        scope (Optional[Dict[str, Any]], optional): Dictionary holding scope that the parsed
            code will be evaluated with. Defaults to None, which will create an empty
            dictionary.

    Returns:
        Reprex: New instance of Reprex.
    """
    if config is None:
        config = ReprexConfig()
    if config.parsing_method == ParsingMethod.AUTO:
        lines = list(auto_parse(input))
    elif config.parsing_method == ParsingMethod.DECLARED:
        lines = list(
            parse(
                input,
                prompt=config.resolved_input_prompt,
                continuation=config.resolved_input_continuation,
                comment=config.resolved_input_comment,
            )
        )
    else:
        raise UnexpectedError(  # pragma: nocover
            f"Parsing method {config.parsing_method} is not implemented."
        )
    return cls.from_input_lines(lines, config=config, scope=scope)
from_input_lines classmethod
from_input_lines(
    lines: Sequence[Tuple[str, LineType]],
    config: Optional[ReprexConfig] = None,
    scope: Optional[Dict[str, Any]] = None,
) -> Self

Creates a Reprex instance from the output of parse.

Parameters:

Name Type Description Default
lines Sequence[Tuple[str, LineType]]

Output from parse.

required
config Optional[ReprexConfig]

Configuration. Defaults to None, which will use default settings.

None
scope Optional[Dict[str, Any]]

Dictionary holding scope that the parsed code will be evaluated with. Defaults to None, which will create an empty dictionary.

None

Returns:

Name Type Description
Reprex Self

New instance of Reprex.

Source code in reprexlite/reprexes.py
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
@classmethod
def from_input_lines(
    cls,
    lines: Sequence[Tuple[str, LineType]],
    config: Optional[ReprexConfig] = None,
    scope: Optional[Dict[str, Any]] = None,
) -> Self:
    """Creates a Reprex instance from the output of [parse][reprexlite.parsing.parse].

    Args:
        lines (Sequence[Tuple[str, LineType]]): Output from parse.
        config (Optional[ReprexConfig], optional): Configuration. Defaults to None, which will
            use default settings.
        scope (Optional[Dict[str, Any]], optional): Dictionary holding scope that the parsed
            code will be evaluated with. Defaults to None, which will create an empty
            dictionary.

    Returns:
        Reprex: New instance of Reprex.
    """
    if config is None:
        config = ReprexConfig()
    statements: List[Statement] = []
    old_results: List[ParsedResult] = []
    current_code_block: List[str] = []
    current_result_block: List[str] = []
    try:
        for line_content, line_type in lines:
            if line_type is LineType.CODE:
                # Flush results
                if current_result_block:
                    old_results += [ParsedResult(config=config, lines=current_result_block)]
                    current_result_block = []
                # Append line to current code
                current_code_block.append(line_content)
            elif line_type is LineType.RESULT:
                # Flush code
                if current_code_block:
                    # Parse code and create Statements
                    tree: cst.Module = cst.parse_module("\n".join(current_code_block))
                    new_statements = (
                        [Statement(config=config, stmt=stmt) for stmt in tree.header]
                        + [Statement(config=config, stmt=stmt) for stmt in tree.body]
                        + [Statement(config=config, stmt=stmt) for stmt in tree.footer]
                    )
                    statements += new_statements
                    # Pad results with empty results, 1 fewer because of current_result_block
                    old_results += [ParsedResult(config=config, lines=[])] * (
                        len(new_statements) - 1
                    )
                    # Reset current code block
                    current_code_block = []
                # Append line to current results
                current_result_block.append(line_content)
        # Flush code
        if current_code_block:
            if all(not line for line in current_code_block):
                # Case where all lines are whitespace: strip and don't add
                new_statements = []
            else:
                # Parse code and create Statements
                tree: cst.Module = cst.parse_module(  # type: ignore[no-redef]
                    "\n".join(current_code_block)
                )
                new_statements = (
                    [Statement(config=config, stmt=stmt) for stmt in tree.header]
                    + [Statement(config=config, stmt=stmt) for stmt in tree.body]
                    + [Statement(config=config, stmt=stmt) for stmt in tree.footer]
                )
            # Pad results with empty results, 1 fewer because of current_result_block
            statements += new_statements
            old_results += [ParsedResult(config=config, lines=[])] * (len(new_statements) - 1)
        # Flush results
        if current_result_block:
            old_results += [ParsedResult(config=config, lines=current_result_block)]
        # Pad results to equal length
        old_results += [ParsedResult(config=config, lines=[])] * (
            len(statements) - len(old_results)
        )

        # Evaluate for new results
        if scope is None:
            scope = {}
        results = [statement.evaluate(scope=scope) for statement in statements]
        return cls(
            config=config,
            statements=statements,
            results=results,
            old_results=old_results,
            scope=scope,
        )
    except cst.ParserSyntaxError as e:
        raise InputSyntaxError(str(e)) from e
render
render(terminal: bool = False) -> str

Render the reprex as code.

Source code in reprexlite/reprexes.py
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
def render(self, terminal: bool = False) -> str:
    """Render the reprex as code."""
    if self.config.keep_old_results:
        lines = chain.from_iterable(zip(self.statements, self.old_results, self.results))
    else:
        lines = chain.from_iterable(zip(self.statements, self.results))
    out = "\n".join(str(line) for line in lines if line)
    if not out.endswith("\n"):
        out += "\n"
    # if terminal=True and Pygments is available, apply syntax highlighting
    if terminal:
        try:
            from pygments import highlight
            from pygments.formatters import Terminal256Formatter
            from pygments.lexers import PythonLexer

            out = highlight(out, PythonLexer(), Terminal256Formatter(style="friendly"))
        except ModuleNotFoundError:
            pass
    return out
render_and_format
render_and_format(terminal: bool = False) -> str

Render the reprex as code and format it for the configured output venue.

Source code in reprexlite/reprexes.py
426
427
428
429
430
def render_and_format(self, terminal: bool = False) -> str:
    """Render the reprex as code and format it for the configured output venue."""
    out = self.render(terminal=terminal)
    formatter_fn = formatter_registry[self.config.venue].fn
    return formatter_fn(out.strip(), config=self.config)

Statement dataclass

Statement(
    config: ReprexConfig,
    stmt: Union[
        SimpleStatementLine,
        BaseCompoundStatement,
        EmptyLine,
    ],
)

Dataclass that holds a LibCST parsed statement. of code.

Parameters:

Name Type Description Default
config ReprexConfig

Configuration for formatting and parsing

required
stmt Union[SimpleStatementLine, BaseCompoundStatement]

LibCST parsed statement.

required

Attributes

code property
code: str

Code of contained statement. May be autoformatted depending on configuration.

raw_code property
raw_code: str

Raw code of contained statement as a string.

Functions

evaluate
evaluate(scope: dict) -> RawResult

Evaluate code statement and produce a RawResult dataclass instance.

Parameters:

Name Type Description Default
scope dict

scope to use for evaluation

required

Returns:

Name Type Description
RawResult RawResult

Dataclass instance holding evaluation results.

Source code in reprexlite/reprexes.py
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
def evaluate(self, scope: dict) -> RawResult:
    """Evaluate code statement and produce a RawResult dataclass instance.

    Args:
        scope (dict): scope to use for evaluation

    Returns:
        RawResult: Dataclass instance holding evaluation results.
    """
    if isinstance(self.stmt, cst.EmptyLine):
        return RawResult(config=self.config, raw=None, stdout=None)

    if "__name__" not in scope:
        scope["__name__"] = "__reprex__"
    stdout_io = StringIO()
    try:
        with redirect_stdout(stdout_io):
            try:
                # Treat as a single expression
                result = eval(self.code.strip(), scope)
            except SyntaxError:
                # Treat as a statement
                exec(self.code.strip(), scope)
                result = None
        stdout = stdout_io.getvalue().strip()
    except Exception as exc:
        result = None
        # 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 RawResult(config=self.config, raw=result, stdout=stdout or None)

Functions

reprex

reprex(
    input: str,
    outfile: Optional[Union[str, PathLike]] = None,
    print_: bool = True,
    terminal: bool = False,
    config: Optional[ReprexConfig] = None,
    **kwargs,
) -> Reprex

A convenient functional interface to render reproducible examples of Python code for sharing. This function will evaluate your code and, by default, print out your code with the evaluated results embedded as comments, formatted with additional markup appropriate to the sharing venue set by the venue keyword argument. The function returns an instance of Reprex which holds the relevant data.

For example, for the gh venue for GitHub Flavored Markdown, you'll get a reprex whose formatted output looks like:

```python
x = 2
x + 2
#> 4
```

<sup>Created at 2021-02-15 16:58:47 PST by [reprexlite](https://github.com/jayqi/reprexlite)</sup>

Parameters:

Name Type Description Default
input str

Input code to create a reprex for.

required
outfile Optional[str | PathLike]

If provided, path to write formatted reprex output to. Defaults to None, which does not write to any file.

None
print_ bool

Whether to print formatted reprex output to console.

True
terminal bool

Whether currently in a terminal. If true, will automatically apply code highlighting if pygments is installed.

False
config Optional[ReprexConfig]

Instance of the configuration dataclass. Default of none will instantiate one with default values.

None
**kwargs

Configuration options from ReprexConfig. Any provided values will override values from provided config or the defaults.

{}

Returns:

Type Description
Reprex

(Reprex) Reprex instance

Source code in reprexlite/reprexes.py
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
def reprex(
    input: str,
    outfile: Optional[Union[str, os.PathLike]] = None,
    print_: bool = True,
    terminal: bool = False,
    config: Optional[ReprexConfig] = None,
    **kwargs,
) -> Reprex:
    """A convenient functional interface to render reproducible examples of Python code for
    sharing. This function will evaluate your code and, by default, print out your code with the
    evaluated results embedded as comments, formatted with additional markup appropriate to the
    sharing venue set by the `venue` keyword argument. The function returns an instance of
    [`Reprex`][reprexlite.reprexes.Reprex] which holds the relevant data.

    For example, for the `gh` venue for GitHub Flavored Markdown, you'll get a reprex whose
    formatted output looks like:

    ````
    ```python
    x = 2
    x + 2
    #> 4
    ```

    <sup>Created at 2021-02-15 16:58:47 PST by [reprexlite](https://github.com/jayqi/reprexlite)</sup>
    ````


    Args:
        input (str): Input code to create a reprex for.
        outfile (Optional[str | os.PathLike]): If provided, path to write formatted reprex
            output to. Defaults to None, which does not write to any file.
        print_ (bool): Whether to print formatted reprex output to console.
        terminal (bool): Whether currently in a terminal. If true, will automatically apply code
            highlighting if pygments is installed.
        config (Optional[ReprexConfig]): Instance of the configuration dataclass. Default of none
            will instantiate one with default values.
        **kwargs: Configuration options from [ReprexConfig][reprexlite.config.ReprexConfig]. Any
            provided values will override values from provided config or the defaults.

    Returns:
        (Reprex) Reprex instance
    """  # noqa: E501

    if config is None:
        config = ReprexConfig(**kwargs)
    else:
        config = dataclasses.replace(config, **kwargs)

    config = ReprexConfig(**kwargs)
    if config.venue in ["html", "rtf"]:
        # Don't screw up output file or lexing for HTML and RTF with terminal syntax highlighting
        terminal = False
    r = Reprex.from_input(input, config=config)
    output = r.render_and_format(terminal=terminal)
    if outfile is not None:
        with Path(outfile).open("w") as fp:
            fp.write(r.render_and_format(terminal=False))
    if print_:
        print(output)
    return r