Open sbordeynemics opened 2 years ago
I also want this for my automation scripts
Hi,
I found a solution to the problem I was working on. My automation script takes and string as CLI argument and writes it after couple of seconds.
python write_text.py "write"
# I converted it in the global script so I can run it from anywhere. Now, I use it like:
write_text "write"
However, there were cases where string I wanted to auto write was coming from some other bash command output so I wanted my script to run in both cases:
write_text "write"
# and, below example read from stdin
echo "write" | write_text
# Error if you don't pass CLI argument or don't provide stdin
This was challenging but thanks to this comment, I got an idea and here's example that let you pass argument and read from stdin.
import sys
import typer
def main(
name: str = typer.Argument(
... if sys.stdin.isatty() else sys.stdin.read().strip()
),
):
typer.echo(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
I hope this will help you in resolving the issue
Not sure why but the above answer doesn't seem to work for me unfortunately...
@app.command()
def echo(
msg: str = typer.Argument(... if sys.stdin.isatty() else sys.stdin.read()),
):
print(msg)
The following test always prints a blank line. Same occurs when cat
-ing a file.
echo hello | myapp echo
So I would definitely support an option that can read -
from stdin and any suggestions from anyone else.
@theelderbeever above code example is full minimal app/script.
Have you tried running that app?
A slightly different approach/workaround looks like this. You can pass an array of strings, or, say an array of filenames into the Python script if you dot-source common.ps1
below and hello.py
is on your PYTHONPATH
or is a module. You could make the -Module
parameter more flexible, and expand the example further, but that's the gist of it.
Of course there's no real parallelism here, the Python interpreter fires up and runs with every input piped to it, there's no benefit of setup/teardown e.g. begin{}
and end{}
clauses in PowerShell's pipeline orchestration mechanisms.
The upside is you don't need to do anything special to your Python module, since all logic is handled on the shell side. The downside is with multiple arguments you're gonna be constructing arrays of arguments on the shell side in order to pipe in. Not sure exactly how that would look, you could probably do some splatting.
PS > python -m hello world
Hello world!
PS > 'world', 'Mom', 'goodbye' | Invoke-Python -Module hello
Hello world!
Hello Mom!
Hello goodbye!
PS > Get-ChildItem -Filter *.md | Invoke-Python -Module hello
Hello C:\test.md
Hello C:\test2.md
The one-liner equivalent of this is 'world', 'Mom', 'goodbye' | ForEach-Object -Process {python -m hello $_}
, but the below Invoke-Python
helper function tucks some of that away.
Contents of hello.py
"""Say hello."""
import typer
def main(name: str = typer.Argument(...)):
typer.echo(f"Hello {name}!")
if __name__ == "__main__":
typer.run(main)
Contents of common.ps1
function Invoke-Python {
<#.SYNOPSIS
Invoke a Python module.
#>
Param(
# Arbitrary input item.
[Parameter(Mandatory, ValueFromPipeline)]$Item,
# Python module to pass input to.
[Parameter(Mandatory)]$Module
)
process {
python -m $Module $Item
}
}
Hi,
I found a solution to the problem I was working on. My automation script takes and string as CLI argument and writes it after couple of seconds.
python write_text.py "write" # I converted it in the global script so I can run it from anywhere. Now, I use it like: write_text "write"
However, there were cases where string I wanted to auto write was coming from some other bash command output so I wanted my script to run in both cases:
write_text "write" # and, below example read from stdin echo "write" | write_text # Error if you don't pass CLI argument or don't provide stdin
This was challenging but thanks to this comment, I got an idea and here's example that let you pass argument and read from stdin.
import sys import typer def main( name: str = typer.Argument( ... if sys.stdin.isatty() else sys.stdin.read().strip() ), ): typer.echo(f"Hello {name}") if __name__ == "__main__": typer.run(main)
I hope this will help you in resolving the issue
This doesn't work if you have more than one command using this pattern thanks to an inconvenient clash of the ways Python reads I/O streams and evaluates default function argument values. sys.stdin
will be read β and its position moved to the end of the stream β as soon as the first command like this is defined, which means future commands that try to do this will read from an empty stream.
I'm currently working around this by using sentinel to indicate that a value should be read from standard input:
# This script is complete and should run as-is.
import sys
import sentinel
import typer
PIPE = sentinel.create("PIPE_FROM_STDIN")
def main(
name: str = typer.Argument(
... if sys.stdin.isatty() else PIPE
),
):
if name == str(PIPE):
name = sys.stdin.read()
typer.echo(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
This looks like it works to me:
import sys
import typer
from typing_extensions import Annotated
def main(input_file: Annotated[typer.FileText, typer.Argument()] = sys.stdin):
typer.echo(input_file.read())
if __name__ == '__main__':
typer.run(main)
This looks like it works to me:
import sys import typer from typing_extensions import Annotated def main(input_file: Annotated[typer.FileText, typer.Argument()] = sys.stdin): typer.echo(input_file.read()) if __name__ == '__main__': typer.run(main)
This works, but then mypy complains with following
Incompatible default for argument "input_file" (default has type "TextIO", argument has type "FileText")
First Check
Commit to Help
Example Code
Description
I would like to have typer integrate seemlessly with sys.stdin. Currently, it is quite complicated to integrate sys.stdin in a typer app. You can obviously use something like :
See this issue : #156
But it is definitely not seemless.
Wanted Solution
The goal would be to have something like in the code example provided, direct access to stdin from the argument type (which is handy when designing CLIs so that they can be used in conjunction with other unix tools -- like cut, cat, sed or find for instance)
Wanted Code
Alternatives
Use the aforementionned workaround, using '-' as the marker for an stdin input.
I also tried reading from sys.stdin directly, but it doesn't integrate well in a typer app (especially around the coding style)
Operating System
Linux, Windows, macOS
Operating System Details
Typer Version
0.3.2
Python Version
3.9.9
Additional Context
No response