Enable static type checking for your Monty code to catch errors before execution
Monty includes ty, a modern Python type checker from Astral, built directly into the binary. This allows you to catch type errors before code execution.
Call type_check() on a Monty instance to verify types:
import pydantic_monty# Code with a type errorm = pydantic_monty.Monty('"hello" + 1')try: m.type_check()except pydantic_monty.MontyTypingError as e: print(e)
Output:
error[unsupported-operator]: Unsupported `+` operation --> main.py:1:1 |1 | "hello" + 1 | -------^^^- | | | | | Has type `Literal[1]` | Has type `Literal["hello"]` |info: rule `unsupported-operator` is enabled by default
Pass type_check=True to enable type checking when creating a Monty instance:
Python
JavaScript
import pydantic_montytry: m = pydantic_monty.Monty( '"hello" + 1', type_check=True # Type check during construction )except pydantic_monty.MontyTypingError as e: print("Type error during construction:", e)
import { Monty, MontyTypingError } from '@pydantic/monty'try { const m = new Monty('"hello" + 1', { typeCheck: true // Type check during construction })} catch (error) { if (error instanceof MontyTypingError) { console.log('Type error:', error.displayDiagnostics('concise')) }}
Type checking is disabled by default. This allows you to run code that may not type-check perfectly but works at runtime.
code = "result = x + 1"# Without stubs, 'x' is undefined and fails type checking# With stubs, we declare x's typem = pydantic_monty.Monty( code, inputs=['x'], type_check=True, type_check_stubs='x = 0' # Declare x as an int)
code = """def foo() -> int: return "not an int""""m = pydantic_monty.Monty(code)try: m.type_check()except pydantic_monty.MontyTypingError as e: print(e)
Output:
error[invalid-return-type]: Return type does not match returned value --> main.py:2:14 |2 | def foo() -> int: | --- Expected `int` because of return type3 | return "not an int" | ^^^^^^^^^^^^ expected `int`, found `Literal["not an int"]` |info: rule `invalid-return-type` is enabled by default
Type checking catches references to undefined variables:
m = pydantic_monty.Monty('print(undefined_var)')try: m.type_check()except pydantic_monty.MontyTypingError as e: print(e)
Output:
error[unresolved-reference]: Name `undefined_var` used when not defined --> main.py:1:7 |1 | print(undefined_var) | ^^^^^^^^^^^^^ |info: rule `unresolved-reference` is enabled by default
import { Monty, MontyTypingError } from '@pydantic/monty'const m = new Monty('"hello" + 1')try { m.typeCheck()} catch (error) { if (error instanceof MontyTypingError) { // Full format (default) console.log(error.displayDiagnostics()) // Concise format console.log(error.displayDiagnostics('concise')) }}
import pydantic_montytry: m = pydantic_monty.Monty('"hello" + 1', type_check=True)except pydantic_monty.MontyTypingError as e: # Specific type error handling print("Type error:", e.display('concise'))except pydantic_monty.MontyError as e: # General Monty error print("Monty error:", e)
You can provide prefix code when calling type_check() manually:
m = pydantic_monty.Monty('result = x + 1')# Without prefix, x is undefined and failstry: m.type_check()except pydantic_monty.MontyTypingError: print("Failed without prefix")# With prefix declaring x, it passesm.type_check(prefix_code='x = 0')print("Passed with prefix")
Use prefix_code parameter for one-off type checks. For construction-time type checking, use type_check_stubs instead.
Use type_check=True during construction to catch errors before execution:
m = pydantic_monty.Monty(llm_generated_code, type_check=True)
4
Handle type errors gracefully
Catch MontyTypingError and provide feedback to your LLM or user:
try: m = pydantic_monty.Monty(code, type_check=True)except pydantic_monty.MontyTypingError as e: # Send error back to LLM for correction print(f"Type error: {e.display('concise')}")
Type checking does not guarantee runtime correctness. It catches many common errors but cannot prevent all runtime issues (e.g., division by zero, logic errors).