How to migrate from mypy to ty

This guide provides practical steps for migrating Python projects from mypy to ty, based on real-world experiments with three open source projects: NetworkX, IPython, and python-genai.

⚠️
ty is currently in beta. While actively developed, it’s still missing some features and may not be ready for full production adoption. This guide helps evaluate readiness and plan for migration.

Understanding ty

ty is an extremely fast Python type checker written in Rust, developed by Astral (the creators of uv and Ruff). It offers significantly faster performance than mypy, especially on large codebases.

When to consider migrating

  • Projects needing faster type checking on large codebases
  • Teams already using other Astral tools (Ruff, uv)
  • Projects without complex per-module mypy overrides
  • Projects not relying on mypy plugins

When to wait

  • Projects requiring Pydantic, Django, or SQLAlchemy mypy plugins
  • Projects with extensive per-module mypy overrides
  • Projects requiring mature, battle-tested tooling

Installation and basic usage

Installing ty

ty is distributed as a standalone binary. The easiest way to use it is via uvx:

# Run ty directly without installation
uvx ty check .

# Or install globally with uv
uv tool install ty

# Then run
ty check .

For other installation methods, see the ty installation guide.

Basic command equivalents

mypy Commandty Equivalent
mypy .uvx ty check .
mypy src/uvx ty check src/
mypy -p mypackageuvx ty check path/to/mypackage/
mypy --python-version 3.11 .uvx ty check . --python-version 3.11
mypy --strict .No direct equivalent (see configuration)

For all available CLI options, see the ty CLI reference.

ℹ️
ty doesn’t accept module names like -p; pass file or directory paths instead, or use --project or environment.root to define source roots.

Pointing to the Python environment

ty needs to find installed packages to resolve imports:

# Point to a virtualenv or interpreter
uvx ty check . --python .venv

# Or let ty auto-detect from an active virtualenv
uvx ty check .

For more details on how ty discovers and resolves modules, see the module discovery documentation.

Output formats

# Default verbose output
uvx ty check .

# Concise one-line-per-error output
uvx ty check . --output-format concise

# GitHub Actions annotations
uvx ty check . --output-format github

# GitLab Code Quality format
uvx ty check . --output-format gitlab

Migrating configuration

Configuration file locations

mypy supports:

  • mypy.ini
  • pyproject.toml[tool.mypy]
  • setup.cfg[mypy]
  • .mypy.ini

ty supports:

  • ty.toml (takes precedence)
  • pyproject.toml[tool.ty]
  • User-level: ~/.config/ty/ty.toml

For complete configuration details, see the ty configuration guide.

Basic option mapping

mypy Optionty EquivalentNotes
python_version = "3.11"environment.python-version = "3.11"
ignore_missing_imports = truerules.unresolved-import = "ignore"Critical for most projects
exclude = ["tests/"]src.exclude = ["**/tests/**"]Uses glob patterns
follow_imports = "silent"No equivalentty doesn’t have import following modes
check_untyped_defs = trueDefault behaviorty checks all code by default
disallow_untyped_defs = trueSee note belowEnforce via Ruff ANN001/ANN201
strict = trueNo equivalentMust enable individual options
plugins = ["pydantic.mypy"]Not supportedMajor migration blocker

If you relied on disallow_untyped_defs = true, you can approximate it with Ruff’s flake8-annotations rules:

[tool.ruff.lint]
select = [
  "ANN001", # flake8-annotations
  "ANN201",
]

See Ruff rule docs for missing-type-function-argument and missing-return-type-undocumented-public-function.

Example configuration migration

mypy (pyproject.toml):

[tool.mypy]
python_version = "3.11"
ignore_missing_imports = true
exclude = ["tests/", "docs/"]
check_untyped_defs = true
warn_redundant_casts = true

ty (pyproject.toml):

[tool.ty.environment]
python-version = "3.11"

[tool.ty.src]
exclude = ["**/tests/**", "**/docs/**"]

[tool.ty.rules]
unresolved-import = "ignore"
# Note: check_untyped_defs is ty's default behavior
# warn_redundant_casts equivalent: warnings are on by default
ℹ️
If using ty.toml, drop the tool.ty prefix (e.g., [environment], [src], [rules]).

Per-module overrides

mypy:

[[tool.mypy.overrides]]
module = ["mypackage.legacy", "mypackage.legacy.*"]
ignore_errors = true
disallow_untyped_defs = false

ty:

[[tool.ty.overrides]]
include = ["mypackage/legacy/**"]
rules = { unresolved-attribute = "ignore", invalid-assignment = "ignore" }
ℹ️
ty’s overrides use file glob patterns, not module names. The syntax and available options differ from mypy.

Handling mypy plugins

ty has no plugin system and no plan to add one. Some popular libraries may get native ty support over time.

If your project uses mypy plugins, this is a significant migration consideration.

Pluginty SupportWorkaround
pydantic.mypy❌ Not supportedIgnore Pydantic-related rules
django-stubs❌ Not supportedWait for native support or use mypy
sqlalchemy[mypy]❌ Not supportedWait for native support or use mypy
numpy.mypy❌ Not supportedMay work with stubs

Workaround for Pydantic projects:

[tool.ty.rules]
invalid-argument-type = "ignore"  # Pydantic model constructors
invalid-key = "ignore"            # TypedDict/Pydantic interop
invalid-parameter-default = "ignore"  # Field defaults

CI integration

Current mypy CI patterns

Common patterns found in production projects:

Pattern 1: Dedicated mypy workflow

name: Mypy
on: [push, pull_request]
jobs:
  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -e .[dev]
      - run: mypy src/

Pattern 2: Matrix across Python versions

strategy:
  matrix:
    python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
  - run: mypy google/genai/

Migrating to ty

Replace mypy with ty:

name: Type Check
on: [push, pull_request]
jobs:
  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -e .[dev]
      - run: pip install uv
      # Use uvx to run ty without installation
      - run: uvx ty check src/ --python .venv
      # Or, if ty is a dev dependency:
      # - run: uv run ty check src/

With GitHub Actions annotations:

- run: uvx ty check src/ --output-format github

Running both type checkers during transition

During migration, run both checkers:

jobs:
  mypy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -e .[dev] mypy
      - run: mypy src/

  ty:
    runs-on: ubuntu-latest
    continue-on-error: true  # Don't fail build yet
    steps:
      - uses: actions/checkout@v4
      - run: pip install -e .[dev]
      - run: uvx ty check src/ --python .venv || true

Common migration issues

Issue 1: ty checks untyped code by default

Symptom: Massive increase in errors for unannotated codebases.

Why it happens: mypy skips function bodies without type annotations by default. ty checks everything.

Example (NetworkX: 580 files, ~192k LOC):

  • mypy: 0 errors
  • ty: ~1,200 errors

Solution for unannotated code:

[tool.ty.rules]
# Ignore errors that arise from missing type info
unresolved-attribute = "ignore"
possibly-missing-attribute = "ignore"
invalid-assignment = "ignore"

Or exclude unannotated modules (see file exclusions):

[tool.ty.src]
exclude = ["legacy/**", "untyped/**"]

Issue 2: Missing import resolution

Symptom: unresolved-import errors for installed packages.

Why it happens: ty needs to know where the Python environment is.

Solution:

[tool.ty.environment]
python = ".venv"

Or via CLI:

uvx ty check . --python .venv

For optional dependencies:

[tool.ty.rules]
unresolved-import = "ignore"

Issue 3: Different type narrowing behavior

Symptom: Errors like “Attribute may be missing” after type guards.

Example (from IPython):

if _is_sizable(completions):
    return len(completions) != 0  # ty: Expected Sized, found Iterator

Why it happens: ty and mypy have different type narrowing algorithms.

Solution: Usually requires code changes or ignoring specific rules:

[tool.ty.rules]
possibly-missing-attribute = "ignore"

Or inline comments:

return len(completions) != 0  # ty: ignore[invalid-argument-type]

Issue 4: Pydantic model errors

Symptom: Many errors related to Pydantic BaseModel, Field, TypedDict.

Example (from python-genai):

options: RequestOptions = {}  # ty error: dict not assignable to RequestOptions

Why it happens: No Pydantic plugin support in ty.

Solution: Extensive rule ignoring:

[tool.ty.rules]
invalid-argument-type = "ignore"
invalid-key = "ignore"
invalid-parameter-default = "ignore"

Issue 5: No equivalent for ignore_errors = true

Symptom: Modules with ignore_errors = true in mypy still report errors.

Solution: Use overrides to ignore all rules for those files:

[[tool.ty.overrides]]
include = ["src/legacy/**"]
rules = {
  unresolved-attribute = "ignore",
  invalid-assignment = "ignore",
  invalid-argument-type = "ignore"
  # ... add more as needed
}

Or exclude them entirely:

[tool.ty.src]
exclude = ["src/legacy/**"]

Error code mapping reference

This table maps common mypy error codes to their ty rule equivalents.

mypy Codety RuleDescription
import-not-foundunresolved-importModule not found
attr-definedunresolved-attributeUnknown attribute
arg-typeinvalid-argument-typeWrong argument type
assignmentinvalid-assignmentWrong assignment type
return-typeinvalid-return-typeWrong return type
call-arginvalid-argument-typeWrong call arguments
indexnon-subscriptableInvalid subscript
union-attrpossibly-missing-attributeAttribute may be missing
overrideinvalid-method-overrideInvalid method override
redundant-castredundant-castUnnecessary cast
unused-ignoreunused-ignore-commentUnused type: ignore / ty: ignore
no-redefconflicting-declarationsName redefinition
valid-typeinvalid-type-formInvalid type expression

Step-by-step migration checklist

Phase 1: Assessment

  • Identify the current mypy configuration

    • Locate config in pyproject.toml, mypy.ini, or setup.cfg
    • Note all options, especially strict, plugins, and per-module overrides
  • Check for plugin dependencies

    • Pydantic, Django, SQLAlchemy plugins are not supported
    • If plugins are critical, consider delaying migration
  • Run baseline mypy

    • Document current error count and types
    • Ensure mypy passes in CI before starting migration

Phase 2: Initial ty setup

  • Create ty configuration file

    • Create ty.toml or add [tool.ty] to pyproject.toml
    • Start minimal:
      [environment]
      python = ".venv"
      
      [rules]
      unresolved-import = "ignore"
  • Run first ty check

    uvx ty check your_package/
    • Document error count and types
    • Compare to mypy baseline
  • Add exclusions (see file exclusions)

    • Match mypy’s exclude patterns
    • Add test directories if not already excluded
    [src]
    exclude = ["**/tests/**", "**/docs/**"]

Phase 3: Configuration tuning

  • Map mypy options to ty

    • Use the configuration mapping table above
    • Accept that some options don’t have equivalents
  • Handle per-module overrides

    • Translate [[tool.mypy.overrides]] to [[tool.ty.overrides]]
    • Use file patterns instead of module names
  • Address plugin functionality

    • For Pydantic projects, ignore relevant rules
    • Document which rules are ignored and why

Phase 4: CI integration

  • Add ty to CI (non-blocking)

    - run: uvx ty check src/ || true
    • Collect data on ty findings
  • Run both checkers in parallel

    • Keep mypy as the source of truth
    • Use ty for additional insights
  • Evaluate findings

    • Are ty’s additional findings valuable?
    • Are false positives acceptable?

Phase 5: Transition decision

  • Compare checker behavior

    • Review unique findings from each checker
    • Assess false positive rates
  • Decide on transition strategy

    • Option A: Replace mypy with ty (if ty meets needs)
    • Option B: Keep both checkers (different purposes)
    • Option C: Stay with mypy (if plugin dependencies are blocking)
  • Update documentation

    • Document which checker is authoritative
    • Update contributing guidelines

Phase 6: Finalization (if replacing mypy)

  • Make ty blocking in CI

    - run: uvx ty check src/
  • Remove mypy configuration

    • Or keep for fallback
  • Update developer tooling

Conclusion

Migration from mypy to ty is feasible for many projects but comes with important caveats.

Good candidates for migration

  • Projects without mypy plugins
  • Projects wanting faster type checking
  • Projects already using Astral tools (uv, Ruff)
  • Projects with simple mypy configuration

Projects that should wait

  • Projects depending on Pydantic, Django, or SQLAlchemy mypy plugins
  • Projects with very complex per-module mypy configurations
  • Projects requiring mature, production-tested tooling

Key takeaways

  1. ty is stricter by default - expect more errors on unannotated code
  2. Plugin support is missing - biggest blocker for some projects
  3. Configuration doesn’t map 1:1 - expect translation work
  4. The speed improvement is real - ty is significantly faster
  5. Consider running both - during transition or permanently

Related resources

Last updated on

Please submit corrections and feedback...