website/docs/migrate/mypy/index.mdx
{/*
This guide covers the simplest migration path to transition from mypy to Pyrefly. It should be suitable for most projects.
If your project uses mypy plugins or sets strict = true, follow this page
first and then read the linked guide for that case.
# uv
uv add --dev pyrefly
# pip
python -m pip install pyrefly
pyrefly init
The pyrefly init command finds your mypy.ini or [tool.mypy] section and
writes the equivalent pyrefly.toml or [tool.pyrefly], preserving the
settings it can: project inputs and import roots, Python version and interpreter
settings, import suppression, selected strictness flags, per-module error
overrides, and a subset of mypy error codes. Use pyrefly init --help for more
options.
Your mypy config is left in place. Pyrefly only reads its own config once one exists, so both checkers keep working and you can back out at any point.
Read the generated config line by line before committing it. Settings and error codes the converter does not recognize are dropped without a warning. The mypy config reference and mypy error-code reference show how each mypy option is mapped, so you can check anything the generated file does not mention.
Alongside your converted settings, pyrefly init writes:
preset = "legacy"
The legacy preset disables a few
checks that mypy does not have, so a migrating project is not immediately
flooded with errors for classes of issue mypy never flagged.
Mypy skips the bodies of completely unannotated functions. Pyrefly checks them and can infer their return types, which gives better editor information but produces differences during a migration. To keep mypy's treatment while you migrate:
preset = "legacy"
check-unannotated-defs = false
infer-return-types = "never"
If mypy sets check_untyped_defs = true, including via strict = true, use
check-unannotated-defs = true instead.
:::warning
Both migration paths read check_untyped_defs, falling back to strict, but
only at the global level. A check_untyped_defs set under a per-module section
([mypy-some.module], or a [[tool.mypy.overrides]] entry) is dropped, so set
check-unannotated-defs by hand if your config only enables it per module. See
mypy strict mode.
:::
Empty-container inference already matches mypy closely. Pyrefly infers the
element type from first use, controlled by infer-with-first-use, which
defaults to true and is normally left alone for a mypy migration.
With the config settled, run both checkers on the same commit and compare:
mypy src
pyrefly check
You may still find some differences in error output as Pyrefly implements the type system independently of mypy and will not reproduce its diagnostics exactly.
Classify each difference: a true bug, an inference difference, a framework behavior gap, a config gap, or a custom-plugin dependency.
Once you know what the differences are, decide how to absorb the ones you are
not fixing up front. Start with suppressions: pyrefly suppress inserts
comments across the project for you, and # pyrefly: ignore[bad-assignment]
silences a single line.
pyrefly suppress
The comments live next to the code they apply to, so the remaining work stays visible and can be cleared file by file.
If your codebase is large enough that suppression comments would mean churn across thousands of lines, use a baseline instead:
pyrefly check --baseline="pyrefly-baseline.json" --update-baseline
pyrefly check --baseline="pyrefly-baseline.json"
Or commit it in the configuration:
baseline = "pyrefly-baseline.json"
A baseline records the current errors in a file rather than in your source. It suppresses matching errors in CLI and CI runs so new errors can be blocked, while still showing the suppressed diagnostics in the IDE. That lets the migration land before the backlog is cleared.
Whichever you choose, Pyrefly respects # type: ignore by default; to also
honor mypy-specific ignores during the transition:
enabled-ignores = ["type", "mypy", "pyrefly"]
Keep that temporary and remove stale tool-specific comments once mypy leaves CI. See the error suppression docs.
Once CI is green on Pyrefly alone and the team is happy with the editor
experience, drop mypy: remove it from your dependencies and CI, delete
mypy.ini or the [tool.mypy] section, and narrow enabled-ignores back to
["type", "pyrefly"]. Then clear the suppressions or the baseline as you go
with pyrefly suppress --remove-unused.
| If your project | Read |
|---|---|
Sets plugins = [...] | Mypy plugins |
Sets strict = true | Mypy strict mode |
Uses ignore_missing_imports or follow_imports = "skip" | Mypy ignore_missing_imports |
| Needs the full option-by-option mapping | Mypy config reference |
| Needs the full error-code mapping | Mypy error codes |
If a specific plugin, error code, or configuration option is what stops you from adopting Pyrefly, please open an issue.