---
name: cache_to_sqlite
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def cache_to_sqlite(db_path: str, namespace: str = 'default') -> CacheStore"
description: "Cache key-value persistido en SQLite con TTL y lazy eviction. Cada namespace es un espacio logico dentro de la misma BD. Keys son strings, values se serializan con JSON. TTL en segundos, 0 = sin expiracion. Thread-safe mediante mutex."
tags: [cache, sqlite, persistence, ttl, memoize, key-value]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["sqlite3", "json", "time", "threading"]
params:
  - name: db_path
    desc: "ruta al archivo SQLite de cache"
  - name: namespace
    desc: "namespace logico dentro de la BD para aislar cachés"
output: "instancia CacheStore con metodos set, get, get_or_set, clear y stats"
tested: true
tests:
  - "Set y get basico"
  - "TTL expirado → None"
  - "TTL 0 → nunca expira"
  - "get_or_set con factory que solo se llama en miss"
  - "Namespaces independientes"
  - "Clear elimina solo el namespace"
  - "Stats contadores correctos"
  - "Concurrencia (threading basico)"
test_file_path: "python/functions/infra/cache_to_sqlite_test.py"
file_path: "python/functions/infra/cache_to_sqlite.py"
---

## Ejemplo

```python
from infra.cache_to_sqlite import cache_to_sqlite

store = cache_to_sqlite("my_cache.db", namespace="llm")

# Almacenar con TTL de 1 hora
store.set("prompt:explain_x", "explanation...", ttl=3600)

# Recuperar (None si miss o expirado)
val = store.get("prompt:explain_x")

# Factory pattern: solo computa si no esta en cache
result = store.get_or_set(
    "prompt:explain_y",
    factory=lambda: call_llm("explain y"),
    ttl=3600,
)

# Estadisticas
print(store.stats())  # {"hits": 2, "misses": 1, "size": 5}
```

## Notas

La eviction de entradas expiradas es lazy: se ejecuta en cada llamada a `get` o `stats`, no en background. El schema SQLite usa `(namespace, key)` como PRIMARY KEY para garantizar upserts atomicos. Usa WAL mode para mejor concurrencia de lecturas. Cada thread mantiene su propia conexion SQLite (thread-local), sincronizada via `threading.Lock` para escrituras.
