Skip to content

Commit

Permalink
feat: Add --code option to ape console (#2370)
Browse files Browse the repository at this point in the history
  • Loading branch information
antazoey authored Nov 4, 2024
1 parent 50299ab commit 75e0d8e
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 5 deletions.
15 changes: 15 additions & 0 deletions docs/userguides/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,18 @@ Out[3]: '0.00040634 ETH'
In [4]: %bal 0xE3747e6341E0d3430e6Ea9e2346cdDCc2F8a4b5b
Out[4]: '0.00040634 ETH'
```

## Executing Code

You can also use the `ape console` to execute programs directly from strings.
This is similar to the `python -c|--code` option except it will display the output cell.
Anything available in `ape console` is also available in `ape console --code`.

```shell
ape console -c 'project.name'
Out[1]: 'my-project'
ape console -c 'x = 3\nx + 1'
Out[1]: 4
ape console -c 'networks.active_provider.name'
Out[1]: 'test'
```
40 changes: 35 additions & 5 deletions src/ape_console/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import TYPE_CHECKING, Any, Optional, cast

import click
from IPython import InteractiveShell

from ape.cli.commands import ConnectedProviderCommand
from ape.cli.options import ape_cli_context, project_option
Expand All @@ -21,17 +22,30 @@
CONSOLE_EXTRAS_FILENAME = "ape_console_extras.py"


def _code_callback(ctx, param, value) -> list[str]:
if not value:
return value

# NOTE: newlines are escaped in code automatically, so we
# need to de-escape them. Any actually escaped newlines
# will still be escaped.
value = value.replace("\\n", "\n").replace("\\t", "\t").replace("\\b", "\b")

return value.splitlines()


@click.command(
cls=ConnectedProviderCommand,
short_help="Load the console",
context_settings=dict(ignore_unknown_options=True),
)
@ape_cli_context()
@project_option(hidden=True) # Hidden as mostly used for test purposes.
def cli(cli_ctx, project):
@click.option("-c", "--code", help="Program passed in as a string", callback=_code_callback)
def cli(cli_ctx, project, code):
"""Opens a console for the local project."""
verbose = cli_ctx.logger.level == logging.DEBUG
return console(project=project, verbose=verbose)
return console(project=project, verbose=verbose, code=code)


def import_extras_file(file_path) -> ModuleType:
Expand Down Expand Up @@ -95,6 +109,7 @@ def console(
verbose: bool = False,
extra_locals: Optional[dict] = None,
embed: bool = False,
code: Optional[list[str]] = None,
):
import IPython
from IPython.terminal.ipapp import Config as IPythonConfig
Expand Down Expand Up @@ -149,16 +164,24 @@ def console(
# Required for click.testing.CliRunner support.
embed = True

_launch_console(namespace, ipy_config, embed, banner)
_launch_console(namespace, ipy_config, embed, banner, code=code)


def _launch_console(namespace: dict, ipy_config: "IPythonConfig", embed: bool, banner: str):
def _launch_console(
namespace: dict,
ipy_config: "IPythonConfig",
embed: bool,
banner: str,
code: Optional[list[str]],
):
import IPython

from ape_console.config import ConsoleConfig

ipython_kwargs = {"user_ns": namespace, "config": ipy_config}
if embed:
if code:
_execute_code(code, **ipython_kwargs)
elif embed:
IPython.embed(**ipython_kwargs, colors="Neutral", banner1=banner)
else:
ipy_config.TerminalInteractiveShell.colors = "Neutral"
Expand All @@ -169,3 +192,10 @@ def _launch_console(namespace: dict, ipy_config: "IPythonConfig", embed: bool, b
ipy_config.InteractiveShellApp.extensions.extend(console_config.plugins)

IPython.start_ipython(**ipython_kwargs, argv=())


def _execute_code(code: list[str], **ipython_kwargs):
shell = InteractiveShell.instance(**ipython_kwargs)
# NOTE: Using `store_history=True` just so the cell IDs are accurate.
for line in code:
shell.run_cell(line, store_history=True)
12 changes: 12 additions & 0 deletions tests/integration/cli/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,15 @@ def test_console_natspecs(integ_project, solidity_contract_type, console_runner)

assert all(ln in actual for ln in expected_method.splitlines())
assert all(ln in actual for ln in expected_event.splitlines())


@skip_projects_except("with-contracts")
def test_console_code(integ_project, mocker, console_runner):
"""
Testing the -c | --code option.
"""
result = console_runner.invoke(
"--project", f"{integ_project.path}", "--code", "chain\nx = 3\nx + 1"
)
expected = "Out[1]: <ChainManager (id=1337)>\nOut[3]: 4\n"
assert result.output == expected

0 comments on commit 75e0d8e

Please sign in to comment.