OCR Caching
doc2mark provides a request-scoped OCR result cache that avoids redundant
(and expensive) calls to vision-model providers when the same image is
processed more than once. Cached values use the cache v4 schema,
which stores the full structured OCRPage document
alongside the legacy plain-text representation, so downstream consumers
always receive complete results regardless of whether the value was served
from cache or computed live.
Caching is wired into the processing pipeline through the ocr_cache
parameter accepted by load() and
UnifiedDocumentLoader. You can create a cache with the
create_ocr_cache() factory, or instantiate a backend class
directly.
Cache backends
OCRCache (abstract base)
All cache backends implement the OCRCache interface.
from doc2mark import OCRCache
Abstract methods
get(key)Return the cached
OCRResultfor key, orNoneon a miss.set(key, result, ttl_seconds=None)Store an
OCRResult. When ttl_seconds isNonethe backend’s default TTL applies.cleanup()Remove expired entries. Returns the number of entries removed.
clear()Drop every entry from the cache.
stats()Return a
dictof runtime counters (hits, misses, sets, evictions, etc.) and configuration metadata such as the backend name.
- class doc2mark.OCRCache[source]
Bases:
ABCInterface for OCR result caches.
- abstractmethod cleanup()[source]
Remove expired entries and return the number removed.
- Return type:
int
- abstractmethod get(key)[source]
Return a cached OCR result, or None on miss.
- Parameters:
key (str)
- Return type:
OCRResult | None
MemoryOCRCache
Thread-safe, in-process LRU cache with TTL, absolute max-age, and
refresh-on-access semantics. This is the default backend returned by
create_ocr_cache() when provider="memory".
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 in seconds. An entry that is accessed before expiry has its deadline extended (up to max_refreshes times).
max_age_seconds(Optional[float], default 43200)Absolute upper bound on how long an entry may live, regardless of refresh activity. Set to
Noneto disable the hard ceiling.max_entries(int, default 1024)Maximum number of entries. When exceeded the least-recently-used entry is evicted.
max_refreshes(Optional[int], default 10)Maximum number of times an entry’s TTL may be extended on access.
Noneallows unlimited refreshes.time_func(Optional[Callable[[], float]], default None)Clock function used for timestamps. Defaults to
time.time(). Mainly useful in tests.
- class doc2mark.MemoryOCRCache(ttl_seconds=3600, max_age_seconds=43200, max_entries=1024, max_refreshes=10, time_func=None)[source]
Bases:
OCRCacheThread-safe in-memory OCR cache with TTL, max-age, and refresh bounds.
- Parameters:
ttl_seconds (float)
max_age_seconds (float | None)
max_entries (int)
max_refreshes (int | None)
time_func (Callable[[], float] | None)
- get(key)[source]
Return a cached OCR result, or None on miss.
- Parameters:
key (str)
- Return type:
OCRResult | None
RedisOCRCache
Persistent, cross-process cache backed by Redis. Requires the optional
redis dependency (install with pip install doc2mark[redis]).
The constructor lazily imports redis, connects using the provided URL,
and verifies reachability with ping(). If the connection fails at
construction time and a fallback is configured via
create_ocr_cache(), the factory falls back transparently.
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-v4",
)
Constructor parameters
redis_url(str)Redis connection URL (e.g.
"redis://localhost:6379/0"). Required; an empty string raisesValueError.ttl_seconds(float, default 3600)Per-entry time-to-live in seconds, extended on access.
max_age_seconds(Optional[float], default 43200)Absolute entry lifetime cap.
Nonedisables the limit.max_refreshes(Optional[int], default 10)Maximum refresh count per entry.
key_prefix(str, default “doc2mark:ocr:ocr-cache-v4”)Prefix prepended to every Redis key. Change this to namespace multiple applications sharing the same Redis instance.
time_func(Optional[Callable[[], float]], default None)Clock override, as in
MemoryOCRCache.
Note
cleanup() is a no-op for the Redis backend because Redis manages
key expiration natively via the EX argument on SET.
- class doc2mark.RedisOCRCache(redis_url, ttl_seconds=3600, max_age_seconds=43200, max_refreshes=10, key_prefix='doc2mark:ocr:ocr-cache-v4', time_func=None)[source]
Bases:
OCRCacheRedis-backed OCR cache.
Redis is imported lazily so doc2mark remains importable without the optional redis extra. The constructor verifies the connection with ping().
- Parameters:
redis_url (str)
ttl_seconds (float)
max_age_seconds (float | None)
max_refreshes (int | None)
key_prefix (str)
time_func (Callable[[], float] | None)
- get(key)[source]
Return a cached OCR result, or None on miss.
- Parameters:
key (str)
- Return type:
OCRResult | None
NoOpOCRCache
A cache that never stores anything. Every get() returns None.
Useful in testing or when you want to satisfy a type constraint without
actually caching.
from doc2mark import NoOpOCRCache
cache = NoOpOCRCache() # always misses
- class doc2mark.NoOpOCRCache[source]
Bases:
OCRCacheCache implementation that always misses.
- get(key)[source]
Return a cached OCR result, or None on miss.
- Parameters:
key (str)
- Return type:
OCRResult | None
CachedOCR
A transparent BaseOCR wrapper that
intercepts process_image and batch_process_images calls, checks
the cache first, and only forwards misses to the underlying provider.
Duplicate images within the same batch are de-duplicated so the provider
sees each unique image at most once.
You rarely need to instantiate CachedOCR yourself – the loader
creates one internally when you pass ocr_cache to
UnifiedDocumentLoader.
from doc2mark.ocr.cache import CachedOCR
from doc2mark import MemoryOCRCache, OCRFactory, OCRConfig
provider = OCRFactory.create("openai", api_key="sk-...")
cache = MemoryOCRCache()
cached_provider = CachedOCR(wrapped=provider, cache=cache)
# Use cached_provider like any BaseOCR instance
result = cached_provider.process_image(image_bytes)
Constructor parameters
wrapped(:class:`~doc2mark.ocr.base.BaseOCR`)The real OCR provider to delegate cache misses to.
cache(Optional[:class:`~doc2mark.OCRCache`])The cache backend. If
None, aNoOpOCRCacheis substituted (effectively disabling caching).cache_version(str, default “ocr-cache-v4”)Schema version embedded in every cache key. Changing this value invalidates all existing entries, which is useful after a breaking change to the serialization format.
- class doc2mark.ocr.cache.CachedOCR(wrapped, cache, cache_version='ocr-cache-v4')[source]
Bases:
BaseOCROCR provider wrapper that checks a cache before calling the provider.
- batch_process_images(images, **kwargs)[source]
Process multiple images in batch using LangChain.
This is the primary method for OCR processing. All implementations must use LangChain for efficient batch processing.
- Parameters:
images (List[bytes]) – List of image data as bytes
**kwargs – Additional provider-specific options
- Returns:
List of OCRResult objects in the same order as input
- Return type:
List[OCRResult]
- preprocess_image(image_data)[source]
Preprocess image before OCR (optional).
- Parameters:
image_data (bytes) – Raw image data
- Returns:
Preprocessed image data
- Return type:
bytes
- property provider_name: str
Get the provider name.
- property requires_api_key: bool
Check if this provider requires an API key.
Factory function
create_ocr_cache
The recommended way to obtain a cache backend. It maps a short string name to the corresponding class, forwarding common tuning parameters.
from doc2mark import create_ocr_cache
# In-memory cache (default settings)
cache = create_ocr_cache("memory")
# Redis-backed cache (requires pip install doc2mark[redis])
cache = create_ocr_cache(
"redis",
redis_url="redis://localhost:6379/0",
fallback="memory",
)
# Disable caching explicitly
cache = create_ocr_cache("none") # returns None
Parameters
provider(Optional[str], default “none”)Backend name. Recognized values:
"memory"/"in-memory"/"in_memory"–MemoryOCRCache"redis"–RedisOCRCache"noop"/"no-op"–NoOpOCRCache"none"/"off"/"false"/"disabled"/""– returnsNone(no caching)
redis_url(Optional[str], default None)Connection URL when provider is
"redis".fallback(str, default “memory”)Backend to use when Redis is unreachable.
"memory"silently degrades to an in-process cache;"none"disables caching;"raise"re-raises the original connection error.ttl_seconds(float, default 3600)Per-entry time-to-live forwarded to the backend.
max_age_seconds(Optional[float], default 43200)Absolute entry lifetime forwarded to the backend.
max_refreshes(Optional[int], default 10)Refresh cap forwarded to the backend.
max_entries(int, default 1024)Maximum entries (memory backend only).
key_prefix(str, default “doc2mark:ocr:ocr-cache-v4”)Redis key namespace (Redis backend only).
Returns
An OCRCache instance, or None when caching is
disabled.
- doc2mark.create_ocr_cache(provider='none', *, redis_url=None, fallback='memory', ttl_seconds=3600, max_age_seconds=43200, max_refreshes=10, max_entries=1024, key_prefix='doc2mark:ocr:ocr-cache-v4')[source]
Create an OCR cache backend from a small provider name.
- Parameters:
provider (str | None)
redis_url (str | None)
fallback (str)
ttl_seconds (float)
max_age_seconds (float | None)
max_refreshes (int | None)
max_entries (int)
key_prefix (str)
- Return type:
OCRCache | None
Wiring caching into the pipeline
Pass the cache to load() or construct a
UnifiedDocumentLoader with it:
from doc2mark import load, create_ocr_cache
cache = create_ocr_cache("memory")
doc = load(
"report.pdf",
extract_images=True,
ocr_images=True,
ocr_cache=cache,
)
from doc2mark import UnifiedDocumentLoader, create_ocr_cache
cache = create_ocr_cache(
"redis",
redis_url="redis://localhost:6379/0",
fallback="memory",
)
loader = UnifiedDocumentLoader(
ocr_provider="openai",
api_key="sk-...",
ocr_cache=cache,
)
doc = loader.load("report.pdf", extract_images=True, ocr_images=True)