Python

Verified against Claude Code · 2026-08-02

Refactor blocking Python code to async without breaking it silently

A prompt for converting synchronous I/O-bound code to async/await that separates what genuinely benefits from async, wraps unavoidable blocking calls correctly, and checks every caller — instead of prefixing every function with async def.

Claude CodeChatGPT (GPT-5.1)Claude (Sonnet 4.6)Cursor 2.1

The prompt

Ready to copy — highlighted parts are example details you can swap.

Refactor the code below from synchronous to async — but only where it actually matters. Do not prefix every function with async def reflexively; that adds overhead without benefit for CPU-bound code and creates a false sense of concurrency.

CODE
def fetch_all_prices(product_ids: list[str]) -> list[Price]: loops and calls requests.get() per id

I/O OPERATIONS INVOLVED
HTTP GET requests to a pricing API, a write to a local CSV file

GOAL
fetch prices for up to 50 products concurrently instead of one request at a time

REQUIREMENTS
1. For each I/O operation, identify whether an async-native library exists (httpx's AsyncClient instead of requests, an async database driver instead of a sync one, aiofiles for file I/O) and use it. If no async equivalent exists for something in HTTP GET requests to a pricing API, a write to a local CSV file, say so explicitly and wrap it with asyncio.to_thread rather than calling it directly inside an async def and silently blocking the event loop.
2. Any genuinely CPU-bound work (parsing, computation) stays synchronous, or is offloaded to asyncio.to_thread or a ProcessPoolExecutor if it's heavy enough to matter — making it async def alone does nothing for CPU-bound code.
3. Use asyncio.gather, or a TaskGroup on Python 3.11+, to run independent I/O calls concurrently where fetch prices for up to 50 products concurrently instead of one request at a time calls for it — don't await a list of calls sequentially and call it async.
4. Propagate cancellation and timeouts correctly: wrap calls that should have a deadline in asyncio.timeout(...), and don't swallow asyncio.CancelledError with a bare except Exception.
5. Every function whose signature changes from sync to async must have every one of its callers updated too — list them, don't leave a caller doing result = my_func() on a coroutine it never awaits.

OUTPUT FORMAT
1. The refactored code.
2. A table: function name, sync or async now, and the reason.
3. Every caller found that also needed updating, and confirmation each one now awaits correctly.
Customize the highlighted detailsoptional — the prompt above already works

Why this works

The explicit instruction against reflexively adding async def everywhere addresses a real, common misconception: async does nothing for CPU-bound work and adds event-loop overhead for no benefit, so a refactor that async-ifies indiscriminately can make code slower and harder to reason about while looking more modern. Naming asyncio.to_thread for any I/O operation with no async-native library available solves the single most common asyncio production bug directly — a blocking call left inside an async def function, which stalls the entire event loop for every concurrent request being served by that worker, not just the one making the slow call. Requiring the caller list is what catches the failure mode that's easy to miss and doesn't raise an exception when it happens: calling a newly-async function without awaiting it produces a coroutine object and a RuntimeWarning, not a crash, so the code silently does nothing at that call site unless someone specifically checks.

What you get back

async def fetch_all_prices(product_ids: list[str]) -> list[Price]: async with httpx.AsyncClient() as client: results = await asyncio.gather(*(fetch_price(client, pid) for pid in product_ids)) return results Table: fetch_price -> async (network I/O, httpx.AsyncClient); fetch_all_prices -> async (fans out via gather); parse_price_response -> stays sync (pure CPU-bound parsing, no I/O). Callers updated: report_generator.build_report() now does "prices = await fetch_all_prices(ids)" instead of a direct call — confirmed it runs inside an async context.

Verified against

Claude Code Sonnet 4.6 · 2026-08-02

Claude Sonnet 4.6 · 2026-08-03

Changelog

  • 2026-08-03 Initial publish, verified against Claude Code and Claude (Sonnet 4.6) on Python 3.12.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Python prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY