# How to add type annotations to a Python project with pyrefly infer


[Pyrefly](https://pydevtools.com/handbook/reference/pyrefly.md) can turn `def total(numbers):` into `def total(numbers: list[int]) -> int:` without running your code. The command is `pyrefly infer`, previously called `autotype`, and it writes draft annotations by reading call sites with static analysis.

For a legacy codebase with thousands of unannotated functions, this turns weeks of manual annotation into a reviewable diff. The output needs a human review pass before merging.

> [!NOTE]
> `pyrefly infer` is under active development. The Pyrefly maintainers explicitly recommend manually reviewing every change it produces.

## Install Pyrefly

Add Pyrefly as a dev dependency in a [uv](https://pydevtools.com/handbook/reference/uv.md) project:

```bash
uv add --dev pyrefly
```

You can also run it ad-hoc without installing:

```bash
uvx pyrefly infer path/to/file.py
```

## Run pyrefly infer on a single file

Start with one file so you can review the diff before committing:

```bash
uv run pyrefly infer path/to/file.py
```

The command edits the file in place. For example, an unannotated function:

```python {filename="example.py"}
def total(numbers):
    result = []
    for n in numbers:
        result.append(n * 2)
    return sum(result)


if __name__ == "__main__":
    print(total([1, 2, 3]))
```

becomes:

```python {filename="example.py"}
def total(numbers: list[int]) -> int:
    result = []
    for n in numbers:
        result.append(n * 2)
    return sum(result)


if __name__ == "__main__":
    print(total([1, 2, 3]))
```

Pyrefly inferred `numbers: list[int]` from the call site `total([1, 2, 3])` and `-> int` from `sum()`.

## Choose what to annotate with flags

`pyrefly infer` exposes four flags that toggle each annotation category. Each accepts `=true` or `=false`:

| Flag | What it adds |
|---|---|
| `--parameter-types` | Annotations on function parameters |
| `--return-types` | Function return annotations |
| `--containers` | Element types for `list`, `dict`, and other containers |
| `--imports` | Imports for symbols introduced by new annotations |

For an incremental rollout, run return types first and parameters later:

```bash
uv run pyrefly infer --return-types --parameter-types=false --containers=false src/
```

Return types are the safest category to add first because they never change how the function is called. Parameter types can surface call-site mismatches that need a wider review.

## Review the diff before checking

Commit existing work before running `pyrefly infer`, then review the diff:

```bash
git diff
```

Two things to watch for:

- Functions whose call sites are not visible to Pyrefly stay unannotated. Parameter inference depends on observing how the function is called inside the project. Internal helpers usually annotate cleanly; functions called only from tests, fixtures, or external consumers may not.
- Pyrefly sometimes infers narrower types than you intend. A function called only with `list[int]` gets `list[int]` even if it should accept `Iterable[int]`. Widen these annotations manually before merging.

When the diff looks right, commit it:

```bash
git commit -am "Add type annotations with pyrefly infer"
```

## Handle the new errors

New annotations turn previously-implicit `Any` into concrete types, which can surface latent inconsistencies. Run a check next:

```bash
uv run pyrefly check
```

A frequent pattern: Pyrefly infers a parameter type from one call site, but other call sites pass a different type. For example, given:

```python {filename="labels.py"}
def label(value):
    return f"label-{value}"


def process(items):
    return [label(item) for item in items]


if __name__ == "__main__":
    process([1, 2, 3])
    label("oops")
```

`pyrefly infer` writes `def label(value: str) -> str` because the direct call uses a string. After annotation, the indirect call from `process` fails:

```console
$ uv run pyrefly check labels.py
ERROR Argument `int` is not assignable to parameter `value` with type `str` in function `label` [bad-argument-type]
 --> labels.py:8:32
  |
8 |     return [label(item) for item in items]
  |                ^^^^
  |
 INFO 1 error
```

Fix these errors by widening the annotation manually (`value: str | int`) or by suppressing the diagnostic with `# pyrefly: ignore[bad-argument-type]` while you investigate. The Pyrefly docs cover the [error suppression syntax](https://pyrefly.org/en/docs/error-suppressions/) in detail.

## Roll out incrementally

Run on small slices, not the whole project at once:

```bash
uv run pyrefly infer src/myproject/api/
```

After each batch, review and commit the diff before checking. Then run `uv run pyrefly check`, address the new errors, and move to the next directory. Smaller batches keep code review tractable and isolate the source of any regressions.

## Compare with other auto-annotation tools

`pyrefly infer` uses static analysis only, so it never executes your code. Two alternatives take different approaches:

- [MonkeyType](https://github.com/Instagram/MonkeyType) records types at runtime by tracing your test suite, then writes them back. It catches dynamic patterns static analysis misses, at the cost of running instrumented Python.
- [autotyping](https://github.com/JelleZijlstra/autotyping) focuses on cheap-to-detect cases (returning `None` or `bool`, simple property setters) and is fast to apply across very large codebases.

MonkeyType can annotate dynamic paths that `pyrefly infer` will miss, but only for code your tests execute. `pyrefly infer` works without running the test suite, but it only sees what static analysis can prove from local call sites.

## Learn More

- [Pyrefly reference](https://pydevtools.com/handbook/reference/pyrefly.md)
- [How to gradually adopt type checking in an existing Python project](https://pydevtools.com/handbook/how-to/how-to-gradually-adopt-type-checking-in-an-existing-python-project.md)
- [How do mypy, pyright, and ty compare?](https://pydevtools.com/handbook/explanation/how-do-mypy-pyright-and-ty-compare.md)
- [Pyrefly Infer documentation](https://pyrefly.org/en/docs/autotype/)
- [Pyrefly error suppressions](https://pyrefly.org/en/docs/error-suppressions/)
