# How Monty Type Checking Works with Python Type Hints: A Complete Guide

> Discover how Monty utilizes Python type hints for static type checking with its Ruff-based engine. Catch errors before execution with detailed diagnostics.

- Repository: [Pydantic/monty](https://github.com/pydantic/monty)
- Tags: deep-dive
- Published: 2026-02-16

---

**Monty performs optional static type checking using a Ruff-based engine that validates Python type hints before execution, raising `MontyTypingError` with detailed diagnostics when violations are detected.**

Monty is a Python interpreter that can optionally enforce type safety through built-in static analysis. By integrating a Ruff-based type checker, Monty validates Python type hints against your code before runtime, catching type errors early while maintaining compatibility with standard `typing` module constructs.

## How Monty Validates Python Type Hints

### Runtime Typing Module Support

Monty ships with a minimal `typing` module implementation located in [`crates/monty/src/modules/typing.rs`](https://github.com/pydantic/monty/blob/main/crates/monty/src/modules/typing.rs). This module provides standard marker objects like `Any`, `List`, `Optional`, and `TYPE_CHECKING` (always set to `False`) so that `from typing import List` works at runtime. These markers serve as runtime placeholders only and do not trigger analysis themselves.

### Explicit Type Checking Invocation

Type checking occurs only when explicitly requested. When creating a `Monty` instance, you can set `type_check=True` in the constructor or call the `type_check()` method later. This invokes the Ruff-based type checker from the `monty_type_checking` crate. The public API is defined in `pydantic_monty/_monty.pyi`, which exposes the `type_check` method and `MontyTypingError` exception.

### Type Checking Implementation Details

The core implementation resides in [`monty_type_checking/src/type_check.rs`](https://github.com/pydantic/monty/blob/main/monty_type_checking/src/type_check.rs). The process works as follows:

1. **Virtual File System Creation**: The checker creates a temporary in-memory file system containing the source code and optional stub declarations.
2. **Stub Injection**: If provided, stub code is injected as a leading `from <stub> import *` statement. This allows declaring variable types or external function signatures without affecting runtime execution.
3. **Ruff Analysis**: The `ruff` library's `check_types` routine analyzes the file and returns diagnostic information.
4. **Error Adjustment**: Line numbers in diagnostics are adjusted to account for injected stub code, ensuring error locations match the original user code.
5. **Exception Raising**: If errors exist, Monty raises `MontyTypingError`, a subclass of `MontyError`. The exception provides a `.display(format, color)` method supporting formats like `full`, `concise`, and `json`.

## Architecture and Data Flow

Monty's type checking system consists of five integrated components:

| Step | Component | Responsibility |
|------|-----------|----------------|
| **A** | `typing` module ([`crates/monty/src/modules/typing.rs`](https://github.com/pydantic/monty/blob/main/crates/monty/src/modules/typing.rs)) | Supplies runtime marker objects (`TYPE_CHECKING = False`) |
| **B** | Python wrapper (`pydantic_monty/_monty.pyi`) | Exposes `Monty.__new__`, `type_check()`, and `MontyTypingError` |
| **C** | `monty_type_checking` crate ([`src/type_check.rs`](https://github.com/pydantic/monty/blob/main/src/type_check.rs)) | Creates virtual file system, runs Ruff checker, adjusts diagnostics |
| **D** | `MontyTypingError` (`_monty.pyi`) | Wraps diagnostics with customizable display formatting |
| **E** | Test suite ([`crates/monty-python/tests/test_type_check.py`](https://github.com/pydantic/monty/blob/main/crates/monty-python/tests/test_type_check.py)) | Validates constructor flags, stub handling, and output formats |

The data flow proceeds as follows:

```

User code → Monty constructor (type_check=True)
                ↓
   monty_type_checking::type_check()
                ↓
        ruff::check_types()
                ↓
   Diagnostics → MontyTypingError (if errors) → Python exception

```

## Practical Usage Examples

### Enabling Type Checking at Construction

To validate code immediately upon loading, pass `type_check=True` when creating a `Monty` instance:

```python
import pydantic_monty

# This raises MontyTypingError because "hello" + 1 is a type error

try:
    pydantic_monty.Monty('"hello" + 1', type_check=True)
except pydantic_monty.MontyTypingError as exc:
    print(exc.display(format='concise'))

```

The constructor validates the code before any execution occurs, ensuring type safety from the start.

### Running Type Checks After Construction

You can also defer type checking until after instantiation using the `type_check()` method:

```python
m = pydantic_monty.Monty('x + 1')
m.type_check()  # Raises MontyTypingError if type violations exist

```

This method is defined in `pydantic_monty/_monty.pyi` and invokes the full Ruff-based analysis pipeline.

### Providing Stub Declarations

For code that relies on external variables or functions, use the `type_check_stubs` parameter to inject type declarations without affecting runtime:

```python
stubs = """
x: int
def fetch(url: str) -> str: ...
"""

m = pydantic_monty.Monty(
    'result = fetch(url) + str(x)',
    type_check=True,
    type_check_stubs=stubs,
)

```

The stub code is injected as a virtual `from <stub> import *` statement and automatically stripped from diagnostic line numbers, ensuring error locations match your original source.

### Customizing Diagnostic Output

`MontyTypingError` provides flexible formatting options for diagnostics:

```python
try:
    m.type_check()
except pydantic_monty.MontyTypingError as exc:
    # Available formats: 'full', 'concise', 'json', etc.

    print(exc.display(format='json', color=True))

```

The `display` method supports multiple output formats and colorization, making it easy to integrate Monty's type checking into various development workflows.

## Summary

- Monty provides **optional static type checking** for Python code using a Ruff-based engine that validates type hints before execution.
- The system relies on a minimal runtime `typing` module ([`crates/monty/src/modules/typing.rs`](https://github.com/pydantic/monty/blob/main/crates/monty/src/modules/typing.rs)) to support standard imports without triggering analysis.
- Type checking is invoked via the `type_check=True` constructor flag or the `type_check()` method, both defined in `pydantic_monty/_monty.pyi`.
- The implementation in [`monty_type_checking/src/type_check.rs`](https://github.com/pydantic/monty/blob/main/monty_type_checking/src/type_check.rs) creates a virtual file system, injects optional stubs, runs `ruff::check_types()`, and adjusts line numbers to match original source.
- Errors are reported through `MontyTypingError`, which offers customizable display formats including `full`, `concise`, and `json`.
- Stub declarations via `type_check_stubs` allow external type definitions without affecting runtime execution.

## Frequently Asked Questions

### How does Monty handle the `typing` module at runtime?

Monty includes a minimal implementation of the `typing` module in [`crates/monty/src/modules/typing.rs`](https://github.com/pydantic/monty/blob/main/crates/monty/src/modules/typing.rs) that provides standard marker objects like `Any`, `List`, and `Optional`. The `TYPE_CHECKING` constant is always set to `False`. These markers serve as runtime placeholders only and do not perform any static analysis themselves.

### Can I use type checking without executing the code?

Yes. When you pass `type_check=True` to the `Monty` constructor, the type checker runs before any code execution begins. If type errors are detected, Monty raises `MontyTypingError` immediately, preventing the program from running. This ensures that only type-correct programs execute when type checking is enabled.

### What formats are available for type error diagnostics?

`MontyTypingError` supports multiple output formats through its `.display()` method. Available formats include `full` for complete diagnostic information, `concise` for abbreviated output, and `json` for machine-readable structured data. You can also enable color output by passing `color=True` to the display method.

### How do stub declarations work in Monty's type checker?

Stub declarations allow you to provide type signatures for external variables or functions without affecting runtime execution. You pass stub code via the `type_check_stubs` parameter when creating a `Monty` instance. The type checker injects this code as a virtual import and automatically adjusts diagnostic line numbers to match your original source code, excluding the injected stub lines.