Decorator, CLI & IPython extension for LLM-powered exception handling.
Project description
F. Incantatem
Python is honest, but not generous.
When code breaks, it hands you an exception type and a message. You see where it failed, but the reasoning that led there is scattered across stack frames, source files, and the half-forgotten state sitting in locals. The error itself is real. The understanding is not.
F. Incantatem reconstructs that understanding. It reads the exception in full context, including the stack trace, the relevant source, the local state at the point of rupture, and produces an explanation of why it happened, what caused it, and what to do about it. The difference between knowing a crash occurred and knowing why it occurred is the difference between debugging and guessing.
It integrates without ceremony: decorator, CLI, or IPython extension. Add it where you want. Leave the rest alone.
Features
- Contextual Analysis — Examines stack traces, source code, and local variables to produce reasoned explanations
- Multiple Integration Points — Use as a decorator, command-line tool, or IPython extension
- Inference Flexibility — OpenRouter, OpenAI, or any OpenAI-compatible API; support for local inference via Ollama or vLLM
- Rich Output — Optional Markdown formatting and interactive chat for follow-up questions
- Zero Core Dependencies — The library itself is lightweight; optional features remain modular
- Security Conscious — Optional cautious mode to automatically redact secrets and PII before transmission
Table of Contents
- Features
- Table of Contents
- Installation
- Usage
- Examples
- Key Features
- Configuration
- Data Risks
- Requirements
- License
- Acknowledgements
- Status
Installation
The library installs with zero dependencies:
pip install fincantatem
Or with uv:
uv add fincantatem
Optional Features
For rich Markdown output in your terminal:
pip install "fincantatem[pretty]"
For automatic redaction of secrets and PII:
pip install "fincantatem[cautious]"
python -m spacy download en_core_web_lg
Usage
As a Decorator
Wrap any function to get AI-powered error analysis when exceptions occur:
from fincantatem import finite
@finite
def process_data(value: int):
return 100 / value
process_data(0) # Exception triggers analysis
Decorator Options
@finite(
preset="openrouter", # "openrouter" or "openai" (default: "openrouter")
snippets=True, # Send code snippets or full source (default: True)
chat=False, # Enable interactive chat after analysis (default: False)
cautious=False, # Redact secrets and PII (default: False)
)
def my_function():
pass
From the Command Line
Run any Python script with automatic error analysis:
python -m fincantatem script.py
Options
python -m fincantatem script.py \
--preset openrouter \
--snippets true \
--cautious false
-p, --preset— Inference preset to use (default:openrouter)-s, --snippets— Show code snippets instead of full source (default:true)-c, --cautious— Enable secret and PII redaction (default:false)
In Jupyter/IPython
Load as an extension to get automatic error analysis on cell failures:
%load_ext fincantatem
Every exception can be analyzed automatically without (onerous) modification to your code.
Examples
1. The "Silent API Change"
Scenario: You are consuming a third-party API. The code has worked for months. Suddenly, it breaks with a KeyError, but the API status code is still 200.
@finite
def sync_user_data(user_id: str):
response = requests.get(f"https://api.vendor.com/users/{user_id}")
payload = response.json()
# CRASH: KeyError: 'active_subscription'
if payload["data"]["active_subscription"]["status"] == "active":
update_local_db(user_id)
Why this is hard: Standard debugging requires you to add print statements to dump payload, rerun the script, and inspect the JSON structure. But here's the insidious part: you might not be able to reproduce it because the API behavior depends on the specific user_id, time of day, or rate limit state.
F. Incantatem Insight: It captures the actual response body that caused the crash—no reproduction needed.
The Explanation:
"The code expects
payload['data']['active_subscription']['status']. However, the actualpayloadcaptured during the crash contains{'error': 'RateLimitExceeded', 'retry_after': 60, 'request_id': 'req_8x2k9'}.The API vendor is returning application-level errors with HTTP 200 status codes—a common but poor API design pattern. Your code assumes
response.status_code == 200means success, but you need defensive key checking or schema validation. Therequest_idin the payload suggests you can report this specific failure to their support."
2. The "Mutable Default Argument" Trap
Scenario: A classic Python foot-gun that manifests as "data bleeding" between requests in a long-running process.
@finite
def add_audit_log(event, _buffer=[]): # The bug is here
_buffer.append(event)
if len(_buffer) >= 3:
flush_to_disk(_buffer)
return True
return False
# Imagine this running in a server context
add_audit_log("login")
add_audit_log("logout")
add_audit_log("view_page") # Flushes
add_audit_log("login_admin") # CRASH or security leak
Why this is hard: This is a Heisenbug—it disappears when you try to debug it with unit tests because each test starts fresh. The failure only emerges in production after the process has been running long enough. You might see symptoms like: duplicate database entries, security logs showing events from different users mixed together, or memory growing unbounded.
F. Incantatem Insight: It shows you the identity of the buffer object and its complete contents at crash time.
The Explanation:
"The local variable _buffer contains ['login', 'logout', 'view_page', 'login_admin']—4 items when you expected 1.
This is the mutable default argument trap. Your function signature is def add_audit_log(event, _buffer=[]). The list contains events from previous function calls because Python evaluates default arguments once at definition time, then reuses the same object. You can verify this yourself by adding print(id(_buffer)) at the start of the function—you'll see the same memory address across calls. In this case, the presence of 4 accumulated items instead of 1 is the smoking gun."
3. The "Unicode Normalization Bomb"
Scenario: A web application that's been running fine suddenly starts having bizarre duplicate key violations in your database, but only for certain users with international names.
@finite
def create_user(username: str, email: str):
# CRASH: IntegrityError - duplicate key value violates unique constraint "users_username_key"
# But you just checked and the username doesn't exist!
user = User.objects.create(username=username, email=email)
return user
Why this is hard: Looking at your database, you see one entry for "José" and you're trying to insert "José"—they look identical. Your uniqueness check passes because Python's == operator says they're different. The bug appears random because it only affects certain character combinations.
F. Incantatem Insight: It shows you the actual byte representation of the username variable.
The Explanation:
"The variable
usernamecontains the string'José'which appears as'Jos\\u00e9'(5 characters, with a single codepoint é). However, your database already contains a user with username'Jose\\u0301'(5 characters, with e + combining acute accent).These are two different Unicode representations of the same visual character—NFC (composed) vs. NFD (decomposed) normalization. Your form input is receiving NFD-normalized text (common on macOS), while your database contains NFC (common on Windows/Linux). Python's
User.objects.filter(username=username)doesn't find a match because you're comparing different byte sequences. The fix: Applyunicodedata.normalize('NFC', username)before any database operation. This affects approximately 2,184 Unicode characters with multiple representations."
Optional Features
For rich Markdown output in your terminal:
pip install "fincantatem[pretty]"
For automatic redaction of secrets and PII:
pip install "fincantatem[cautious]"
Or with uv:
uv add "fincantatem[pretty]"
uv add "fincantatem[cautious]"
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
FI_PRESET |
openrouter |
Preset name or custom identifier |
FI_API_KEY |
None | API key for the inference service |
FI_URL |
https://openrouter.ai/api/v1/chat/completions |
OpenAI-compatible API endpoint |
FI_MODEL |
google/gemini-2.5-flash |
Model identifier (overrides preset) |
Presets
OpenRouter (default)
FI_API_KEY=sk_your_key python -m fincantatem script.py
OpenAI
FI_PRESET=openai FI_API_KEY=sk_your_key python -m fincantatem script.py
Local Inference (e.g., Ollama)
FI_PRESET=local \
FI_URL=http://localhost:11434/v1/chat/completions \
FI_MODEL=llama2 \
python -m fincantatem script.py
Data Risks
By default, the following is transmitted to the inference API:
- Source code and file paths
- Full stack trace
- Exception messages
- Local variables in the call stack
- Any values or secrets present in your code
This may be undesirable in environments handling sensitive information. F. Incantatem offers several mitigation strategies.
Cautious Mode
When enabled, cautious mode attempts to automatically redact secrets and personally identifiable information from source code and local variables before transmission. Enable it with:
@finite(cautious=True)
def my_function():
pass
Or via the CLI:
python -m fincantatem script.py --cautious true
How It Works
Cautious mode employs two complementary detection mechanisms:
1. Secret Detection — Uses pattern matching and entropy analysis (via detect-secrets) to identify API keys, tokens, private keys, and other cryptographic material. Detected secrets are replaced with a deterministic placeholder derived from their SHA-256 hash, allowing correlation without exposing the actual value.
2. PII Detection — Uses Presidio, a Microsoft library trained to recognize personally identifiable information including names, email addresses, phone numbers, credit card numbers, and more. Detected entities are anonymized in-place.
Important Caveats
Cautious mode is a best-effort mechanism, not a guarantee. Users should understand its limitations:
-
Incompleteness — Detection patterns may miss secrets in unusual formats or custom PII that doesn't match known patterns. A secret hidden in a comment, a name disguised as a variable, or a partially exposed credential may slip through.
-
False Positives — Legitimate values (e.g., a string containing "password") may be incorrectly flagged as sensitive. This can result in unnecessary redaction of benign data.
-
Heuristic Nature — Both
detect-secretsand Presidio rely on heuristics rather than definitive signatures. They are probabilistic tools designed for high recall at the cost of some precision. -
No Guarantee — Cautious mode reduces risk but does not eliminate it. Never enable cautious mode as a substitute for data governance. If transmitting source code to any external service is unacceptable in your environment, cautious mode is insufficient—use local inference instead.
-
Performance Cost — Secret and PII scanning adds latency to exception processing, particularly on large codebases or with complex stack traces.
Recommendation: Cautious mode is suitable for development workflows where occasional false redactions are tolerable and where the code does not contain highly sensitive information by design. For production systems handling financial data, credentials, or protected health information, consider local inference or private endpoints instead.
Alternative Mitigation Strategies
- Local Inference — Run a model locally via Ollama or vLLM, keeping all data on your machine
- Private API — Route requests through a trusted organizational endpoint with appropriate access controls and data handling policies
- Selective Decoration — Use
@finiteonly on non-sensitive functions, or on functions unlikely to encounter data exposure
Key Features
Basic Debugging
from fincantatem import finite
@finite
def divide(a: int, b: int):
return a / b
divide(10, 0) # Receives AI analysis of ZeroDivisionError
Interactive Chat
@finite(chat=True)
def load_config(path: str):
with open(path) as f:
return json.loads(f.read())
# After analysis, continue conversing with the AI
load_config("missing.json")
Cautious Mode
@finite(cautious=True)
def query_api(token: str):
response = requests.get("https://api.example.com", headers={
"Authorization": f"Bearer {token}"
})
return response.json()
# Token will be redacted before transmission
query_api("secret_key_123")
Requirements
- Python 3.10+
- Optional:
richfor formatted output - Optional:
presidio-analyzer,presidio-anonymizer,spacy,detect-secrets,bip-utilsfor cautious mode
License
Apache License 2.0
Acknowledgements
The pipe utility is lifted from the toolz library.
Status
F. Incantatem is actively maintained and used in production environments.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fincantatem-0.2.0.tar.gz.
File metadata
- Download URL: fincantatem-0.2.0.tar.gz
- Upload date:
- Size: 273.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9bd97bb6519398e0a5c00cc06288226a33dd81e1de37cea28fad961426a4ceb
|
|
| MD5 |
9a451eee0cabe42510dd1acc703de680
|
|
| BLAKE2b-256 |
c037ee92786d29e08047f9101172ebe9d3303e65878c511269c51a62699e259e
|
File details
Details for the file fincantatem-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fincantatem-0.2.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5e5ce539244c203037303eac2c7e301d42753b8dbcd1123077a0d81c189048c
|
|
| MD5 |
6064b85fc5e295dbf08d8b636314e73b
|
|
| BLAKE2b-256 |
3ebf7586f372ce13eca735fd18fafb49917f88bb1b409080e21e944f6b9ebc92
|