Skip to main content

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

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 actual payload captured 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 == 200 means success, but you need defensive key checking or schema validation. The request_id in 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 username contains 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: Apply unicodedata.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-secrets and 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

  1. Local Inference — Run a model locally via Ollama or vLLM, keeping all data on your machine
  2. Private API — Route requests through a trusted organizational endpoint with appropriate access controls and data handling policies
  3. Selective Decoration — Use @finite only 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: rich for formatted output
  • Optional: presidio-analyzer, presidio-anonymizer, spacy, detect-secrets, bip-utils for 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

fincantatem-0.2.2.tar.gz (274.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fincantatem-0.2.2-py3-none-any.whl (32.4 kB view details)

Uploaded Python 3

File details

Details for the file fincantatem-0.2.2.tar.gz.

File metadata

  • Download URL: fincantatem-0.2.2.tar.gz
  • Upload date:
  • Size: 274.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fincantatem-0.2.2.tar.gz
Algorithm Hash digest
SHA256 41e4641f05d81ec01d7cc4e6b6c7d837a9034fb5691edd227ec9ed94c6d5c268
MD5 0ce1ac81a9209aca88a52e1cc7571ea2
BLAKE2b-256 6e77469ee607b29e346c7fae1872fb815caeb498406502cf8ab89b4c1eb17489

See more details on using hashes here.

Provenance

The following attestation bundles were made for fincantatem-0.2.2.tar.gz:

Publisher: publish.yml on aguilar-ai/fincantatem

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fincantatem-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: fincantatem-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 32.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fincantatem-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d896edda9978a13831df4bd5198dc7c5cd983c97ef3023b76dbf9edb65bbfb94
MD5 866089a351ca59f68aa0d444679e1e9d
BLAKE2b-256 12b0614f7d3a917101139bbe74765c26a23dea5ffcbb9f6766af40c8820373fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for fincantatem-0.2.2-py3-none-any.whl:

Publisher: publish.yml on aguilar-ai/fincantatem

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page