Skip to content

How to migrate from mypy to Pyrefly

Pyrefly converts supported mypy configuration, but its diagnostics and inference differ. Keep mypy in CI until the converted settings and a representative set of framework calls have been checked.

Install Pyrefly and preview the conversion

From the root of a project managed by uv:

uv add --dev pyrefly
uv run pyrefly init --dry-run --print-config

The --dev flag marks Pyrefly as a development dependencyA package needed during development (testing, linting, formatting) that is not shipped to users of your project. Installed with the --dev flag. , keeping it out of production installs. uv run executes the command inside the project’s virtual environment, ensuring all dependencies are installed first.

The preview prints the proposed TOML without changing configuration. Pyrefly migrates mypy.ini or [tool.mypy] in pyproject.toml. It does not discover .mypy.ini or setup.cfg for conversion. Copy their [mypy] settings into mypy.ini first.

Write and review the configuration

uv run pyrefly init --non-interactive

The command writes pyrefly.toml, or adds [tool.pyrefly] when migrating pyproject.toml, then runs a check. It leaves mypy’s configuration in place. It may exit successfully even when that initial check reports errors, so run pyrefly check separately before accepting the migration.

Review every setting against Pyrefly’s mypy configuration mapping. Unsupported settings may be dropped without a warning. Do not assume that mypy plugins or every per-module strictness option survive conversion.

The converter uses preset = "legacy" and normally disables return inference to preserve mypy’s behavior. A project with check_untyped_defs = true should have:

pyrefly.toml
preset = "legacy"
check-unannotated-defs = true
infer-return-types = "never"

This is only the core behavior mapping, not a replacement for the full generated file. Preserve converted import paths and strictness rules the project needs. Without check_untyped_defs, the converter can set check-unannotated-defs = false instead.

Compare diagnostics before recording a baseline

uv run mypy src/
uv run pyrefly check

The first command should retain the project’s existing mypy result. Review Pyrefly’s additional diagnostics as possible bugs, inference differences, missing dependency information, or unsupported framework behavior. A higher error count alone does not establish better coverage.

Before collecting the experimental baseline, add this top-level setting to pyrefly.toml (or its [tool.pyrefly] table):

baseline-matching-mode = "column-ordered"

The default matching mode allows one baseline entry to suppress several diagnostics sharing its file, error kind, and column. column-ordered limits suppression to the recorded sequence, so additional occurrences are reported. Then collect the reviewed backlog:

uv run pyrefly check --baseline=pyrefly-baseline.json --update-baseline
uv run pyrefly check --baseline=pyrefly-baseline.json

The update command writes the baseline and can still exit 1 while printing existing errors. The second command should report zero errors and exit 0 when every diagnostic matches the baseline. Add this setting to the same configuration and commit it with the baseline:

baseline = "pyrefly-baseline.json"

Pin Pyrefly through the committed lockfile. Re-generating a baseline accepts all current diagnostics, including newly introduced ones, so review the diff each time.

Replace the CI check after a parallel trial

After the workflow installs the project’s locked dependencies, use:

- name: Type check
  run: uv run --locked pyrefly check

With the committed configuration and baseline, this fails on errors outside the baseline. Keep the existing mypy step until framework and configuration differences have been reviewed.

Shrink the migration backlog

Fix diagnostics in small batches. To remove resolved baseline entries without accepting new errors:

uv run pyrefly check --baseline=pyrefly-baseline.json --prune-baseline

The baseline loses stale entries; unmatched diagnostics still appear and fail the check. Alternatively, uv run pyrefly suppress inserts inline comments at existing error locations. Review that source diff before committing it.

Use # pyrefly: ignore[bad-assignment] for a targeted assignment exception. A bare # type: ignore suppresses Pyrefly diagnostics on that line; review inherited mypy ignores before assuming they are still necessary.

Remove mypy while retaining needed stubs

Once Pyrefly alone provides the intended CI and editor coverage:

uv remove --dev mypy

uv updates the project and lockfile. Remove mypy’s configuration and CI step, but keep any stub packages Pyrefly still uses. Track the remaining baseline as migration debt rather than evidence that the code has no type errors.

Last updated on