Created
December 19, 2018 10:30
-
-
Save minrk/2e14bec8e842e5664101c1310b3d7bdb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "# Making IPython magics from click functions\n\nTwo fun facts:\n\n- click makes nice command-line APIs in Python\n- IPython uses magics to expose Python functions via a shell-like syntax\n\nFirst, let's create the hello world click command from the click docs:" | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "import click\n\[email protected]()\[email protected]('--count', default=1, help='Number of greetings.')\[email protected]('--name', prompt='Your name',\n help='The person to greet.')\ndef hello(count, name):\n \"\"\"Simple program that greets NAME for a total of COUNT times.\"\"\"\n for x in range(count):\n click.echo('Hello %s!' % click.style(name, fg='green'))", | |
"execution_count": 1, | |
"outputs": [] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "which we can call:" | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "hello(['--count', '2'])", | |
"execution_count": 2, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": "Your name: min\nHello min!\nHello min!\n", | |
"name": "stdout" | |
}, | |
{ | |
"output_type": "error", | |
"ename": "SystemExit", | |
"evalue": "0", | |
"traceback": [ | |
"An exception has occurred, use %tb to see the full traceback.\n", | |
"\u001b[1;31mSystemExit\u001b[0m\u001b[1;31m:\u001b[0m 0\n" | |
] | |
}, | |
{ | |
"output_type": "stream", | |
"text": "/Users/benjaminrk/dev/ip/ipython/IPython/core/interactiveshell.py:3283: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.\n warn(\"To exit: use 'exit', 'quit', or Ctrl-D.\", stacklevel=1)\n", | |
"name": "stderr" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Two things:\n\n1. it raises `SystemExit`, and\n2. `min` is not green\n\nThe latter is because click checks if sys.stdin is a tty, which it isn't in the notebook,\nand only enables colors if it is.\n\nThe check is in `click.utils.should_strip_ansi`:" | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "s = click.style('Hello World!', fg='green')\ns", | |
"execution_count": 3, | |
"outputs": [ | |
{ | |
"output_type": "execute_result", | |
"execution_count": 3, | |
"data": { | |
"text/plain": "'\\x1b[32mHello World!\\x1b[0m'" | |
}, | |
"metadata": {} | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "click.utils.should_strip_ansi??", | |
"execution_count": 4, | |
"outputs": [ | |
{ | |
"output_type": "display_data", | |
"data": { | |
"text/plain": "\u001b[1;31mSignature:\u001b[0m \u001b[0mclick\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mutils\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mshould_strip_ansi\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstream\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcolor\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;31mDocstring:\u001b[0m <no docstring>\n\u001b[1;31mSource:\u001b[0m \n\u001b[1;32mdef\u001b[0m \u001b[0mshould_strip_ansi\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstream\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcolor\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mcolor\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n\u001b[1;33m\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mstream\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\n\u001b[1;33m\u001b[0m \u001b[0mstream\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msys\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstdin\u001b[0m\u001b[1;33m\u001b[0m\n\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0misatty\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstream\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\n\u001b[1;33m\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mcolor\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;31mFile:\u001b[0m ~/conda/lib/python3.6/site-packages/click/_compat.py\n\u001b[1;31mType:\u001b[0m function\n" | |
}, | |
"metadata": {} | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "click.echo(s)", | |
"execution_count": 5, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": "Hello World!\n", | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "We can force click not to check this and then echo should produce colored output:" | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "try:\n from unittest import mock\nexcept ImportError: # py2\n import mock\n\nwith mock.patch(\n 'click.utils.should_strip_ansi',\n lambda *args, **kwargs: False\n):\n click.echo(s)", | |
"execution_count": 6, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": "\u001b[32mHello World!\u001b[0m\n", | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"metadata": {}, | |
"cell_type": "markdown", | |
"source": "Next, we can write the transform to turn a click command into an IPython magic.\nAn IPython magic is a Python function called where the rest of the line\nis passed as a simple string.\nThe click function wants a list of arguments, `sys.argv`-style.\n\nSo we need to do the following things:\n\n1. split the line into arguments with [`shlex.split`](https://docs.python.org/3/library/shlex.html#shlex.split)\n2. tell the command that it's name is `%magicname`\n3. tell `click.echo` that it should always color output\n4. catch `SystemExit` errors and turning them into IPython magic `UsageError`s\n5. finally, register the magic via IPython's [`register_magic_function`](https://ipython.readthedocs.io/en/stable/api/generated/IPython.core.interactiveshell.html#IPython.core.interactiveshell.InteractiveShell.register_magic_function)" | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "import shlex\ntry:\n from unittest import mock\nexcept ImportError: # py2\n import mock\n\nfrom IPython.core.error import UsageError\n\ndef click_magic(click_command, name=None):\n if name is None:\n name = click_command.name\n\n def magic_func(line):\n args = shlex.split(line)\n # bypass click's check for whether colors should be enabled\n with mock.patch(\n 'click.utils.should_strip_ansi',\n lambda *args, **kwargs: False\n ):\n try:\n click_command(\n shlex.split(line),\n prog_name='%' + name,\n )\n except SystemExit as e:\n if e.code != 0:\n raise UsageError(\"Command exited with status=%s\" % e.code)\n \n get_ipython().register_magic_function(magic_func, magic_name=name)\n", | |
"execution_count": 7, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "click_magic(hello)", | |
"execution_count": 8, | |
"outputs": [] | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "%hello --help", | |
"execution_count": 9, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": "Usage: %hello [OPTIONS]\n\n Simple program that greets NAME for a total of COUNT times.\n\nOptions:\n --count INTEGER Number of greetings.\n --name TEXT The person to greet.\n --help Show this message and exit.\n", | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "%hello --count 2 --name myname", | |
"execution_count": 10, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": "Hello \u001b[32mmyname\u001b[0m!\nHello \u001b[32mmyname\u001b[0m!\n", | |
"name": "stdout" | |
} | |
] | |
}, | |
{ | |
"metadata": { | |
"trusted": true | |
}, | |
"cell_type": "code", | |
"source": "%hello --unrecognized", | |
"execution_count": 11, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"text": "Error: no such option: --unrecognized\nUsageError: Command exited with status=2\n", | |
"name": "stderr" | |
} | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3", | |
"language": "python" | |
}, | |
"language_info": { | |
"name": "python", | |
"version": "3.6.5", | |
"mimetype": "text/x-python", | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"pygments_lexer": "ipython3", | |
"nbconvert_exporter": "python", | |
"file_extension": ".py" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment