How Monty Type Checking Works with Python Type Hints: A Complete Guide
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. 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. The process works as follows:
- Virtual File System Creation: The checker creates a temporary in-memory file system containing the source code and optional stub declarations.
- 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. - Ruff Analysis: The
rufflibrary'scheck_typesroutine analyzes the file and returns diagnostic information. - Error Adjustment: Line numbers in diagnostics are adjusted to account for injected stub code, ensuring error locations match the original user code.
- Exception Raising: If errors exist, Monty raises
MontyTypingError, a subclass ofMontyError. The exception provides a.display(format, color)method supporting formats likefull,concise, andjson.
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) |
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) |
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) |
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:
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:
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:
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:
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
typingmodule (crates/monty/src/modules/typing.rs) to support standard imports without triggering analysis. - Type checking is invoked via the
type_check=Trueconstructor flag or thetype_check()method, both defined inpydantic_monty/_monty.pyi. - The implementation in
monty_type_checking/src/type_check.rscreates a virtual file system, injects optional stubs, runsruff::check_types(), and adjusts line numbers to match original source. - Errors are reported through
MontyTypingError, which offers customizable display formats includingfull,concise, andjson. - Stub declarations via
type_check_stubsallow 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →