Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.refactron.dev/llms.txt

Use this file to discover all available pages before exploring further.

Transform ID: callback_to_async_await Language: Python

What it does

Detects functions whose last positional parameter is a callback (named callback, cb, or done) that is invoked exactly once at the tail of every control-flow branch. Rewrites the function as async def, drops the callback parameter, and returns what the callback was being passed.

Detector pattern

The detector lives at src/analyze/detectors/python/callback-pattern.ts. It scans for def statements where the last positional parameter name matches the callback alias set, then walks the function body to verify the call shape.

Preconditions

  1. The callback parameter is the last positional parameter of the function.
  2. The function is not already async (async def).
  3. The function is not a generator (no yield / yield from).
  4. The callback is invoked exactly once, on the tail of every reachable branch.
  5. Cross-file: no external file imports this function and passes a callback at the call site (would orphan that caller). The transform skips itself when an import <module> followed by <module>.<fn>(... cb) or from <module> import <fn> followed by <fn>(... cb) is found in the project.

Before / after

def fetch_user(user_id, callback):
    """Simulate fetching a user record and pass it to ``callback``."""
    result = {"id": user_id, "name": "user-%d" % user_id}
    callback(result)


def save_data(payload, done):
    """Simulate persisting ``payload`` then invoking ``done`` with a receipt."""
    receipt = {"ok": True, "size": len(payload)}
    done(receipt)

Edge cases handled

  • Callback aliases: callback, cb, done.
  • Preserves preceding decorators (e.g. @retry).
  • Preserves type hints on the remaining parameters.

Edge cases NOT handled (skip via precondition)

  • Callback is not the last positional parameter (e.g. def f(callback, x)).
  • Function is a generator (yield).
  • Callback is called more than once (multiple branches each invoking it).
  • Function is already declared async.
  • An external file in the project calls this function passing a callback argument.
Actual emitted form may differ slightly per the transform’s idempotency rules — run refactron run --dry-run to see the exact diff for your code.