Skip to content

Commit a886108

Browse files
authored
Merge pull request #255 from realpython/subprocess
Python `subprocess`
2 parents f10cb57 + 2e096bc commit a886108

17 files changed

+323
-0
lines changed

subprocess/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Python `subprocess` Examples
2+
3+
Here are supporting materials for the Real Python tutorial [The `subprocess` Module: Wrapping Programs With Python](https://realpython.com/python-subprocess/).
4+
5+
Be aware that some examples are designed for particular operating systems. The `basics_unix.py` file won't work on Windows, for instance.
6+
7+
You'll find quick descriptions of what the different scripts do in the docstring of each file.

subprocess/basics_unix.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
**Only works on Linux or macOS**
3+
4+
Demonstrates basic usage of `subprocess.run()`.
5+
"""
6+
7+
import subprocess
8+
9+
subprocess.run(["sh", "-c", "ls"])
10+
11+
subprocess.run(["ls"], shell=True)

subprocess/basics_win.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
**Only works on Windows**
3+
4+
Demonstrates basic usage of `subprocess.run()`.
5+
"""
6+
7+
import subprocess
8+
9+
# https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
10+
subprocess.run("dir", shell=True) # COMSPEC env variable
11+
subprocess.run(["cmd.exe", "/c", "dir"])
12+
13+
# https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh?view=powershell-7.2
14+
subprocess.run(["powershell", "-Command", "ls"])
15+
subprocess.run(["python", "helloworld.py"])

subprocess/create_project.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Uses `subprocess` to creates a Python project, complete with a virtual
3+
environment and initialized Git repository.
4+
5+
Must have Git installed on the system with the `git` command available.
6+
7+
If your Python command is `python3`, change the `PYTHON_COMMAND` variable.
8+
"""
9+
10+
from argparse import ArgumentParser
11+
from pathlib import Path
12+
import subprocess
13+
14+
PYTHON_COMMAND = "python"
15+
16+
17+
def create_new_project(name):
18+
project_folder = Path.cwd().absolute() / name
19+
project_folder.mkdir()
20+
(project_folder / "README.md").touch()
21+
with open(project_folder / ".gitignore", mode="w") as f:
22+
f.write("\n".join(["venv", "__pycache__"]))
23+
commands = [
24+
[
25+
PYTHON_COMMAND,
26+
"-m",
27+
"venv",
28+
f"{project_folder}/venv",
29+
],
30+
["git", "-C", project_folder, "init"],
31+
["git", "-C", project_folder, "add", "."],
32+
["git", "-C", project_folder, "commit", "-m", "Initial commit"],
33+
]
34+
for command in commands:
35+
try:
36+
subprocess.run(command, check=True, timeout=60)
37+
except FileNotFoundError as exc:
38+
print(
39+
f"Command {command} failed because the process "
40+
f"could not be found.\n{exc}"
41+
)
42+
except subprocess.CalledProcessError as exc:
43+
print(
44+
f"Command {command} failed because the process "
45+
f"did not return a successful return code.\n{exc}"
46+
)
47+
except subprocess.TimeoutExpired as exc:
48+
print(f"Command {command} timed out.\n {exc}")
49+
50+
51+
if __name__ == "__main__":
52+
parser = ArgumentParser()
53+
parser.add_argument("project_name", type=str)
54+
args = parser.parse_args()
55+
create_new_project(args.project_name)

subprocess/custom_exit.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
Raises custom exit codes:
3+
4+
```shell
5+
$ python custom_exit.py 3
6+
```
7+
8+
This will exit with error code `3`. By default, will exit with code `0`.
9+
"""
10+
11+
import sys
12+
13+
print(sys.argv)
14+
15+
try:
16+
raise SystemExit(int(sys.argv[1]))
17+
except IndexError as e:
18+
raise SystemExit(0) from e
19+
except ValueError as e:
20+
print("Argument must be an integer")
21+
raise SystemExit() from e

subprocess/error_handling.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
Demonstrates error handling with `subprocess.run()`, catching common errors
3+
that `subprocess.run()` can encounter. Try changing the commands in
4+
`subprocess.run()` to raise different errors.
5+
"""
6+
7+
import subprocess
8+
9+
10+
try:
11+
subprocess.run(["python", "timer.py", "10"], timeout=5, check=True)
12+
except FileNotFoundError as exc:
13+
print(f"Process failed because the executable could not be found.\n{exc}")
14+
except subprocess.CalledProcessError as exc:
15+
print(
16+
f"Process failed because did not return a successful return code. "
17+
f"Returned {exc.returncode}\n{exc}"
18+
)
19+
except subprocess.TimeoutExpired as exc:
20+
print(f"Process timed out.\n{exc}")

subprocess/exiter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""
2+
Immediately exits with exit code 1
3+
"""
4+
5+
import sys
6+
7+
sys.exit(1)

subprocess/exiter_run.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
Calls [custom_exit.py](custom_exit.py) with `subprocess.run()`,
3+
demonstrating the `check` argument to `.run()`.
4+
"""
5+
6+
import subprocess
7+
8+
subprocess.run(["python", "custom_exit.py", "5"], check=False)
9+
10+
try:
11+
subprocess.run(["python", "custom_exit.py", "5"], check=True)
12+
except subprocess.CalledProcessError as exc:
13+
print(exc)

subprocess/popen_pipe.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
**Only works on Linux or macOS**
3+
4+
Demonstrates using `subprocess.Popen()` to pipe one command into the other.
5+
"""
6+
7+
import subprocess
8+
9+
ls_process = subprocess.Popen(["ls", "/usr/bin"], stdout=subprocess.PIPE)
10+
grep_process = subprocess.Popen(
11+
["grep", "python"], stdin=ls_process.stdout, stdout=subprocess.PIPE
12+
)
13+
14+
for line in grep_process.stdout:
15+
print(line.decode("utf-8").strip())

subprocess/popen_timer.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
Demonstrates `subprocess.Popen()` calling [timer.py](timer.py).
3+
"""
4+
5+
import subprocess
6+
from time import sleep
7+
8+
with subprocess.Popen(
9+
["python", "timer.py", "5"], stdout=subprocess.PIPE
10+
) as process:
11+
12+
def poll_and_read():
13+
print(f"Output from poll: {process.poll()}")
14+
print(f"Output from stdout: {process.stdout.read1().decode('utf-8')}")
15+
16+
poll_and_read()
17+
sleep(3)
18+
poll_and_read()
19+
sleep(3)
20+
poll_and_read()

0 commit comments

Comments
 (0)