Search code examples
pythonpython-3.xpython-click

how to detect if input is stdin or not using python click?


Consider the following code:

import click

@click.command()
@click.argument("file", type=click.File())
def cli(file):
   print(file)

if __name__ == "__main__":
    cli()

Executing as:

$ python ./cmd.py -
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>
$ touch '<stdin>'
$ python ./cmd.py '<stdin>'
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>

There is suprising difference in encoding.

How can I detect if the input was actual - and not anything else in python click?


Solution

  • '<stdin>' doesn't means filename but special object sys.stdin

    And you can compare file == sys.stdin


    Every opened file has number which system uses to work with this opened file and sys.stdin also has this number.

    Normally

    • sys.stdin.fileno() is 0,
    • sys.stdout.fileno() is 1,
    • sys.sterr.fileno() is 2

    So you can compare file.fileno() == 0


    sys.stdin is usually also assigned to console/terminal but normal file is not assigned - and you can check it with .isatty()

    So you can compare file.isatty() is False

    But this is not good method because sometimes sys.stdin is not assigned - ie.

    • when it runs in script without access to terminal - like in cron
    • when it works in pipe like echo "text" | script.py (but it will be it can be assigned when it is first in pipe like script.py | sort

    But this method can be useful when you want to draw colored text on screen and send not colored text to file. But this may need to check also sys.stdout.


    import click
    import sys
    
    @click.command()
    @click.argument("file", type=click.File())
    def cli(file):
       print('file  :', file)
       print('number:', file.fileno())
       print('  ==  :', file == sys.stdin)
       print('isatty:', file.isatty())
       print('stdin :', sys.stdin.isatty())
       print('stdout:', sys.stdout.isatty())   
    
    if __name__ == "__main__":
        cli()
    
    $ python3 ./cmd.py -
    
    <_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>
    number: 0
     ==   : True
    isatty: True
    
    $ python3 ./cmd.py cmd.py
    
    <_io.TextIOWrapper name='cmd.py' mode='r' encoding='UTF-8'>
    number: 3
     ==   : False
    isatty: False