Modify REPL behaviour#

Remove repl command after invoking the REPL#

The register_repl() decorator assigns the repl() function as a command to your group. However, it’s possible to initialize a REPL session within an another REPL session, because the repl command is still available within the REPL session itself.

 1import click
 2from click_repl import register_repl
 3
 4@register_repl
 5@click.group()
 6def main():
 7    pass
 8
 9@main.command()
10def my_command():
11    pass
12
13
14main()
nesting_repl_issue

As shown, invoking the repl() command within its own REPL leads to a nested REPL. Exiting the entire REPL requires exiting each layer of the REPL individually.

To prevent this behavior and remove the repl() command before invoking the REPL, set the remove_command_before_repl parameter to True in register_repl(). By default, this parameter is set to False, meaning it doesn’t remove the repl command.

 1import click
 2from click_repl import register_repl
 3
 4@register_repl(remove_command_before_repl=True)
 5@click.group()
 6def main():
 7    pass
 8
 9@main.command()
10def my_command():
11    pass
12
13
14main()

ReplGroup#

The ReplGroup class inherits from Group, and can also invoke REPL.

 1# file: app.py
 2
 3import click
 4from click_repl import ReplGroup
 5
 6@click.group(cls=ReplGroup)
 7def main():
 8    pass
 9
10@main.command()
11@click.argument('name')
12def greet(name):
13    print(f'Hi {name}!')
14
15
16main()

It invokes REPL only when no extra arguments were passed to the group.

$ python app.py greet Sam
Hi Sam!
$ python app.py
> greet Sam
Hi Sam!
>

However, ReplGroup offers more features than using either register_repl() or repl().

Startup and Cleanup Callbacks#

ReplGroup allows you to run code before invoking the REPL, and after exiting it. You can provide the code to be executed before invoking the REPL as a callback to the startup parameter of ReplGroup, and similarly for cleanup using the cleanup parameter.

Note

The startup and cleanup callbacks should be of type Callable[[], None].

 1# file: app.py
 2
 3import click
 4from click_repl import ReplGroup
 5
 6@click.group(
 7    cls=ReplGroup,
 8    startup=lambda: print('Entering REPL...'),
 9    cleanup=lambda: print('Exiting REPL...')
10)
11def main():
12    pass
13
14@main.command()
15@click.argument('name')
16def greet(name):
17    print(f'Hi {name}!')
18
19
20main()
$ python app.py greet Sam
Hi Sam!
$ python app.py
Entering REPL...
> greet Sam
Hi Sam!
> :exit
Exiting REPL...
 $

Custom Prompt#

By default, click-repl uses > as its prompt. You can customize the prompt by:

  1. Assigning your prompt to the message key in repl()’s prompt_kwargs dictionary.

     1# file: app.py
     2
     3import click
     4from click_repl import repl
     5
     6@click.group(invoke_without_command=True)
     7@click.pass_context
     8def main(ctx):
     9    repl(ctx, prompt_kwargs={
    10        'message': '>>> '
    11    })
    12
    13
    14main()
    
    $ python app.py
    >>>
    
  2. Pass it via the prompt parameter in ReplGroup.

    1import click
    2from click_repl import ReplGroup
    3
    4@click.group(cls=ReplGroup, prompt='>>> ')
    5def main():
    6    pass
    7
    8
    9main()
    
  3. Accessing and modifying the prompt during runtime using the prompt property.

     1import os
     2
     3import click
     4import click_repl
     5from pathlib import Path
     6
     7@click.group(cls=click_repl.ReplGroup, prompt='user@/$ ')
     8def main():
     9    pass
    10
    11@main.command('cd')
    12@click.argument('path', type=click.Path(file_okay=False))
    13@click_repl.pass_context
    14def change_directory(repl_ctx, path):
    15    resolved_path = Path(repl_ctx.prompt.split('@')[1].removesuffix('$ ') + path).resolve()
    16    os.chdir(resolved_path)
    17    repl_ctx.prompt = f"user@{resolved_path}$ "
    18
    19
    20main()
    

prompt_kwargs#

click-repl uses an instance of PromptSession as its prompt interface. You can provide custom arguments to this PromptSession instance via the prompt_kwargs parameter of repl() function or ReplGroup class.

 1import click
 2from click_repl import ReplGroup
 3from prompt_toolkit.history import FileHistory
 4
 5@click.group(
 6    cls=ReplGroup,
 7    prompt_kwargs={
 8        "history": FileHistory("/etc/myrepl/myrepl-history"),
 9    }
10)
11def main():
12    pass
13
14
15main()

With this configuration, the click-repl application stores a history of previously executed commands in the specified file.

This dictionary of keyword arguments will be updated with the default keyword arguments of PromptSession when initializing the REPL. The default arguments and their values for PromptSession are:

  1. history - InMemoryHistory (Object for storing previous command history per REPL session.)

  2. message - "> "

  3. complete_in_thread - True

  4. complete_while_typing - True

  5. validate_while_typing - True

  6. mouse_support - True

  7. refresh_interval - 0.15

These default values are supplied from get_default_prompt_kwargs() method. For further details about these parameters, refer to PromptSession docs.

Repl#

The Repl class is the central component of this module, responsible for configuring and executing the REPL action through its loop() method.

Custom Repl#

If you require extensive customization of the REPL configuration and execution, you can create your own Repl class based on the blueprint/template of the Repl. It’s recommended to inherit and use it from the Repl class.

Once you’ve created your custom Repl class, you can use it by passing it into cls parameter of repl() function.

 1import click
 2from click_repl import Repl, repl
 3
 4class MyRepl(Repl):
 5    # Implement your own REPL customization.
 6    ...
 7
 8@click.group(invoke_without_command=True)
 9@click.pass_context
10def main(ctx):
11    repl(ctx, cls=MyRepl)
12
13
14main()

ReplContext#

Unlike Context, the ReplContext class is instantiated for every new REPL session. This object tracks the current REPL’s state, while parsing arguments from the prompt while typing.

From this context object, you can obtain many objects responsible for the REPL’s functionality, allowing extreme flexibility in customizing your REPL session during runtime.

You can access it using the click_repl’s pass_context() decorator, which is similar to click’s pass_context(). Ensure not to accidentally switch them.

Note

A ReplContext is instantiated only when the REPL is invoked. Therefore, you won’t be able to use it inside the group.

 1import click
 2import click_repl
 3
 4@click_repl.register_repl
 5@click.group()
 6@click.pass_context
 7def main(ctx):
 8    pass
 9
10@main.command()
11@click.pass_context
12@click_repl.pass_context
13def command(ctx, repl_ctx):
14    # You can do whatever you want with the current repl session's context object.
15    ...

PromptSession object#

click-repl utilzes the PromptSession object, resopnsible for the REPL’s functionality. This object can be accessed via the session attribute of the ReplContext object. You can leverage this to extend the functionality of the REPL. Refer to python-prompt-toolkit’s PromptSession docs.