OCR Result Caching

doc2mark can cache OCR results so that repeated processing of the same images skips the OCR provider call entirely. Caching is opt-in: pass an ocr_cache instance to load(), UnifiedDocumentLoader, or any of the batch helpers.

Two backends are included:

  • MemoryOCRCache – thread-safe, in-process LRU cache with TTL.

  • RedisOCRCache – persistent cache backed by Redis (requires pip install doc2mark[redis]).

A NoOpOCRCache is also available for testing; it accepts writes but always returns a miss.

Quick start

from doc2mark import load, MemoryOCRCache

cache = MemoryOCRCache(
    ttl_seconds=3600,          # time-to-live per entry
    max_age_seconds=43200,     # hard upper bound on entry lifetime
    max_entries=1024,          # LRU eviction limit
    max_refreshes=10,          # how many times a hit extends the TTL
)

result = load(
    "scan.pdf",
    extract_images=True,
    ocr_images=True,
    ocr_cache=cache,
)

Factory helper

create_ocr_cache() creates a backend by name and handles fallback when Redis is unavailable:

from doc2mark import create_ocr_cache

# In-memory cache
cache = create_ocr_cache("memory", ttl_seconds=7200)

# Redis cache (falls back to memory if Redis is unreachable)
cache = create_ocr_cache(
    "redis",
    redis_url="redis://localhost:6379/0",
    ttl_seconds=3600,
    max_age_seconds=43200,
    max_refreshes=10,
    key_prefix="doc2mark:ocr:ocr-cache-v3",  # default prefix
    fallback="memory",                        # "memory", "none", or "raise"
)

# Disable caching explicitly
cache = create_ocr_cache("none")

Using with UnifiedDocumentLoader

from doc2mark import UnifiedDocumentLoader, create_ocr_cache

cache = create_ocr_cache("redis", redis_url="redis://localhost:6379/0")

loader = UnifiedDocumentLoader(
    ocr_provider="openai",
    ocr_cache=cache,
)
result = loader.load("scan.pdf", extract_images=True, ocr_images=True)

The same ocr_cache parameter is accepted by the convenience functions load(), document_to_markdown(), batch_convert_to_markdown(), and batch_process_documents().

MemoryOCRCache

from doc2mark import MemoryOCRCache

cache = MemoryOCRCache(
    ttl_seconds=3600,
    max_age_seconds=43200,
    max_entries=1024,
    max_refreshes=10,
)

Constructor parameters:

ttl_seconds (float, default 3600)

Time-to-live for each entry. A cache hit extends the expiry by this amount (up to max_refreshes times).

max_age_seconds (float or None, default 43200)

Absolute maximum lifetime measured from creation. Set to None for no hard limit.

max_entries (int, default 1024)

Maximum number of cached entries. The least-recently-used entry is evicted when the limit is exceeded.

max_refreshes (int or None, default 10)

Maximum number of times a hit can extend the TTL. Set to None for unlimited refreshes.

RedisOCRCache

Requires the redis extra:

pip install doc2mark[redis]
from doc2mark import RedisOCRCache

cache = RedisOCRCache(
    redis_url="redis://localhost:6379/0",
    ttl_seconds=3600,
    max_age_seconds=43200,
    max_refreshes=10,
    key_prefix="doc2mark:ocr:ocr-cache-v3",
)

The constructor verifies the connection with ping() and raises on failure. Redis handles expiry natively via EX on each key, so cleanup() is a no-op.

Constructor parameters:

redis_url (str, required)

Redis connection URL, e.g. redis://localhost:6379/0.

ttl_seconds (float, default 3600)

Same semantics as MemoryOCRCache.

max_age_seconds (float or None, default 43200)

Same semantics as MemoryOCRCache.

max_refreshes (int or None, default 10)

Same semantics as MemoryOCRCache.

key_prefix (str, default "doc2mark:ocr:ocr-cache-v3")

Prefix for all Redis keys. Useful for namespacing when multiple applications share a Redis instance.

Cache statistics

All backends expose a stats() method:

print(cache.stats())
# {'hits': 12, 'misses': 3, 'sets': 3, 'backend': 'memory', ...}

Counters include hits, misses, sets, refreshes, refresh_skipped, expired, evictions, deletes, and errors.