# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['shellous']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'shellous',
    'version': '0.21.0',
    'description': 'Async Processes and Pipelines',
    'long_description': '# Async Processes and Pipelines\n\n[![docs](https://img.shields.io/badge/-documentation-informational)](https://byllyfish.github.io/shellous/shellous.html) [![PyPI](https://img.shields.io/pypi/v/shellous)](https://pypi.org/project/shellous/) [![CI](https://github.com/byllyfish/shellous/actions/workflows/ci.yml/badge.svg)](https://github.com/byllyfish/shellous/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/byllyfish/shellous/branch/main/graph/badge.svg?token=W44NZE89AW)](https://codecov.io/gh/byllyfish/shellous) [![Downloads](https://pepy.tech/badge/shellous)](https://pepy.tech/project/shellous)\n\n**shellous** provides a concise API for running subprocesses using [asyncio](https://docs.python.org/3/library/asyncio.html). It is \nsimilar to and inspired by [sh](https://pypi.org/project/sh/).\n\n```python\nimport asyncio\nfrom shellous import sh\n\nasync def main():\n    result = await sh("echo", "hello")\n    print(result)\n\nasyncio.run(main())\n```\n\n## Benefits\n\n- Run programs asychronously in a single line.\n- Redirect stdin, stdout and stderr to files, memory buffers or loggers.\n- Construct [pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)) and use [process substitution](https://en.wikipedia.org/wiki/Process_substitution).\n- Set timeouts and reliably cancel running processes.\n- Run a program with a pseudo-terminal (pty).\n- Runs on Linux, MacOS, FreeBSD and Windows.\n- Monitor processes being started and stopped with `audit_callback` API.\n\n## Requirements\n\n- Requires Python 3.9 or later.\n- Requires an asyncio event loop.\n- Process substitution requires a Unix system with /dev/fd support.\n- Pseudo-terminals require a Unix system.\n\n## Running a Command\n\nThe tutorial in this README uses the asyncio REPL built into Python. In these examples, `>>>`\nis the REPL prompt.\n\nStart the asyncio REPL by typing `python3 -m asyncio`, and import **sh** from the **shellous** module:\n\n```pycon\n>>> from shellous import sh\n```\n\nHere\'s a command that runs `echo "hello, world"`.\n\n```pycon\n>>> await sh("echo", "hello, world")\n\'hello, world\\n\'\n```\n\nThe first argument to `sh` is the program name. It is followed by zero or more arguments. Each argument will be\nconverted to a string. If an argument is a list or tuple, it is flattened recursively.\n\n```pycon\n>>> await sh("echo", 1, 2, [3, 4, (5, 6)])\n\'1 2 3 4 5 6\\n\'\n```\n\nA command does not run until you `await` it. When you run a command using `await`, it returns the value of the standard output interpreted as a UTF-8 string.\n\nHere, we create our own echo command with "-n" to omit the newline. Note, `echo("abc")` will run the same command as `echo -n "abc"`.\n\n```pycon\n>>> echo = sh("echo", "-n")\n>>> await echo("abc")\n\'abc\'\n```\n\nCommands are **immutable** objects that represent a program invocation: program name, arguments, environment\nvariables, redirection operators and other settings. When you use a method to modify a `Command`, you are\nreturning a new `Command` object. The original object is unchanged.\n\nIn this example, we use the `set()` modifier to change the output encoding. The new command `echob`\nwill return standard output as bytes (encoding=None).\n\n\n```pycon\n>>> echob = echo.set(encoding=None)\n>>> await echob("def")\nb\'def\'\n```\n\n### Async For\n\nUsing `await` to run a command collects the entire output of the command before returning it. You can also\niterate over the output lines as they arrive using `async for`.\n\n```pycon\n>>> [line async for line in echo("hi\\n", "there")]\n[\'hi\\n\', \' there\']\n```\n\nUse an `async for` loop when you want to examine the stream of output from a command, line by line. For example, suppose you want to run tail on a log file.\n\n```python\nasync for line in sh("tail", "-f", "/var/log/syslog"):\n    if "ERROR" in line:\n        print(line.rstrip())\n```\n\n### Async With\n\nYou can use a command as an asynchronous context manager. Use `async with` when you need byte-by-byte control over the individual process streams: stdin, stdout and stderr. To control standard input, we need to explicitly\ntell shellous to "capture" standard input (For more on this, see [Redirection](#redirection).)\n\n```python\nasync with sh("cat").stdin(sh.CAPTURE) as run:\n    run.stdin.write(b"abc")\n    run.stdin.close()\n    print(await run.stdout.readline())\n\nresult = run.result()\n```\n\nWhen reading or writing individual streams, you are responsible for managing reads and writes so they don\'t\ndeadlock. The streams on the `run` object are `asyncio.StreamReader` and `asyncio.StreamWriter` objects.\n\n\n### ResultError\n\nWhen a command fails, it raises a `ResultError` exception:\n\n```pycon\n>>> await sh("cat", "does_not_exist")\nTraceback (most recent call last):\n  ...\nshellous.result.ResultError: Result(output_bytes=b\'\', exit_code=1, cancelled=False, encoding=\'utf-8\', extra=None)\n```\n\nThe `ResultError` exception contains a `Result` object with the exit_code.\n\nIn some cases, you want to ignore certain exit code values. That is, you want to treat them as if they are normal.\nTo do this, you can set the `exit_codes` option:\n\n```pycon\n>>> await sh("cat", "does_not_exist").set(exit_codes={0,1})\n\'\'\n```\n\nIf there is a problem launching a process, shellous can also raise a separate `FileNotFoundError` or  `PermissionError`.\n\n## Results\n\nWhen a command completes successfully, it returns the standard output (unless it is redirected). For a more detailed response, you can specify that the command should return a `Result` object by using the `.result` modifier:\n\n```pycon\n>>> await echo("abc").result\nResult(output_bytes=b\'abc\', exit_code=0, cancelled=False, encoding=\'utf-8\', extra=None)\n```\n\nA `Result` object contains the command\'s `exit_code` in addition to its output. A `Result` is True if \nthe command\'s exit_code is zero. You can access the string value of the output using the `.output` property:\n\n```python\nif result := await sh("cat", "some-file").result:\n    output = result.output\nelse:\n    print(f"Command failed with exit_code={result.exit_code})\n```\n\n## Redirection\n\nshellous supports the redirection operators `|` and `>>`. They work similar to how they work in \nthe unix shell. Shellous does not support use of `<` or `>` for redirection. Instead, replace these \nwith `|`.\n\nTo redirect to or from a file, use a `pathlib.Path` object. Alternatively, you can redirect input/output\nto a StringIO object, an open file, a Logger, or use a special redirection constant like `sh.DEVNULL`.\n\n### Redirecting Standard Input\n\nTo redirect standard input, use the pipe operator `|` with the argument on the left-side.\nHere is an example that passes the string "abc" as standard input.\n\n```pycon\n>>> cmd = "abc" | sh("wc", "-c")\n>>> await cmd\n\'       3\\n\'\n```\n\nTo read input from a file, use a `Path` object from `pathlib`.\n\n```pycon\n>>> from pathlib import Path\n>>> cmd = Path("LICENSE") | sh("wc", "-l")\n>>> await cmd\n\'     201\\n\'\n```\n\nShellous supports different STDIN behavior when using different Python types.\n\n| Python Type | Behavior as STDIN |\n| ----------- | --------------- |\n| str | Read input from string object. |\n| bytes, bytearray | Read input from bytes object. |\n| Path | Read input from file specified by `Path`. |\n| File, StringIO, ByteIO | Read input from open file object. |\n| int | Read input from existing file descriptor. |\n| asyncio.StreamReader | Read input from `StreamReader`. |\n| sh.DEVNULL | Read input from `/dev/null`. |\n| sh.INHERIT  | Read input from existing `sys.stdin`. |\n| sh.CAPTURE | You will write to stdin interactively. |\n\n### Redirecting Standard Output\n\nTo redirect standard output, use the pipe operator `|` with the argument on the right-side. Here is an example\nthat writes to a temporary file.\n\n```pycon\n>>> output_file = Path("/tmp/output_file")\n>>> cmd = sh("echo", "abc") | output_file\n>>> await cmd\n\'\'\n>>> output_file.read_bytes()\nb\'abc\\n\'\n```\n\nTo redirect standard output with append, use the `>>` operator.\n\n```pycon\n>>> cmd = sh("echo", "def") >> output_file\n>>> await cmd\n\'\'\n>>> output_file.read_bytes()\nb\'abc\\ndef\\n\'\n```\n\nShellous supports different STDOUT behavior when using different Python types.\n\n| Python Type | Behavior as STDOUT/STDERR | append=True\n| ----------- | --------------- | ------\n| Path | Write output to file path specified by `Path`. | Open file for append\n| bytearray | Write output to mutable byte array. | TypeError\n| File, StringIO, ByteIO | Write output to open file object. | TypeError\n| int | Write output to existing file descriptor. | TypeError\n| logging.Logger | Log each line of output. | TypeError\n| asyncio.StreamWriter | Write output to `StreamWriter`. | TypeError\n| sh.CAPTURE | Return standard output or error. See *Multiple Capture*. | TypeError\n| sh.DEVNULL | Write output to `/dev/null`. | TypeError\n| sh.INHERIT  | Write output to existing `sys.stdout` or `sys.stderr`. | TypeError\n| sh.STDOUT | Redirect stderr to same place as stdout. | TypeError\n\nShellous does not support redirecting standard output/error to a plain `str` or `bytes` object. \nIf you intend to redirect output to a file, you must use a `pathlib.Path` object.\n\n### Redirecting Standard Error\n\nTo redirect standard error, use the `stderr` method. Standard error supports the\nsame Python types as standard output. To redirect stderr to the same place as stdout, \nuse the `sh.STDOUT` constant.\n\n```pycon\n>>> cmd = sh("cat", "does_not_exist").stderr(sh.STDOUT)\n>>> await cmd.set(exit_codes={0,1})\n\'cat: does_not_exist: No such file or directory\\n\'\n```\n\nTo redirect standard error to the hosting program\'s `sys.stderr`, use the `sh.INHERIT` redirect\noption.\n\n```pycon\n>>> cmd = sh("cat", "does_not_exist").stderr(sh.INHERIT)\n>>> await cmd\ncat: does_not_exist: No such file or directory\nTraceback (most recent call last):\n  ...\nshellous.result.ResultError: Result(output_bytes=b\'\', exit_code=1, cancelled=False, encoding=\'utf-8\', extra=None)\n```\n\n### Default Redirections\n\nFor regular commands, the default redirections are:\n\n- Standard input is read from the empty string ("").\n- Standard out is captured by the program and returned (CAPTURE).\n- Standard error is discarded (DEVNULL).\n\nHowever, the default redirections are adjusted when using a pseudo-terminal (pty):\n\n- Standard input is captured and ignored (CAPTURE).\n- Standard out is captured by the program and returned (CAPTURE).\n- Standard error is redirected to standard output (STDOUT).\n\n\n## Pipelines\n\nYou can create a pipeline by combining commands using the `|` operator.\n\n```pycon\n>>> pipe = sh("ls") | sh("grep", "README")\n>>> await pipe\n\'README.md\\n\'\n```\n\n## Process Substitution (Unix Only)\n\nYou can pass a shell command as an argument to another.\n\n```pycon\n>>> cmd = sh("grep", "README", sh("ls"))\n>>> await cmd\n\'README.md\\n\'\n```\n\nUse `.writable` to write to a command instead.\n\n```pycon\n>>> buf = bytearray()\n>>> cmd = sh("ls") | sh("tee", sh("grep", "README").writable | buf) | sh.DEVNULL\n>>> await cmd\n\'\'\n>>> buf\nbytearray(b\'README.md\\n\')\n```\n\n## Timeouts\n\nYou can specify a timeout using the `timeout` option. If the timeout expires, shellous will raise\na `TimeoutError`.\n\n```pycon\n>>> await sh("sleep", 60).set(timeout=0.1)\nTraceback (most recent call last):\n  ...\nTimeoutError\n```\n\nTimeouts are just a special case of **cancellation**. When a command is cancelled, shellous terminates \nthe running process and raises a `CancelledError`.\n\n```pycon\n>>> t = asyncio.create_task(sh("sleep", 60).coro())\n>>> t.cancel()\nTrue\n>>> await t\nTraceback (most recent call last):\n  ...\nCancelledError\n```\n\nBy default, shellous will send a SIGTERM signal to the process to tell it to exit. If the process does not\nexit within 3 seconds, shellous will send a SIGKILL signal. You can change these defaults with the\n`cancel_signal` and `cancel_timeout` settings. A command is not considered fully cancelled until the \nprocess exits.\n\n## Pseudo-Terminal Support (Unix Only)\n\nTo run a command through a pseudo-terminal, set the `pty` option to True. Alternatively, you can pass\na function to configure the tty mode and size.\n\n```pycon\n>>> ls = sh("ls").set(pty=shellous.cooked(cols=40, rows=10, echo=False))\n>>> await ls("README.md", "CHANGELOG.md")\n\'CHANGELOG.md\\tREADME.md\\r\\n\'\n```\n\n## Context Objects\n\nYou can store shared command settings in an immutable context object. To create a new \ncontext object, specify your changes to the default context **sh**:\n\n```pycon\n>>> auditor = lambda phase, info: print(phase, info["runner"].name)\n>>> sh_audit = sh.set(audit_callback=auditor)\n```\n\nNow all commands created with `sh_audit` will log their progress using the audit callback.\n\n```pycon\n>>> await sh_audit("echo", "goodbye")\nstart echo\nstop echo\n\'goodbye\\n\'\n```\n',
    'author': 'Bill Fisher',
    'author_email': 'william.w.fisher@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/byllyfish/shellous',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.9,<4.0',
}


setup(**setup_kwargs)
