This rule raises an issue when synchronous subprocess calls are used within asynchronous functions.

Why is this an issue?

Using synchronous subprocess calls like subprocess.Popen or similar functions in asynchronous code blocks the entire event loop. This undermines the primary advantage of asynchronous programming - the ability to perform concurrent operations without blocking execution.

When an async function makes a synchronous call to create a subprocess:

Instead, async libraries provide dedicated APIs for running subprocesses in a non-blocking way:

Using these APIs allows other tasks to continue executing while waiting for the subprocess to complete.

How to fix it in Asyncio

Replace synchronous subprocess calls with asyncio.create_subprocess_exec() or asyncio.create_subprocess_shell() depending on whether you need to run a specific command with arguments or a shell command string.

Code examples

Noncompliant code example

import subprocess

async def process_data():
    subprocess.run(["wget", "https://example.com/file.zip"])  # Noncompliant

Compliant solution

import asyncio

async def process_data():
    proc = await asyncio.create_subprocess_exec("wget", "https://example.com/file.zip")
    result = await proc.wait()

How to fix it in Trio

Replace synchronous subprocess calls with trio.run_process(), which handles both command arrays and shell commands.

Code examples

Noncompliant code example

import trio
import subprocess

async def download_files():
    result = subprocess.run(["wget", "https://example.com/file.zip"])  # Noncompliant

Compliant solution

import trio

async def download_files():
    result = await trio.run_process(["wget", "https://example.com/file.zip"])

How to fix it in AnyIO

Replace synchronous subprocess calls with anyio.run_process(), which works similar to Trio’s API and supports both command arrays and shell commands.

Code examples

Noncompliant code example

import subprocess

async def process_image():
    result = subprocess.run(["wget", "https://example.com/file.zip"])  # Noncompliant

Compliant solution

import anyio

async def process_image():
    result = await anyio.run_process(["wget", "https://example.com/file.zip"])

Resources

Documentation

Articles & blog posts