Oppy
Oppy

Reputation: 2897

Using click library in jupyter notebook cell

Is there a way to use the click library in a Jupyter notebook cell? I would like to pass flags to my Jupyter notebook code, within the notebook, to make it smoother transiting to a stand alone script. For instance, using OptionParser from a Jupyter notebook cell:

from optparse import OptionParser
import sys


def main():
    parser = OptionParser()
    parser.add_option('-f', '--fake',
                    default='False',
                help='Fake data')
    (options,args) = parser.parse_args()
    print('options:{} args: {}'.format(options, args))
    if options.fake:
        print('Fake detected')

def test_args():

    print('hello')

if __name__ == '__main__':

    sys.argv = ['--fake', 'True' '--help']
    main()

output: options:{'fake': 'False'} args: ['True--help'] Fake detected

Using the click library, I get a string of errors. I ran this code from a Jupyter notebook cell:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
            help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

Ouput (truncated):

UnsupportedOperation                      Traceback (most recent call last)
<ipython-input-6-ad31be7bf0fe> in <module>()
    12 if __name__ == '__main__':
    13     sys.argv = ['--count', '3']
---> 14     hello()

~/.local/lib/python3.6/site-packages/click/core.py in __call__(self, *args, **kwargs)
    720     def __call__(self, *args, **kwargs):
    721         """Alias for :meth:`main`."""
--> 722         return self.main(*args, **kwargs)
    723 
    724 
...
257 
    258     if message:
--> 259         file.write(message)
    260     file.flush()
    261 

UnsupportedOperation: not writable

Upvotes: 7

Views: 4453

Answers (2)

wassname
wassname

Reputation: 569

Jupyter hijacks the stdout/stderr/stdin feeds. You can see this using import sys; type(sys.stdin), which gives ipykernel.iostream.OutStream. A workaround that lets jupyter and click operate together is to pass sys.stdout directly to click.

def monkey_patch_jupyter_click_sreams():
    """see https://stackoverflow.com/a/49595790/221742 ."""
    import sys
    import ipykernel
    import click  
    if not click._compat.PY2 and isinstance(sys.stdout, ipykernel.iostream.OutStream):
        click._compat._force_correct_text_writer = lambda stream, encoding, errors: stream

monkey_patch_jupyter_click_sreams()
# your code here
hello()

This works on by bypassing the click wrapper for the stdout and other streams and just passes stdout._buffer. This works with click, even if stdout has been replaced with ipythons ipykernel.iostream.OutStream.

Upvotes: 2

Mike M&#252;ller
Mike M&#252;ller

Reputation: 85552

You can use the %%python magic command to start a new Python propcess:

%%python

import sys
import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
            help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    with open('echo.txt', 'w') as fobj:
        for x in range(count):
            click.echo('Hello %s!' % name)

if __name__ == '__main__':
    # first element is the script name, use empty string instead
    sys.argv = ['', '--name', 'Max', '--count', '3']
    hello()

Output:

Hello Max!
Hello Max!
Hello Max!

Upvotes: 4

Related Questions